summaryrefslogtreecommitdiffstats
path: root/xbmc/cores
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/cores')
-rw-r--r--xbmc/cores/AudioEngine/AEResampleFactory.cpp20
-rw-r--r--xbmc/cores/AudioEngine/AEResampleFactory.h33
-rw-r--r--xbmc/cores/AudioEngine/AESinkFactory.cpp114
-rw-r--r--xbmc/cores/AudioEngine/AESinkFactory.h58
-rw-r--r--xbmc/cores/AudioEngine/CMakeLists.txt166
-rw-r--r--xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp322
-rw-r--r--xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h55
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp3498
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h408
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp703
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h147
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.cpp351
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.h61
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp277
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h53
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp191
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.h69
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp1175
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h158
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp158
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h71
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp797
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h246
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AE.h323
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AEEncoder.h95
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AEResample.h34
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AESink.h85
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AESound.h44
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AEStream.h272
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/IAudioCallback.h23
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/ThreadedAE.h20
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp1691
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkALSA.h100
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp1286
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h103
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.h56
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.mm766
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.cpp556
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.h62
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.h57
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.mm898
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp758
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h73
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp544
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkOSS.h46
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp1297
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h81
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.cpp316
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.h49
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp1002
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h76
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp890
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h131
-rw-r--r--xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.cpp128
-rw-r--r--xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h34
-rw-r--r--xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.cpp168
-rw-r--r--xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h54
-rw-r--r--xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.cpp92
-rw-r--r--xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h17
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.cpp837
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h246
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.cpp273
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h75
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.cpp938
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.h96
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.cpp286
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.h28
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.cpp498
-rw-r--r--xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.h57
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.cpp492
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h48
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.cpp88
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.h77
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.cpp34
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.h42
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.cpp70
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.h56
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.cpp208
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.h80
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.cpp78
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.h53
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.cpp111
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.h57
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.cpp161
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.h72
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.cpp67
-rw-r--r--xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h51
-rw-r--r--xbmc/cores/AudioEngine/Sinks/test/CMakeLists.txt7
-rw-r--r--xbmc/cores/AudioEngine/Sinks/test/TestAESinkDARWINOSX.cpp384
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp174
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h218
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp245
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp313
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEAudioFormat.h69
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp439
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h66
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEChannelData.h111
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp385
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEChannelInfo.h55
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp66
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h52
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEELDParser.cpp198
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEELDParser.h20
-rw-r--r--xbmc/cores/AudioEngine/Utils/AELimiter.cpp77
-rw-r--r--xbmc/cores/AudioEngine/Utils/AELimiter.h43
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp235
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h76
-rw-r--r--xbmc/cores/AudioEngine/Utils/AERingBuffer.h284
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEStreamData.h19
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEStreamInfo.cpp865
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEStreamInfo.h102
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEUtil.cpp583
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEUtil.h176
-rw-r--r--xbmc/cores/CMakeLists.txt14
-rw-r--r--xbmc/cores/DataCacheCore.cpp493
-rw-r--r--xbmc/cores/DataCacheCore.h335
-rw-r--r--xbmc/cores/DllLoader/CMakeLists.txt38
-rw-r--r--xbmc/cores/DllLoader/DllLoader-linux.cpp69
-rw-r--r--xbmc/cores/DllLoader/DllLoader.cpp808
-rw-r--r--xbmc/cores/DllLoader/DllLoader.h113
-rw-r--r--xbmc/cores/DllLoader/DllLoaderContainer.cpp344
-rw-r--r--xbmc/cores/DllLoader/DllLoaderContainer.h37
-rw-r--r--xbmc/cores/DllLoader/LibraryLoader.cpp62
-rw-r--r--xbmc/cores/DllLoader/LibraryLoader.h46
-rw-r--r--xbmc/cores/DllLoader/SoLoader.cpp101
-rw-r--r--xbmc/cores/DllLoader/SoLoader.h35
-rw-r--r--xbmc/cores/DllLoader/Win32DllLoader.cpp408
-rw-r--r--xbmc/cores/DllLoader/Win32DllLoader.h49
-rw-r--r--xbmc/cores/DllLoader/coff.cpp997
-rw-r--r--xbmc/cores/DllLoader/coff.h491
-rw-r--r--xbmc/cores/DllLoader/coffldr.h79
-rw-r--r--xbmc/cores/DllLoader/dll.cpp252
-rw-r--r--xbmc/cores/DllLoader/dll.h20
-rw-r--r--xbmc/cores/DllLoader/dll_tracker.cpp145
-rw-r--r--xbmc/cores/DllLoader/dll_tracker.h132
-rw-r--r--xbmc/cores/DllLoader/dll_tracker_file.cpp134
-rw-r--r--xbmc/cores/DllLoader/dll_tracker_file.h27
-rw-r--r--xbmc/cores/DllLoader/dll_tracker_library.cpp122
-rw-r--r--xbmc/cores/DllLoader/dll_tracker_library.h17
-rw-r--r--xbmc/cores/DllLoader/dll_util.cpp119
-rw-r--r--xbmc/cores/DllLoader/dll_util.h24
-rw-r--r--xbmc/cores/DllLoader/exports/CMakeLists.txt33
-rw-r--r--xbmc/cores/DllLoader/exports/emu_dummy.cpp20
-rw-r--r--xbmc/cores/DllLoader/exports/emu_dummy.h22
-rw-r--r--xbmc/cores/DllLoader/exports/emu_msvcrt.cpp2074
-rw-r--r--xbmc/cores/DllLoader/exports/emu_msvcrt.h167
-rwxr-xr-xxbmc/cores/DllLoader/exports/kernel32.def25
-rwxr-xr-xxbmc/cores/DllLoader/exports/msvcrt.def123
-rwxr-xr-xxbmc/cores/DllLoader/exports/pncrt.def114
-rw-r--r--xbmc/cores/DllLoader/exports/util/CMakeLists.txt5
-rw-r--r--xbmc/cores/DllLoader/exports/util/EmuFileWrapper.cpp232
-rw-r--r--xbmc/cores/DllLoader/exports/util/EmuFileWrapper.h73
-rwxr-xr-xxbmc/cores/DllLoader/exports/winMM.def6
-rw-r--r--xbmc/cores/DllLoader/exports/wrapper.c513
-rw-r--r--xbmc/cores/DllLoader/exports/wrapper_mach_alias62
-rwxr-xr-xxbmc/cores/DllLoader/exports/ws2_32.def25
-rw-r--r--xbmc/cores/DllLoader/ldt_keeper.c282
-rw-r--r--xbmc/cores/DllLoader/ldt_keeper.h38
-rw-r--r--xbmc/cores/DllLoader/mmap_anon.c75
-rw-r--r--xbmc/cores/DllLoader/mmap_anon.h14
-rw-r--r--xbmc/cores/EdlEdit.h31
-rw-r--r--xbmc/cores/ExternalPlayer/CMakeLists.txt5
-rw-r--r--xbmc/cores/ExternalPlayer/ExternalPlayer.cpp689
-rw-r--r--xbmc/cores/ExternalPlayer/ExternalPlayer.h89
-rw-r--r--xbmc/cores/FFmpeg.cpp119
-rw-r--r--xbmc/cores/FFmpeg.h53
-rw-r--r--xbmc/cores/GameSettings.h67
-rw-r--r--xbmc/cores/IPlayer.h277
-rw-r--r--xbmc/cores/IPlayerCallback.h37
-rw-r--r--xbmc/cores/MenuType.h22
-rw-r--r--xbmc/cores/RetroPlayer/CMakeLists.txt14
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayer.cpp698
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayer.h145
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerAutoSave.cpp63
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerAutoSave.h51
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerDefines.h12
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerInput.cpp73
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerInput.h60
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerTypes.h35
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerUtils.cpp45
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayerUtils.h44
-rw-r--r--xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp74
-rw-r--r--xbmc/cores/RetroPlayer/audio/AudioTranslator.h36
-rw-r--r--xbmc/cores/RetroPlayer/audio/CMakeLists.txt7
-rw-r--r--xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.cpp39
-rw-r--r--xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.h40
-rw-r--r--xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.cpp156
-rw-r--r--xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.h66
-rw-r--r--xbmc/cores/RetroPlayer/buffers/CMakeLists.txt34
-rw-r--r--xbmc/cores/RetroPlayer/buffers/IRenderBuffer.h70
-rw-r--r--xbmc/cores/RetroPlayer/buffers/IRenderBufferPool.h70
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.cpp125
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.h67
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferManager.cpp104
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferManager.h51
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.cpp76
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.h44
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.cpp101
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.h51
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.cpp60
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.h45
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.cpp64
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h45
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.cpp76
-rw-r--r--xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.h47
-rw-r--r--xbmc/cores/RetroPlayer/buffers/video/CMakeLists.txt9
-rw-r--r--xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.cpp105
-rw-r--r--xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.h50
-rw-r--r--xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.cpp53
-rw-r--r--xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.h45
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt6
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp183
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/Cheevos.h90
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h85
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/CMakeLists.txt24
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.cpp50
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.h42
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.cpp310
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.h185
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.cpp70
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.h54
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.cpp59
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h91
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.cpp39
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.h34
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.cpp62
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.h76
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.cpp66
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.h94
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.cpp29
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.h40
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/IGUIRenderSettings.h64
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/IGameCallback.h78
-rw-r--r--xbmc/cores/RetroPlayer/guibridge/IRenderCallback.h26
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/CMakeLists.txt9
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.cpp190
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.dox45
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.h82
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.cpp103
-rw-r--r--xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.h58
-rw-r--r--xbmc/cores/RetroPlayer/guiplayback/CMakeLists.txt7
-rw-r--r--xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.cpp155
-rw-r--r--xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.h52
-rw-r--r--xbmc/cores/RetroPlayer/guiwindows/CMakeLists.txt9
-rw-r--r--xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.cpp236
-rw-r--r--xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.h60
-rw-r--r--xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.cpp128
-rw-r--r--xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.h84
-rw-r--r--xbmc/cores/RetroPlayer/messages/CMakeLists.txt25
-rw-r--r--xbmc/cores/RetroPlayer/messages/savestate.fbs64
-rw-r--r--xbmc/cores/RetroPlayer/messages/video.fbs43
-rw-r--r--xbmc/cores/RetroPlayer/playback/CMakeLists.txt10
-rw-r--r--xbmc/cores/RetroPlayer/playback/GameLoop.cpp135
-rw-r--r--xbmc/cores/RetroPlayer/playback/GameLoop.h69
-rw-r--r--xbmc/cores/RetroPlayer/playback/IPlayback.h46
-rw-r--r--xbmc/cores/RetroPlayer/playback/IPlaybackControl.h52
-rw-r--r--xbmc/cores/RetroPlayer/playback/RealtimePlayback.h41
-rw-r--r--xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp443
-rw-r--r--xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h109
-rw-r--r--xbmc/cores/RetroPlayer/process/CMakeLists.txt7
-rw-r--r--xbmc/cores/RetroPlayer/process/RPProcessInfo.cpp232
-rw-r--r--xbmc/cores/RetroPlayer/process/RPProcessInfo.h201
-rw-r--r--xbmc/cores/RetroPlayer/process/X11/CMakeLists.txt5
-rw-r--r--xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.h26
-rw-r--r--xbmc/cores/RetroPlayer/process/android/CMakeLists.txt4
-rw-r--r--xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.h26
-rw-r--r--xbmc/cores/RetroPlayer/process/gbm/CMakeLists.txt4
-rw-r--r--xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h26
-rw-r--r--xbmc/cores/RetroPlayer/process/ios/CMakeLists.txt5
-rw-r--r--xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.h28
-rw-r--r--xbmc/cores/RetroPlayer/process/osx/CMakeLists.txt5
-rw-r--r--xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.h26
-rw-r--r--xbmc/cores/RetroPlayer/process/wayland/CMakeLists.txt5
-rw-r--r--xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.cpp26
-rw-r--r--xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h26
-rw-r--r--xbmc/cores/RetroPlayer/process/windows/CMakeLists.txt4
-rw-r--r--xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.cpp28
-rw-r--r--xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.h26
-rw-r--r--xbmc/cores/RetroPlayer/rendering/CMakeLists.txt18
-rw-r--r--xbmc/cores/RetroPlayer/rendering/IRenderManager.h56
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp1066
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RPRenderManager.h271
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderContext.cpp321
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderContext.h122
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderSettings.cpp32
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderSettings.h34
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderTranslator.cpp77
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderTranslator.h43
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderUtils.cpp222
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderUtils.h43
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.cpp89
-rw-r--r--xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h60
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt26
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp238
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h118
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.cpp134
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h53
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.cpp286
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.h66
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp337
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h92
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.cpp317
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h81
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.cpp305
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h132
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoShaders/CMakeLists.txt3
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/CMakeLists.txt5
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.cpp120
-rw-r--r--xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h61
-rw-r--r--xbmc/cores/RetroPlayer/savestates/CMakeLists.txt19
-rw-r--r--xbmc/cores/RetroPlayer/savestates/ISavestate.h214
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp272
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h61
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp637
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h127
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateTypes.h28
-rw-r--r--xbmc/cores/RetroPlayer/streams/CMakeLists.txt15
-rw-r--r--xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h67
-rw-r--r--xbmc/cores/RetroPlayer/streams/IStreamManager.h41
-rw-r--r--xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp65
-rw-r--r--xbmc/cores/RetroPlayer/streams/RPStreamManager.h42
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp142
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h67
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp19
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h86
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp116
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h112
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp55
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h48
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt12
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp99
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h67
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h144
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp104
-rw-r--r--xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h69
-rw-r--r--xbmc/cores/VideoPlayer/AudioSinkAE.cpp387
-rw-r--r--xbmc/cores/VideoPlayer/AudioSinkAE.h85
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/CMakeLists.txt13
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBuffer.cpp466
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBuffer.h197
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.cpp187
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.h61
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.cpp159
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h100
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.cpp129
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h44
-rw-r--r--xbmc/cores/VideoPlayer/CMakeLists.txt46
-rw-r--r--xbmc/cores/VideoPlayer/DVDClock.cpp309
-rw-r--r--xbmc/cores/VideoPlayer/DVDClock.h85
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/CMakeLists.txt13
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h123
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp807
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h75
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.cpp402
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.h64
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.cpp261
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.h56
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/CMakeLists.txt8
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.cpp77
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h18
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecs.h27
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp289
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h80
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/CMakeLists.txt27
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h176
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.cpp40
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h99
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.cpp165
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.h38
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.cpp300
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.h42
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.cpp141
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.h33
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp295
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.h33
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.cpp150
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.h43
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h86
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h35
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h28
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h95
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayText.h30
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.cpp168
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.h38
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.c874
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.h107
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.cpp1192
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.h304
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp387
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.h54
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt44
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp83
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h267
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp1745
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h194
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp647
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h48
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp1380
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.h117
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.cpp130
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.h42
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp1573
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.h262
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp3242
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.h648
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.cpp3464
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.h661
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.cpp232
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.h69
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxSPU.cpp660
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxSPU.h54
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/CMakeLists.txt23
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp96
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.h381
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.cpp186
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.h69
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp414
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.h58
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.cpp191
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.h48
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp764
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.h66
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp2549
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h181
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.cpp111
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h24
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp269
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.h88
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.cpp80
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h21
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.cpp236
-rw-r--r--xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.h64
-rw-r--r--xbmc/cores/VideoPlayer/DVDFileInfo.cpp497
-rw-r--r--xbmc/cores/VideoPlayer/DVDFileInfo.h52
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp84
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h47
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt38
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp189
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h24
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.cpp47
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.h215
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp1307
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.h192
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp127
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.h40
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.cpp162
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.h33
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.cpp96
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.h29
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp1577
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.h194
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.cpp169
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.h46
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp127
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h67
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/DllDvdNav.h275
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp777
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h169
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.cpp141
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.h42
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiStreams.h31
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp369
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h83
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp125
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h33
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp102
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h28
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/config.h76
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_reader.h370
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_types.h282
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav.h789
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h226
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h754
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/nav_types.h258
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/version.h29
-rw-r--r--xbmc/cores/VideoPlayer/DVDMessage.cpp110
-rw-r--r--xbmc/cores/VideoPlayer/DVDMessage.h333
-rw-r--r--xbmc/cores/VideoPlayer/DVDMessageQueue.cpp351
-rw-r--r--xbmc/cores/VideoPlayer/DVDMessageQueue.h116
-rw-r--r--xbmc/cores/VideoPlayer/DVDOverlayContainer.cpp195
-rw-r--r--xbmc/cores/VideoPlayer/DVDOverlayContainer.h60
-rw-r--r--xbmc/cores/VideoPlayer/DVDResource.h37
-rw-r--r--xbmc/cores/VideoPlayer/DVDStreamInfo.cpp310
-rw-r--r--xbmc/cores/VideoPlayer/DVDStreamInfo.h123
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/CMakeLists.txt33
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp82
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.h25
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.cpp111
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.h48
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h88
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.cpp72
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.h27
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.cpp79
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.h28
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.cpp57
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.h27
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.cpp157
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.h26
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.cpp94
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.h25
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.cpp89
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.h28
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.cpp138
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h65
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.cpp165
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.h33
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp308
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h65
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp742
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h169
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.cpp84
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.h24
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.cpp96
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.h91
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h134
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/CMakeLists.txt7
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp1166
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h235
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.cpp150
-rw-r--r--xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.h40
-rw-r--r--xbmc/cores/VideoPlayer/Edl.cpp1040
-rw-r--r--xbmc/cores/VideoPlayer/Edl.h210
-rw-r--r--xbmc/cores/VideoPlayer/IVideoPlayer.h126
-rw-r--r--xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h69
-rw-r--r--xbmc/cores/VideoPlayer/Interface/DemuxPacket.h50
-rw-r--r--xbmc/cores/VideoPlayer/Interface/InputStreamConstants.h11
-rw-r--r--xbmc/cores/VideoPlayer/Interface/StreamInfo.h83
-rw-r--r--xbmc/cores/VideoPlayer/Interface/TimingConstants.h24
-rw-r--r--xbmc/cores/VideoPlayer/PTSTracker.cpp323
-rw-r--r--xbmc/cores/VideoPlayer/PTSTracker.h59
-rw-r--r--xbmc/cores/VideoPlayer/Process/CMakeLists.txt4
-rw-r--r--xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp696
-rw-r--r--xbmc/cores/VideoPlayer/Process/ProcessInfo.h180
-rw-r--r--xbmc/cores/VideoPlayer/Process/X11/CMakeLists.txt5
-rw-r--r--xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.cpp59
-rw-r--r--xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.h26
-rw-r--r--xbmc/cores/VideoPlayer/Process/android/CMakeLists.txt5
-rw-r--r--xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.cpp41
-rw-r--r--xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.h27
-rw-r--r--xbmc/cores/VideoPlayer/Process/gbm/CMakeLists.txt4
-rw-r--r--xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.cpp37
-rw-r--r--xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h27
-rw-r--r--xbmc/cores/VideoPlayer/Process/ios/CMakeLists.txt5
-rw-r--r--xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIOS.h25
-rw-r--r--xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIos.cpp43
-rw-r--r--xbmc/cores/VideoPlayer/Process/osx/CMakeLists.txt5
-rw-r--r--xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.cpp58
-rw-r--r--xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.h27
-rw-r--r--xbmc/cores/VideoPlayer/Process/wayland/CMakeLists.txt5
-rw-r--r--xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.cpp71
-rw-r--r--xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h27
-rw-r--r--xbmc/cores/VideoPlayer/Process/windows/CMakeLists.txt9
-rw-r--r--xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.cpp41
-rw-r--r--xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.h27
-rw-r--r--xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.cpp43
-rw-r--r--xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.h27
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayer.cpp5364
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayer.h567
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp713
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerAudio.h123
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerAudioID3.cpp351
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerAudioID3.h58
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp1678
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.h144
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerSubtitle.cpp215
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerSubtitle.h69
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp760
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerTeletext.h59
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp1166
-rw-r--r--xbmc/cores/VideoPlayer/VideoPlayerVideo.h144
-rw-r--r--xbmc/cores/VideoPlayer/VideoReferenceClock.cpp280
-rw-r--r--xbmc/cores/VideoPlayer/VideoReferenceClock.h57
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp522
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h144
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt58
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.cpp618
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.h190
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/DebugInfo.h33
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.cpp152
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.h53
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.cpp127
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.h76
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt69
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp133
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.h38
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp631
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h92
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp264
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h61
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp417
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h70
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.cpp243
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h42
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.cpp194
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h58
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp314
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h58
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp266
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h65
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.cpp464
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.h62
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp236
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h37
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.cpp268
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h50
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp691
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.h130
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.cpp222
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.h81
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp209
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.h53
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp2757
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h233
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp1782
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h216
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp638
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.h198
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.cpp353
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.h56
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.cpp619
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.h66
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.cpp290
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.h50
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.cpp24
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.h102
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp191
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.h39
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.cpp186
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.h32
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.cpp29
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.h23
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.cpp57
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.h36
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.cpp89
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.h68
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderInfo.h40
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp1309
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h247
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt41
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.cpp645
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h240
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.cpp298
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.h47
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.cpp172
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.h61
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ShaderFormats.h62
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.cpp266
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.h118
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.cpp212
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.h102
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp1228
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h240
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.cpp395
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.h164
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp295
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h138
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h74
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/CMakeLists.txt3
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp183
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp353
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.h62
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/CMakeLists.txt13
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp726
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h191
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp383
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h64
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.cpp160
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.h33
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp474
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h71
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.cpp204
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.h54
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/CMakeLists.txt3
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp461
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.txt5
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.txt5
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.edl7
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/mplayerframebased.edl5
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebased.edl6
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedinterleavedcuts.edl4
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedmixed.edl5
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/snapstream.mkv.chapters.xml14
-rw-r--r--xbmc/cores/VideoPlayer/test/edl/testdata/videoredo.Vprj13
-rw-r--r--xbmc/cores/VideoSettings.cpp149
-rw-r--r--xbmc/cores/VideoSettings.h264
-rw-r--r--xbmc/cores/paplayer/AudioDecoder.cpp401
-rw-r--r--xbmc/cores/paplayer/AudioDecoder.h91
-rw-r--r--xbmc/cores/paplayer/CMakeLists.txt13
-rw-r--r--xbmc/cores/paplayer/CachingCodec.h18
-rw-r--r--xbmc/cores/paplayer/CodecFactory.cpp112
-rw-r--r--xbmc/cores/paplayer/CodecFactory.h23
-rw-r--r--xbmc/cores/paplayer/ICodec.h82
-rw-r--r--xbmc/cores/paplayer/PAPlayer.cpp1207
-rw-r--r--xbmc/cores/paplayer/PAPlayer.h154
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.cpp533
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.h65
-rw-r--r--xbmc/cores/playercorefactory/CMakeLists.txt9
-rw-r--r--xbmc/cores/playercorefactory/PlayerCoreConfig.cpp83
-rw-r--r--xbmc/cores/playercorefactory/PlayerCoreConfig.h56
-rw-r--r--xbmc/cores/playercorefactory/PlayerCoreFactory.cpp477
-rw-r--r--xbmc/cores/playercorefactory/PlayerCoreFactory.h69
-rw-r--r--xbmc/cores/playercorefactory/PlayerSelectionRule.cpp198
-rw-r--r--xbmc/cores/playercorefactory/PlayerSelectionRule.h62
708 files changed, 150804 insertions, 0 deletions
diff --git a/xbmc/cores/AudioEngine/AEResampleFactory.cpp b/xbmc/cores/AudioEngine/AEResampleFactory.cpp
new file mode 100644
index 0000000..224910f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/AEResampleFactory.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEResampleFactory.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h"
+
+namespace ActiveAE
+{
+
+IAEResample *CAEResampleFactory::Create(uint32_t flags /* = 0 */)
+{
+ return new CActiveAEResampleFFMPEG();
+}
+
+}
diff --git a/xbmc/cores/AudioEngine/AEResampleFactory.h b/xbmc/cores/AudioEngine/AEResampleFactory.h
new file mode 100644
index 0000000..e2a7b19
--- /dev/null
+++ b/xbmc/cores/AudioEngine/AEResampleFactory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AEResample.h"
+
+class IAEResample;
+
+namespace ActiveAE
+{
+
+/**
+ * Bit options to pass to CAEResampleFactory::Create
+ */
+enum AEResampleFactoryOptions
+{
+ /* This is a quick resample job (e.g. resample a single noise packet) and may not be worth using GPU acceleration */
+ AERESAMPLEFACTORY_QUICK_RESAMPLE = 0x01
+};
+
+class CAEResampleFactory
+{
+public:
+ static IAEResample *Create(uint32_t flags = 0U);
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp
new file mode 100644
index 0000000..d86a83e
--- /dev/null
+++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkFactory.h"
+
+#include "Interfaces/AESink.h"
+#include "ServiceBroker.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+using namespace AE;
+
+std::map<std::string, AESinkRegEntry> CAESinkFactory::m_AESinkRegEntry;
+
+void CAESinkFactory::RegisterSink(const AESinkRegEntry& regEntry)
+{
+ m_AESinkRegEntry[regEntry.sinkName] = regEntry;
+
+ IAE *ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->DeviceChange();
+}
+
+void CAESinkFactory::ClearSinks()
+{
+ m_AESinkRegEntry.clear();
+}
+
+bool CAESinkFactory::HasSinks()
+{
+ return !m_AESinkRegEntry.empty();
+}
+
+void CAESinkFactory::ParseDevice(std::string &device, std::string &driver)
+{
+ int pos = device.find_first_of(':');
+ bool found = false;
+ if (pos > 0)
+ {
+ driver = device.substr(0, pos);
+
+ for (const auto& reg : m_AESinkRegEntry)
+ {
+ if (!StringUtils::EqualsNoCase(driver, reg.second.sinkName))
+ continue;
+
+ device = device.substr(pos + 1, device.length() - pos - 1);
+ found = true;
+ }
+ }
+
+ if (!found)
+ driver.clear();
+}
+
+IAESink *CAESinkFactory::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ // extract the driver from the device string if it exists
+ std::string driver;
+ ParseDevice(device, driver);
+
+ AEAudioFormat tmpFormat = desiredFormat;
+ IAESink *sink;
+ std::string tmpDevice = device;
+
+ for (const auto& reg : m_AESinkRegEntry)
+ {
+ if (driver != reg.second.sinkName)
+ continue;
+
+ sink = reg.second.createFunc(tmpDevice, tmpFormat);
+ if (sink)
+ {
+ desiredFormat = tmpFormat;
+ return sink;
+ }
+ }
+ return nullptr;
+}
+
+void CAESinkFactory::EnumerateEx(std::vector<AESinkInfo>& list,
+ bool force,
+ const std::string& driver)
+{
+ AESinkInfo info;
+
+ for (const auto& reg : m_AESinkRegEntry)
+ {
+ if (!driver.empty() && driver != reg.second.sinkName)
+ continue;
+
+ info.m_deviceInfoList.clear();
+ info.m_sinkName = reg.second.sinkName;
+ reg.second.enumerateFunc(info.m_deviceInfoList, force);
+ if (!info.m_deviceInfoList.empty())
+ list.push_back(info);
+ }
+}
+
+void CAESinkFactory::Cleanup()
+{
+ for (const auto& reg : m_AESinkRegEntry)
+ {
+ if (reg.second.cleanupFunc)
+ reg.second.cleanupFunc();
+ }
+}
diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h
new file mode 100644
index 0000000..e8e069f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/AESinkFactory.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Utils/AEAudioFormat.h"
+#include "Utils/AEDeviceInfo.h"
+
+#include <map>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+class IAESink;
+
+namespace AE
+{
+
+struct AESinkInfo
+{
+ std::string m_sinkName;
+ AEDeviceInfoList m_deviceInfoList;
+};
+
+typedef IAESink* (*CreateSink)(std::string &device, AEAudioFormat &desiredFormat);
+typedef void (*Enumerate)(AEDeviceInfoList &list, bool force);
+typedef void (*Cleanup)();
+
+struct AESinkRegEntry
+{
+ std::string sinkName;
+ CreateSink createFunc = nullptr;
+ Enumerate enumerateFunc = nullptr;
+ Cleanup cleanupFunc = nullptr;
+};
+
+class CAESinkFactory
+{
+public:
+ static void RegisterSink(const AESinkRegEntry& regEntry);
+ static void ClearSinks();
+ static bool HasSinks();
+
+ static void ParseDevice(std::string &device, std::string &driver);
+ static IAESink *Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateEx(std::vector<AESinkInfo>& list, bool force, const std::string& driver);
+ static void Cleanup();
+
+protected:
+ static std::map<std::string, AESinkRegEntry> m_AESinkRegEntry;
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt
new file mode 100644
index 0000000..c5d4d97
--- /dev/null
+++ b/xbmc/cores/AudioEngine/CMakeLists.txt
@@ -0,0 +1,166 @@
+set(SOURCES AEResampleFactory.cpp
+ AESinkFactory.cpp
+ Encoders/AEEncoderFFmpeg.cpp
+ Engines/ActiveAE/ActiveAE.cpp
+ Engines/ActiveAE/ActiveAEBuffer.cpp
+ Engines/ActiveAE/ActiveAEFilter.cpp
+ Engines/ActiveAE/ActiveAESink.cpp
+ Engines/ActiveAE/ActiveAEStream.cpp
+ Engines/ActiveAE/ActiveAESound.cpp
+ Engines/ActiveAE/ActiveAESettings.cpp
+ Utils/AEBitstreamPacker.cpp
+ Utils/AEChannelInfo.cpp
+ Utils/AEDeviceInfo.cpp
+ Utils/AELimiter.cpp
+ Utils/AEPackIEC61937.cpp
+ Utils/AEStreamInfo.cpp
+ Utils/AEUtil.cpp)
+
+set(HEADERS AEResampleFactory.h
+ AESinkFactory.h
+ Encoders/AEEncoderFFmpeg.h
+ Engines/ActiveAE/ActiveAE.h
+ Engines/ActiveAE/ActiveAEBuffer.h
+ Engines/ActiveAE/ActiveAEFilter.h
+ Engines/ActiveAE/ActiveAESink.h
+ Engines/ActiveAE/ActiveAESound.h
+ Engines/ActiveAE/ActiveAEStream.h
+ Engines/ActiveAE/ActiveAESettings.h
+ Interfaces/AE.h
+ Interfaces/AEEncoder.h
+ Interfaces/AEResample.h
+ Interfaces/AESink.h
+ Interfaces/AESound.h
+ Interfaces/AEStream.h
+ Interfaces/IAudioCallback.h
+ Interfaces/ThreadedAE.h
+ Utils/AEAudioFormat.h
+ Utils/AEBitstreamPacker.h
+ Utils/AEChannelData.h
+ Utils/AEChannelInfo.h
+ Utils/AEDeviceInfo.h
+ Utils/AELimiter.h
+ Utils/AEPackIEC61937.h
+ Utils/AERingBuffer.h
+ Utils/AEStreamData.h
+ Utils/AEStreamInfo.h
+ Utils/AEUtil.h)
+
+if(ALSA_FOUND)
+ list(APPEND SOURCES Sinks/AESinkALSA.cpp
+ Utils/AEELDParser.cpp)
+ list(APPEND HEADERS Sinks/AESinkALSA.h
+ Utils/AEELDParser.h)
+
+ if(NOT "x11" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES Sinks/alsa/ALSAHControlMonitor.cpp)
+ list(APPEND HEADERS Sinks/alsa/ALSAHControlMonitor.h)
+ endif()
+
+ if(UDEV_FOUND)
+ list(APPEND SOURCES Sinks/alsa/ALSADeviceMonitor.cpp)
+ list(APPEND HEADERS Sinks/alsa/ALSADeviceMonitor.h)
+ endif()
+endif()
+
+if(PULSEAUDIO_FOUND)
+ list(APPEND SOURCES Sinks/AESinkPULSE.cpp)
+ list(APPEND HEADERS Sinks/AESinkPULSE.h)
+endif()
+
+if(PIPEWIRE_FOUND)
+ list(APPEND SOURCES Sinks/pipewire/AESinkPipewire.cpp
+ Sinks/pipewire/Pipewire.cpp
+ Sinks/pipewire/PipewireContext.cpp
+ Sinks/pipewire/PipewireCore.cpp
+ Sinks/pipewire/PipewireNode.cpp
+ Sinks/pipewire/PipewireProxy.cpp
+ Sinks/pipewire/PipewireRegistry.cpp
+ Sinks/pipewire/PipewireStream.cpp
+ Sinks/pipewire/PipewireThreadLoop.cpp)
+ list(APPEND HEADERS Sinks/pipewire/AESinkPipewire.h
+ Sinks/pipewire/Pipewire.h
+ Sinks/pipewire/PipewireContext.h
+ Sinks/pipewire/PipewireCore.h
+ Sinks/pipewire/PipewireNode.h
+ Sinks/pipewire/PipewireProxy.h
+ Sinks/pipewire/PipewireRegistry.h
+ Sinks/pipewire/PipewireStream.h
+ Sinks/pipewire/PipewireThreadLoop.h)
+endif()
+
+if(SNDIO_FOUND)
+ list(APPEND SOURCES Sinks/AESinkSNDIO.cpp)
+ list(APPEND HEADERS Sinks/AESinkSNDIO.h)
+endif()
+
+if(FFMPEG_FOUND)
+ list(APPEND SOURCES Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp)
+ list(APPEND HEADERS Engines/ActiveAE/ActiveAEResampleFFMPEG.h)
+endif()
+
+if(CORE_SYSTEM_NAME MATCHES windows)
+ list(APPEND SOURCES Sinks/AESinkWASAPI.cpp
+ Sinks/windows/AESinkFactoryWin.cpp)
+ list(APPEND HEADERS Sinks/AESinkWASAPI.h
+ Sinks/windows/AESinkFactoryWin.h)
+ if(CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES Sinks/AESinkXAudio.cpp
+ Sinks/windows/AESinkFactoryWin10.cpp)
+ list(APPEND SOURCES Sinks/AESinkXAudio.h)
+ elseif(CORE_SYSTEM_NAME STREQUAL windows)
+ list(APPEND SOURCES Sinks/AESinkDirectSound.cpp
+ Sinks/windows/AESinkFactoryWin32.cpp)
+ list(APPEND SOURCES Sinks/AESinkDirectSound.h)
+ endif()
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL osx)
+ list(APPEND SOURCES Sinks/AESinkDARWINOSX.cpp
+ Sinks/darwin/CoreAudioHelpers.cpp
+ Sinks/osx/AEDeviceEnumerationOSX.cpp
+ Sinks/osx/CoreAudioChannelLayout.cpp
+ Sinks/osx/CoreAudioDevice.cpp
+ Sinks/osx/CoreAudioHardware.cpp
+ Sinks/osx/CoreAudioStream.cpp)
+ list(APPEND HEADERS Sinks/AESinkDARWINOSX.h
+ Sinks/darwin/CoreAudioHelpers.h
+ Sinks/osx/AEDeviceEnumerationOSX.h
+ Sinks/osx/CoreAudioChannelLayout.h
+ Sinks/osx/CoreAudioDevice.h
+ Sinks/osx/CoreAudioHardware.h
+ Sinks/osx/CoreAudioStream.h)
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL darwin_embedded)
+ list(APPEND SOURCES Sinks/darwin/CoreAudioHelpers.cpp)
+ list(APPEND HEADERS Sinks/darwin/CoreAudioHelpers.h)
+ if("ios" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES Sinks/AESinkDARWINIOS.mm)
+ list(APPEND HEADERS Sinks/AESinkDARWINIOS.h)
+ elseif("tvos" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES Sinks/AESinkDARWINTVOS.mm)
+ list(APPEND HEADERS Sinks/AESinkDARWINTVOS.h)
+ endif()
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL android)
+ list(APPEND SOURCES Sinks/AESinkAUDIOTRACK.cpp)
+ list(APPEND HEADERS Sinks/AESinkAUDIOTRACK.h)
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL freebsd)
+ list(APPEND SOURCES Sinks/AESinkOSS.cpp)
+ list(APPEND HEADERS Sinks/AESinkOSS.h)
+endif()
+
+core_add_library(audioengine)
+target_include_directories(${CORE_LIBRARY} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
+ if(HAVE_SSE)
+ target_compile_options(${CORE_LIBRARY} PRIVATE -msse)
+ endif()
+ if(HAVE_SSE2)
+ target_compile_options(${CORE_LIBRARY} PRIVATE -msse2)
+ endif()
+endif()
diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp
new file mode 100644
index 0000000..09bb26a
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#define AC3_ENCODE_BITRATE 640000
+#define DTS_ENCODE_BITRATE 1411200
+
+#include "cores/AudioEngine/Encoders/AEEncoderFFmpeg.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "ServiceBroker.h"
+#include "utils/log.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include <string.h>
+#include <cassert>
+
+CAEEncoderFFmpeg::CAEEncoderFFmpeg() : m_CodecCtx(NULL), m_SwrCtx(NULL)
+{
+}
+
+CAEEncoderFFmpeg::~CAEEncoderFFmpeg()
+{
+ Reset();
+ swr_free(&m_SwrCtx);
+ avcodec_free_context(&m_CodecCtx);
+}
+
+bool CAEEncoderFFmpeg::IsCompatible(const AEAudioFormat& format)
+{
+ if (!m_CodecCtx)
+ return false;
+
+ bool match = (
+ format.m_dataFormat == m_CurrentFormat.m_dataFormat &&
+ format.m_sampleRate == m_CurrentFormat.m_sampleRate
+ );
+
+ if (match)
+ {
+ CAEChannelInfo layout;
+ BuildChannelLayout(AV_CH_LAYOUT_5POINT1_BACK, layout); /* hard coded for AC3 & DTS currently */
+ match = (m_CurrentFormat.m_channelLayout == layout);
+ }
+
+ return match;
+}
+
+unsigned int CAEEncoderFFmpeg::BuildChannelLayout(const int64_t ffmap, CAEChannelInfo& layout)
+{
+ /* build the channel layout and count the channels */
+ layout.Reset();
+ if (ffmap & AV_CH_FRONT_LEFT ) layout += AE_CH_FL ;
+ if (ffmap & AV_CH_FRONT_RIGHT ) layout += AE_CH_FR ;
+ if (ffmap & AV_CH_FRONT_CENTER ) layout += AE_CH_FC ;
+ if (ffmap & AV_CH_LOW_FREQUENCY ) layout += AE_CH_LFE ;
+ if (ffmap & AV_CH_BACK_LEFT ) layout += AE_CH_BL ;
+ if (ffmap & AV_CH_BACK_RIGHT ) layout += AE_CH_BR ;
+ if (ffmap & AV_CH_FRONT_LEFT_OF_CENTER ) layout += AE_CH_FLOC;
+ if (ffmap & AV_CH_FRONT_RIGHT_OF_CENTER) layout += AE_CH_FROC;
+ if (ffmap & AV_CH_BACK_CENTER ) layout += AE_CH_BC ;
+ if (ffmap & AV_CH_SIDE_LEFT ) layout += AE_CH_SL ;
+ if (ffmap & AV_CH_SIDE_RIGHT ) layout += AE_CH_SR ;
+ if (ffmap & AV_CH_TOP_CENTER ) layout += AE_CH_TC ;
+ if (ffmap & AV_CH_TOP_FRONT_LEFT ) layout += AE_CH_TFL ;
+ if (ffmap & AV_CH_TOP_FRONT_CENTER ) layout += AE_CH_TFC ;
+ if (ffmap & AV_CH_TOP_FRONT_RIGHT ) layout += AE_CH_TFR ;
+ if (ffmap & AV_CH_TOP_BACK_LEFT ) layout += AE_CH_TBL ;
+ if (ffmap & AV_CH_TOP_BACK_CENTER ) layout += AE_CH_TBC ;
+ if (ffmap & AV_CH_TOP_BACK_RIGHT ) layout += AE_CH_TBR ;
+
+ return layout.Count();
+}
+
+bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format, bool allow_planar_input)
+{
+ Reset();
+
+ bool ac3 = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH);
+
+ AVCodec *codec = NULL;
+
+ /* fallback to ac3 if we support it, we might not have DTS support */
+ if (ac3)
+ {
+ m_CodecName = "AC3";
+ m_CodecID = AV_CODEC_ID_AC3;
+ m_BitRate = AC3_ENCODE_BITRATE;
+ codec = avcodec_find_encoder(m_CodecID);
+ }
+
+ /* check we got the codec */
+ if (!codec)
+ return false;
+
+ m_CodecCtx = avcodec_alloc_context3(codec);
+ if (!m_CodecCtx)
+ return false;
+
+ m_CodecCtx->bit_rate = m_BitRate;
+ m_CodecCtx->sample_rate = format.m_sampleRate;
+ m_CodecCtx->channel_layout = AV_CH_LAYOUT_5POINT1_BACK;
+
+ /* select a suitable data format */
+ if (codec->sample_fmts)
+ {
+ bool hasFloat = false;
+ bool hasDouble = false;
+ bool hasS32 = false;
+ bool hasS16 = false;
+ bool hasU8 = false;
+ bool hasFloatP = false;
+ bool hasUnknownFormat = false;
+
+ for(int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i)
+ {
+ switch (codec->sample_fmts[i])
+ {
+ case AV_SAMPLE_FMT_FLT: hasFloat = true; break;
+ case AV_SAMPLE_FMT_DBL: hasDouble = true; break;
+ case AV_SAMPLE_FMT_S32: hasS32 = true; break;
+ case AV_SAMPLE_FMT_S16: hasS16 = true; break;
+ case AV_SAMPLE_FMT_U8 : hasU8 = true; break;
+ case AV_SAMPLE_FMT_FLTP:
+ if (allow_planar_input)
+ hasFloatP = true;
+ else
+ hasUnknownFormat = true;
+ break;
+ case AV_SAMPLE_FMT_NONE: return false;
+ default: hasUnknownFormat = true; break;
+ }
+ }
+
+ if (hasFloat)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLT;
+ format.m_dataFormat = AE_FMT_FLOAT;
+ }
+ else if (hasFloatP)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
+ format.m_dataFormat = AE_FMT_FLOATP;
+ }
+ else if (hasDouble)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_DBL;
+ format.m_dataFormat = AE_FMT_DOUBLE;
+ }
+ else if (hasS32)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_S32;
+ format.m_dataFormat = AE_FMT_S32NE;
+ }
+ else if (hasS16)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
+ format.m_dataFormat = AE_FMT_S16NE;
+ }
+ else if (hasU8)
+ {
+ m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_U8;
+ format.m_dataFormat = AE_FMT_U8;
+ }
+ else if (hasUnknownFormat)
+ {
+ m_CodecCtx->sample_fmt = codec->sample_fmts[0];
+ format.m_dataFormat = AE_FMT_FLOAT;
+ m_NeedConversion = true;
+ CLog::Log(LOGINFO,
+ "CAEEncoderFFmpeg::Initialize - Unknown audio format, it will be resampled.");
+ }
+ else
+ {
+ CLog::Log(
+ LOGERROR,
+ "CAEEncoderFFmpeg::Initialize - Unable to find a suitable data format for the codec ({})",
+ m_CodecName);
+ avcodec_free_context(&m_CodecCtx);
+ return false;
+ }
+ }
+
+ m_CodecCtx->channels = BuildChannelLayout(m_CodecCtx->channel_layout, m_Layout);
+
+ /* open the codec */
+ if (avcodec_open2(m_CodecCtx, codec, NULL))
+ {
+ avcodec_free_context(&m_CodecCtx);
+ return false;
+ }
+
+ format.m_frames = m_CodecCtx->frame_size;
+ format.m_frameSize = m_CodecCtx->channels * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
+ format.m_channelLayout = m_Layout;
+
+ m_CurrentFormat = format;
+ m_NeededFrames = format.m_frames;
+ m_OutputRatio = (double)m_NeededFrames / m_OutputSize;
+ m_SampleRateMul = 1.0 / (double)m_CodecCtx->sample_rate;
+
+ if (m_NeedConversion)
+ {
+ m_SwrCtx = swr_alloc_set_opts(NULL,
+ m_CodecCtx->channel_layout, m_CodecCtx->sample_fmt, m_CodecCtx->sample_rate,
+ m_CodecCtx->channel_layout, AV_SAMPLE_FMT_FLT, m_CodecCtx->sample_rate,
+ 0, NULL);
+ if (!m_SwrCtx || swr_init(m_SwrCtx) < 0)
+ {
+ CLog::Log(LOGERROR, "CAEEncoderFFmpeg::Initialize - Failed to initialise resampler.");
+ swr_free(&m_SwrCtx);
+ avcodec_free_context(&m_CodecCtx);
+ return false;
+ }
+ }
+ CLog::Log(LOGINFO, "CAEEncoderFFmpeg::Initialize - {} encoder ready", m_CodecName);
+ return true;
+}
+
+void CAEEncoderFFmpeg::Reset()
+{
+ m_BufferSize = 0;
+}
+
+unsigned int CAEEncoderFFmpeg::GetBitRate()
+{
+ return m_BitRate;
+}
+
+AVCodecID CAEEncoderFFmpeg::GetCodecID()
+{
+ return m_CodecID;
+}
+
+unsigned int CAEEncoderFFmpeg::GetFrames()
+{
+ return m_NeededFrames;
+}
+
+int CAEEncoderFFmpeg::Encode(uint8_t *in, int in_size, uint8_t *out, int out_size)
+{
+ int got_output;
+ AVFrame *frame;
+
+ if (!m_CodecCtx)
+ return 0;
+
+ /* allocate the input frame
+ * sadly, we have to alloc/dealloc it everytime since we have no guarantee the
+ * data argument will be constant over iterated calls and the frame needs to
+ * setup pointers inside data */
+ frame = av_frame_alloc();
+ if (!frame)
+ return 0;
+
+ frame->nb_samples = m_CodecCtx->frame_size;
+ frame->format = m_CodecCtx->sample_fmt;
+ frame->channel_layout = m_CodecCtx->channel_layout;
+ frame->channels = m_CodecCtx->channels;
+
+ avcodec_fill_audio_frame(frame, m_CodecCtx->channels, m_CodecCtx->sample_fmt,
+ in, in_size, 0);
+
+ /* initialize the output packet */
+ AVPacket* pkt = av_packet_alloc();
+ if (!pkt)
+ {
+ CLog::Log(LOGERROR, "CAEEncoderFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ av_frame_free(&frame);
+ return 0;
+ }
+
+ pkt->size = out_size;
+ pkt->data = out;
+
+ /* encode it */
+ int ret = avcodec_encode_audio2(m_CodecCtx, pkt, frame, &got_output);
+
+ int size = pkt->size;
+
+ /* free temporary data */
+ av_frame_free(&frame);
+
+ /* free the packet */
+ av_packet_free(&pkt);
+
+ if (ret < 0 || !got_output)
+ {
+ CLog::Log(LOGERROR, "CAEEncoderFFmpeg::Encode - Encoding failed");
+ return 0;
+ }
+
+ /* return the number of frames used */
+ return size;
+}
+
+
+int CAEEncoderFFmpeg::GetData(uint8_t **data)
+{
+ int size;
+ *data = m_Buffer;
+ size = m_BufferSize;
+ m_BufferSize = 0;
+ return size;
+}
+
+double CAEEncoderFFmpeg::GetDelay(unsigned int bufferSize)
+{
+ if (!m_CodecCtx)
+ return 0;
+
+ int frames = m_CodecCtx->delay;
+ if (m_BufferSize)
+ frames += m_NeededFrames;
+
+ return ((double)frames + ((double)bufferSize * m_OutputRatio)) * m_SampleRateMul;
+}
+
diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h
new file mode 100644
index 0000000..3274d3a
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AEEncoder.h"
+
+extern "C" {
+#include <libswresample/swresample.h>
+}
+
+/* ffmpeg re-defines this, so undef it to squash the warning */
+#undef restrict
+
+class CAEEncoderFFmpeg: public IAEEncoder
+{
+public:
+ CAEEncoderFFmpeg();
+ ~CAEEncoderFFmpeg() override;
+
+ bool IsCompatible(const AEAudioFormat& format) override;
+ bool Initialize(AEAudioFormat &format, bool allow_planar_input = false) override;
+ void Reset() override;
+
+ unsigned int GetBitRate() override;
+ AVCodecID GetCodecID() override;
+ unsigned int GetFrames() override;
+
+ int Encode(uint8_t *in, int in_size, uint8_t *out, int out_size) override;
+ int GetData(uint8_t **data) override;
+ double GetDelay(unsigned int bufferSize) override;
+private:
+ unsigned int BuildChannelLayout(const int64_t ffmap, CAEChannelInfo& layout);
+
+ std::string m_CodecName;
+ AVCodecID m_CodecID;
+ unsigned int m_BitRate = 0;
+ AEAudioFormat m_CurrentFormat;
+ AVCodecContext *m_CodecCtx;
+ SwrContext *m_SwrCtx;
+ CAEChannelInfo m_Layout;
+ uint8_t m_Buffer[8 + AV_INPUT_BUFFER_MIN_SIZE];
+ int m_BufferSize = 0;
+ int m_OutputSize = 0;
+ double m_OutputRatio = 0.0;
+ double m_SampleRateMul = 0.0;
+ unsigned int m_NeededFrames = 0;
+ bool m_NeedConversion = false;
+};
+
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
new file mode 100644
index 0000000..9053603
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
@@ -0,0 +1,3498 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAE.h"
+
+#include <mutex>
+
+using namespace AE;
+using namespace ActiveAE;
+#include "ActiveAESettings.h"
+#include "ActiveAESound.h"
+#include "ActiveAEStream.h"
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/IAudioCallback.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/AudioEngine/Utils/AEStreamData.h"
+#include "cores/AudioEngine/Utils/AEStreamInfo.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Encoders/AEEncoderFFmpeg.h"
+
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "windowing/WinSystem.h"
+#include "utils/log.h"
+
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr float MAX_CACHE_LEVEL = 0.4f; // total cache time of stream in seconds;
+constexpr float MAX_WATER_LEVEL = 0.2f; // buffered time after stream stages in seconds;
+constexpr double MAX_BUFFER_TIME = 0.1; // max time of a buffer in seconds;
+} // unnamed namespace
+
+void CEngineStats::Reset(unsigned int sampleRate, bool pcm)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_sinkDelay.SetDelay(0.0);
+ m_sinkSampleRate = sampleRate;
+ m_bufferedSamples = 0;
+ m_suspended = false;
+ m_pcmOutput = pcm;
+}
+
+void CEngineStats::UpdateSinkDelay(const AEDelayStatus& status, int samples)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_sinkDelay = status;
+ if (samples > m_bufferedSamples)
+ {
+ CLog::Log(LOGERROR, "CEngineStats::UpdateSinkDelay - inconsistency in buffer time");
+ }
+ else
+ m_bufferedSamples -= samples;
+}
+
+void CEngineStats::AddSamples(int samples, const std::list<CActiveAEStream*>& streams)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_bufferedSamples += samples;
+
+ for (auto stream : streams)
+ {
+ UpdateStream(stream);
+ }
+}
+
+void CEngineStats::GetDelay(AEDelayStatus& status)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ status = m_sinkDelay;
+ if (m_pcmOutput)
+ status.delay += (double)m_bufferedSamples / m_sinkSampleRate;
+ else
+ status.delay +=
+ static_cast<double>(m_bufferedSamples) * m_sinkFormat.m_streamInfo.GetDuration() / 1000;
+}
+
+void CEngineStats::AddStream(unsigned int streamid)
+{
+ StreamStats stream;
+ stream.m_streamId = streamid;
+ stream.m_bufferedTime = 0;
+ stream.m_resampleRatio = 1.0;
+ stream.m_syncError = 0;
+ stream.m_syncState = CAESyncInfo::AESyncState::SYNC_OFF;
+ m_streamStats.push_back(stream);
+}
+
+void CEngineStats::RemoveStream(unsigned int streamid)
+{
+ for (auto it = m_streamStats.begin(); it != m_streamStats.end(); ++it)
+ {
+ if (it->m_streamId == streamid)
+ {
+ m_streamStats.erase(it);
+ return;
+ }
+ }
+}
+
+void CEngineStats::UpdateStream(CActiveAEStream *stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ for (auto &str : m_streamStats)
+ {
+ if (str.m_streamId == stream->m_id)
+ {
+ float delay = 0;
+ str.m_syncState = stream->m_syncState;
+ str.m_syncError = stream->m_syncError.GetLastError(str.m_errorTime);
+ if (stream->m_processingBuffers)
+ {
+ str.m_resampleRatio = stream->m_processingBuffers->GetRR();
+ delay += stream->m_processingBuffers->GetDelay();
+ }
+ else
+ {
+ str.m_resampleRatio = 1.0;
+ }
+
+ std::unique_lock<CCriticalSection> lock(stream->m_statsLock);
+ std::deque<CSampleBuffer*>::iterator itBuf;
+ for(itBuf=stream->m_processingSamples.begin(); itBuf!=stream->m_processingSamples.end(); ++itBuf)
+ {
+ if (m_pcmOutput)
+ delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate;
+ else
+ delay += static_cast<float>(m_sinkFormat.m_streamInfo.GetDuration() / 1000.0);
+ }
+ str.m_bufferedTime = static_cast<double>(delay);
+ stream->m_bufferedTime = 0;
+ break;
+ }
+ }
+}
+
+// this is used to sync a/v so we need to add sink latency here
+void CEngineStats::GetDelay(AEDelayStatus& status, CActiveAEStream *stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ status = m_sinkDelay;
+ status.delay += static_cast<double>(m_sinkLatency);
+ if (m_pcmOutput)
+ status.delay += (double)m_bufferedSamples / m_sinkSampleRate;
+ else
+ status.delay +=
+ static_cast<double>(m_bufferedSamples) * m_sinkFormat.m_streamInfo.GetDuration() / 1000;
+
+ if (!m_pcmOutput && m_sinkNeedIecPack &&
+ m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ // take into account MAT packer latency (half duration of MAT frame)
+ status.delay += m_sinkFormat.m_streamInfo.GetDuration() / 1000 / 2;
+ }
+
+ for (auto &str : m_streamStats)
+ {
+ if (str.m_streamId == stream->m_id)
+ {
+ std::unique_lock<CCriticalSection> lock(stream->m_statsLock);
+ float buffertime = static_cast<float>(str.m_bufferedTime) + stream->m_bufferedTime;
+ status.delay += static_cast<double>(buffertime) / str.m_resampleRatio;
+ return;
+ }
+ }
+}
+
+// this is used to sync a/v so we need to add sink latency here
+void CEngineStats::GetSyncInfo(CAESyncInfo& info, CActiveAEStream *stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ AEDelayStatus status;
+ status = m_sinkDelay;
+ if (m_pcmOutput)
+ status.delay += (double)m_bufferedSamples / m_sinkSampleRate;
+ else
+ status.delay +=
+ static_cast<double>(m_bufferedSamples) * m_sinkFormat.m_streamInfo.GetDuration() / 1000;
+
+ status.delay += static_cast<double>(m_sinkLatency);
+
+ if (!m_pcmOutput && m_sinkNeedIecPack &&
+ m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ // take into account MAT packer latency (half duration of MAT frame)
+ status.delay += m_sinkFormat.m_streamInfo.GetDuration() / 1000 / 2;
+ }
+
+ for (auto &str : m_streamStats)
+ {
+ if (str.m_streamId == stream->m_id)
+ {
+ std::unique_lock<CCriticalSection> lock(stream->m_statsLock);
+ float buffertime = static_cast<float>(str.m_bufferedTime) + stream->m_bufferedTime;
+ status.delay += static_cast<double>(buffertime) / str.m_resampleRatio;
+ info.delay = status.GetDelay();
+ info.error = str.m_syncError;
+ info.errortime = str.m_errorTime;
+ info.state = str.m_syncState;
+ info.rr = str.m_resampleRatio;
+ return;
+ }
+ }
+}
+
+float CEngineStats::GetCacheTime(CActiveAEStream *stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ float delay = 0;
+
+ for (auto &str : m_streamStats)
+ {
+ if (str.m_streamId == stream->m_id)
+ {
+ std::unique_lock<CCriticalSection> lock(stream->m_statsLock);
+ float buffertime = static_cast<float>(str.m_bufferedTime) + stream->m_bufferedTime;
+ delay += buffertime / static_cast<float>(str.m_resampleRatio);
+ break;
+ }
+ }
+ return delay;
+}
+
+float CEngineStats::GetCacheTotal()
+{
+ return MAX_CACHE_LEVEL;
+}
+
+float CEngineStats::GetMaxDelay() const
+{
+ return MAX_CACHE_LEVEL + MAX_WATER_LEVEL + m_sinkCacheTotal;
+}
+
+float CEngineStats::GetWaterLevel()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ if (m_pcmOutput)
+ return static_cast<float>(m_bufferedSamples) / m_sinkSampleRate;
+ else
+ return static_cast<float>(m_bufferedSamples * m_sinkFormat.m_streamInfo.GetDuration()) / 1000;
+}
+
+void CEngineStats::SetSuspended(bool state)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_suspended = state;
+}
+
+bool CEngineStats::IsSuspended()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return m_suspended;
+}
+
+void CEngineStats::SetCurrentSinkFormat(const AEAudioFormat& SinkFormat)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_sinkFormat = SinkFormat;
+}
+
+AEAudioFormat CEngineStats::GetCurrentSinkFormat()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return m_sinkFormat;
+}
+
+CActiveAE::CActiveAE() :
+ CThread("ActiveAE"),
+ m_controlPort("OutputControlPort", &m_inMsgEvent, &m_outMsgEvent),
+ m_dataPort("OutputDataPort", &m_inMsgEvent, &m_outMsgEvent),
+ m_sink(&m_outMsgEvent)
+{
+ m_sinkBuffers = NULL;
+ m_silenceBuffers = NULL;
+ m_encoderBuffers = NULL;
+ m_vizBuffers = NULL;
+ m_vizBuffersInput = NULL;
+ m_volume = 1.0;
+ m_volumeScaled = 1.0;
+ m_aeVolume = 1.0;
+ m_muted = false;
+ m_aeMuted = false;
+ m_mode = MODE_PCM;
+ m_encoder = NULL;
+ m_vizInitialized = false;
+ m_sinkHasVolume = false;
+ m_aeGUISoundForce = false;
+ m_stats.Reset(44100, true);
+ m_streamIdGen = 0;
+
+ m_settingsHandler.reset(new CActiveAESettings(*this));
+}
+
+CActiveAE::~CActiveAE()
+{
+ m_settingsHandler.reset();
+
+ Dispose();
+}
+
+void CActiveAE::Dispose()
+{
+ if (m_isWinSysReg)
+ {
+ CWinSystemBase *winsystem = CServiceBroker::GetWinSystem();
+ if (winsystem)
+ winsystem->Unregister(this);
+ m_isWinSysReg = false;
+ }
+
+ m_bStop = true;
+ m_outMsgEvent.Set();
+ StopThread();
+ m_controlPort.Purge();
+ m_dataPort.Purge();
+ m_sink.Dispose();
+}
+
+//-----------------------------------------------------------------------------
+// Behavior
+//-----------------------------------------------------------------------------
+
+enum AE_STATES
+{
+ AE_TOP = 0, // 0
+ AE_TOP_WAIT_PRECOND, // 1
+ AE_TOP_ERROR, // 2
+ AE_TOP_UNCONFIGURED, // 3
+ AE_TOP_RECONFIGURING, // 4
+ AE_TOP_CONFIGURED, // 5
+ AE_TOP_CONFIGURED_SUSPEND, // 6
+ AE_TOP_CONFIGURED_IDLE, // 6
+ AE_TOP_CONFIGURED_PLAY, // 7
+};
+
+int AE_parentStates[] = {
+ -1,
+ 0, //TOP_ERROR
+ 0, //AE_TOP_WAIT_PRECOND
+ 0, //TOP_UNCONFIGURED
+ 0, //TOP_CONFIGURED
+ 0, //TOP_RECONFIGURING
+ 5, //TOP_CONFIGURED_SUSPEND
+ 5, //TOP_CONFIGURED_IDLE
+ 5, //TOP_CONFIGURED_PLAY
+};
+
+void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg)
+{
+ for (int state = m_state; ; state = AE_parentStates[state])
+ {
+ switch (state)
+ {
+ case AE_TOP: // TOP
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::GETSTATE:
+ msg->Reply(CActiveAEControlProtocol::ACC, &m_state, sizeof(m_state));
+ return;
+ case CActiveAEControlProtocol::VOLUME:
+ m_volume = *(float*)msg->data;
+ m_volumeScaled = CAEUtil::GainToScale(CAEUtil::PercentToGain(m_volume));
+ if (m_sinkHasVolume)
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float));
+ return;
+ case CActiveAEControlProtocol::MUTE:
+ m_muted = *(bool*)msg->data;
+ return;
+ case CActiveAEControlProtocol::KEEPCONFIG:
+ m_extKeepConfig = std::chrono::milliseconds(*reinterpret_cast<unsigned int*>(msg->data));
+ return;
+ case CActiveAEControlProtocol::DISPLAYRESET:
+ return;
+ case CActiveAEControlProtocol::APPFOCUSED:
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::APPFOCUSED, msg->data, sizeof(bool));
+ return;
+ case CActiveAEControlProtocol::STREAMRESAMPLEMODE:
+ MsgStreamParameter *par;
+ par = reinterpret_cast<MsgStreamParameter*>(msg->data);
+ if (par->stream)
+ {
+ par->stream->m_resampleMode = par->parameter.int_par;
+ par->stream->m_resampleIntegral = 0.0;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEDataProtocol::NEWSOUND:
+ CActiveAESound *sound;
+ sound = *(CActiveAESound**)msg->data;
+ if (sound)
+ {
+ m_sounds.push_back(sound);
+ ResampleSounds();
+ }
+ return;
+ case CActiveAEDataProtocol::FREESTREAM:
+ MsgStreamFree *msgStreamFree;
+ msgStreamFree = *(MsgStreamFree**)msg->data;
+ DiscardStream(msgStreamFree->stream);
+ msg->Reply(CActiveAEDataProtocol::ACC);
+ return;
+ case CActiveAEDataProtocol::FREESOUND:
+ sound = *(CActiveAESound**)msg->data;
+ DiscardSound(sound);
+ return;
+ case CActiveAEDataProtocol::DRAINSTREAM:
+ CActiveAEStream *stream;
+ stream = *(CActiveAEStream**)msg->data;
+ stream->m_drain = true;
+ stream->m_processingBuffers->SetDrain(true);
+ msg->Reply(CActiveAEDataProtocol::ACC);
+ stream->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMDRAINED);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_sink.m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::RETURNSAMPLE:
+ CSampleBuffer **buffer;
+ buffer = (CSampleBuffer**)msg->data;
+ if (buffer)
+ {
+ (*buffer)->Return();
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ {
+ std::string portName = port == NULL ? "timer" : port->portName;
+ CLog::Log(LOGWARNING, "CActiveAE::{} - signal: {} from port: {} not handled for state: {}",
+ __FUNCTION__, signal, portName, m_state);
+ }
+ return;
+
+ case AE_TOP_WAIT_PRECOND:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::INIT:
+ LoadSettings();
+ if (!m_settings.device.empty() && CAESinkFactory::HasSinks())
+ {
+ m_state = AE_TOP_UNCONFIGURED;
+ m_bStateMachineSelfTrigger = true;
+ }
+ else
+ {
+ // Application can't handle error case and work without an AE
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ }
+ return;
+
+ case CActiveAEControlProtocol::DEVICECHANGE:
+ case CActiveAEControlProtocol::DEVICECOUNTCHANGE:
+ LoadSettings();
+ if (!m_settings.device.empty() && CAESinkFactory::HasSinks())
+ {
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::INIT);
+ }
+ return;
+
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_ERROR:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::TIMEOUT:
+ m_extError = false;
+ LoadSettings();
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_UNCONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::INIT:
+ m_extError = false;
+ m_sink.EnumerateSinkList(false, "");
+ LoadSettings();
+ Configure();
+ if (!m_isWinSysReg)
+ {
+ CWinSystemBase *winsystem = CServiceBroker::GetWinSystem();
+ if (winsystem)
+ {
+ winsystem->Register(this);
+ m_isWinSysReg = true;
+ }
+ }
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ return;
+
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_RECONFIGURING:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::TIMEOUT:
+ // drain
+ if (RunStages())
+ {
+ m_extTimeout = 0ms;
+ return;
+ }
+ if (!m_sinkBuffers->m_inputSamples.empty() || !m_sinkBuffers->m_outputSamples.empty())
+ {
+ m_extTimeout = 100ms;
+ return;
+ }
+ if (NeedReconfigureSink())
+ DrainSink();
+
+ if (!m_extError)
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ m_extDeferData = false;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_CONFIGURED:
+ if (port == &m_controlPort)
+ {
+ bool streaming;
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::RECONFIGURE:
+ {
+ if (m_streams.empty())
+ {
+ streaming = false;
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::STREAMING, &streaming, sizeof(bool));
+ }
+ LoadSettings();
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SETNOISETYPE, &m_settings.streamNoise, sizeof(bool));
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SETSILENCETIMEOUT,
+ &m_settings.silenceTimeoutMinutes, sizeof(int));
+ ChangeResamplers();
+ if (!NeedReconfigureBuffers() && !NeedReconfigureSink())
+ return;
+ m_state = AE_TOP_RECONFIGURING;
+ m_extTimeout = 0ms;
+ // don't accept any data until we are reconfigured
+ m_extDeferData = true;
+ return;
+ }
+ case CActiveAEControlProtocol::SUSPEND:
+ UnconfigureSink();
+ m_stats.SetSuspended(true);
+ m_state = AE_TOP_CONFIGURED_SUSPEND;
+ m_extDeferData = true;
+ m_extSuspended = true;
+ return;
+ case CActiveAEControlProtocol::DISPLAYLOST:
+ if (m_sink.GetDeviceType(m_mode == MODE_PCM ? m_settings.device : m_settings.passthroughdevice) == AE_DEVTYPE_HDMI)
+ {
+ UnconfigureSink();
+ m_stats.SetSuspended(true);
+ m_state = AE_TOP_CONFIGURED_SUSPEND;
+ m_extDeferData = true;
+ }
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ return;
+ case CActiveAEControlProtocol::DEVICECHANGE:
+ time_t now;
+ time(&now);
+ CLog::Log(LOGDEBUG,"CActiveAE - device change event");
+ while (!m_extLastDeviceChange.empty() && (now - m_extLastDeviceChange.front() > 0))
+ {
+ m_extLastDeviceChange.pop();
+ }
+ if (m_extLastDeviceChange.size() > 2)
+ {
+ CLog::Log(LOGWARNING, "CActiveAE - received {} device change events within one second",
+ m_extLastDeviceChange.size());
+ return;
+ }
+ m_extLastDeviceChange.push(now);
+ UnconfigureSink();
+ m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE);
+ m_sink.EnumerateSinkList(true, "");
+ LoadSettings();
+ m_extError = false;
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ return;
+ case CActiveAEControlProtocol::DEVICECOUNTCHANGE:
+ const char* param;
+ param = reinterpret_cast<const char*>(msg->data);
+ CLog::Log(LOGDEBUG, "CActiveAE - device count change event from driver: {}", param);
+ m_sink.EnumerateSinkList(true, param);
+ if (!m_sink.DeviceExist(m_settings.driver, m_currDevice))
+ {
+ UnconfigureSink();
+ LoadSettings();
+ m_extError = false;
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ }
+ return;
+ case CActiveAEControlProtocol::PAUSESTREAM:
+ CActiveAEStream *stream;
+ stream = *(CActiveAEStream**)msg->data;
+ if (!stream->m_paused && m_streams.size() == 1)
+ {
+ FlushEngine();
+ streaming = false;
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::STREAMING, &streaming, sizeof(bool));
+ }
+ stream->m_paused = true;
+ return;
+ case CActiveAEControlProtocol::RESUMESTREAM:
+ stream = *(CActiveAEStream**)msg->data;
+ if (stream->m_paused)
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_START;
+ stream->m_paused = false;
+ streaming = true;
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::STREAMING, &streaming, sizeof(bool));
+ m_extTimeout = 0ms;
+ return;
+ case CActiveAEControlProtocol::FLUSHSTREAM:
+ stream = *(CActiveAEStream**)msg->data;
+ SFlushStream(stream);
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ m_extTimeout = 0ms;
+ return;
+ case CActiveAEControlProtocol::STREAMAMP:
+ MsgStreamParameter *par;
+ par = reinterpret_cast<MsgStreamParameter*>(msg->data);
+ par->stream->m_limiter.SetAmplification(par->parameter.float_par);
+ par->stream->m_amplify = par->parameter.float_par;
+ return;
+ case CActiveAEControlProtocol::STREAMVOLUME:
+ par = reinterpret_cast<MsgStreamParameter*>(msg->data);
+ par->stream->m_volume = par->parameter.float_par;
+ return;
+ case CActiveAEControlProtocol::STREAMRGAIN:
+ par = reinterpret_cast<MsgStreamParameter*>(msg->data);
+ par->stream->m_rgain = par->parameter.float_par;
+ return;
+ case CActiveAEControlProtocol::STREAMRESAMPLERATIO:
+ par = reinterpret_cast<MsgStreamParameter*>(msg->data);
+ if (par->stream->m_processingBuffers)
+ {
+ par->stream->m_processingBuffers->SetRR(par->parameter.double_par, m_settings.atempoThreshold);
+ }
+ return;
+ case CActiveAEControlProtocol::STREAMFFMPEGINFO:
+ MsgStreamFFmpegInfo *info;
+ info = reinterpret_cast<MsgStreamFFmpegInfo*>(msg->data);
+ info->stream->m_profile = info->profile;
+ info->stream->m_matrixEncoding = info->matrix_encoding;
+ info->stream->m_audioServiceType = info->audio_service_type;
+ return;
+ case CActiveAEControlProtocol::STREAMFADE:
+ MsgStreamFade *fade;
+ fade = reinterpret_cast<MsgStreamFade*>(msg->data);
+ fade->stream->m_fadingBase = fade->from;
+ fade->stream->m_fadingTarget = fade->target;
+ fade->stream->m_fadingTime = fade->millis;
+ fade->stream->m_fadingSamples = -1;
+ return;
+ case CActiveAEControlProtocol::STOPSOUND:
+ CActiveAESound *sound;
+ sound = *(CActiveAESound**)msg->data;
+ SStopSound(sound);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEDataProtocol::PLAYSOUND:
+ CActiveAESound *sound;
+ sound = *(CActiveAESound**)msg->data;
+ if (sound)
+ {
+ if (m_settings.guisoundmode == AE_SOUND_OFF ||
+ (m_settings.guisoundmode == AE_SOUND_IDLE && !m_streams.empty()))
+ return;
+
+ SoundState st = {sound, 0};
+ m_sounds_playing.push_back(st);
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ }
+ return;
+ case CActiveAEDataProtocol::NEWSTREAM:
+ MsgStreamNew *streamMsg;
+ CActiveAEStream *stream;
+ streamMsg = reinterpret_cast<MsgStreamNew*>(msg->data);
+ stream = CreateStream(streamMsg);
+ if(stream)
+ {
+ msg->Reply(CActiveAEDataProtocol::ACC, &stream, sizeof(CActiveAEStream*));
+ LoadSettings();
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ }
+ else
+ msg->Reply(CActiveAEDataProtocol::ERR);
+ return;
+ case CActiveAEDataProtocol::STREAMSAMPLE:
+ MsgStreamSample *msgData;
+ CSampleBuffer *samples;
+ msgData = reinterpret_cast<MsgStreamSample*>(msg->data);
+ samples = msgData->stream->m_processingSamples.front();
+ msgData->stream->m_processingSamples.pop_front();
+ if (samples != msgData->buffer)
+ CLog::Log(LOGERROR, "CActiveAE - inconsistency in stream sample message");
+ if (msgData->buffer->pkt->nb_samples == 0)
+ msgData->buffer->Return();
+ else
+ msgData->stream->m_processingBuffers->m_inputSamples.push_back(msgData->buffer);
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ return;
+ case CActiveAEDataProtocol::FREESTREAM:
+ MsgStreamFree *msgStreamFree;
+ msgStreamFree = reinterpret_cast<MsgStreamFree*>(msg->data);
+ DiscardStream(msgStreamFree->stream);
+ msg->Reply(CActiveAEDataProtocol::ACC);
+ if (m_streams.empty())
+ {
+ if (m_extKeepConfig > 0ms)
+ m_extDrainTimer.Set(m_extKeepConfig);
+ else
+ {
+ AEDelayStatus status;
+ m_stats.GetDelay(status);
+ if (msgStreamFree->finish)
+ m_extDrainTimer.Set(
+ std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 1000)));
+ else
+ m_extDrainTimer.Set(
+ std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 1000)) + 1s);
+ }
+ m_extDrain = true;
+ }
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ return;
+ case CActiveAEDataProtocol::DRAINSTREAM:
+ stream = *(CActiveAEStream**)msg->data;
+ stream->m_drain = true;
+ stream->m_processingBuffers->SetDrain(true);
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ msg->Reply(CActiveAEDataProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_sink.m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::RETURNSAMPLE:
+ CSampleBuffer **buffer;
+ buffer = (CSampleBuffer**)msg->data;
+ if (buffer)
+ {
+ (*buffer)->Return();
+ }
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_CONFIGURED_SUSPEND:
+ if (port == &m_controlPort)
+ {
+ bool displayReset = false;
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::DISPLAYRESET:
+ if (m_extSuspended)
+ return;
+ CLog::Log(LOGDEBUG,"CActiveAE - display reset event");
+ displayReset = true;
+ [[fallthrough]];
+ case CActiveAEControlProtocol::INIT:
+ m_extError = false;
+ m_extSuspended = false;
+ if (!displayReset)
+ {
+ m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE);
+ m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECOUNTCHANGE);
+ m_sink.EnumerateSinkList(true, "");
+ LoadSettings();
+ }
+ Configure();
+ if (!displayReset)
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ m_stats.SetSuspended(false);
+ m_extDeferData = false;
+ return;
+ case CActiveAEControlProtocol::DEVICECHANGE:
+ case CActiveAEControlProtocol::DEVICECOUNTCHANGE:
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_sink.m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::RETURNSAMPLE:
+ CSampleBuffer **buffer;
+ buffer = (CSampleBuffer**)msg->data;
+ if (buffer)
+ {
+ (*buffer)->Return();
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::TIMEOUT:
+ m_extTimeout = 1000ms;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_CONFIGURED_IDLE:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::RESUMESTREAM:
+ CActiveAEStream *stream;
+ stream = *(CActiveAEStream**)msg->data;
+ stream->m_paused = false;
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_START;
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ return;
+ case CActiveAEControlProtocol::FLUSHSTREAM:
+ stream = *(CActiveAEStream**)msg->data;
+ SFlushStream(stream);
+ msg->Reply(CActiveAEControlProtocol::ACC);
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::TIMEOUT:
+ ResampleSounds();
+ ClearDiscardedBuffers();
+ if (m_extDrain)
+ {
+ if (m_extDrainTimer.IsTimePast())
+ {
+ Configure();
+ if (!m_extError)
+ {
+ m_state = AE_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 500ms;
+ }
+ }
+ else
+ m_extTimeout = m_extDrainTimer.GetTimeLeft();
+ }
+ else
+ m_extTimeout = 5000ms;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case AE_TOP_CONFIGURED_PLAY:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CActiveAEControlProtocol::TIMEOUT:
+ if (m_extError)
+ {
+ m_state = AE_TOP_ERROR;
+ m_extTimeout = 100ms;
+ return;
+ }
+ if (RunStages())
+ {
+ m_extTimeout = 0ms;
+ return;
+ }
+ if (!m_extDrain && HasWork())
+ {
+ ClearDiscardedBuffers();
+ m_extTimeout = 100ms;
+ return;
+ }
+ m_extTimeout = 0ms;
+ m_state = AE_TOP_CONFIGURED_IDLE;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default: // we are in no state, should not happen
+ CLog::Log(LOGERROR, "CActiveAE::{} - no valid state: {}", __FUNCTION__, m_state);
+ return;
+ }
+ } // for
+}
+
+void CActiveAE::Process()
+{
+ Message *msg = NULL;
+ Protocol *port = NULL;
+ bool gotMsg;
+ XbmcThreads::EndTime<> timer;
+
+ m_state = AE_TOP_WAIT_PRECOND;
+ m_extTimeout = 1000ms;
+ m_bStateMachineSelfTrigger = false;
+ m_extDrain = false;
+ m_extDeferData = false;
+ m_extKeepConfig = 0ms;
+
+ // start sink
+ m_sink.Start();
+
+ while (!m_bStop)
+ {
+ gotMsg = false;
+ timer.Set(m_extTimeout);
+
+ if (m_bStateMachineSelfTrigger)
+ {
+ m_bStateMachineSelfTrigger = false;
+ // self trigger state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+ // check control port
+ else if (m_controlPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_controlPort;
+ }
+ // check sink data port
+ else if (m_sink.m_dataPort.ReceiveInMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_sink.m_dataPort;
+ }
+ else if (!m_extDeferData)
+ {
+ // check data port
+ if (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ }
+ // stream data ports
+ else
+ {
+ std::list<CActiveAEStream*>::iterator it;
+ for(it=m_streams.begin(); it!=m_streams.end(); ++it)
+ {
+ if((*it)->m_streamPort->ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ break;
+ }
+ }
+ }
+ }
+
+ if (gotMsg)
+ {
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+
+ // wait for message
+ else if (m_outMsgEvent.Wait(m_extTimeout))
+ {
+ m_extTimeout = timer.GetTimeLeft();
+ continue;
+ }
+ // time out
+ else
+ {
+ msg = m_controlPort.GetMessage();
+ msg->signal = CActiveAEControlProtocol::TIMEOUT;
+ port = 0;
+ // signal timeout to state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ }
+ }
+}
+
+AEAudioFormat CActiveAE::GetInputFormat(AEAudioFormat *desiredFmt)
+{
+ AEAudioFormat inputFormat;
+
+ if (m_streams.empty())
+ {
+ inputFormat.m_dataFormat = AE_FMT_FLOAT;
+ inputFormat.m_sampleRate = 44100;
+ inputFormat.m_channelLayout = AE_CH_LAYOUT_2_0;
+ inputFormat.m_frames = 0;
+ inputFormat.m_frameSize = 0;
+ }
+ // force input format after unpausing slave
+ else if (desiredFmt != NULL)
+ {
+ inputFormat = *desiredFmt;
+ }
+ // keep format when having multiple streams
+ else if (m_streams.size() > 1 && m_silenceBuffers == NULL)
+ {
+ inputFormat = m_inputFormat;
+ }
+ else
+ {
+ inputFormat = m_streams.front()->m_format;
+ m_inputFormat = inputFormat;
+ }
+
+ return inputFormat;
+}
+
+void CActiveAE::Configure(AEAudioFormat *desiredFmt)
+{
+ bool initSink = false;
+
+ AEAudioFormat sinkInputFormat, inputFormat;
+ AEAudioFormat oldInternalFormat = m_internalFormat;
+ AEAudioFormat oldSinkRequestFormat = m_sinkRequestFormat;
+
+ inputFormat = GetInputFormat(desiredFmt);
+
+ m_sinkRequestFormat = inputFormat;
+ ApplySettingsToFormat(m_sinkRequestFormat, m_settings, (int*)&m_mode);
+ m_extKeepConfig = 0ms;
+
+ std::string device = (m_sinkRequestFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device;
+ std::string driver;
+ CAESinkFactory::ParseDevice(device, driver);
+ if ((!CompareFormat(m_sinkRequestFormat, m_sinkFormat) && !CompareFormat(m_sinkRequestFormat, oldSinkRequestFormat)) ||
+ m_currDevice.compare(device) != 0 ||
+ m_settings.driver.compare(driver) != 0)
+ {
+ FlushEngine();
+ if (!InitSink())
+ return;
+ m_settings.driver = driver;
+ m_currDevice = device;
+ initSink = true;
+ m_stats.Reset(m_sinkFormat.m_sampleRate, m_mode == MODE_PCM);
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float));
+
+ if (m_sinkRequestFormat.m_dataFormat != AE_FMT_RAW)
+ {
+ // limit buffer size in case of sink returns large buffer
+ double buffertime = (double)m_sinkFormat.m_frames / m_sinkFormat.m_sampleRate;
+ if (buffertime > MAX_BUFFER_TIME)
+ {
+ CLog::Log(LOGWARNING,
+ "ActiveAE::{} - sink returned large period time of {} ms, reducing to {} ms",
+ __FUNCTION__, (int)(buffertime * 1000), (int)(MAX_BUFFER_TIME * 1000));
+ m_sinkFormat.m_frames = MAX_BUFFER_TIME * m_sinkFormat.m_sampleRate;
+ }
+ }
+ }
+
+ if (m_silenceBuffers)
+ {
+ m_discardBufferPools.push_back(m_silenceBuffers);
+ m_silenceBuffers = NULL;
+ }
+
+ // buffers for driving gui sounds if no streams are active
+ if (m_streams.empty())
+ {
+ inputFormat = m_sinkFormat;
+ if (m_sinkFormat.m_channelLayout.Count() > m_sinkRequestFormat.m_channelLayout.Count())
+ {
+ inputFormat.m_channelLayout = m_sinkRequestFormat.m_channelLayout;
+ inputFormat.m_channelLayout.ResolveChannels(m_sinkFormat.m_channelLayout);
+ }
+ inputFormat.m_dataFormat = AE_FMT_FLOAT;
+ inputFormat.m_frameSize = inputFormat.m_channelLayout.Count() *
+ (CAEUtil::DataFormatToBits(inputFormat.m_dataFormat) >> 3);
+ m_silenceBuffers = new CActiveAEBufferPool(inputFormat);
+ m_silenceBuffers->Create(MAX_WATER_LEVEL*1000);
+ sinkInputFormat = inputFormat;
+ m_internalFormat = inputFormat;
+
+ bool streaming = false;
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::STREAMING, &streaming, sizeof(bool));
+
+ delete m_encoder;
+ m_encoder = NULL;
+
+ if (m_encoderBuffers)
+ {
+ m_discardBufferPools.push_back(m_encoderBuffers);
+ m_encoderBuffers = NULL;
+ }
+ if (m_vizBuffers)
+ {
+ m_discardBufferPools.push_back(m_vizBuffers);
+ m_vizBuffers = NULL;
+ }
+ if (m_vizBuffersInput)
+ {
+ m_discardBufferPools.push_back(m_vizBuffersInput);
+ m_vizBuffersInput = NULL;
+ }
+ }
+ // resample buffers for streams
+ else
+ {
+ bool streaming = true;
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::STREAMING, &streaming, sizeof(bool));
+
+ AEAudioFormat outputFormat;
+ if (m_mode == MODE_RAW)
+ {
+ inputFormat.m_frames = m_sinkFormat.m_frames;
+ outputFormat = inputFormat;
+ sinkInputFormat = m_sinkFormat;
+ }
+ // transcode everything with more than 2 channels
+ else if (m_mode == MODE_TRANSCODE)
+ {
+ outputFormat = inputFormat;
+ outputFormat.m_dataFormat = AE_FMT_FLOATP;
+ outputFormat.m_sampleRate = 48000;
+
+ // setup encoder
+ if (!m_encoder)
+ {
+ m_encoder = new CAEEncoderFFmpeg();
+ m_encoder->Initialize(outputFormat, true);
+ m_encoderFormat = outputFormat;
+ }
+ else
+ outputFormat = m_encoderFormat;
+
+ outputFormat.m_channelLayout = m_encoderFormat.m_channelLayout;
+ outputFormat.m_frames = m_encoderFormat.m_frames;
+
+ // encoder buffer
+ if (m_encoder->GetCodecID() == AV_CODEC_ID_AC3)
+ {
+ AEAudioFormat format;
+ format.m_channelLayout += AE_CH_FC;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_sampleRate = 48000;
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ format.m_streamInfo.m_channels = 2;
+ format.m_streamInfo.m_sampleRate = 48000;
+ format.m_streamInfo.m_ac3FrameSize = m_encoderFormat.m_frames;
+ //! @todo implement
+ if (m_encoderBuffers && initSink)
+ {
+ m_discardBufferPools.push_back(m_encoderBuffers);
+ m_encoderBuffers = NULL;
+ }
+ if (!m_encoderBuffers)
+ {
+ m_encoderBuffers = new CActiveAEBufferPool(format);
+ m_encoderBuffers->Create(MAX_WATER_LEVEL*1000);
+ }
+ }
+
+ sinkInputFormat = m_sinkFormat;
+ }
+ else
+ {
+ outputFormat = m_sinkFormat;
+ outputFormat.m_dataFormat = AE_IS_PLANAR(outputFormat.m_dataFormat) ? AE_FMT_FLOATP : AE_FMT_FLOAT;
+ outputFormat.m_frameSize = outputFormat.m_channelLayout.Count() *
+ (CAEUtil::DataFormatToBits(outputFormat.m_dataFormat) >> 3);
+
+ // due to channel ordering of the driver, a sink may return more channels than
+ // requested, i.e. 2.1 request returns FL,FR,BL,BR,FC,LFE for ALSA
+ // in this case we need to downmix to requested format
+ if (m_sinkFormat.m_channelLayout.Count() > m_sinkRequestFormat.m_channelLayout.Count())
+ {
+ outputFormat.m_channelLayout = m_sinkRequestFormat.m_channelLayout;
+ outputFormat.m_channelLayout.ResolveChannels(m_sinkFormat.m_channelLayout);
+ }
+
+ // internally we use ffmpeg layouts, means that layout won't change in resample
+ // stage. preserve correct layout for sink stage where remapping is done
+ uint64_t avlayout = CAEUtil::GetAVChannelLayout(outputFormat.m_channelLayout);
+ outputFormat.m_channelLayout = CAEUtil::GetAEChannelLayout(avlayout);
+
+ //! @todo adjust to decoder
+ sinkInputFormat = outputFormat;
+ }
+ m_internalFormat = outputFormat;
+
+ std::list<CActiveAEStream*>::iterator it;
+ for(it=m_streams.begin(); it!=m_streams.end(); ++it)
+ {
+ if (!(*it)->m_inputBuffers)
+ {
+ // align input buffers with period of sink or encoder
+ (*it)->m_format.m_frames = m_internalFormat.m_frames * ((float)(*it)->m_format.m_sampleRate / m_internalFormat.m_sampleRate);
+
+ // create buffer pool
+ (*it)->m_inputBuffers = new CActiveAEBufferPool((*it)->m_format);
+ (*it)->m_inputBuffers->Create(MAX_CACHE_LEVEL*1000);
+ (*it)->m_streamSpace = (*it)->m_format.m_frameSize * (*it)->m_format.m_frames;
+
+ // if input format does not follow ffmpeg channel mask, we may need to remap channels
+ (*it)->InitRemapper();
+ }
+ if (initSink && (*it)->m_processingBuffers)
+ {
+ (*it)->m_processingBuffers->Flush();
+ m_discardBufferPools.push_back((*it)->m_processingBuffers->GetResampleBuffers());
+ m_discardBufferPools.push_back((*it)->m_processingBuffers->GetAtempoBuffers());
+ delete (*it)->m_processingBuffers;
+ (*it)->m_processingBuffers = nullptr;
+ }
+ if (!(*it)->m_processingBuffers)
+ {
+ (*it)->m_processingBuffers = new CActiveAEStreamBuffers((*it)->m_inputBuffers->m_format, outputFormat, m_settings.resampleQuality);
+ (*it)->m_processingBuffers->ForceResampler((*it)->m_forceResampler);
+
+ (*it)->m_processingBuffers->Create(MAX_CACHE_LEVEL*1000, false, m_settings.stereoupmix, m_settings.normalizelevels);
+ }
+ if (m_mode == MODE_TRANSCODE || m_streams.size() > 1)
+ (*it)->m_processingBuffers->FillBuffer();
+
+ // amplification
+ (*it)->m_limiter.SetSamplerate(outputFormat.m_sampleRate);
+ }
+
+ // update buffered time of streams
+ m_stats.AddSamples(0, m_streams);
+
+ // buffers for viz
+ if (!(inputFormat.m_dataFormat == AE_FMT_RAW))
+ {
+ if (initSink && m_vizBuffers)
+ {
+ m_discardBufferPools.push_back(m_vizBuffers);
+ m_vizBuffers = NULL;
+ m_discardBufferPools.push_back(m_vizBuffersInput);
+ m_vizBuffersInput = NULL;
+ }
+ if (!m_vizBuffers && !m_audioCallback.empty())
+ {
+ AEAudioFormat vizFormat = m_internalFormat;
+ vizFormat.m_channelLayout = AE_CH_LAYOUT_2_0;
+ vizFormat.m_dataFormat = AE_FMT_FLOAT;
+ vizFormat.m_sampleRate = 44100;
+ vizFormat.m_frames =
+ m_internalFormat.m_frames *
+ (static_cast<float>(vizFormat.m_sampleRate) / m_internalFormat.m_sampleRate);
+
+ // input buffers
+ m_vizBuffersInput = new CActiveAEBufferPool(m_internalFormat);
+ m_vizBuffersInput->Create(2000 + m_stats.GetMaxDelay() * 1000);
+
+ // resample buffers
+ m_vizBuffers = new CActiveAEBufferPoolResample(m_internalFormat, vizFormat, m_settings.resampleQuality);
+ //! @todo use cache of sync + water level
+ m_vizBuffers->Create(2000 + m_stats.GetMaxDelay() * 1000, false, false);
+ m_vizInitialized = false;
+ }
+ }
+
+ // buffers need to sync
+ m_silenceBuffers = new CActiveAEBufferPool(outputFormat);
+ m_silenceBuffers->Create(500);
+ }
+
+ // resample buffers for sink
+ if (m_sinkBuffers &&
+ (!CompareFormat(m_sinkBuffers->m_format,m_sinkFormat) ||
+ !CompareFormat(m_sinkBuffers->m_inputFormat, sinkInputFormat) ||
+ m_sinkBuffers->m_format.m_frames != m_sinkFormat.m_frames))
+ {
+ m_discardBufferPools.push_back(m_sinkBuffers);
+ m_sinkBuffers = NULL;
+ }
+ if (!m_sinkBuffers)
+ {
+ m_sinkBuffers = new CActiveAEBufferPoolResample(sinkInputFormat, m_sinkFormat, m_settings.resampleQuality);
+ m_sinkBuffers->Create(MAX_WATER_LEVEL*1000, true, false);
+ }
+
+ // reset gui sounds
+ if (!CompareFormat(oldInternalFormat, m_internalFormat))
+ {
+ if (m_settings.guisoundmode == AE_SOUND_ALWAYS ||
+ (m_settings.guisoundmode == AE_SOUND_IDLE && m_streams.empty()) ||
+ m_aeGUISoundForce)
+ {
+ std::vector<CActiveAESound*>::iterator it;
+ for (it = m_sounds.begin(); it != m_sounds.end(); ++it)
+ {
+ (*it)->SetConverted(false);
+ }
+ }
+ m_sounds_playing.clear();
+ }
+
+ ClearDiscardedBuffers();
+ m_extDrain = false;
+}
+
+CActiveAEStream* CActiveAE::CreateStream(MsgStreamNew *streamMsg)
+{
+ // we only can handle a single pass through stream
+ bool hasRawStream = false;
+ bool hasStream = false;
+ std::list<CActiveAEStream*>::iterator it;
+ for(it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if ((*it)->IsDrained())
+ continue;
+ if ((*it)->m_format.m_dataFormat == AE_FMT_RAW)
+ hasRawStream = true;
+ hasStream = true;
+ }
+ if (hasRawStream || (hasStream && (streamMsg->format.m_dataFormat == AE_FMT_RAW)))
+ {
+ return NULL;
+ }
+
+ // create the stream
+ CActiveAEStream *stream;
+ stream = new CActiveAEStream(&streamMsg->format, m_streamIdGen++, this);
+ stream->m_streamPort = new CActiveAEDataProtocol("stream",
+ &stream->m_inMsgEvent, &m_outMsgEvent);
+
+ // create buffer pool
+ stream->m_inputBuffers = NULL; // create in Configure when we know the sink format
+ stream->m_processingBuffers = NULL; // create in Configure when we know the sink format
+ stream->m_fadingSamples = 0;
+ stream->m_started = false;
+ stream->m_resampleMode = 0;
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_OFF;
+
+ if (streamMsg->options & AESTREAM_PAUSED)
+ {
+ stream->m_paused = true;
+ stream->m_streamIsBuffering = true;
+ }
+
+ if (streamMsg->options & AESTREAM_FORCE_RESAMPLE)
+ stream->m_forceResampler = true;
+
+ stream->m_pClock = streamMsg->clock;
+
+ m_streams.push_back(stream);
+ m_stats.AddStream(stream->m_id);
+
+ return stream;
+}
+
+void CActiveAE::DiscardStream(CActiveAEStream *stream)
+{
+ std::list<CActiveAEStream*>::iterator it;
+ for (it=m_streams.begin(); it!=m_streams.end(); )
+ {
+ if (stream == (*it))
+ {
+ while (!(*it)->m_processingSamples.empty())
+ {
+ (*it)->m_processingSamples.front()->Return();
+ (*it)->m_processingSamples.pop_front();
+ }
+ if ((*it)->m_inputBuffers)
+ m_discardBufferPools.push_back((*it)->m_inputBuffers);
+ if ((*it)->m_processingBuffers)
+ {
+ (*it)->m_processingBuffers->Flush();
+ m_discardBufferPools.push_back((*it)->m_processingBuffers->GetResampleBuffers());
+ m_discardBufferPools.push_back((*it)->m_processingBuffers->GetAtempoBuffers());
+ }
+ delete (*it)->m_processingBuffers;
+ CLog::Log(LOGDEBUG, "CActiveAE::DiscardStream - audio stream deleted");
+ m_stats.RemoveStream((*it)->m_id);
+ delete (*it)->m_streamPort;
+ delete (*it);
+ it = m_streams.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ ClearDiscardedBuffers();
+}
+
+void CActiveAE::SFlushStream(CActiveAEStream *stream)
+{
+ while (!stream->m_processingSamples.empty())
+ {
+ stream->m_processingSamples.front()->Return();
+ stream->m_processingSamples.pop_front();
+ }
+ stream->m_processingBuffers->Flush();
+ stream->m_streamPort->Purge();
+ stream->m_bufferedTime = 0.0;
+ stream->m_paused = false;
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_START;
+ stream->m_syncError.Flush();
+ stream->ResetFreeBuffers();
+
+ // flush the engine if we only have a single stream
+ if (m_streams.size() == 1)
+ {
+ FlushEngine();
+ }
+
+ m_stats.UpdateStream(stream);
+}
+
+void CActiveAE::FlushEngine()
+{
+ if (m_sinkBuffers)
+ m_sinkBuffers->Flush();
+ if (m_vizBuffers)
+ m_vizBuffers->Flush();
+
+ // send message to sink
+ Message *reply;
+ if (m_sink.m_controlPort.SendOutMessageSync(CSinkControlProtocol::FLUSH,
+ &reply, 2000))
+ {
+ bool success = reply->signal == CSinkControlProtocol::ACC;
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error on flush", __FUNCTION__);
+ m_extError = true;
+ }
+ reply->Release();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to flush", __FUNCTION__);
+ m_extError = true;
+ }
+ m_stats.Reset(m_sinkFormat.m_sampleRate, m_mode == MODE_PCM);
+}
+
+void CActiveAE::ClearDiscardedBuffers()
+{
+ auto it = m_discardBufferPools.begin();
+ while (it != m_discardBufferPools.end())
+ {
+ CActiveAEBufferPoolResample *rbuf = dynamic_cast<CActiveAEBufferPoolResample*>(*it);
+ if (rbuf)
+ {
+ rbuf->Flush();
+ }
+ // if all buffers have returned, we can delete the buffer pool
+ if ((*it)->m_allSamples.size() == (*it)->m_freeSamples.size())
+ {
+ delete (*it);
+ CLog::Log(LOGDEBUG, "CActiveAE::ClearDiscardedBuffers - buffer pool deleted");
+ it = m_discardBufferPools.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void CActiveAE::SStopSound(CActiveAESound *sound)
+{
+ std::list<SoundState>::iterator it;
+ for (it=m_sounds_playing.begin(); it!=m_sounds_playing.end(); ++it)
+ {
+ if (it->sound == sound)
+ {
+ if (sound->GetChannel() != AE_CH_NULL)
+ m_aeGUISoundForce = false;
+ m_sounds_playing.erase(it);
+ return;
+ }
+ }
+}
+
+void CActiveAE::DiscardSound(CActiveAESound *sound)
+{
+ SStopSound(sound);
+
+ std::vector<CActiveAESound*>::iterator it;
+ for (it=m_sounds.begin(); it!=m_sounds.end(); ++it)
+ {
+ if ((*it) == sound)
+ {
+ m_sounds.erase(it);
+ delete sound;
+ return;
+ }
+ }
+}
+
+void CActiveAE::ChangeResamplers()
+{
+ std::list<CActiveAEStream*>::iterator it;
+ for(it=m_streams.begin(); it!=m_streams.end(); ++it)
+ {
+ (*it)->m_processingBuffers->ConfigureResampler(m_settings.normalizelevels, m_settings.stereoupmix, m_settings.resampleQuality);
+ }
+}
+
+void CActiveAE::ApplySettingsToFormat(AEAudioFormat& format,
+ const AudioSettings& settings,
+ int* mode)
+{
+ int oldMode = m_mode;
+ if (mode)
+ *mode = MODE_PCM;
+
+ // raw pass through
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ if (mode)
+ *mode = MODE_RAW;
+ }
+ // transcode
+ else if (settings.channels <= AE_CH_LAYOUT_2_0 && // no multichannel pcm
+ settings.passthrough &&
+ settings.ac3passthrough &&
+ settings.ac3transcode &&
+ !m_streams.empty() &&
+ (format.m_channelLayout.Count() > 2 || settings.stereoupmix))
+ {
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_sampleRate = 48000;
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ format.m_streamInfo.m_channels = 2;
+ format.m_streamInfo.m_sampleRate = 48000;
+ if (mode)
+ *mode = MODE_TRANSCODE;
+ }
+ else
+ {
+ format.m_dataFormat = AE_IS_PLANAR(format.m_dataFormat) ? AE_FMT_FLOATP : AE_FMT_FLOAT;
+ // consider user channel layout for those cases
+ // 1. input stream is multichannel
+ // 2. stereo upmix is selected
+ // 3. fixed mode
+ if ((format.m_channelLayout.Count() > 2) ||
+ settings.stereoupmix ||
+ (settings.config == AE_CONFIG_FIXED))
+ {
+ CAEChannelInfo stdLayout;
+ switch (settings.channels)
+ {
+ default:
+ case 0: stdLayout = AE_CH_LAYOUT_2_0; break;
+ case 1: stdLayout = AE_CH_LAYOUT_2_0; break;
+ case 2: stdLayout = AE_CH_LAYOUT_2_1; break;
+ case 3: stdLayout = AE_CH_LAYOUT_3_0; break;
+ case 4: stdLayout = AE_CH_LAYOUT_3_1; break;
+ case 5: stdLayout = AE_CH_LAYOUT_4_0; break;
+ case 6: stdLayout = AE_CH_LAYOUT_4_1; break;
+ case 7: stdLayout = AE_CH_LAYOUT_5_0; break;
+ case 8: stdLayout = AE_CH_LAYOUT_5_1; break;
+ case 9: stdLayout = AE_CH_LAYOUT_7_0; break;
+ case 10: stdLayout = AE_CH_LAYOUT_7_1; break;
+ }
+
+ if (m_settings.config == AE_CONFIG_FIXED || (settings.stereoupmix && format.m_channelLayout.Count() <= 2))
+ format.m_channelLayout = stdLayout;
+ else if ((m_extKeepConfig > 0ms) && (settings.config == AE_CONFIG_AUTO) &&
+ (oldMode != MODE_RAW))
+ format.m_channelLayout = m_internalFormat.m_channelLayout;
+ else
+ {
+ if (stdLayout == AE_CH_LAYOUT_5_0 || stdLayout == AE_CH_LAYOUT_5_1)
+ {
+ std::vector<CAEChannelInfo> alts;
+ alts.push_back(stdLayout);
+ stdLayout.ReplaceChannel(AE_CH_BL, AE_CH_SL);
+ stdLayout.ReplaceChannel(AE_CH_BR, AE_CH_SR);
+ alts.push_back(stdLayout);
+ int bestMatch = format.m_channelLayout.BestMatch(alts);
+ stdLayout = alts[bestMatch];
+ }
+ format.m_channelLayout.ResolveChannels(stdLayout);
+ }
+ }
+ // don't change from multi to stereo in AUTO mode
+ else if ((settings.config == AE_CONFIG_AUTO) &&
+ m_stats.GetWaterLevel() > 0 && m_internalFormat.m_channelLayout.Count() > 2)
+ {
+ format.m_channelLayout = m_internalFormat.m_channelLayout;
+ }
+
+ if (m_sink.GetDeviceType(m_settings.device) == AE_DEVTYPE_IEC958)
+ {
+ if (format.m_sampleRate > m_settings.samplerate)
+ {
+ format.m_sampleRate = m_settings.samplerate;
+ CLog::Log(LOGINFO, "CActiveAE::ApplySettings - limit samplerate for SPDIF to {}",
+ format.m_sampleRate);
+ }
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ }
+
+ if (m_settings.config == AE_CONFIG_FIXED)
+ {
+ format.m_sampleRate = m_settings.samplerate;
+ format.m_dataFormat = AE_FMT_FLOAT;
+ CLog::Log(LOGINFO, "CActiveAE::ApplySettings - Forcing samplerate to {}",
+ format.m_sampleRate);
+ }
+
+ // sinks may not support mono
+ if (format.m_channelLayout.Count() == 1)
+ {
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ }
+ }
+}
+
+bool CActiveAE::NeedReconfigureBuffers()
+{
+ AEAudioFormat newFormat = GetInputFormat();
+ ApplySettingsToFormat(newFormat, m_settings);
+
+ return newFormat.m_dataFormat != m_sinkRequestFormat.m_dataFormat ||
+ newFormat.m_channelLayout != m_sinkRequestFormat.m_channelLayout ||
+ newFormat.m_sampleRate != m_sinkRequestFormat.m_sampleRate;
+}
+
+bool CActiveAE::NeedReconfigureSink()
+{
+ AEAudioFormat newFormat = GetInputFormat();
+ ApplySettingsToFormat(newFormat, m_settings);
+
+ std::string device = (newFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device;
+ std::string driver;
+ CAESinkFactory::ParseDevice(device, driver);
+
+ return !CompareFormat(newFormat, m_sinkFormat) ||
+ m_currDevice.compare(device) != 0 ||
+ m_settings.driver.compare(driver) != 0;
+}
+
+bool CActiveAE::InitSink()
+{
+ SinkConfig config;
+ config.format = m_sinkRequestFormat;
+ config.stats = &m_stats;
+ config.device = (m_sinkRequestFormat.m_dataFormat == AE_FMT_RAW) ? &m_settings.passthroughdevice :
+ &m_settings.device;
+
+ // send message to sink
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SETNOISETYPE, &m_settings.streamNoise, sizeof(bool));
+ m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SETSILENCETIMEOUT,
+ &m_settings.silenceTimeoutMinutes, sizeof(int));
+
+ Message *reply;
+ if (m_sink.m_controlPort.SendOutMessageSync(CSinkControlProtocol::CONFIGURE,
+ &reply,
+ 5000,
+ &config, sizeof(config)))
+ {
+ bool success = reply->signal == CSinkControlProtocol::ACC;
+ if (!success)
+ {
+ reply->Release();
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error", __FUNCTION__);
+ m_extError = true;
+ return false;
+ }
+ SinkReply *data;
+ data = reinterpret_cast<SinkReply*>(reply->data);
+ if (data)
+ {
+ m_sinkFormat = data->format;
+ m_sinkHasVolume = data->hasVolume;
+ m_stats.SetSinkCacheTotal(data->cacheTotal);
+ m_stats.SetSinkLatency(data->latency);
+ m_stats.SetCurrentSinkFormat(m_sinkFormat);
+ m_stats.SetSinkNeedIec(m_sink.NeedIecPack());
+ }
+ reply->Release();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to init", __FUNCTION__);
+ m_stats.SetSinkCacheTotal(0);
+ m_stats.SetSinkLatency(0);
+ AEAudioFormat invalidFormat;
+ invalidFormat.m_dataFormat = AE_FMT_INVALID;
+ m_stats.SetCurrentSinkFormat(invalidFormat);
+ m_extError = true;
+ return false;
+ }
+
+ m_inMsgEvent.Reset();
+ return true;
+}
+
+void CActiveAE::DrainSink()
+{
+ // send message to sink
+ Message *reply;
+ if (m_sink.m_dataPort.SendOutMessageSync(CSinkDataProtocol::DRAIN,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == CSinkDataProtocol::ACC;
+ if (!success)
+ {
+ reply->Release();
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error on drain", __FUNCTION__);
+ m_extError = true;
+ return;
+ }
+ reply->Release();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to drain", __FUNCTION__);
+ m_extError = true;
+ return;
+ }
+}
+
+void CActiveAE::UnconfigureSink()
+{
+ // send message to sink
+ Message *reply;
+ if (m_sink.m_controlPort.SendOutMessageSync(CSinkControlProtocol::UNCONFIGURE,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == CSinkControlProtocol::ACC;
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error", __FUNCTION__);
+ m_extError = true;
+ }
+ reply->Release();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to unconfigure", __FUNCTION__);
+ m_extError = true;
+ }
+
+ // make sure we open sink on next configure
+ m_currDevice = "";
+
+ m_inMsgEvent.Reset();
+}
+
+
+bool CActiveAE::RunStages()
+{
+ bool busy = false;
+
+ // serve input streams
+ std::list<CActiveAEStream*>::iterator it;
+ for (it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if ((*it)->m_processingBuffers && !(*it)->m_paused)
+ busy = (*it)->m_processingBuffers->ProcessBuffers();
+
+ if ((*it)->m_streamIsBuffering &&
+ (*it)->m_processingBuffers &&
+ ((*it)->m_processingBuffers->HasInputLevel(50)))
+ {
+ std::unique_lock<CCriticalSection> lock((*it)->m_streamLock);
+ (*it)->m_streamIsBuffering = false;
+ }
+
+ // provide buffers to stream
+ float time = m_stats.GetCacheTime((*it));
+ CSampleBuffer *buffer;
+ if (!(*it)->m_drain)
+ {
+ float buftime = (float)(*it)->m_inputBuffers->m_format.m_frames / (*it)->m_inputBuffers->m_format.m_sampleRate;
+ if ((*it)->m_inputBuffers->m_format.m_dataFormat == AE_FMT_RAW)
+ buftime = (*it)->m_inputBuffers->m_format.m_streamInfo.GetDuration() / 1000;
+ while ((time < MAX_CACHE_LEVEL || (*it)->m_streamIsBuffering) &&
+ !(*it)->m_inputBuffers->m_freeSamples.empty())
+ {
+ buffer = (*it)->m_inputBuffers->GetFreeBuffer();
+ (*it)->m_processingSamples.push_back(buffer);
+ (*it)->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMBUFFER, &buffer, sizeof(CSampleBuffer*));
+ (*it)->IncFreeBuffers();
+ time += buftime;
+ }
+ }
+ else
+ {
+ if ((*it)->m_processingBuffers->IsDrained() &&
+ (*it)->m_processingSamples.empty())
+ {
+ (*it)->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMDRAINED);
+ (*it)->m_drain = false;
+ (*it)->m_processingBuffers->SetDrain(false);
+ (*it)->m_started = false;
+
+ // set variables being polled via stream interface
+ std::unique_lock<CCriticalSection> lock((*it)->m_streamLock);
+ if ((*it)->m_streamSlave)
+ {
+ CActiveAEStream *slave = (CActiveAEStream*)((*it)->m_streamSlave);
+ slave->m_paused = false;
+
+ //! @todo find better solution for this gapless bites audiophile
+ if (m_settings.config == AE_CONFIG_MATCH)
+ Configure(&slave->m_format);
+
+ (*it)->m_streamSlave = NULL;
+ }
+ (*it)->m_streamDrained = true;
+ (*it)->m_streamDraining = false;
+ (*it)->m_streamFading = false;
+ }
+ }
+ }
+
+ // TrueHD is very jumpy, meaning the frames don't come in equidistantly. They are only smoothed
+ // at the end when the IEC packing happens. Therefore adjust earlier.
+ const bool ignoreWL =
+ (m_mode == MODE_RAW && m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD);
+
+ if ((m_stats.GetWaterLevel() < (MAX_WATER_LEVEL + 0.0001f) || ignoreWL) &&
+ (m_mode != MODE_TRANSCODE || (m_encoderBuffers && !m_encoderBuffers->m_freeSamples.empty())))
+ {
+ // calculate sync error
+ for (it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if ((*it)->m_paused || !(*it)->m_started || !(*it)->m_processingBuffers || !(*it)->m_pClock)
+ continue;
+
+ if ((*it)->m_processingBuffers->m_outputSamples.empty())
+ continue;
+
+ CSampleBuffer *buf = (*it)->m_processingBuffers->m_outputSamples.front();
+ if (buf->timestamp)
+ {
+ AEDelayStatus status;
+ m_stats.GetDelay(status);
+ double pts = buf->timestamp - (buf->pkt_start_offset * 1000 / buf->pkt->config.sample_rate);
+ double delay = status.GetDelay() * 1000;
+ double playingPts = pts - delay;
+ double maxError = ((*it)->m_syncState == CAESyncInfo::SYNC_INSYNC) ? 1000 : 5000;
+ double error = playingPts - (*it)->m_pClock->GetClock();
+ if (error > maxError)
+ {
+ CLog::Log(LOGWARNING, "ActiveAE - large audio sync error: {:f}", error);
+ error = maxError;
+ }
+ else if (error < -maxError)
+ {
+ CLog::Log(LOGWARNING, "ActiveAE - large audio sync error: {:f}", error);
+ error = -maxError;
+ }
+ (*it)->m_syncError.Add(error);
+ }
+ }
+
+ // mix streams and sounds sounds
+ if (m_mode != MODE_RAW)
+ {
+ CSampleBuffer *out = NULL;
+ if (!m_sounds_playing.empty() && m_streams.empty())
+ {
+ if (m_silenceBuffers && !m_silenceBuffers->m_freeSamples.empty())
+ {
+ out = m_silenceBuffers->GetFreeBuffer();
+ for (int i=0; i<out->pkt->planes; i++)
+ {
+ memset(out->pkt->data[i], 0, out->pkt->linesize);
+ }
+ out->pkt->nb_samples = out->pkt->max_nb_samples;
+ }
+ }
+
+ // mix streams
+ std::list<CActiveAEStream*>::iterator it;
+
+ // if we deal with more than a single stream, all streams
+ // must provide samples for mixing
+ bool allStreamsReady = true;
+ for (it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if ((*it)->m_paused || !(*it)->m_started || !(*it)->m_processingBuffers)
+ continue;
+
+ if ((*it)->m_processingBuffers->m_outputSamples.empty())
+ allStreamsReady = false;
+ }
+
+ bool needClamp = false;
+ for (it = m_streams.begin(); it != m_streams.end() && allStreamsReady; ++it)
+ {
+ if ((*it)->m_paused || !(*it)->m_processingBuffers)
+ continue;
+
+ if (!(*it)->m_processingBuffers->m_outputSamples.empty())
+ {
+ CSampleBuffer *tmp = SyncStream(*it);
+ m_stats.UpdateStream(*it);
+ if (tmp)
+ {
+ if (!out)
+ out = tmp;
+ continue;
+ }
+
+ (*it)->m_started = true;
+
+ if (!out)
+ {
+ out = (*it)->m_processingBuffers->m_outputSamples.front();
+ (*it)->m_processingBuffers->m_outputSamples.pop_front();
+
+ int nb_floats = out->pkt->nb_samples * out->pkt->config.channels / out->pkt->planes;
+ int nb_loops = 1;
+ float fadingStep = 0.0f;
+
+ // fading
+ if ((*it)->m_fadingSamples == -1)
+ {
+ (*it)->m_fadingSamples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f;
+ if ((*it)->m_fadingSamples > 0)
+ (*it)->m_volume = (*it)->m_fadingBase;
+ else
+ {
+ (*it)->m_volume = (*it)->m_fadingTarget;
+ std::unique_lock<CCriticalSection> lock((*it)->m_streamLock);
+ (*it)->m_streamFading = false;
+ }
+ }
+ if ((*it)->m_fadingSamples > 0)
+ {
+ nb_floats = out->pkt->config.channels / out->pkt->planes;
+ nb_loops = out->pkt->nb_samples;
+ float delta = (*it)->m_fadingTarget - (*it)->m_fadingBase;
+ int samples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f;
+ fadingStep = delta / samples;
+ }
+
+ // for stream amplification,
+ // turned off downmix normalization,
+ // or if sink format is float (in order to prevent from clipping)
+ // we need to run on a per sample basis
+ if ((*it)->m_amplify != 1.0f || !(*it)->m_processingBuffers->DoesNormalize() ||
+ (m_sinkFormat.m_dataFormat == AE_FMT_FLOAT))
+ {
+ nb_floats = out->pkt->config.channels / out->pkt->planes;
+ nb_loops = out->pkt->nb_samples;
+ }
+
+ for(int i=0; i<nb_loops; i++)
+ {
+ if ((*it)->m_fadingSamples > 0)
+ {
+ (*it)->m_volume += fadingStep;
+ (*it)->m_fadingSamples--;
+
+ if ((*it)->m_fadingSamples == 0)
+ {
+ // set variables being polled via stream interface
+ std::unique_lock<CCriticalSection> lock((*it)->m_streamLock);
+ (*it)->m_streamFading = false;
+ }
+ }
+
+ // volume for stream
+ float volume = (*it)->m_volume * (*it)->m_rgain;
+ if(nb_loops > 1)
+ volume *= (*it)->m_limiter.Run((float**)out->pkt->data, out->pkt->config.channels, i*nb_floats, out->pkt->planes > 1);
+
+ for(int j=0; j<out->pkt->planes; j++)
+ {
+#if defined(HAVE_SSE) && defined(__SSE__)
+ CAEUtil::SSEMulArray((float*)out->pkt->data[j]+i*nb_floats, volume, nb_floats);
+#else
+ float* fbuffer = (float*) out->pkt->data[j]+i*nb_floats;
+ for (int k = 0; k < nb_floats; ++k)
+ {
+ fbuffer[k] *= volume;
+ }
+#endif
+ }
+ }
+ }
+ else
+ {
+ CSampleBuffer *mix = NULL;
+ mix = (*it)->m_processingBuffers->m_outputSamples.front();
+ (*it)->m_processingBuffers->m_outputSamples.pop_front();
+
+ int nb_floats = mix->pkt->nb_samples * mix->pkt->config.channels / mix->pkt->planes;
+ int nb_loops = 1;
+ float fadingStep = 0.0f;
+
+ // fading
+ if ((*it)->m_fadingSamples == -1)
+ {
+ (*it)->m_fadingSamples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f;
+ (*it)->m_volume = (*it)->m_fadingBase;
+ }
+ if ((*it)->m_fadingSamples > 0)
+ {
+ nb_floats = mix->pkt->config.channels / mix->pkt->planes;
+ nb_loops = mix->pkt->nb_samples;
+ float delta = (*it)->m_fadingTarget - (*it)->m_fadingBase;
+ int samples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f;
+ fadingStep = delta / samples;
+ }
+
+ // for streams amplification of turned off downmix normalization
+ // we need to run on a per sample basis
+ if ((*it)->m_amplify != 1.0f || !(*it)->m_processingBuffers->DoesNormalize())
+ {
+ nb_floats = out->pkt->config.channels / out->pkt->planes;
+ nb_loops = out->pkt->nb_samples;
+ }
+
+ for(int i=0; i<nb_loops; i++)
+ {
+ if ((*it)->m_fadingSamples > 0)
+ {
+ (*it)->m_volume += fadingStep;
+ (*it)->m_fadingSamples--;
+
+ if ((*it)->m_fadingSamples == 0)
+ {
+ // set variables being polled via stream interface
+ std::unique_lock<CCriticalSection> lock((*it)->m_streamLock);
+ (*it)->m_streamFading = false;
+ }
+ }
+
+ // volume for stream
+ float volume = (*it)->m_volume * (*it)->m_rgain;
+ if(nb_loops > 1)
+ volume *= (*it)->m_limiter.Run((float**)mix->pkt->data, mix->pkt->config.channels, i*nb_floats, mix->pkt->planes > 1);
+
+ for(int j=0; j<out->pkt->planes && j<mix->pkt->planes; j++)
+ {
+ float *dst = (float*)out->pkt->data[j]+i*nb_floats;
+ float *src = (float*)mix->pkt->data[j]+i*nb_floats;
+#if defined(HAVE_SSE) && defined(__SSE__)
+ CAEUtil::SSEMulAddArray(dst, src, volume, nb_floats);
+ for (int k = 0; k < nb_floats; ++k)
+ {
+ if (fabs(dst[k]) > 1.0f)
+ {
+ needClamp = true;
+ break;
+ }
+ }
+#else
+ for (int k = 0; k < nb_floats; ++k)
+ {
+ dst[k] += src[k] * volume;
+ if (fabs(dst[k]) > 1.0f)
+ needClamp = true;
+ }
+#endif
+ }
+ }
+ mix->Return();
+ }
+ busy = true;
+ }
+ }// for
+
+ // finally clamp samples
+ if (out && needClamp)
+ {
+ int nb_floats = out->pkt->nb_samples * out->pkt->config.channels / out->pkt->planes;
+ for (int i=0; i<out->pkt->planes; i++)
+ {
+ CAEUtil::ClampArray((float*)out->pkt->data[i], nb_floats);
+ }
+ }
+
+ // process output buffer, gui sounds, encode, viz
+ if (out)
+ {
+ // viz
+ {
+ std::unique_lock<CCriticalSection> lock(m_vizLock);
+ if (!m_audioCallback.empty() && !m_streams.empty())
+ {
+ if (!m_vizInitialized || !m_vizBuffers)
+ {
+ Configure();
+ for (auto& it : m_audioCallback)
+ it->OnInitialize(2, m_vizBuffers->m_format.m_sampleRate, 32);
+ m_vizInitialized = true;
+ }
+
+ if (!m_vizBuffersInput->m_freeSamples.empty())
+ {
+ // copy the samples into the viz input buffer
+ CSampleBuffer *viz = m_vizBuffersInput->GetFreeBuffer();
+ int samples = out->pkt->nb_samples;
+ int bytes = samples * out->pkt->config.channels / out->pkt->planes * out->pkt->bytes_per_sample;
+ for(int i= 0; i < out->pkt->planes; i++)
+ {
+ memcpy(viz->pkt->data[i], out->pkt->data[i], bytes);
+ }
+ viz->pkt->nb_samples = samples;
+ m_vizBuffers->m_inputSamples.push_back(viz);
+ }
+ else
+ CLog::Log(LOGWARNING, "ActiveAE::{} - viz ran out of free buffers", __FUNCTION__);
+ AEDelayStatus status;
+ m_stats.GetDelay(status);
+ int64_t now = std::chrono::steady_clock::now().time_since_epoch().count();
+ int64_t timestamp = now + status.GetDelay() * 1000;
+ busy |= m_vizBuffers->ResampleBuffers(timestamp);
+ while(!m_vizBuffers->m_outputSamples.empty())
+ {
+ CSampleBuffer *buf = m_vizBuffers->m_outputSamples.front();
+ if ((now - buf->timestamp) < 0)
+ break;
+ else
+ {
+ unsigned int samples = static_cast<unsigned int>(buf->pkt->nb_samples) *
+ buf->pkt->config.channels / buf->pkt->planes;
+ for (auto& it : m_audioCallback)
+ it->OnAudioData((float*)(buf->pkt->data[0]), samples);
+ buf->Return();
+ m_vizBuffers->m_outputSamples.pop_front();
+ }
+ }
+ }
+ else if (m_vizBuffers)
+ m_vizBuffers->Flush();
+ }
+
+ // mix gui sounds
+ MixSounds(*(out->pkt));
+ if (!m_sinkHasVolume || m_muted)
+ Deamplify(*(out->pkt));
+
+ if (m_mode == MODE_TRANSCODE && m_encoder)
+ {
+ CSampleBuffer *buf = nullptr;
+ if (out->pkt->nb_samples)
+ {
+ buf = m_encoderBuffers->GetFreeBuffer();
+ buf->pkt->nb_samples = m_encoder->Encode(out->pkt->data[0], out->pkt->planes*out->pkt->linesize,
+ buf->pkt->data[0], buf->pkt->planes*buf->pkt->linesize);
+
+ // set pts of last sample
+ buf->pkt_start_offset = buf->pkt->nb_samples;
+ buf->timestamp = out->timestamp;
+ }
+
+ out->Return();
+ out = buf;
+ }
+ busy = true;
+ }
+
+ // update stats
+ if(out)
+ {
+ int samples = (m_mode == MODE_TRANSCODE) ? 1 : out->pkt->nb_samples;
+ m_stats.AddSamples(samples, m_streams);
+ m_sinkBuffers->m_inputSamples.push_back(out);
+ }
+ }
+ // pass through
+ else
+ {
+ std::list<CActiveAEStream*>::iterator it;
+ CSampleBuffer *buffer;
+ for (it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if (!(*it)->m_processingBuffers->m_outputSamples.empty() && !(*it)->m_paused)
+ {
+ (*it)->m_started = true;
+ buffer = SyncStream(*it);
+ m_stats.UpdateStream(*it);
+ if (!buffer)
+ {
+ buffer = (*it)->m_processingBuffers->m_outputSamples.front();
+ (*it)->m_processingBuffers->m_outputSamples.pop_front();
+ }
+ m_stats.AddSamples(1, m_streams);
+ m_sinkBuffers->m_inputSamples.push_back(buffer);
+ }
+ }
+ }
+ }
+
+ // serve sink buffers
+ busy |= m_sinkBuffers->ResampleBuffers();
+ while(!m_sinkBuffers->m_outputSamples.empty())
+ {
+ CSampleBuffer *out = NULL;
+ out = m_sinkBuffers->m_outputSamples.front();
+ m_sinkBuffers->m_outputSamples.pop_front();
+ m_sink.m_dataPort.SendOutMessage(CSinkDataProtocol::SAMPLE,
+ &out, sizeof(CSampleBuffer*));
+ busy = true;
+ }
+
+ return busy;
+}
+
+bool CActiveAE::HasWork()
+{
+ if (!m_sounds_playing.empty())
+ return true;
+ if (!m_sinkBuffers->m_inputSamples.empty())
+ return true;
+ if (!m_sinkBuffers->m_outputSamples.empty())
+ return true;
+
+ std::list<CActiveAEStream*>::iterator it;
+ for (it = m_streams.begin(); it != m_streams.end(); ++it)
+ {
+ if (!(*it)->m_processingBuffers->HasWork())
+ return true;
+ if (!(*it)->m_processingSamples.empty())
+ return true;
+ }
+
+ return false;
+}
+
+CSampleBuffer* CActiveAE::SyncStream(CActiveAEStream *stream)
+{
+ CSampleBuffer *ret = NULL;
+
+ if (!stream->m_pClock)
+ return ret;
+
+ if (stream->m_syncState == CAESyncInfo::AESyncState::SYNC_START)
+ {
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_MUTE;
+ stream->m_syncError.Flush(100ms);
+ stream->m_processingBuffers->SetRR(1.0, m_settings.atempoThreshold);
+ stream->m_resampleIntegral = 0;
+ CLog::Log(LOGDEBUG,"ActiveAE - start sync of audio stream");
+ }
+
+ double error;
+ double threshold = 100;
+ if (stream->m_resampleMode)
+ {
+ threshold *= 2;
+ if (stream->m_pClock)
+ {
+ double clockspeed = stream->m_pClock->GetClockSpeed();
+ if (clockspeed >= 1.05 || clockspeed <= 0.95)
+ threshold *= 5;
+ }
+ }
+
+ std::chrono::milliseconds timeout = (stream->m_syncState != CAESyncInfo::AESyncState::SYNC_INSYNC)
+ ? 100ms
+ : stream->GetErrorInterval();
+ bool newerror = stream->m_syncError.Get(error, timeout);
+
+ if (newerror && fabs(error) > threshold && stream->m_syncState == CAESyncInfo::AESyncState::SYNC_INSYNC)
+ {
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_ADJUST;
+ stream->m_processingBuffers->SetRR(1.0, m_settings.atempoThreshold);
+ stream->m_resampleIntegral = 0;
+ stream->m_lastSyncError = error;
+ CLog::Log(LOGDEBUG, "ActiveAE::SyncStream - average error {:f} above threshold of {:f}", error,
+ threshold);
+ }
+ else if (newerror && stream->m_syncState == CAESyncInfo::AESyncState::SYNC_MUTE)
+ {
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_ADJUST;
+ stream->m_lastSyncError = error;
+ CLog::Log(LOGDEBUG, "ActiveAE::SyncStream - average error of {:f}, start adjusting", error);
+ }
+
+ if (stream->m_syncState == CAESyncInfo::AESyncState::SYNC_MUTE)
+ {
+ CSampleBuffer *buf = stream->m_processingBuffers->m_outputSamples.front();
+ if (m_mode == MODE_RAW)
+ {
+ buf->pkt->nb_samples = 0;
+ buf->pkt->pause_burst_ms = stream->m_processingBuffers->m_inputFormat.m_streamInfo.GetDuration();
+ }
+ else
+ {
+ for(int i=0; i<buf->pkt->planes; i++)
+ {
+ memset(buf->pkt->data[i], 0, buf->pkt->linesize);
+ }
+ }
+ }
+ else if (stream->m_syncState == CAESyncInfo::AESyncState::SYNC_ADJUST)
+ {
+ if (error > 0)
+ {
+ ret = m_silenceBuffers->GetFreeBuffer();
+ if (ret)
+ {
+ ret->pkt->nb_samples = 0;
+ ret->pkt->pause_burst_ms = 0;
+ int framesToDelay = error / 1000 * ret->pkt->config.sample_rate;
+ if (framesToDelay > ret->pkt->max_nb_samples)
+ framesToDelay = ret->pkt->max_nb_samples;
+ if (m_mode == MODE_TRANSCODE)
+ {
+ if (framesToDelay > (int) (m_encoderFormat.m_frames / 2))
+ framesToDelay = m_encoderFormat.m_frames;
+ else
+ framesToDelay = 0;
+ }
+ ret->pkt->nb_samples = framesToDelay;
+ if (m_mode == MODE_RAW)
+ {
+ ret->pkt->nb_samples = 0;
+ ret->pkt->pause_burst_ms = error;
+ if (error > stream->m_format.m_streamInfo.GetDuration())
+ ret->pkt->pause_burst_ms = stream->m_format.m_streamInfo.GetDuration();
+
+ stream->m_syncError.Correction(-ret->pkt->pause_burst_ms);
+ error -= ret->pkt->pause_burst_ms;
+ }
+ else
+ {
+ stream->m_syncError.Correction(-framesToDelay*1000/ret->pkt->config.sample_rate);
+ error -= framesToDelay*1000/ret->pkt->config.sample_rate;
+ for(int i=0; i<ret->pkt->planes; i++)
+ {
+ memset(ret->pkt->data[i], 0, ret->pkt->linesize);
+ }
+ }
+
+ if ((ret->pkt->nb_samples == 0) && (ret->pkt->pause_burst_ms == 0))
+ {
+ ret->Return();
+ ret = nullptr;
+ }
+ }
+ }
+ else
+ {
+ CSampleBuffer *buf = stream->m_processingBuffers->m_outputSamples.front();
+ int framesToSkip = -error / 1000 * buf->pkt->config.sample_rate;
+ if (framesToSkip > buf->pkt->nb_samples)
+ framesToSkip = buf->pkt->nb_samples;
+ if (m_mode == MODE_TRANSCODE)
+ {
+ if (framesToSkip > (int) (m_encoderFormat.m_frames / 2))
+ framesToSkip = buf->pkt->nb_samples;
+ else
+ framesToSkip = 0;
+ }
+ if (m_mode == MODE_RAW)
+ {
+ if (-error > stream->m_format.m_streamInfo.GetDuration() / 2)
+ {
+ stream->m_syncError.Correction(stream->m_format.m_streamInfo.GetDuration());
+ error += stream->m_format.m_streamInfo.GetDuration();
+ buf->pkt->nb_samples = 0;
+ }
+ }
+ else
+ {
+ int bytesToSkip = framesToSkip * buf->pkt->bytes_per_sample *
+ buf->pkt->config.channels / buf->pkt->planes;
+ for (int i=0; i<buf->pkt->planes; i++)
+ {
+ memmove(buf->pkt->data[i], buf->pkt->data[i]+bytesToSkip, buf->pkt->linesize - bytesToSkip);
+ }
+ buf->pkt->nb_samples -= framesToSkip;
+ stream->m_syncError.Correction((double)framesToSkip * 1000 / buf->pkt->config.sample_rate);
+ error += (double)framesToSkip * 1000 / buf->pkt->config.sample_rate;
+ }
+ }
+
+ if (fabs(error) < 30)
+ {
+ if (stream->m_lastSyncError > threshold * 2)
+ {
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_MUTE;
+ stream->m_syncError.Flush(100ms);
+ CLog::Log(LOGDEBUG, "ActiveAE::SyncStream - average error {:f}, last average error: {:f}",
+ error, stream->m_lastSyncError);
+ stream->m_lastSyncError = error;
+ }
+ else
+ {
+ stream->m_syncState = CAESyncInfo::AESyncState::SYNC_INSYNC;
+ stream->m_syncError.Flush(1000ms);
+ stream->m_resampleIntegral = 0;
+ stream->m_processingBuffers->SetRR(1.0, m_settings.atempoThreshold);
+ CLog::Log(LOGDEBUG, "ActiveAE::SyncStream - average error {:f} below threshold of {:f}",
+ error, 30.0);
+ }
+ }
+
+ return ret;
+ }
+
+ if (!newerror || stream->m_syncState != CAESyncInfo::AESyncState::SYNC_INSYNC)
+ return ret;
+
+ if (stream->m_resampleMode)
+ {
+ if (stream->m_processingBuffers)
+ {
+ stream->m_processingBuffers->SetRR(stream->CalcResampleRatio(error), m_settings.atempoThreshold);
+ }
+ }
+ else if (stream->m_processingBuffers)
+ {
+ stream->m_processingBuffers->SetRR(1.0, m_settings.atempoThreshold);
+ }
+
+ stream->m_syncError.SetErrorInterval(stream->GetErrorInterval());
+
+ return ret;
+}
+
+void CActiveAE::MixSounds(CSoundPacket &dstSample)
+{
+ if (m_sounds_playing.empty())
+ return;
+
+ float volume;
+ float *out;
+ float *sample_buffer;
+ int max_samples = dstSample.nb_samples;
+
+ std::list<SoundState>::iterator it;
+ for (it = m_sounds_playing.begin(); it != m_sounds_playing.end(); )
+ {
+ if (!it->sound->IsConverted())
+ ResampleSound(it->sound);
+ int available_samples = it->sound->GetSound(false)->nb_samples - it->samples_played;
+ int mix_samples = std::min(max_samples, available_samples);
+ int start = it->samples_played *
+ av_get_bytes_per_sample(it->sound->GetSound(false)->config.fmt) *
+ it->sound->GetSound(false)->config.channels /
+ it->sound->GetSound(false)->planes;
+
+ for(int j=0; j<dstSample.planes; j++)
+ {
+ volume = it->sound->GetVolume();
+ out = (float*)dstSample.data[j];
+ sample_buffer = (float*)(it->sound->GetSound(false)->data[j]+start);
+ int nb_floats = mix_samples * dstSample.config.channels / dstSample.planes;
+#if defined(HAVE_SSE) && defined(__SSE__)
+ CAEUtil::SSEMulAddArray(out, sample_buffer, volume, nb_floats);
+#else
+ for (int k = 0; k < nb_floats; ++k)
+ *out++ += *sample_buffer++ * volume;
+#endif
+ }
+
+ it->samples_played += mix_samples;
+
+ // no more frames, so remove it from the list
+ if (it->samples_played >= it->sound->GetSound(false)->nb_samples)
+ {
+ it = m_sounds_playing.erase(it);
+ continue;
+ }
+ ++it;
+ }
+}
+
+void CActiveAE::Deamplify(CSoundPacket &dstSample)
+{
+ if (m_volumeScaled < 1.0f || m_muted)
+ {
+ int nb_floats = dstSample.nb_samples * dstSample.config.channels / dstSample.planes;
+ float volume = m_muted ? 0.0f : m_volumeScaled;
+
+ for(int j=0; j<dstSample.planes; j++)
+ {
+ float* buffer = reinterpret_cast<float*>(dstSample.data[j]);
+#if defined(HAVE_SSE) && defined(__SSE__)
+ CAEUtil::SSEMulArray(buffer, volume, nb_floats);
+#else
+ float *fbuffer = buffer;
+ for (int i = 0; i < nb_floats; i++)
+ *fbuffer++ *= volume;
+#endif
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Configuration
+//-----------------------------------------------------------------------------
+
+void CActiveAE::LoadSettings()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ m_settings.device = settings->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE);
+ m_settings.passthroughdevice = settings->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE);
+
+ m_settings.config = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG);
+ m_settings.channels = (m_sink.GetDeviceType(m_settings.device) == AE_DEVTYPE_IEC958) ? AE_CH_LAYOUT_2_0 : settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CHANNELS);
+ m_settings.samplerate = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_SAMPLERATE);
+
+ m_settings.stereoupmix = IsSettingVisible(CSettings::SETTING_AUDIOOUTPUT_STEREOUPMIX) ? settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_STEREOUPMIX) : false;
+ m_settings.normalizelevels = !settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME);
+ m_settings.guisoundmode = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDMODE);
+
+ m_settings.passthrough = m_settings.config == AE_CONFIG_FIXED ? false : settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH);
+ if (!m_sink.HasPassthroughDevice())
+ m_settings.passthrough = false;
+ m_settings.ac3passthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH);
+ m_settings.ac3transcode = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_AC3TRANSCODE);
+ m_settings.eac3passthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_EAC3PASSTHROUGH);
+ m_settings.truehdpassthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_TRUEHDPASSTHROUGH);
+ m_settings.dtspassthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_DTSPASSTHROUGH);
+ m_settings.dtshdpassthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_DTSHDPASSTHROUGH);
+ m_settings.usesdtscorefallback =
+ settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK);
+
+ m_settings.resampleQuality = static_cast<AEQuality>(settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_PROCESSQUALITY));
+ m_settings.atempoThreshold = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD) / 100.0;
+ m_settings.streamNoise = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE);
+ m_settings.silenceTimeoutMinutes = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE);
+}
+
+void CActiveAE::Start()
+{
+ Create();
+ Message *reply;
+ if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::INIT,
+ &reply,
+ 10000))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error", __FUNCTION__);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to init", __FUNCTION__);
+ }
+
+ m_inMsgEvent.Reset();
+}
+
+void CActiveAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
+{
+ m_sink.EnumerateOutputDevices(devices, passthrough);
+}
+
+void CActiveAE::OnSettingsChange()
+{
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::RECONFIGURE);
+}
+
+bool CActiveAE::SupportsRaw(AEAudioFormat &format)
+{
+ // check if passthrough is enabled
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH))
+ return false;
+
+ // fixed config disabled passthrough
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) == AE_CONFIG_FIXED)
+ return false;
+
+ // check if the format is enabled in settings
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_AC3 && !m_settings.ac3passthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_512 && !m_settings.dtspassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_1024 && !m_settings.dtspassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_2048 && !m_settings.dtspassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_CORE && !m_settings.dtspassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3 && !m_settings.eac3passthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD && !m_settings.truehdpassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD && !m_settings.dtshdpassthrough)
+ return false;
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA && !m_settings.dtshdpassthrough)
+ return false;
+
+ if (!m_sink.SupportsFormat(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE), format))
+ return false;
+
+ return true;
+}
+
+bool CActiveAE::UsesDtsCoreFallback()
+{
+ return m_settings.usesdtscorefallback;
+}
+
+bool CActiveAE::SupportsSilenceTimeout()
+{
+ return true;
+}
+
+bool CActiveAE::HasStereoAudioChannelCount()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ const std::string device = settings->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE);
+ int numChannels = (m_sink.GetDeviceType(device) == AE_DEVTYPE_IEC958) ? AE_CH_LAYOUT_2_0 : settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CHANNELS);
+ bool passthrough = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) == AE_CONFIG_FIXED ? false : settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH);
+ return numChannels == AE_CH_LAYOUT_2_0 && !passthrough;
+}
+
+bool CActiveAE::HasHDAudioChannelCount()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ const std::string device = settings->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE);
+ int numChannels = (m_sink.GetDeviceType(device) == AE_DEVTYPE_IEC958) ? AE_CH_LAYOUT_2_0 : settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CHANNELS);
+ return numChannels > AE_CH_LAYOUT_5_1;
+}
+
+bool CActiveAE::SupportsQualityLevel(enum AEQuality level)
+{
+ if (level == AE_QUALITY_LOW || level == AE_QUALITY_MID || level == AE_QUALITY_HIGH)
+ return true;
+
+ return false;
+}
+
+bool CActiveAE::IsSettingVisible(const std::string &settingId)
+{
+ if (settingId == CSettings::SETTING_AUDIOOUTPUT_SAMPLERATE)
+ {
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.GetDeviceType(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE)) == AE_DEVTYPE_IEC958)
+ return true;
+ if (settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) == AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_CHANNELS)
+ {
+ if (m_sink.GetDeviceType(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE)) != AE_DEVTYPE_IEC958)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH)
+ {
+ if (m_sink.HasPassthroughDevice() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_DTSPASSTHROUGH)
+ {
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTS_512;
+ format.m_sampleRate = 48000;
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.SupportsFormat(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE), format) &&
+ settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_TRUEHDPASSTHROUGH)
+ {
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_TRUEHD;
+ format.m_streamInfo.m_sampleRate = 192000;
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.SupportsFormat(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE), format) &&
+ settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_DTSHDPASSTHROUGH)
+ {
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD;
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.SupportsFormat(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE), format) &&
+ settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_EAC3PASSTHROUGH)
+ {
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.SupportsFormat(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE), format) &&
+ settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_STEREOUPMIX)
+ {
+ if (m_sink.HasPassthroughDevice() ||
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_CHANNELS) > AE_CH_LAYOUT_2_0)
+ return true;
+ }
+ else if (settingId == CSettings::SETTING_AUDIOOUTPUT_AC3TRANSCODE)
+ {
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_sink.HasPassthroughDevice() &&
+ settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH) &&
+ settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CONFIG) != AE_CONFIG_FIXED &&
+ (settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_CHANNELS) <= AE_CH_LAYOUT_2_0 || m_sink.GetDeviceType(settings->GetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE)) == AE_DEVTYPE_IEC958))
+ return true;
+ }
+ return false;
+}
+
+void CActiveAE::Shutdown()
+{
+ Dispose();
+}
+
+bool CActiveAE::Suspend()
+{
+ return m_controlPort.SendOutMessage(CActiveAEControlProtocol::SUSPEND);
+}
+
+bool CActiveAE::Resume()
+{
+ Message *reply;
+ if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::INIT,
+ &reply,
+ 5000))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error", __FUNCTION__);
+ return false;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - failed to init", __FUNCTION__);
+ return false;
+ }
+
+ m_inMsgEvent.Reset();
+ return true;
+}
+
+bool CActiveAE::IsSuspended()
+{
+ return m_stats.IsSuspended();
+}
+
+float CActiveAE::GetVolume()
+{
+ return m_aeVolume;
+}
+
+void CActiveAE::SetVolume(const float volume)
+{
+ m_aeVolume = std::max( 0.0f, std::min(1.0f, volume));
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::VOLUME, &m_aeVolume, sizeof(float));
+}
+
+void CActiveAE::SetMute(const bool enabled)
+{
+ m_aeMuted = enabled;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::MUTE, &m_aeMuted, sizeof(bool));
+}
+
+bool CActiveAE::IsMuted()
+{
+ return m_aeMuted;
+}
+
+void CActiveAE::KeepConfiguration(unsigned int millis)
+{
+ unsigned int timeMs = millis;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::KEEPCONFIG, &timeMs, sizeof(unsigned int));
+}
+
+void CActiveAE::DeviceChange()
+{
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::DEVICECHANGE);
+}
+
+void CActiveAE::DeviceCountChange(const std::string& driver)
+{
+ const char* name = driver.c_str();
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::DEVICECOUNTCHANGE, name,
+ driver.length() + 1);
+}
+
+bool CActiveAE::GetCurrentSinkFormat(AEAudioFormat &SinkFormat)
+{
+ SinkFormat = m_stats.GetCurrentSinkFormat();
+ return true;
+}
+
+void CActiveAE::OnLostDisplay()
+{
+ Message *reply;
+ if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::DISPLAYLOST,
+ &reply,
+ 5000))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - returned error", __FUNCTION__);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "ActiveAE::{} - timed out", __FUNCTION__);
+ }
+}
+
+void CActiveAE::OnResetDisplay()
+{
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::DISPLAYRESET);
+}
+
+void CActiveAE::OnAppFocusChange(bool focus)
+{
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::APPFOCUSED, &focus, sizeof(focus));
+}
+
+//-----------------------------------------------------------------------------
+// Utils
+//-----------------------------------------------------------------------------
+
+uint8_t **CActiveAE::AllocSoundSample(SampleConfig &config, int &samples, int &bytes_per_sample, int &planes, int &linesize)
+{
+ uint8_t **buffer;
+ planes = av_sample_fmt_is_planar(config.fmt) ? config.channels : 1;
+ buffer = new uint8_t*[planes];
+
+ // align buffer to 16 in order to be compatible with sse in CAEConvert
+ av_samples_alloc(buffer, &linesize, config.channels,
+ samples, config.fmt, 16);
+ bytes_per_sample = av_get_bytes_per_sample(config.fmt);
+ return buffer;
+}
+
+void CActiveAE::FreeSoundSample(uint8_t **data)
+{
+ av_freep(data);
+ delete [] data;
+}
+
+bool CActiveAE::CompareFormat(const AEAudioFormat& lhs, const AEAudioFormat& rhs)
+{
+ if (lhs.m_channelLayout != rhs.m_channelLayout ||
+ lhs.m_dataFormat != rhs.m_dataFormat ||
+ lhs.m_sampleRate != rhs.m_sampleRate)
+ return false;
+ else if (lhs.m_dataFormat == AE_FMT_RAW && rhs.m_dataFormat == AE_FMT_RAW &&
+ lhs.m_streamInfo.m_type != rhs.m_streamInfo.m_type)
+ return false;
+ else
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// GUI Sounds
+//-----------------------------------------------------------------------------
+
+/**
+ * load sound from an audio file and store original format
+ * register the sound in ActiveAE
+ * later when the engine is idle it will convert the sound to sink format
+ */
+
+IAE::SoundPtr CActiveAE::MakeSound(const std::string& file)
+{
+ AVFormatContext *fmt_ctx = nullptr;
+ AVCodecContext *dec_ctx = nullptr;
+ AVIOContext *io_ctx;
+ AVInputFormat *io_fmt = nullptr;
+ AVCodec *dec = nullptr;
+ SampleConfig config;
+
+ // No custom deleter until sound is registered
+ auto sound = std::make_unique<CActiveAESound>(file, this);
+ if (!sound->Prepare())
+ {
+ return nullptr;
+ }
+ int fileSize = sound->GetFileSize();
+
+ int bufferSize = 4096;
+ int blockSize = sound->GetChunkSize();
+ if (blockSize > 1)
+ bufferSize = blockSize;
+
+ fmt_ctx = avformat_alloc_context();
+ unsigned char* buffer = (unsigned char*)av_malloc(bufferSize);
+ io_ctx = avio_alloc_context(buffer, bufferSize, 0, sound.get(), CActiveAESound::Read, NULL,
+ CActiveAESound::Seek);
+
+ io_ctx->max_packet_size = bufferSize;
+
+ if (!sound->IsSeekPossible())
+ {
+ io_ctx->seekable = 0;
+ io_ctx->max_packet_size = 0;
+ }
+
+ fmt_ctx->pb = io_ctx;
+
+ av_probe_input_buffer(io_ctx, &io_fmt, file.c_str(), nullptr, 0, 0);
+ if (!io_fmt)
+ {
+ avformat_close_input(&fmt_ctx);
+ if (io_ctx)
+ {
+ av_freep(&io_ctx->buffer);
+ av_freep(&io_ctx);
+ }
+ return nullptr;
+ }
+
+ // find decoder
+ if (avformat_open_input(&fmt_ctx, file.c_str(), nullptr, nullptr) == 0)
+ {
+ fmt_ctx->flags |= AVFMT_FLAG_NOPARSE;
+ if (avformat_find_stream_info(fmt_ctx, nullptr) >= 0)
+ {
+ AVCodecID codecId = fmt_ctx->streams[0]->codecpar->codec_id;
+ dec = avcodec_find_decoder(codecId);
+ config.sample_rate = fmt_ctx->streams[0]->codecpar->sample_rate;
+ config.channels = fmt_ctx->streams[0]->codecpar->channels;
+ config.channel_layout = fmt_ctx->streams[0]->codecpar->channel_layout;
+ }
+ }
+ if (dec == nullptr)
+ {
+ avformat_close_input(&fmt_ctx);
+ if (io_ctx)
+ {
+ av_freep(&io_ctx->buffer);
+ av_freep(&io_ctx);
+ }
+ return nullptr;
+ }
+
+ dec_ctx = avcodec_alloc_context3(dec);
+ dec_ctx->sample_rate = config.sample_rate;
+ dec_ctx->channels = config.channels;
+ if (!config.channel_layout)
+ config.channel_layout = av_get_default_channel_layout(config.channels);
+ dec_ctx->channel_layout = config.channel_layout;
+
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ CLog::Log(LOGERROR, "CActiveAE::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+
+ AVFrame* decoded_frame = av_frame_alloc();
+ if (!decoded_frame)
+ CLog::Log(LOGERROR, "CActiveAE::{} - av_frame_alloc failed: {}", __FUNCTION__, strerror(errno));
+
+ bool error = false;
+
+ if (avpkt && decoded_frame && avcodec_open2(dec_ctx, dec, nullptr) >= 0)
+ {
+ bool init = false;
+
+ // decode until eof
+ int ret;
+ while (av_read_frame(fmt_ctx, avpkt) >= 0 && !error)
+ {
+ ret = avcodec_send_packet(dec_ctx, avpkt);
+ if (ret < 0)
+ {
+ error = true;
+ break;
+ }
+
+ while ((ret = avcodec_receive_frame(dec_ctx, decoded_frame)) == 0)
+ {
+ if (!init)
+ {
+ int samples = fileSize / av_get_bytes_per_sample(dec_ctx->sample_fmt) / config.channels;
+ config.fmt = dec_ctx->sample_fmt;
+ config.bits_per_sample = dec_ctx->bits_per_coded_sample;
+ sound->InitSound(true, config, samples);
+ init = true;
+ }
+ sound->StoreSound(true, decoded_frame->extended_data,
+ decoded_frame->nb_samples, decoded_frame->linesize[0]);
+ }
+ av_packet_unref(avpkt);
+
+ if (ret < 0 && ret != AVERROR(EAGAIN))
+ {
+ error = true;
+ break;
+ }
+ }
+ ret = avcodec_send_packet(dec_ctx, nullptr);
+ while ((ret = avcodec_receive_frame(dec_ctx, decoded_frame)) != AVERROR_EOF)
+ {
+ if (ret == 0)
+ {
+ sound->StoreSound(true, decoded_frame->extended_data,
+ decoded_frame->nb_samples, decoded_frame->linesize[0]);
+ }
+ else
+ {
+ error = true;
+ break;
+ }
+ }
+ }
+
+ av_packet_free(&avpkt);
+ av_frame_free(&decoded_frame);
+ avcodec_free_context(&dec_ctx);
+ avformat_close_input(&fmt_ctx);
+ if (io_ctx)
+ {
+ av_freep(&io_ctx->buffer);
+ av_freep(&io_ctx);
+ }
+
+ if (error)
+ {
+ return nullptr;
+ }
+
+ sound->Finish();
+
+ // register sound
+ m_dataPort.SendOutMessage(CActiveAEDataProtocol::NEWSOUND, &sound, sizeof(CActiveAESound*));
+
+ // transfer ownership to std::unique_ptr that unregisters the sound
+ return {sound.release(), IAESoundDeleter(*this)};
+}
+
+void CActiveAE::FreeSound(IAESound *sound)
+{
+ m_dataPort.SendOutMessage(CActiveAEDataProtocol::FREESOUND, &sound, sizeof(CActiveAESound*));
+}
+
+void CActiveAE::PlaySound(CActiveAESound *sound)
+{
+ m_dataPort.SendOutMessage(CActiveAEDataProtocol::PLAYSOUND, &sound, sizeof(CActiveAESound*));
+}
+
+void CActiveAE::StopSound(CActiveAESound *sound)
+{
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STOPSOUND, &sound, sizeof(CActiveAESound*));
+}
+
+/**
+ * resample sounds to destination format for mixing
+ * destination format is either format of stream or
+ * default sink format when no stream is playing
+ */
+void CActiveAE::ResampleSounds()
+{
+ if ((m_settings.guisoundmode == AE_SOUND_OFF ||
+ (m_settings.guisoundmode == AE_SOUND_IDLE && !m_streams.empty())) &&
+ !m_aeGUISoundForce)
+ return;
+
+ std::vector<CActiveAESound*>::iterator it;
+ for (it = m_sounds.begin(); it != m_sounds.end(); ++it)
+ {
+ if (!(*it)->IsConverted())
+ {
+ ResampleSound(*it);
+ // only do one sound, then yield to main loop
+ break;
+ }
+ }
+}
+
+bool CActiveAE::ResampleSound(CActiveAESound *sound)
+{
+ SampleConfig orig_config, dst_config;
+ uint8_t **dst_buffer;
+ int dst_samples;
+
+ if (m_mode == MODE_RAW || m_internalFormat.m_dataFormat == AE_FMT_INVALID)
+ return false;
+
+ if (!sound->GetSound(true))
+ return false;
+
+ orig_config = sound->GetSound(true)->config;
+
+ dst_config.channel_layout = CAEUtil::GetAVChannelLayout(m_internalFormat.m_channelLayout);
+ dst_config.channels = m_internalFormat.m_channelLayout.Count();
+ dst_config.sample_rate = m_internalFormat.m_sampleRate;
+ dst_config.fmt = CAEUtil::GetAVSampleFormat(m_internalFormat.m_dataFormat);
+ dst_config.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_internalFormat.m_dataFormat);
+ dst_config.dither_bits = CAEUtil::DataFormatToDitherBits(m_internalFormat.m_dataFormat);
+
+ AEChannel testChannel = sound->GetChannel();
+ CAEChannelInfo outChannels;
+ if (sound->GetSound(true)->config.channels == 1 && testChannel != AE_CH_NULL)
+ {
+ for (unsigned int out=0; out < m_internalFormat.m_channelLayout.Count(); out++)
+ {
+ if (m_internalFormat.m_channelLayout[out] == AE_CH_FC && testChannel != AE_CH_FC) /// To become center clear on position test ??????
+ outChannels += AE_CH_FL;
+ else if (m_internalFormat.m_channelLayout[out] == testChannel)
+ outChannels += AE_CH_FC;
+ else
+ outChannels += m_internalFormat.m_channelLayout[out];
+ }
+ }
+
+ IAEResample *resampler = CAEResampleFactory::Create(AERESAMPLEFACTORY_QUICK_RESAMPLE);
+
+ resampler->Init(dst_config, orig_config,
+ false,
+ true,
+ M_SQRT1_2,
+ outChannels.Count() > 0 ? &outChannels : nullptr,
+ m_settings.resampleQuality,
+ false);
+
+ dst_samples = resampler->CalcDstSampleCount(sound->GetSound(true)->nb_samples,
+ m_internalFormat.m_sampleRate,
+ orig_config.sample_rate);
+
+ dst_buffer = sound->InitSound(false, dst_config, dst_samples);
+ if (!dst_buffer)
+ {
+ delete resampler;
+ return false;
+ }
+ int samples = resampler->Resample(dst_buffer, dst_samples,
+ sound->GetSound(true)->data,
+ sound->GetSound(true)->nb_samples,
+ 1.0);
+
+ sound->GetSound(false)->nb_samples = samples;
+
+ delete resampler;
+ sound->SetConverted(true);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Streams
+//-----------------------------------------------------------------------------
+
+IAE::StreamPtr CActiveAE::MakeStream(AEAudioFormat& audioFormat,
+ unsigned int options,
+ IAEClockCallback* clock)
+{
+ if (audioFormat.m_dataFormat <= AE_FMT_INVALID || audioFormat.m_dataFormat >= AE_FMT_MAX)
+ {
+ return nullptr;
+ }
+
+ if (IsSuspended())
+ return NULL;
+
+ //! @todo pass number of samples in audio packet
+
+ AEAudioFormat format = audioFormat;
+ format.m_frames = format.m_sampleRate / 10;
+
+ if (format.m_dataFormat != AE_FMT_RAW)
+ {
+ format.m_frameSize = format.m_channelLayout.Count() *
+ (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
+ }
+ else
+ format.m_frameSize = 1;
+
+ MsgStreamNew msg;
+ msg.format = format;
+ msg.options = options;
+ msg.clock = clock;
+
+ Message *reply;
+ if (m_dataPort.SendOutMessageSync(CActiveAEDataProtocol::NEWSTREAM,
+ &reply,10000,
+ &msg, sizeof(MsgStreamNew)))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ if (success)
+ {
+ IAE::StreamPtr stream(*reinterpret_cast<CActiveAEStream**>(reply->data),
+ IAEStreamDeleter(*this));
+ reply->Release();
+ return stream;
+ }
+ reply->Release();
+ }
+
+ CLog::Log(LOGERROR, "ActiveAE::{} - could not create stream", __FUNCTION__);
+ return NULL;
+}
+
+bool CActiveAE::FreeStream(IAEStream *stream, bool finish)
+{
+ MsgStreamFree msg;
+ msg.stream = static_cast<CActiveAEStream*>(stream);
+ msg.finish = finish;
+
+ Message *reply;
+ if (m_dataPort.SendOutMessageSync(CActiveAEDataProtocol::FREESTREAM,
+ &reply,1000,
+ &msg, sizeof(MsgStreamFree)))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ reply->Release();
+ if (success)
+ {
+ return true;
+ }
+ }
+ CLog::Log(LOGERROR, "CActiveAE::FreeStream - failed");
+ return false;
+}
+
+void CActiveAE::FlushStream(CActiveAEStream *stream)
+{
+ Message *reply;
+ if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::FLUSHSTREAM,
+ &reply,1000,
+ &stream, sizeof(CActiveAEStream*)))
+ {
+ bool success = reply->signal == CActiveAEControlProtocol::ACC;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "CActiveAE::FlushStream - failed");
+ }
+ }
+}
+
+void CActiveAE::PauseStream(CActiveAEStream *stream, bool pause)
+{
+ //! @todo pause sink, needs api change
+ if (pause)
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::PAUSESTREAM,
+ &stream, sizeof(CActiveAEStream*));
+ else
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::RESUMESTREAM,
+ &stream, sizeof(CActiveAEStream*));
+}
+
+void CActiveAE::SetStreamAmplification(CActiveAEStream *stream, float amplify)
+{
+ MsgStreamParameter msg;
+ msg.stream = stream;
+ msg.parameter.float_par = amplify;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMAMP,
+ &msg, sizeof(MsgStreamParameter));
+}
+
+void CActiveAE::SetStreamReplaygain(CActiveAEStream *stream, float rgain)
+{
+ MsgStreamParameter msg;
+ msg.stream = stream;
+ msg.parameter.float_par = rgain;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMRGAIN,
+ &msg, sizeof(MsgStreamParameter));
+}
+
+void CActiveAE::SetStreamVolume(CActiveAEStream *stream, float volume)
+{
+ MsgStreamParameter msg;
+ msg.stream = stream;
+ msg.parameter.float_par = volume;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMVOLUME,
+ &msg, sizeof(MsgStreamParameter));
+}
+
+void CActiveAE::SetStreamResampleRatio(CActiveAEStream *stream, double ratio)
+{
+ MsgStreamParameter msg;
+ msg.stream = stream;
+ msg.parameter.double_par = ratio;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMRESAMPLERATIO,
+ &msg, sizeof(MsgStreamParameter));
+}
+
+void CActiveAE::SetStreamResampleMode(CActiveAEStream *stream, int mode)
+{
+ MsgStreamParameter msg;
+ msg.stream = stream;
+ msg.parameter.int_par = mode;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMRESAMPLEMODE,
+ &msg, sizeof(MsgStreamParameter));
+}
+
+void CActiveAE::SetStreamFFmpegInfo(CActiveAEStream *stream, int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type)
+{
+ MsgStreamFFmpegInfo msg;
+ msg.stream = stream;
+ msg.profile = profile;
+ msg.matrix_encoding = matrix_encoding;
+ msg.audio_service_type = audio_service_type;
+
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMFFMPEGINFO, &msg, sizeof(MsgStreamFFmpegInfo));
+}
+
+void CActiveAE::SetStreamFade(CActiveAEStream *stream, float from, float target, unsigned int millis)
+{
+ MsgStreamFade msg;
+ msg.stream = stream;
+ msg.from = from;
+ msg.target = target;
+ msg.millis = millis;
+ m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMFADE,
+ &msg, sizeof(MsgStreamFade));
+}
+
+void CActiveAE::RegisterAudioCallback(IAudioCallback* pCallback)
+{
+ std::unique_lock<CCriticalSection> lock(m_vizLock);
+ m_audioCallback.push_back(pCallback);
+ m_vizInitialized = false;
+}
+
+void CActiveAE::UnregisterAudioCallback(IAudioCallback* pCallback)
+{
+ std::unique_lock<CCriticalSection> lock(m_vizLock);
+ auto it = std::find(m_audioCallback.begin(), m_audioCallback.end(), pCallback);
+ if (it != m_audioCallback.end())
+ m_audioCallback.erase(it);
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
new file mode 100644
index 0000000..c396c18
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ActiveAESink.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h"
+#include "cores/AudioEngine/Interfaces/AESound.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "guilib/DispResource.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+
+#include <list>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+
+// ffmpeg
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+}
+
+class IAESink;
+class IAEEncoder;
+
+namespace ActiveAE
+{
+
+class CActiveAESound;
+class CActiveAEStream;
+class CActiveAESettings;
+
+struct AudioSettings
+{
+ std::string device;
+ std::string driver;
+ std::string passthroughdevice;
+ int channels;
+ bool ac3passthrough;
+ bool ac3transcode;
+ bool eac3passthrough;
+ bool dtspassthrough;
+ bool truehdpassthrough;
+ bool dtshdpassthrough;
+ bool usesdtscorefallback;
+ bool stereoupmix;
+ bool normalizelevels;
+ bool passthrough;
+ int config;
+ int guisoundmode;
+ unsigned int samplerate;
+ AEQuality resampleQuality;
+ double atempoThreshold;
+ bool streamNoise;
+ int silenceTimeoutMinutes;
+};
+
+class CActiveAEControlProtocol : public Protocol
+{
+public:
+ CActiveAEControlProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ INIT = 0,
+ RECONFIGURE,
+ SUSPEND,
+ DEVICECHANGE,
+ DEVICECOUNTCHANGE,
+ MUTE,
+ VOLUME,
+ PAUSESTREAM,
+ RESUMESTREAM,
+ FLUSHSTREAM,
+ STREAMRGAIN,
+ STREAMVOLUME,
+ STREAMAMP,
+ STREAMRESAMPLERATIO,
+ STREAMRESAMPLEMODE,
+ STREAMFADE,
+ STREAMFFMPEGINFO,
+ STOPSOUND,
+ GETSTATE,
+ DISPLAYLOST,
+ DISPLAYRESET,
+ APPFOCUSED,
+ KEEPCONFIG,
+ TIMEOUT,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERR,
+ STATS,
+ };
+};
+
+class CActiveAEDataProtocol : public Protocol
+{
+public:
+ CActiveAEDataProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ NEWSOUND = 0,
+ PLAYSOUND,
+ FREESOUND,
+ NEWSTREAM,
+ FREESTREAM,
+ STREAMSAMPLE,
+ DRAINSTREAM,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERR,
+ STREAMBUFFER,
+ STREAMDRAINED,
+ };
+};
+
+struct MsgStreamNew
+{
+ AEAudioFormat format;
+ unsigned int options;
+ IAEClockCallback *clock;
+};
+
+struct MsgStreamFree
+{
+ CActiveAEStream *stream;
+ bool finish; // if true switch back to gui sound mode
+};
+
+struct MsgStreamSample
+{
+ CSampleBuffer *buffer;
+ CActiveAEStream *stream;
+};
+
+struct MsgStreamParameter
+{
+ CActiveAEStream *stream;
+ union
+ {
+ float float_par;
+ double double_par;
+ int int_par;
+ } parameter;
+};
+
+struct MsgStreamFade
+{
+ CActiveAEStream *stream;
+ float from;
+ float target;
+ unsigned int millis;
+};
+
+struct MsgStreamFFmpegInfo
+{
+ CActiveAEStream *stream;
+ int profile;
+ enum AVMatrixEncoding matrix_encoding;
+ enum AVAudioServiceType audio_service_type;
+};
+
+class CEngineStats
+{
+public:
+ void Reset(unsigned int sampleRate, bool pcm);
+ void UpdateSinkDelay(const AEDelayStatus& status, int samples);
+ void AddSamples(int samples, const std::list<CActiveAEStream*>& streams);
+ void GetDelay(AEDelayStatus& status);
+ void AddStream(unsigned int streamid);
+ void RemoveStream(unsigned int streamid);
+ void UpdateStream(CActiveAEStream *stream);
+ void GetDelay(AEDelayStatus& status, CActiveAEStream *stream);
+ void GetSyncInfo(CAESyncInfo& info, CActiveAEStream *stream);
+ float GetCacheTime(CActiveAEStream *stream);
+ float GetCacheTotal();
+ float GetMaxDelay() const;
+ float GetWaterLevel();
+ void SetSuspended(bool state);
+ void SetCurrentSinkFormat(const AEAudioFormat& SinkFormat);
+ void SetSinkCacheTotal(float time) { m_sinkCacheTotal = time; }
+ void SetSinkLatency(float time) { m_sinkLatency = time; }
+ void SetSinkNeedIec(bool needIEC) { m_sinkNeedIecPack = needIEC; }
+ bool IsSuspended();
+ AEAudioFormat GetCurrentSinkFormat();
+protected:
+ float m_sinkCacheTotal;
+ float m_sinkLatency;
+ int m_bufferedSamples;
+ unsigned int m_sinkSampleRate;
+ AEDelayStatus m_sinkDelay;
+ bool m_suspended;
+ AEAudioFormat m_sinkFormat;
+ bool m_pcmOutput;
+ bool m_sinkNeedIecPack{false};
+ CCriticalSection m_lock;
+ struct StreamStats
+ {
+ unsigned int m_streamId;
+ double m_bufferedTime;
+ double m_resampleRatio;
+ double m_syncError;
+ unsigned int m_errorTime;
+ CAESyncInfo::AESyncState m_syncState;
+ };
+ std::vector<StreamStats> m_streamStats;
+};
+
+class CActiveAE : public IAE, public IDispResource, private CThread
+{
+protected:
+ friend class CActiveAESound;
+ friend class CActiveAEStream;
+ friend class CSoundPacket;
+ friend class CActiveAEBufferPoolResample;
+
+public:
+ CActiveAE();
+ ~CActiveAE() override;
+ void Start() override;
+ void Shutdown() override;
+ bool Suspend() override;
+ bool Resume() override;
+ bool IsSuspended() override;
+ void OnSettingsChange();
+
+ float GetVolume() override;
+ void SetVolume(const float volume) override;
+ void SetMute(const bool enabled) override;
+ bool IsMuted() override;
+
+ /* returns a new stream for data in the specified format */
+ IAE::StreamPtr MakeStream(AEAudioFormat& audioFormat,
+ unsigned int options = 0,
+ IAEClockCallback* clock = NULL) override;
+
+ /* returns a new sound object */
+ IAE::SoundPtr MakeSound(const std::string& file) override;
+
+ void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) override;
+ bool SupportsRaw(AEAudioFormat &format) override;
+ bool SupportsSilenceTimeout() override;
+ bool UsesDtsCoreFallback() override;
+ bool HasStereoAudioChannelCount() override;
+ bool HasHDAudioChannelCount() override;
+ bool SupportsQualityLevel(enum AEQuality level) override;
+ bool IsSettingVisible(const std::string &settingId) override;
+ void KeepConfiguration(unsigned int millis) override;
+ void DeviceChange() override;
+ void DeviceCountChange(const std::string& driver) override;
+ bool GetCurrentSinkFormat(AEAudioFormat &SinkFormat) override;
+
+ void RegisterAudioCallback(IAudioCallback* pCallback) override;
+ void UnregisterAudioCallback(IAudioCallback* pCallback) override;
+
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+ void OnAppFocusChange(bool focus) override;
+
+private:
+ bool FreeStream(IAEStream* stream, bool finish) override;
+ void FreeSound(IAESound* sound) override;
+
+protected:
+ void PlaySound(CActiveAESound *sound);
+ static uint8_t **AllocSoundSample(SampleConfig &config, int &samples, int &bytes_per_sample, int &planes, int &linesize);
+ static void FreeSoundSample(uint8_t **data);
+ void GetDelay(AEDelayStatus& status, CActiveAEStream *stream) { m_stats.GetDelay(status, stream); }
+ void GetSyncInfo(CAESyncInfo& info, CActiveAEStream *stream) { m_stats.GetSyncInfo(info, stream); }
+ float GetCacheTime(CActiveAEStream *stream) { return m_stats.GetCacheTime(stream); }
+ float GetCacheTotal() { return m_stats.GetCacheTotal(); }
+ float GetMaxDelay() { return m_stats.GetMaxDelay(); }
+ void FlushStream(CActiveAEStream *stream);
+ void PauseStream(CActiveAEStream *stream, bool pause);
+ void StopSound(CActiveAESound *sound);
+ void SetStreamAmplification(CActiveAEStream *stream, float amplify);
+ void SetStreamReplaygain(CActiveAEStream *stream, float rgain);
+ void SetStreamVolume(CActiveAEStream *stream, float volume);
+ void SetStreamResampleRatio(CActiveAEStream *stream, double ratio);
+ void SetStreamResampleMode(CActiveAEStream *stream, int mode);
+ void SetStreamFFmpegInfo(CActiveAEStream *stream, int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type);
+ void SetStreamFade(CActiveAEStream *stream, float from, float target, unsigned int millis);
+
+protected:
+ void Process() override;
+ void StateMachine(int signal, Protocol *port, Message *msg);
+ bool InitSink();
+ void DrainSink();
+ void UnconfigureSink();
+ void Dispose();
+ void LoadSettings();
+ bool NeedReconfigureBuffers();
+ bool NeedReconfigureSink();
+ void ApplySettingsToFormat(AEAudioFormat& format,
+ const AudioSettings& settings,
+ int* mode = NULL);
+ void Configure(AEAudioFormat *desiredFmt = NULL);
+ AEAudioFormat GetInputFormat(AEAudioFormat *desiredFmt = NULL);
+ CActiveAEStream* CreateStream(MsgStreamNew *streamMsg);
+ void DiscardStream(CActiveAEStream *stream);
+ void SFlushStream(CActiveAEStream *stream);
+ void FlushEngine();
+ void ClearDiscardedBuffers();
+ void SStopSound(CActiveAESound *sound);
+ void DiscardSound(CActiveAESound *sound);
+ void ChangeResamplers();
+
+ bool RunStages();
+ bool HasWork();
+ CSampleBuffer* SyncStream(CActiveAEStream *stream);
+
+ void ResampleSounds();
+ bool ResampleSound(CActiveAESound *sound);
+ void MixSounds(CSoundPacket &dstSample);
+ void Deamplify(CSoundPacket &dstSample);
+
+ bool CompareFormat(const AEAudioFormat& lhs, const AEAudioFormat& rhs);
+
+ CEvent m_inMsgEvent;
+ CEvent m_outMsgEvent;
+ CActiveAEControlProtocol m_controlPort;
+ CActiveAEDataProtocol m_dataPort;
+ int m_state;
+ bool m_bStateMachineSelfTrigger;
+ std::chrono::milliseconds m_extTimeout;
+ bool m_extError;
+ bool m_extDrain;
+ XbmcThreads::EndTime<> m_extDrainTimer;
+ std::chrono::milliseconds m_extKeepConfig;
+ bool m_extDeferData;
+ std::queue<time_t> m_extLastDeviceChange;
+ bool m_extSuspended = false;
+ bool m_isWinSysReg = false;
+
+ enum
+ {
+ MODE_RAW,
+ MODE_TRANSCODE,
+ MODE_PCM
+ }m_mode;
+
+ CActiveAESink m_sink;
+ AEAudioFormat m_sinkFormat;
+ AEAudioFormat m_sinkRequestFormat;
+ AEAudioFormat m_encoderFormat;
+ AEAudioFormat m_internalFormat;
+ AEAudioFormat m_inputFormat;
+ AudioSettings m_settings;
+ CEngineStats m_stats;
+ IAEEncoder *m_encoder;
+ std::string m_currDevice;
+ std::unique_ptr<CActiveAESettings> m_settingsHandler;
+
+ // buffers
+ CActiveAEBufferPoolResample *m_sinkBuffers;
+ CActiveAEBufferPoolResample *m_vizBuffers;
+ CActiveAEBufferPool *m_vizBuffersInput;
+ CActiveAEBufferPool *m_silenceBuffers; // needed to drive gui sounds if we have no streams
+ CActiveAEBufferPool *m_encoderBuffers;
+
+ // streams
+ std::list<CActiveAEStream*> m_streams;
+ std::list<CActiveAEBufferPool*> m_discardBufferPools;
+ unsigned int m_streamIdGen;
+
+ // gui sounds
+ struct SoundState
+ {
+ CActiveAESound *sound;
+ int samples_played;
+ };
+ std::list<SoundState> m_sounds_playing;
+ std::vector<CActiveAESound*> m_sounds;
+
+ float m_volume; // volume on a 0..1 scale corresponding to a proportion along the dB scale
+ float m_volumeScaled; // multiplier to scale samples in order to achieve the volume specified in m_volume
+ bool m_muted;
+ bool m_sinkHasVolume;
+
+ // viz
+ std::vector<IAudioCallback*> m_audioCallback;
+ bool m_vizInitialized;
+ CCriticalSection m_vizLock;
+
+ // polled via the interface
+ float m_aeVolume;
+ bool m_aeMuted;
+ bool m_aeGUISoundForce;
+};
+};
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp
new file mode 100644
index 0000000..7768a62
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAEBuffer.h"
+
+#include "ActiveAE.h"
+#include "ActiveAEFilter.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+
+using namespace ActiveAE;
+
+CSoundPacket::CSoundPacket(const SampleConfig& conf, int samples) : config(conf)
+{
+ data = CActiveAE::AllocSoundSample(config, samples, bytes_per_sample, planes, linesize);
+ max_nb_samples = samples;
+ nb_samples = 0;
+ pause_burst_ms = 0;
+}
+
+CSoundPacket::~CSoundPacket()
+{
+ if (data)
+ CActiveAE::FreeSoundSample(data);
+}
+
+CSampleBuffer::~CSampleBuffer()
+{
+ delete pkt;
+}
+
+CSampleBuffer* CSampleBuffer::Acquire()
+{
+ refCount++;
+ return this;
+}
+
+void CSampleBuffer::Return()
+{
+ refCount--;
+ if (pool && refCount <= 0)
+ pool->ReturnBuffer(this);
+}
+
+CActiveAEBufferPool::CActiveAEBufferPool(const AEAudioFormat& format) : m_format(format)
+{
+ if (m_format.m_dataFormat == AE_FMT_RAW)
+ {
+ m_format.m_frameSize = 1;
+ m_format.m_frames = 61440;
+ m_format.m_channelLayout.Reset();
+ m_format.m_channelLayout += AE_CH_FC;
+ }
+}
+
+CActiveAEBufferPool::~CActiveAEBufferPool()
+{
+ CSampleBuffer *buffer;
+ while(!m_allSamples.empty())
+ {
+ buffer = m_allSamples.front();
+ m_allSamples.pop_front();
+ delete buffer;
+ }
+}
+
+CSampleBuffer* CActiveAEBufferPool::GetFreeBuffer()
+{
+ CSampleBuffer* buf = NULL;
+
+ if (!m_freeSamples.empty())
+ {
+ buf = m_freeSamples.front();
+ m_freeSamples.pop_front();
+ buf->refCount = 1;
+ buf->centerMixLevel = M_SQRT1_2;
+ }
+ return buf;
+}
+
+void CActiveAEBufferPool::ReturnBuffer(CSampleBuffer *buffer)
+{
+ buffer->pkt->nb_samples = 0;
+ buffer->pkt->pause_burst_ms = 0;
+ m_freeSamples.push_back(buffer);
+}
+
+bool CActiveAEBufferPool::Create(unsigned int totaltime)
+{
+ CSampleBuffer *buffer;
+ SampleConfig config;
+ config.fmt = CAEUtil::GetAVSampleFormat(m_format.m_dataFormat);
+ config.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat);
+ config.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat);
+ config.channels = m_format.m_channelLayout.Count();
+ config.sample_rate = m_format.m_sampleRate;
+ config.channel_layout = CAEUtil::GetAVChannelLayout(m_format.m_channelLayout);
+
+ unsigned int time = 0;
+ unsigned int buffertime = (m_format.m_frames*1000) / m_format.m_sampleRate;
+ if (m_format.m_dataFormat == AE_FMT_RAW)
+ {
+ buffertime = m_format.m_streamInfo.GetDuration();
+ }
+ unsigned int n = 0;
+ while (time < totaltime || n < 5)
+ {
+ buffer = new CSampleBuffer();
+ buffer->pool = this;
+ buffer->pkt = new CSoundPacket(config, m_format.m_frames);
+
+ m_allSamples.push_back(buffer);
+ m_freeSamples.push_back(buffer);
+ time += buffertime;
+ n++;
+ }
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------------
+// Resample
+// ----------------------------------------------------------------------------------
+
+CActiveAEBufferPoolResample::CActiveAEBufferPoolResample(const AEAudioFormat& inputFormat,
+ const AEAudioFormat& outputFormat,
+ AEQuality quality)
+ : CActiveAEBufferPool(outputFormat), m_inputFormat(inputFormat)
+{
+ if (m_inputFormat.m_dataFormat == AE_FMT_RAW)
+ {
+ m_format.m_frameSize = 1;
+ m_format.m_frames = 61440;
+ m_inputFormat.m_channelLayout.Reset();
+ m_inputFormat.m_channelLayout += AE_CH_FC;
+ }
+ m_resampleQuality = quality;
+}
+
+CActiveAEBufferPoolResample::~CActiveAEBufferPoolResample()
+{
+ Flush();
+
+ delete m_resampler;
+}
+
+bool CActiveAEBufferPoolResample::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize)
+{
+ CActiveAEBufferPool::Create(totaltime);
+
+ m_remap = remap;
+ m_stereoUpmix = upmix;
+
+ m_normalize = true;
+ if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count() && !normalize))
+ m_normalize = false;
+
+ if (m_inputFormat.m_channelLayout != m_format.m_channelLayout ||
+ m_inputFormat.m_sampleRate != m_format.m_sampleRate ||
+ m_inputFormat.m_dataFormat != m_format.m_dataFormat ||
+ m_changeResampler)
+ {
+ ChangeResampler();
+ }
+ return true;
+}
+
+void CActiveAEBufferPoolResample::ChangeResampler()
+{
+ if (m_resampler)
+ {
+ delete m_resampler;
+ m_resampler = NULL;
+ }
+
+ m_resampler = CAEResampleFactory::Create();
+
+ SampleConfig dstConfig, srcConfig;
+ dstConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_format.m_channelLayout);
+ dstConfig.channels = m_format.m_channelLayout.Count();
+ dstConfig.sample_rate = m_format.m_sampleRate;
+ dstConfig.fmt = CAEUtil::GetAVSampleFormat(m_format.m_dataFormat);
+ dstConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat);
+ dstConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat);
+
+ srcConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_inputFormat.m_channelLayout);
+ srcConfig.channels = m_inputFormat.m_channelLayout.Count();
+ srcConfig.sample_rate = m_inputFormat.m_sampleRate;
+ srcConfig.fmt = CAEUtil::GetAVSampleFormat(m_inputFormat.m_dataFormat);
+ srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_inputFormat.m_dataFormat);
+ srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_inputFormat.m_dataFormat);
+
+ m_resampler->Init(dstConfig, srcConfig,
+ m_stereoUpmix,
+ m_normalize,
+ m_centerMixLevel,
+ m_remap ? &m_format.m_channelLayout : nullptr,
+ m_resampleQuality,
+ m_forceResampler);
+
+ m_changeResampler = false;
+}
+
+bool CActiveAEBufferPoolResample::ResampleBuffers(int64_t timestamp)
+{
+ bool busy = false;
+ CSampleBuffer *in;
+
+ if (!m_resampler)
+ {
+ if (m_changeResampler)
+ {
+ if (m_changeResampler)
+ ChangeResampler();
+ return true;
+ }
+ while(!m_inputSamples.empty())
+ {
+ in = m_inputSamples.front();
+ m_inputSamples.pop_front();
+ if (timestamp)
+ {
+ in->timestamp = timestamp;
+ }
+ m_outputSamples.push_back(in);
+ busy = true;
+ }
+ }
+ else if (m_procSample || !m_freeSamples.empty())
+ {
+ int free_samples;
+ if (m_procSample)
+ free_samples = m_procSample->pkt->max_nb_samples - m_procSample->pkt->nb_samples;
+ else
+ free_samples = m_format.m_frames;
+
+ bool skipInput = false;
+ // avoid that ffmpeg resample buffer grows too large
+ if (!m_resampler->WantsNewSamples(free_samples) && !m_empty)
+ skipInput = true;
+
+ bool hasInput = !m_inputSamples.empty();
+
+ if (hasInput || skipInput || m_drain || m_changeResampler)
+ {
+ if (!m_procSample)
+ {
+ m_procSample = GetFreeBuffer();
+ }
+
+ if (hasInput && !skipInput && !m_changeResampler)
+ {
+ in = m_inputSamples.front();
+ if (in->centerMixLevel != m_centerMixLevel)
+ {
+ m_centerMixLevel = in->centerMixLevel;
+ m_changeResampler = true;
+ in = nullptr;
+ }
+ else
+ m_inputSamples.pop_front();
+ }
+ else
+ in = nullptr;
+
+ int start = m_procSample->pkt->nb_samples *
+ m_procSample->pkt->bytes_per_sample *
+ m_procSample->pkt->config.channels /
+ m_procSample->pkt->planes;
+
+ for(int i=0; i<m_procSample->pkt->planes; i++)
+ {
+ m_planes[i] = m_procSample->pkt->data[i] + start;
+ }
+
+ int out_samples = m_resampler->Resample(m_planes,
+ m_procSample->pkt->max_nb_samples - m_procSample->pkt->nb_samples,
+ in ? in->pkt->data : NULL,
+ in ? in->pkt->nb_samples : 0,
+ m_resampleRatio);
+ // in case of error, trigger re-create of resampler
+ if (out_samples < 0)
+ {
+ out_samples = 0;
+ m_changeResampler = true;
+ }
+
+ m_procSample->pkt->nb_samples += out_samples;
+ busy = true;
+ m_empty = (out_samples == 0);
+
+ if (in)
+ {
+ if (!timestamp)
+ {
+ if (in->timestamp)
+ m_lastSamplePts = in->timestamp;
+ else
+ in->pkt_start_offset = 0;
+ }
+ else
+ {
+ m_lastSamplePts = timestamp;
+ in->pkt_start_offset = 0;
+ }
+
+ // pts of last sample we added to the buffer
+ m_lastSamplePts +=
+ (in->pkt->nb_samples - in->pkt_start_offset) * 1000 / in->pkt->config.sample_rate;
+ }
+
+ // calculate pts for last sample in m_procSample
+ int bufferedSamples = m_resampler->GetBufferedSamples();
+ m_procSample->pkt_start_offset = m_procSample->pkt->nb_samples;
+ m_procSample->timestamp = m_lastSamplePts - bufferedSamples * 1000 / m_format.m_sampleRate;
+
+ if ((m_drain || m_changeResampler) && m_empty)
+ {
+ if (m_fillPackets && m_procSample->pkt->nb_samples != 0)
+ {
+ // pad with zero
+ start = m_procSample->pkt->nb_samples *
+ m_procSample->pkt->bytes_per_sample *
+ m_procSample->pkt->config.channels /
+ m_procSample->pkt->planes;
+ for(int i=0; i<m_procSample->pkt->planes; i++)
+ {
+ memset(m_procSample->pkt->data[i]+start, 0, m_procSample->pkt->linesize-start);
+ }
+ }
+
+ // check if draining is finished
+ if (m_drain && m_procSample->pkt->nb_samples == 0)
+ {
+ m_procSample->Return();
+ busy = false;
+ }
+ else
+ m_outputSamples.push_back(m_procSample);
+
+ m_procSample = NULL;
+ if (m_changeResampler)
+ ChangeResampler();
+ }
+ // some methods like encode require completely filled packets
+ else if (!m_fillPackets || (m_procSample->pkt->nb_samples == m_procSample->pkt->max_nb_samples))
+ {
+ m_outputSamples.push_back(m_procSample);
+ m_procSample = NULL;
+ }
+
+ if (in)
+ in->Return();
+ }
+ }
+ return busy;
+}
+
+void CActiveAEBufferPoolResample::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality)
+{
+ bool normalize = true;
+ if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count()) && !normalizelevels)
+ {
+ normalize = false;
+ }
+
+ if (m_normalize != normalize || m_resampleQuality != quality)
+ {
+ m_changeResampler = true;
+ }
+
+ m_resampleQuality = quality;
+ m_normalize = normalize;
+}
+
+float CActiveAEBufferPoolResample::GetDelay()
+{
+ float delay = 0;
+ std::deque<CSampleBuffer*>::iterator itBuf;
+
+ if (m_procSample)
+ delay += (float)m_procSample->pkt->nb_samples / m_procSample->pkt->config.sample_rate;
+
+ for(itBuf=m_inputSamples.begin(); itBuf!=m_inputSamples.end(); ++itBuf)
+ {
+ delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate;
+ }
+
+ for(itBuf=m_outputSamples.begin(); itBuf!=m_outputSamples.end(); ++itBuf)
+ {
+ delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate;
+ }
+
+ if (m_resampler)
+ {
+ int samples = m_resampler->GetBufferedSamples();
+ delay += (float)samples / m_format.m_sampleRate;
+ }
+
+ return delay;
+}
+
+void CActiveAEBufferPoolResample::Flush()
+{
+ if (m_procSample)
+ {
+ m_procSample->Return();
+ m_procSample = NULL;
+ }
+ while (!m_inputSamples.empty())
+ {
+ m_inputSamples.front()->Return();
+ m_inputSamples.pop_front();
+ }
+ while (!m_outputSamples.empty())
+ {
+ m_outputSamples.front()->Return();
+ m_outputSamples.pop_front();
+ }
+ if (m_resampler)
+ ChangeResampler();
+}
+
+void CActiveAEBufferPoolResample::SetDrain(bool drain)
+{
+ m_drain = drain;
+}
+
+void CActiveAEBufferPoolResample::SetRR(double rr)
+{
+ m_resampleRatio = rr;
+}
+
+double CActiveAEBufferPoolResample::GetRR() const
+{
+ return m_resampleRatio;
+}
+
+void CActiveAEBufferPoolResample::FillBuffer()
+{
+ m_fillPackets = true;
+}
+
+bool CActiveAEBufferPoolResample::DoesNormalize() const
+{
+ return m_normalize;
+}
+
+void CActiveAEBufferPoolResample::ForceResampler(bool force)
+{
+ m_forceResampler = force;
+}
+
+
+// ----------------------------------------------------------------------------------
+// Atempo
+// ----------------------------------------------------------------------------------
+
+CActiveAEBufferPoolAtempo::CActiveAEBufferPoolAtempo(const AEAudioFormat& format) : CActiveAEBufferPool(format)
+{
+ m_drain = false;
+ m_empty = true;
+ m_tempo = 1.0;
+ m_changeFilter = false;
+ m_procSample = nullptr;
+}
+
+CActiveAEBufferPoolAtempo::~CActiveAEBufferPoolAtempo()
+{
+ Flush();
+}
+
+bool CActiveAEBufferPoolAtempo::Create(unsigned int totaltime)
+{
+ CActiveAEBufferPool::Create(totaltime);
+
+ m_pTempoFilter.reset(new CActiveAEFilter());
+ m_pTempoFilter->Init(CAEUtil::GetAVSampleFormat(m_format.m_dataFormat), m_format.m_sampleRate, CAEUtil::GetAVChannelLayout(m_format.m_channelLayout));
+
+ return true;
+}
+
+void CActiveAEBufferPoolAtempo::ChangeFilter()
+{
+ m_pTempoFilter->SetTempo(m_tempo);
+ m_changeFilter = false;
+}
+
+bool CActiveAEBufferPoolAtempo::ProcessBuffers()
+{
+ bool busy = false;
+ CSampleBuffer *in;
+
+ if (!m_pTempoFilter->IsActive())
+ {
+ if (m_changeFilter)
+ {
+ if (m_changeFilter)
+ ChangeFilter();
+ return true;
+ }
+ while(!m_inputSamples.empty())
+ {
+ in = m_inputSamples.front();
+ m_inputSamples.pop_front();
+ m_outputSamples.push_back(in);
+ busy = true;
+ }
+ }
+ else if (m_procSample || !m_freeSamples.empty())
+ {
+ bool skipInput = false;
+
+ // avoid that bufferscr grows too large
+ if (!m_pTempoFilter->NeedData())
+ skipInput = true;
+
+ bool hasInput = !m_inputSamples.empty();
+
+ if (hasInput || skipInput || m_drain || m_changeFilter)
+ {
+ if (!m_procSample)
+ {
+ m_procSample = GetFreeBuffer();
+ }
+
+ if (hasInput && !skipInput && !m_changeFilter)
+ {
+ in = m_inputSamples.front();
+ m_inputSamples.pop_front();
+ }
+ else
+ in = nullptr;
+
+ int start = m_procSample->pkt->nb_samples *
+ m_procSample->pkt->bytes_per_sample *
+ m_procSample->pkt->config.channels /
+ m_procSample->pkt->planes;
+
+ for (int i=0; i<m_procSample->pkt->planes; i++)
+ {
+ m_planes[i] = m_procSample->pkt->data[i] + start;
+ }
+
+ int out_samples = m_pTempoFilter->ProcessFilter(m_planes,
+ m_procSample->pkt->max_nb_samples - m_procSample->pkt->nb_samples,
+ in ? in->pkt->data : nullptr,
+ in ? in->pkt->nb_samples : 0,
+ in ? in->pkt->linesize * in->pkt->planes : 0);
+
+ // in case of error, trigger re-create of filter
+ if (out_samples < 0)
+ {
+ out_samples = 0;
+ m_changeFilter = true;
+ }
+
+ m_procSample->pkt->nb_samples += out_samples;
+ busy = true;
+ m_empty = m_pTempoFilter->IsEof();
+
+ if (in)
+ {
+ if (in->timestamp)
+ m_lastSamplePts = in->timestamp;
+ else
+ in->pkt_start_offset = 0;
+
+ // pts of last sample we added to the buffer
+ m_lastSamplePts += (in->pkt->nb_samples-in->pkt_start_offset) * 1000 / m_format.m_sampleRate;
+ }
+
+ // calculate pts for last sample in m_procSample
+ int bufferedSamples = m_pTempoFilter->GetBufferedSamples();
+ m_procSample->pkt_start_offset = m_procSample->pkt->nb_samples;
+ m_procSample->timestamp = m_lastSamplePts - bufferedSamples * 1000 / m_format.m_sampleRate;
+
+ if ((m_drain || m_changeFilter) && m_empty)
+ {
+ if (m_fillPackets && m_procSample->pkt->nb_samples != 0)
+ {
+ // pad with zero
+ start = m_procSample->pkt->nb_samples *
+ m_procSample->pkt->bytes_per_sample *
+ m_procSample->pkt->config.channels /
+ m_procSample->pkt->planes;
+ for (int i=0; i<m_procSample->pkt->planes; i++)
+ {
+ memset(m_procSample->pkt->data[i]+start, 0, m_procSample->pkt->linesize-start);
+ }
+ }
+
+ // check if draining is finished
+ if (m_drain && m_procSample->pkt->nb_samples == 0)
+ {
+ m_procSample->Return();
+ busy = false;
+ }
+ else
+ m_outputSamples.push_back(m_procSample);
+
+ m_procSample = nullptr;
+
+ if (m_changeFilter)
+ {
+ ChangeFilter();
+ }
+ }
+ // some methods like encode require completely filled packets
+ else if (!m_fillPackets || (m_procSample->pkt->nb_samples == m_procSample->pkt->max_nb_samples))
+ {
+ m_outputSamples.push_back(m_procSample);
+ m_procSample = nullptr;
+ }
+
+ if (in)
+ in->Return();
+ }
+ }
+ return busy;
+}
+
+void CActiveAEBufferPoolAtempo::Flush()
+{
+ if (m_procSample)
+ {
+ m_procSample->Return();
+ m_procSample = nullptr;
+ }
+ while (!m_inputSamples.empty())
+ {
+ m_inputSamples.front()->Return();
+ m_inputSamples.pop_front();
+ }
+ while (!m_outputSamples.empty())
+ {
+ m_outputSamples.front()->Return();
+ m_outputSamples.pop_front();
+ }
+ if (m_pTempoFilter)
+ ChangeFilter();
+}
+
+float CActiveAEBufferPoolAtempo::GetDelay()
+{
+ float delay = 0;
+
+ if (m_procSample)
+ delay += (float)m_procSample->pkt->nb_samples / m_procSample->pkt->config.sample_rate;
+
+ for (auto &buf : m_inputSamples)
+ {
+ delay += (float)buf->pkt->nb_samples / buf->pkt->config.sample_rate;
+ }
+
+ for (auto &buf : m_outputSamples)
+ {
+ delay += (float)buf->pkt->nb_samples / buf->pkt->config.sample_rate;
+ }
+
+ if (m_pTempoFilter->IsActive())
+ {
+ int samples = m_pTempoFilter->GetBufferedSamples();
+ delay += (float)samples / m_format.m_sampleRate;
+ }
+
+ return delay;
+}
+
+void CActiveAEBufferPoolAtempo::SetTempo(float tempo)
+{
+ if (tempo > 2.0f)
+ tempo = 2.0;
+ else if (tempo < 0.5f)
+ tempo = 0.5;
+
+ if (tempo != m_tempo)
+ m_changeFilter = true;
+
+ m_tempo = tempo;
+}
+
+float CActiveAEBufferPoolAtempo::GetTempo() const
+{
+ return m_tempo;
+}
+
+void CActiveAEBufferPoolAtempo::FillBuffer()
+{
+ m_fillPackets = true;
+}
+
+void CActiveAEBufferPoolAtempo::SetDrain(bool drain)
+{
+ m_drain = drain;
+ if (!m_drain)
+ m_changeFilter = true;
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h
new file mode 100644
index 0000000..423b59e
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include <cmath>
+#include <deque>
+#include <memory>
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libswresample/swresample.h>
+}
+
+namespace ActiveAE
+{
+
+/**
+ * the variables here follow ffmpeg naming
+ */
+class CSoundPacket
+{
+public:
+ CSoundPacket(const SampleConfig& conf, int samples);
+ ~CSoundPacket();
+ uint8_t **data; // array with pointers to planes of data
+ SampleConfig config;
+ int bytes_per_sample; // bytes per sample and per channel
+ int linesize; // see ffmpeg, required for planar formats
+ int planes; // 1 for non planar formats, #channels for planar
+ int nb_samples; // number of frames used
+ int max_nb_samples; // max number of frames this packet can hold
+ int pause_burst_ms;
+};
+
+class CActiveAEBufferPool;
+
+class CSampleBuffer
+{
+public:
+ CSampleBuffer() = default;
+ ~CSampleBuffer();
+ CSampleBuffer *Acquire();
+ void Return();
+ CSoundPacket *pkt = nullptr;
+ CActiveAEBufferPool *pool = nullptr;
+ int64_t timestamp;
+ int pkt_start_offset = 0;
+ int refCount = 0;
+ double centerMixLevel;
+};
+
+class CActiveAEBufferPool
+{
+public:
+ explicit CActiveAEBufferPool(const AEAudioFormat& format);
+ virtual ~CActiveAEBufferPool();
+ virtual bool Create(unsigned int totaltime);
+ CSampleBuffer *GetFreeBuffer();
+ void ReturnBuffer(CSampleBuffer *buffer);
+ AEAudioFormat m_format;
+ std::deque<CSampleBuffer*> m_allSamples;
+ std::deque<CSampleBuffer*> m_freeSamples;
+};
+
+class IAEResample;
+
+class CActiveAEBufferPoolResample : public CActiveAEBufferPool
+{
+public:
+ CActiveAEBufferPoolResample(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality);
+ ~CActiveAEBufferPoolResample() override;
+ using CActiveAEBufferPool::Create;
+ bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true);
+ bool ResampleBuffers(int64_t timestamp = 0);
+ void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality);
+ float GetDelay();
+ void Flush();
+ void SetDrain(bool drain);
+ void SetRR(double rr);
+ double GetRR() const;
+ void FillBuffer();
+ bool DoesNormalize() const;
+ void ForceResampler(bool force);
+ AEAudioFormat m_inputFormat;
+ std::deque<CSampleBuffer*> m_inputSamples;
+ std::deque<CSampleBuffer*> m_outputSamples;
+
+protected:
+ void ChangeResampler();
+
+ uint8_t *m_planes[16];
+ bool m_empty = true;
+ bool m_drain = false;
+ int64_t m_lastSamplePts = 0;
+ bool m_remap = false;
+ CSampleBuffer *m_procSample = nullptr;
+ IAEResample *m_resampler = nullptr;
+ double m_resampleRatio = 1.0;
+ double m_centerMixLevel = M_SQRT1_2;
+ bool m_fillPackets = false;
+ bool m_normalize = true;
+ bool m_changeResampler = false;
+ bool m_forceResampler = false;
+ AEQuality m_resampleQuality;
+ bool m_stereoUpmix = false;
+};
+
+class CActiveAEFilter;
+
+class CActiveAEBufferPoolAtempo : public CActiveAEBufferPool
+{
+public:
+ explicit CActiveAEBufferPoolAtempo(const AEAudioFormat& format);
+ ~CActiveAEBufferPoolAtempo() override;
+ bool Create(unsigned int totaltime) override;
+ bool ProcessBuffers();
+ float GetDelay();
+ void Flush();
+ void SetTempo(float tempo);
+ float GetTempo() const;
+ void FillBuffer();
+ void SetDrain(bool drain);
+ std::deque<CSampleBuffer*> m_inputSamples;
+ std::deque<CSampleBuffer*> m_outputSamples;
+
+protected:
+ void ChangeFilter();
+ std::unique_ptr<CActiveAEFilter> m_pTempoFilter;
+ uint8_t *m_planes[16];
+ CSampleBuffer *m_procSample;
+ bool m_empty;
+ bool m_drain;
+ bool m_changeFilter;
+ float m_tempo;
+ int64_t m_lastSamplePts;
+ bool m_fillPackets;
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.cpp
new file mode 100644
index 0000000..26b669a
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAEFilter.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+#include <algorithm>
+
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libavcodec/avcodec.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+#include <libswresample/swresample.h>
+}
+
+using namespace ActiveAE;
+
+CActiveAEFilter::CActiveAEFilter()
+{
+ m_pFilterGraph = nullptr;
+ m_pFilterCtxIn = nullptr;
+ m_pFilterCtxOut = nullptr;
+ m_pOutFrame = nullptr;
+ m_pConvertCtx = nullptr;
+ m_pConvertFrame = nullptr;
+ m_needConvert = false;
+}
+
+CActiveAEFilter::~CActiveAEFilter()
+{
+ CloseFilter();
+}
+
+void CActiveAEFilter::Init(AVSampleFormat fmt, int sampleRate, uint64_t channelLayout)
+{
+ m_sampleFormat = fmt;
+ m_sampleRate = sampleRate;
+ m_channelLayout = channelLayout;
+ m_tempo = 1.0;
+ m_SamplesIn = 0;
+ m_SamplesOut = 0;
+}
+
+bool CActiveAEFilter::SetTempo(float tempo)
+{
+ m_tempo = tempo;
+ if (m_tempo == 1.0f)
+ {
+ CloseFilter();
+ return true;
+ }
+
+ if (!CreateFilterGraph())
+ return false;
+
+ if (!CreateAtempoFilter())
+ {
+ CloseFilter();
+ return false;
+ }
+
+ m_SamplesIn = 0;
+ m_SamplesOut = 0;
+ return true;
+}
+
+bool CActiveAEFilter::CreateFilterGraph()
+{
+ CloseFilter();
+
+ m_pFilterGraph = avfilter_graph_alloc();
+ if (!m_pFilterGraph)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateFilterGraph - unable to alloc filter graph");
+ return false;
+ }
+
+ const AVFilter* srcFilter = avfilter_get_by_name("abuffer");
+ const AVFilter* outFilter = avfilter_get_by_name("abuffersink");
+
+ std::string args = StringUtils::Format(
+ "time_base=1/{}:sample_rate={}:sample_fmt={}:channel_layout={}", m_sampleRate, m_sampleRate,
+ av_get_sample_fmt_name(m_sampleFormat), m_channelLayout);
+
+ int ret = avfilter_graph_create_filter(&m_pFilterCtxIn, srcFilter, "in", args.c_str(), NULL, m_pFilterGraph);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateFilterGraph - avfilter_graph_create_filter: src");
+ return false;
+ }
+
+ ret = avfilter_graph_create_filter(&m_pFilterCtxOut, outFilter, "out", NULL, NULL, m_pFilterGraph);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateFilterGraph - avfilter_graph_create_filter: out");
+ return false;
+ }
+
+ m_pOutFrame = av_frame_alloc();
+
+ return true;
+}
+
+bool CActiveAEFilter::CreateAtempoFilter()
+{
+ const AVFilter *atempo;
+
+ atempo = avfilter_get_by_name("atempo");
+ m_pFilterCtxAtempo = avfilter_graph_alloc_filter(m_pFilterGraph, atempo, "atempo");
+ std::string args = StringUtils::Format("tempo={:f}", m_tempo);
+ int ret = avfilter_init_str(m_pFilterCtxAtempo, args.c_str());
+
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateAtempoFilter - avfilter_init_str failed");
+ return false;
+ }
+
+ ret = avfilter_link(m_pFilterCtxIn, 0, m_pFilterCtxAtempo, 0);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateAtempoFilter - avfilter_link failed for in filter");
+ return false;
+ }
+
+ ret = avfilter_link(m_pFilterCtxAtempo, 0, m_pFilterCtxOut, 0);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateAtempoFilter - avfilter_link failed for out filter");
+ return false;
+ }
+
+ ret = avfilter_graph_config(m_pFilterGraph, NULL);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::CreateAtempoFilter - avfilter_graph_config failed");
+ return false;
+ }
+
+ m_needConvert = false;
+ if (m_pFilterCtxAtempo->outputs[0]->format != m_sampleFormat)
+ {
+ m_needConvert = true;
+ m_pConvertCtx = swr_alloc();
+ m_pConvertFrame = av_frame_alloc();
+ }
+
+ m_hasData = false;
+ m_needData = true;
+ m_filterEof = false;
+ m_started = false;
+ m_ptsInitialized = false;
+
+ return true;
+}
+
+void CActiveAEFilter::CloseFilter()
+{
+ if (m_pFilterGraph)
+ {
+ avfilter_graph_free(&m_pFilterGraph);
+
+ m_pFilterCtxIn = nullptr;
+ m_pFilterCtxOut = nullptr;
+ }
+
+ if (m_pOutFrame)
+ av_frame_free(&m_pOutFrame);
+
+ if (m_pConvertFrame)
+ av_frame_free(&m_pConvertFrame);
+
+ if (m_pConvertCtx)
+ swr_free(&m_pConvertCtx);
+
+ m_SamplesIn = 0;
+ m_SamplesOut = 0;
+
+ m_ptsInitialized = false;
+}
+
+int CActiveAEFilter::ProcessFilter(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, int src_bufsize)
+{
+ if (m_filterEof)
+ {
+ if (src_samples)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - adding data while already eof");
+ return -1;
+ }
+ return 0;
+ }
+
+ int result;
+
+ if (src_samples)
+ {
+ AVFrame *frame = av_frame_alloc();
+ if (!frame)
+ return -1;
+
+ int channels = av_get_channel_layout_nb_channels(m_channelLayout);
+
+ frame->channel_layout = m_channelLayout;
+ frame->channels = channels;
+ frame->sample_rate = m_sampleRate;
+ frame->nb_samples = src_samples;
+ frame->format = m_sampleFormat;
+ if (!m_ptsInitialized)
+ {
+ frame->pts = 0;
+ m_ptsInitialized = true;
+ }
+
+ m_SamplesIn += src_samples;
+
+ result = avcodec_fill_audio_frame(frame, channels, m_sampleFormat,
+ src_buffer[0], src_bufsize, 16);
+ if (result < 0)
+ {
+ av_frame_free(&frame);
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - avcodec_fill_audio_frame failed");
+ m_filterEof = true;
+ return -1;
+ }
+
+ result = av_buffersrc_write_frame(m_pFilterCtxIn, frame);
+ av_frame_free(&frame);
+ if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - av_buffersrc_add_frame failed");
+ m_filterEof = true;
+ return -1;
+ }
+
+ m_started = true;
+ }
+ else if (!m_filterEof && m_needData)
+ {
+ result = av_buffersrc_write_frame(m_pFilterCtxIn, nullptr);
+ if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - av_buffersrc_add_frame");
+ m_filterEof = true;
+ return -1;
+ }
+ }
+
+ if (!m_hasData && m_started)
+ {
+ m_needData = false;
+ AVFrame *outFrame = m_needConvert ? m_pConvertFrame : m_pOutFrame;
+
+ result = av_buffersink_get_frame(m_pFilterCtxOut, outFrame);
+
+ if (result == AVERROR(EAGAIN))
+ {
+ m_needData = true;
+ return 0;
+ }
+ else if (result == AVERROR_EOF)
+ {
+ result = av_buffersink_get_frame(m_pFilterCtxOut, outFrame);
+ m_filterEof = true;
+ if (result < 0)
+ return 0;
+ }
+ else if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - av_buffersink_get_frame");
+ m_filterEof = true;
+ return -1;
+ }
+
+ m_SamplesOut = outFrame->pts;
+
+ if (m_needConvert)
+ {
+ av_frame_unref(m_pOutFrame);
+ m_pOutFrame->format = m_sampleFormat;
+ m_pOutFrame->channel_layout = m_channelLayout;
+ m_pOutFrame->sample_rate = m_sampleRate;
+ result = swr_convert_frame(m_pConvertCtx, m_pOutFrame, m_pConvertFrame);
+ av_frame_unref(m_pConvertFrame);
+ if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEFilter::ProcessFilter - swr_convert_frame failed");
+ m_filterEof = true;
+ return -1;
+ }
+ }
+
+ m_hasData = true;
+ m_sampleOffset = 0;
+ }
+
+ if (m_hasData)
+ {
+ int channels = av_get_channel_layout_nb_channels(m_channelLayout);
+ int planes = av_sample_fmt_is_planar(m_sampleFormat) ? channels : 1;
+ int samples = std::min(dst_samples, m_pOutFrame->nb_samples - m_sampleOffset);
+ int bytes = samples * av_get_bytes_per_sample(m_sampleFormat) * channels / planes;
+ int bytesOffset = m_sampleOffset * av_get_bytes_per_sample(m_sampleFormat) * channels / planes;
+ for (int i=0; i<planes; i++)
+ {
+ memcpy(dst_buffer[i], m_pOutFrame->extended_data[i] + bytesOffset, bytes);
+ }
+ m_sampleOffset += samples;
+
+ if (m_sampleOffset >= m_pOutFrame->nb_samples)
+ {
+ av_frame_unref(m_pOutFrame);
+ m_hasData = false;
+ }
+
+ return samples;
+ }
+
+ return 0;
+}
+
+bool CActiveAEFilter::IsEof() const
+{
+ return m_filterEof;
+}
+
+bool CActiveAEFilter::NeedData() const
+{
+ return m_needData;
+}
+
+bool CActiveAEFilter::IsActive() const
+{
+ return m_pFilterGraph != nullptr && !m_filterEof;
+}
+
+int CActiveAEFilter::GetBufferedSamples() const
+{
+ int ret = m_SamplesIn - (m_SamplesOut * m_tempo);
+ if (m_hasData)
+ {
+ ret += (m_pOutFrame->nb_samples - m_sampleOffset);
+ }
+ return ret;
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.h
new file mode 100644
index 0000000..8cb15e7
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEFilter.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libavutil/frame.h>
+}
+
+struct SwrContext;
+
+namespace ActiveAE
+{
+
+class CActiveAEFilter
+{
+public:
+ CActiveAEFilter();
+ virtual ~CActiveAEFilter();
+ void Init(AVSampleFormat fmt, int sampleRate, uint64_t channelLayout);
+ int ProcessFilter(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, int src_bufsize);
+ bool SetTempo(float tempo);
+ bool NeedData() const;
+ bool IsEof() const;
+ bool IsActive() const;
+ int GetBufferedSamples() const;
+
+protected:
+ bool CreateFilterGraph();
+ bool CreateAtempoFilter();
+ void CloseFilter();
+
+ AVSampleFormat m_sampleFormat;
+ int m_sampleRate;
+ uint64_t m_channelLayout;
+ AVFilterGraph* m_pFilterGraph;
+ AVFilterContext* m_pFilterCtxIn;
+ AVFilterContext* m_pFilterCtxOut;
+ AVFilterContext* m_pFilterCtxAtempo;
+ AVFrame* m_pOutFrame;
+ SwrContext* m_pConvertCtx;
+ AVFrame* m_pConvertFrame;
+ bool m_needConvert;
+ float m_tempo;
+ bool m_filterEof;
+ bool m_started;
+ bool m_hasData;
+ bool m_needData;
+ bool m_ptsInitialized;
+ int m_sampleOffset;
+ int64_t m_SamplesIn;
+ int64_t m_SamplesOut;
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp
new file mode 100644
index 0000000..bfef837
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "ActiveAEResampleFFMPEG.h"
+#include "utils/log.h"
+
+extern "C" {
+#include <libavutil/channel_layout.h>
+#include <libavutil/opt.h>
+#include <libswresample/swresample.h>
+}
+
+using namespace ActiveAE;
+
+CActiveAEResampleFFMPEG::CActiveAEResampleFFMPEG()
+{
+ m_pContext = NULL;
+ m_doesResample = false;
+}
+
+CActiveAEResampleFFMPEG::~CActiveAEResampleFFMPEG()
+{
+ swr_free(&m_pContext);
+}
+
+bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
+ CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample)
+{
+ m_dst_chan_layout = dstConfig.channel_layout;
+ m_dst_channels = dstConfig.channels;
+ m_dst_rate = dstConfig.sample_rate;
+ m_dst_fmt = dstConfig.fmt;
+ m_dst_bits = dstConfig.bits_per_sample;
+ m_dst_dither_bits = dstConfig.dither_bits;
+ m_src_chan_layout = srcConfig.channel_layout;
+ m_src_channels = srcConfig.channels;
+ m_src_rate = srcConfig.sample_rate;
+ m_src_fmt = srcConfig.fmt;
+ m_src_bits = srcConfig.bits_per_sample;
+ m_src_dither_bits = srcConfig.dither_bits;
+
+ if (m_src_rate != m_dst_rate)
+ m_doesResample = true;
+
+ if (m_dst_chan_layout == 0)
+ m_dst_chan_layout = av_get_default_channel_layout(m_dst_channels);
+ if (m_src_chan_layout == 0)
+ m_src_chan_layout = av_get_default_channel_layout(m_src_channels);
+
+ m_pContext = swr_alloc_set_opts(NULL, m_dst_chan_layout, m_dst_fmt, m_dst_rate,
+ m_src_chan_layout, m_src_fmt, m_src_rate,
+ 0, NULL);
+
+ if (!m_pContext)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Init - create context failed");
+ return false;
+ }
+
+ if(quality == AE_QUALITY_HIGH)
+ {
+ av_opt_set_double(m_pContext, "cutoff", 1.0, 0);
+ av_opt_set_int(m_pContext,"filter_size", 256, 0);
+ }
+ else if(quality == AE_QUALITY_MID)
+ {
+ // 0.97 is default cutoff so use (1.0 - 0.97) / 2.0 + 0.97
+ av_opt_set_double(m_pContext, "cutoff", 0.985, 0);
+ av_opt_set_int(m_pContext,"filter_size", 64, 0);
+ }
+ else if(quality == AE_QUALITY_LOW)
+ {
+ av_opt_set_double(m_pContext, "cutoff", 0.97, 0);
+ av_opt_set_int(m_pContext,"filter_size", 32, 0);
+ }
+
+ if (m_dst_fmt == AV_SAMPLE_FMT_S32 || m_dst_fmt == AV_SAMPLE_FMT_S32P)
+ {
+ av_opt_set_int(m_pContext, "output_sample_bits", m_dst_bits, 0);
+ }
+
+ // tell resampler to clamp float values
+ // not required for sink stage (remapLayout == true)
+ if ((m_dst_fmt == AV_SAMPLE_FMT_FLT || m_dst_fmt == AV_SAMPLE_FMT_FLTP) &&
+ (m_src_fmt == AV_SAMPLE_FMT_FLT || m_src_fmt == AV_SAMPLE_FMT_FLTP) &&
+ !remapLayout && normalize)
+ {
+ av_opt_set_double(m_pContext, "rematrix_maxval", 1.0, 0);
+ }
+
+ av_opt_set_double(m_pContext, "center_mix_level", centerMix, 0);
+
+ if (remapLayout)
+ {
+ // one-to-one mapping of channels
+ // remapLayout is the layout of the sink, if the channel is in our src layout
+ // the channel is mapped by setting coef 1.0
+ memset(m_rematrix, 0, sizeof(m_rematrix));
+ m_dst_chan_layout = 0;
+ for (unsigned int out=0; out<remapLayout->Count(); out++)
+ {
+ m_dst_chan_layout += ((uint64_t)1) << out;
+ int idx = CAEUtil::GetAVChannelIndex((*remapLayout)[out], m_src_chan_layout);
+ if (idx >= 0)
+ {
+ m_rematrix[out][idx] = 1.0;
+ }
+ }
+
+ av_opt_set_int(m_pContext, "out_channel_count", m_dst_channels, 0);
+ av_opt_set_int(m_pContext, "out_channel_layout", m_dst_chan_layout, 0);
+
+ if (swr_set_matrix(m_pContext, (const double*)m_rematrix, AE_CH_MAX) < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Init - setting channel matrix failed");
+ return false;
+ }
+ }
+ // stereo upmix
+ else if (upmix && m_src_channels == 2 && m_dst_channels > 2)
+ {
+ memset(m_rematrix, 0, sizeof(m_rematrix));
+ for (int out=0; out<m_dst_channels; out++)
+ {
+ uint64_t out_chan = av_channel_layout_extract_channel(m_dst_chan_layout, out);
+ switch(out_chan)
+ {
+ case AV_CH_FRONT_LEFT:
+ case AV_CH_BACK_LEFT:
+ case AV_CH_SIDE_LEFT:
+ m_rematrix[out][0] = 1.0;
+ break;
+ case AV_CH_FRONT_RIGHT:
+ case AV_CH_BACK_RIGHT:
+ case AV_CH_SIDE_RIGHT:
+ m_rematrix[out][1] = 1.0;
+ break;
+ case AV_CH_FRONT_CENTER:
+ m_rematrix[out][0] = 0.5;
+ m_rematrix[out][1] = 0.5;
+ break;
+ case AV_CH_LOW_FREQUENCY:
+ m_rematrix[out][0] = 0.5;
+ m_rematrix[out][1] = 0.5;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (swr_set_matrix(m_pContext, (const double*)m_rematrix, AE_CH_MAX) < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Init - setting channel matrix failed");
+ return false;
+ }
+ }
+
+ if(swr_init(m_pContext) < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Init - init resampler failed");
+ return false;
+ }
+ return true;
+}
+
+int CActiveAEResampleFFMPEG::Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio)
+{
+ int delta = 0;
+ int distance = 0;
+ if (ratio != 1.0)
+ {
+ delta = (src_samples*ratio-src_samples)*m_dst_rate/m_src_rate;
+ distance = src_samples*m_dst_rate/m_src_rate;
+ m_doesResample = true;
+ }
+
+ if (m_doesResample)
+ {
+ if (swr_set_compensation(m_pContext, delta, distance) < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Resample - set compensation failed");
+ return -1;
+ }
+ }
+
+ //! @bug libavresample isn't const correct
+ int ret = swr_convert(m_pContext, dst_buffer, dst_samples, const_cast<const uint8_t**>(src_buffer), src_samples);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CActiveAEResampleFFMPEG::Resample - resample failed");
+ return -1;
+ }
+
+ // special handling for S24 formats which are carried in S32
+ if (m_dst_fmt == AV_SAMPLE_FMT_S32 || m_dst_fmt == AV_SAMPLE_FMT_S32P)
+ {
+ // S24NE3
+ if (m_dst_bits == 24 && m_dst_dither_bits == -8)
+ {
+ int planes = av_sample_fmt_is_planar(m_dst_fmt) ? m_dst_channels : 1;
+ int samples = ret * m_dst_channels / planes;
+ uint8_t *src, *dst;
+ for (int i=0; i<planes; i++)
+ {
+ src = dst = dst_buffer[i];
+ for (int j=0; j<samples; j++)
+ {
+#ifndef WORDS_BIGENDIAN
+ src++;
+#endif
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+#ifdef WORDS_BIGENDIAN
+ src++;
+#endif
+ }
+ }
+ }
+ // shift bits if destination format requires it, swr_resamples aligns to the left
+ // Example:
+ // ALSA uses SNE24NE that means 24 bit load in 32 bit package and 0 dither bits
+ // WASAPI uses SNE24NEMSB which is 24 bit load in 32 bit package and 8 dither bits
+ // dither bits are always assumed from the right
+ // FFmpeg internally calculates with S24NEMSB which means, that we need to shift the
+ // data 8 bits to the right in order to get the correct alignment of 0 dither bits
+ // if we want to use ALSA as output. For WASAPI nothing had to be done.
+ // SNE24NEMSB 1 1 1 0 >> 8 = 0 1 1 1 = SNE24NE
+ else if (m_dst_bits != 32 && (m_dst_dither_bits + m_dst_bits) != 32)
+ {
+ int planes = av_sample_fmt_is_planar(m_dst_fmt) ? m_dst_channels : 1;
+ int samples = ret * m_dst_channels / planes;
+ for (int i=0; i<planes; i++)
+ {
+ uint32_t* buf = (uint32_t*)dst_buffer[i];
+ for (int j=0; j<samples; j++)
+ {
+ *buf = *buf >> (32 - m_dst_bits - m_dst_dither_bits);
+ buf++;
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+int64_t CActiveAEResampleFFMPEG::GetDelay(int64_t base)
+{
+ return swr_get_delay(m_pContext, base);
+}
+
+int CActiveAEResampleFFMPEG::GetBufferedSamples()
+{
+ return av_rescale_rnd(swr_get_delay(m_pContext, m_src_rate),
+ m_dst_rate, m_src_rate, AV_ROUND_UP);
+}
+
+int CActiveAEResampleFFMPEG::CalcDstSampleCount(int src_samples, int dst_rate, int src_rate)
+{
+ return av_rescale_rnd(src_samples, dst_rate, src_rate, AV_ROUND_UP);
+}
+
+int CActiveAEResampleFFMPEG::GetSrcBufferSize(int samples)
+{
+ return av_samples_get_buffer_size(NULL, m_src_channels, samples, m_src_fmt, 1);
+}
+
+int CActiveAEResampleFFMPEG::GetDstBufferSize(int samples)
+{
+ return av_samples_get_buffer_size(NULL, m_dst_channels, samples, m_dst_fmt, 1);
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h
new file mode 100644
index 0000000..5fbce37
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEChannelInfo.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AEResample.h"
+
+extern "C" {
+#include <libavutil/samplefmt.h>
+}
+
+struct SwrContext;
+
+namespace ActiveAE
+{
+
+class CActiveAEResampleFFMPEG : public IAEResample
+{
+public:
+ const char *GetName() override { return "ActiveAEResampleFFMPEG"; }
+ CActiveAEResampleFFMPEG();
+ ~CActiveAEResampleFFMPEG() override;
+ bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
+ CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) override;
+ int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) override;
+ int64_t GetDelay(int64_t base) override;
+ int GetBufferedSamples() override;
+ bool WantsNewSamples(int samples) override { return GetBufferedSamples() <= samples * 2; }
+ int CalcDstSampleCount(int src_samples, int dst_rate, int src_rate) override;
+ int GetSrcBufferSize(int samples) override;
+ int GetDstBufferSize(int samples) override;
+
+protected:
+ bool m_loaded;
+ bool m_doesResample;
+ uint64_t m_src_chan_layout, m_dst_chan_layout;
+ int m_src_rate, m_dst_rate;
+ int m_src_channels, m_dst_channels;
+ AVSampleFormat m_src_fmt, m_dst_fmt;
+ int m_src_bits, m_dst_bits;
+ int m_src_dither_bits, m_dst_dither_bits;
+ SwrContext *m_pContext;
+ double m_rematrix[AE_CH_MAX][AE_CH_MAX];
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp
new file mode 100644
index 0000000..5f9a1fb
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+
+#include "ActiveAESettings.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/StringUtils.h"
+
+#include <mutex>
+
+namespace ActiveAE
+{
+
+CActiveAESettings* CActiveAESettings::m_instance = nullptr;
+
+CActiveAESettings::CActiveAESettings(CActiveAE &ae) : m_audioEngine(ae)
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ m_instance = this;
+
+ std::set<std::string> settingSet;
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_CONFIG);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_SAMPLERATE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_CHANNELS);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_PROCESSQUALITY);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDMODE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STEREOUPMIX);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_AC3TRANSCODE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_EAC3PASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_DTSPASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_TRUEHDPASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_DTSHDPASSTHROUGH);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK);
+ settings->GetSettingsManager()->RegisterCallback(this, settingSet);
+
+ settings->GetSettingsManager()->RegisterSettingOptionsFiller("aequalitylevels", SettingOptionsAudioQualityLevelsFiller);
+ settings->GetSettingsManager()->RegisterSettingOptionsFiller("audiodevices", SettingOptionsAudioDevicesFiller);
+ settings->GetSettingsManager()->RegisterSettingOptionsFiller("audiodevicespassthrough", SettingOptionsAudioDevicesPassthroughFiller);
+ settings->GetSettingsManager()->RegisterSettingOptionsFiller("audiostreamsilence", SettingOptionsAudioStreamsilenceFiller);
+}
+
+CActiveAESettings::~CActiveAESettings()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ settings->GetSettingsManager()->UnregisterSettingOptionsFiller("aequalitylevels");
+ settings->GetSettingsManager()->UnregisterSettingOptionsFiller("audiodevices");
+ settings->GetSettingsManager()->UnregisterSettingOptionsFiller("audiodevicespassthrough");
+ settings->GetSettingsManager()->UnregisterSettingOptionsFiller("audiostreamsilence");
+ settings->GetSettingsManager()->UnregisterCallback(this);
+ m_instance = nullptr;
+}
+
+void CActiveAESettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ m_instance->m_audioEngine.OnSettingsChange();
+}
+
+void CActiveAESettings::SettingOptionsAudioDevicesFiller(const SettingConstPtr& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ SettingOptionsAudioDevicesFillerGeneral(setting, list, current, false);
+}
+
+void CActiveAESettings::SettingOptionsAudioDevicesPassthroughFiller(
+ const SettingConstPtr& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ SettingOptionsAudioDevicesFillerGeneral(setting, list, current, true);
+}
+
+void CActiveAESettings::SettingOptionsAudioQualityLevelsFiller(
+ const SettingConstPtr& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data)
+{
+ std::unique_lock<CCriticalSection> lock(m_instance->m_cs);
+
+ if (m_instance->m_audioEngine.SupportsQualityLevel(AE_QUALITY_LOW))
+ list.emplace_back(g_localizeStrings.Get(13506), AE_QUALITY_LOW);
+ if (m_instance->m_audioEngine.SupportsQualityLevel(AE_QUALITY_MID))
+ list.emplace_back(g_localizeStrings.Get(13507), AE_QUALITY_MID);
+ if (m_instance->m_audioEngine.SupportsQualityLevel(AE_QUALITY_HIGH))
+ list.emplace_back(g_localizeStrings.Get(13508), AE_QUALITY_HIGH);
+ if (m_instance->m_audioEngine.SupportsQualityLevel(AE_QUALITY_REALLYHIGH))
+ list.emplace_back(g_localizeStrings.Get(13509), AE_QUALITY_REALLYHIGH);
+ if (m_instance->m_audioEngine.SupportsQualityLevel(AE_QUALITY_GPU))
+ list.emplace_back(g_localizeStrings.Get(38010), AE_QUALITY_GPU);
+}
+
+void CActiveAESettings::SettingOptionsAudioStreamsilenceFiller(
+ const SettingConstPtr& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data)
+{
+ std::unique_lock<CCriticalSection> lock(m_instance->m_cs);
+
+ list.emplace_back(g_localizeStrings.Get(20422),
+ XbmcThreads::EndTime<std::chrono::minutes>::Max().count());
+ list.emplace_back(g_localizeStrings.Get(13551), 0);
+
+ if (m_instance->m_audioEngine.SupportsSilenceTimeout())
+ {
+ list.emplace_back(StringUtils::Format(g_localizeStrings.Get(13554), 1), 1);
+ for (int i = 2; i <= 10; i++)
+ {
+ list.emplace_back(StringUtils::Format(g_localizeStrings.Get(13555), i), i);
+ }
+ }
+}
+
+bool CActiveAESettings::IsSettingVisible(const std::string& condition,
+ const std::string& value,
+ const SettingConstPtr& setting,
+ void* data)
+{
+ if (setting == NULL || value.empty())
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_instance->m_cs);
+ if (!m_instance)
+ return false;
+
+ return m_instance->m_audioEngine.IsSettingVisible(value);
+}
+
+void CActiveAESettings::SettingOptionsAudioDevicesFillerGeneral(
+ const SettingConstPtr& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ bool passthrough)
+{
+ current = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
+ std::string firstDevice;
+
+ std::unique_lock<CCriticalSection> lock(m_instance->m_cs);
+
+ bool foundValue = false;
+ AEDeviceList sinkList;
+ m_instance->m_audioEngine.EnumerateOutputDevices(sinkList, passthrough);
+ if (sinkList.empty())
+ list.emplace_back("Error - no devices found", "error");
+ else
+ {
+ for (AEDeviceList::const_iterator sink = sinkList.begin(); sink != sinkList.end(); ++sink)
+ {
+ if (sink == sinkList.begin())
+ firstDevice = sink->second;
+
+ list.emplace_back(sink->first, sink->second);
+
+ if (StringUtils::EqualsNoCase(current, sink->second))
+ foundValue = true;
+ }
+ }
+
+ if (!foundValue)
+ current = firstDevice;
+}
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.h
new file mode 100644
index 0000000..7f814d0
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "settings/lib/ISettingCallback.h"
+#include "threads/CriticalSection.h"
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <vector>
+
+class CSetting;
+class CAEStreamInfo;
+struct IntegerSettingOption;
+struct StringSettingOption;
+
+namespace ActiveAE
+{
+class CActiveAE;
+
+class CActiveAESettings : public ISettingCallback
+{
+public:
+ CActiveAESettings(CActiveAE &ae);
+ ~CActiveAESettings() override;
+
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+
+ static void SettingOptionsAudioDevicesFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+ static void SettingOptionsAudioDevicesPassthroughFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+ static void SettingOptionsAudioQualityLevelsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data);
+ static void SettingOptionsAudioStreamsilenceFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data);
+ static bool IsSettingVisible(const std::string& condition,
+ const std::string& value,
+ const std::shared_ptr<const CSetting>& setting,
+ void* data);
+
+protected:
+ static void SettingOptionsAudioDevicesFillerGeneral(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ bool passthrough);
+
+ CActiveAE &m_audioEngine;
+ CCriticalSection m_cs;
+ static CActiveAESettings* m_instance;
+};
+};
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
new file mode 100644
index 0000000..477129f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
@@ -0,0 +1,1175 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAESink.h"
+
+#include "ActiveAE.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Utils/AEBitstreamPacker.h"
+#include "cores/AudioEngine/Utils/AEStreamInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/EndianSwap.h"
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <new> // for std::bad_alloc
+#include <sstream>
+
+using namespace AE;
+using namespace ActiveAE;
+using namespace std::chrono_literals;
+
+CActiveAESink::CActiveAESink(CEvent *inMsgEvent) :
+ CThread("AESink"),
+ m_controlPort("SinkControlPort", inMsgEvent, &m_outMsgEvent),
+ m_dataPort("SinkDataPort", inMsgEvent, &m_outMsgEvent)
+{
+ m_inMsgEvent = inMsgEvent;
+ m_sink = nullptr;
+ m_stats = nullptr;
+ m_volume = 0.0;
+ m_packer = nullptr;
+ m_streamNoise = true;
+}
+
+void CActiveAESink::Start()
+{
+ if (!IsRunning())
+ {
+ Create();
+ SetPriority(ThreadPriority::ABOVE_NORMAL);
+ }
+}
+
+void CActiveAESink::Dispose()
+{
+ m_bStop = true;
+ m_outMsgEvent.Set();
+ StopThread();
+ m_controlPort.Purge();
+ m_dataPort.Purge();
+
+ if (m_sink)
+ {
+ m_sink->Drain();
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ }
+
+ delete m_sampleOfSilence.pkt;
+ m_sampleOfSilence.pkt = nullptr;
+
+ delete m_packer;
+ m_packer = nullptr;
+
+ CAESinkFactory::Cleanup();
+}
+
+AEDeviceType CActiveAESink::GetDeviceType(const std::string &device)
+{
+ std::string dev = device;
+ std::string dri;
+ CAESinkFactory::ParseDevice(dev, dri);
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo& info = *itt2;
+ if (info.m_deviceName == dev)
+ return info.m_deviceType;
+ }
+ }
+ return AE_DEVTYPE_PCM;
+}
+
+bool CActiveAESink::HasPassthroughDevice()
+{
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo& info = *itt2;
+ if (info.m_deviceType != AE_DEVTYPE_PCM && !info.m_streamTypes.empty())
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CActiveAESink::SupportsFormat(const std::string &device, AEAudioFormat &format)
+{
+ std::string dev = device;
+ std::string dri;
+
+ CAESinkFactory::ParseDevice(dev, dri);
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ if (dri == itt->m_sinkName)
+ {
+ for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo& info = *itt2;
+ if (info.m_deviceName == dev)
+ {
+ bool isRaw = format.m_dataFormat == AE_FMT_RAW;
+ bool formatExists = false;
+
+ // PCM sample rate
+ unsigned int samplerate = format.m_sampleRate;
+
+ if (isRaw && info.m_wantsIECPassthrough)
+ {
+ switch (format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ samplerate = 192000;
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ if (format.m_streamInfo.m_sampleRate == 48000 || format.m_streamInfo.m_sampleRate == 96000 || format.m_streamInfo.m_sampleRate == 192000)
+ samplerate = 192000;
+ else
+ samplerate = 176400;
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ samplerate = 192000;
+ break;
+
+ default:
+ break;
+ }
+ AEDataTypeList::iterator iit3;
+ iit3 = find(info.m_streamTypes.begin(), info.m_streamTypes.end(), format.m_streamInfo.m_type);
+ formatExists = (iit3 != info.m_streamTypes.end());
+ }
+ else if (isRaw && !info.m_wantsIECPassthrough)
+ {
+ samplerate = 48000;
+ AEDataTypeList::iterator iit3;
+ iit3 = find(info.m_streamTypes.begin(), info.m_streamTypes.end(), format.m_streamInfo.m_type);
+ formatExists = (iit3 != info.m_streamTypes.end());
+ }
+ else // PCM case
+ {
+ AEDataFormatList::iterator itt3;
+ itt3 = find(info.m_dataFormats.begin(), info.m_dataFormats.end(), format.m_dataFormat);
+ formatExists = (itt3 != info.m_dataFormats.end());
+ }
+
+ // check if samplerate is available
+ if (formatExists)
+ {
+ AESampleRateList::iterator itt4;
+ itt4 = find(info.m_sampleRates.begin(), info.m_sampleRates.end(), samplerate);
+ return itt4 != info.m_sampleRates.end();
+ }
+ else // format is not existent
+ {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool CActiveAESink::NeedIECPacking()
+{
+ std::string dev = m_device;
+ std::string dri;
+
+ CAESinkFactory::ParseDevice(dev, dri);
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ if (dri == itt->m_sinkName)
+ {
+ for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo& info = *itt2;
+ if (info.m_deviceName == dev)
+ {
+ return info.m_wantsIECPassthrough;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool CActiveAESink::DeviceExist(std::string driver, const std::string& device)
+{
+ if (driver.empty() && m_sink)
+ driver = m_sink->GetName();
+
+ for (const auto& itt : m_sinkInfoList)
+ {
+ if (itt.m_sinkName != driver)
+ continue;
+
+ for (const CAEDeviceInfo& info : itt.m_deviceInfoList)
+ {
+ if (info.m_deviceName == device)
+ return true;
+ }
+ }
+ return false;
+}
+
+enum SINK_STATES
+{
+ S_TOP = 0, // 0
+ S_TOP_UNCONFIGURED, // 1
+ S_TOP_CONFIGURED, // 2
+ S_TOP_CONFIGURED_SUSPEND, // 3
+ S_TOP_CONFIGURED_IDLE, // 4
+ S_TOP_CONFIGURED_PLAY, // 5
+ S_TOP_CONFIGURED_SILENCE, // 6
+};
+
+int SINK_parentStates[] = {
+ -1,
+ 0, //TOP_UNCONFIGURED
+ 0, //TOP_CONFIGURED
+ 2, //TOP_CONFIGURED_SUSPEND
+ 2, //TOP_CONFIGURED_IDLE
+ 2, //TOP_CONFIGURED_PLAY
+ 2, //TOP_CONFIGURED_SILENCE
+};
+
+void CActiveAESink::StateMachine(int signal, Protocol *port, Message *msg)
+{
+ for (int state = m_state; ; state = SINK_parentStates[state])
+ {
+ switch (state)
+ {
+ case S_TOP: // TOP
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::CONFIGURE:
+ SinkConfig *data;
+ data = reinterpret_cast<SinkConfig*>(msg->data);
+ if (data)
+ {
+ m_requestedFormat = data->format;
+ m_stats = data->stats;
+ m_device = *(data->device);
+ }
+ m_extError = false;
+ m_extSilenceTimer.Set(0ms);
+ m_extStreaming = false;
+ ReturnBuffers();
+ OpenSink();
+
+ if (!m_extError)
+ {
+ SinkReply reply;
+ reply.format = m_sinkFormat;
+ //! @todo
+ //! use max raw packet size, for now use max size of an IEC packed packet
+ //! maxIECPpacket > maxRawPacket
+ //! for raw packets frameSize is set to 1
+ if (m_requestedFormat.m_dataFormat == AE_FMT_RAW)
+ {
+ reply.format.m_frames = 61440;
+ }
+ reply.cacheTotal = m_sink->GetCacheTotal();
+ reply.latency = m_sink->GetLatency();
+ reply.hasVolume = m_sink->HasVolume();
+ m_state = S_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 10s;
+ m_sinkLatency = (int64_t)(reply.latency * 1000);
+ msg->Reply(CSinkControlProtocol::ACC, &reply, sizeof(SinkReply));
+ }
+ else
+ {
+ m_state = S_TOP_UNCONFIGURED;
+ msg->Reply(CSinkControlProtocol::ERR);
+ }
+ return;
+
+ case CSinkControlProtocol::UNCONFIGURE:
+ ReturnBuffers();
+ if (m_sink)
+ {
+ m_sink->Drain();
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ }
+ m_state = S_TOP_UNCONFIGURED;
+ msg->Reply(CSinkControlProtocol::ACC);
+ return;
+
+ case CSinkControlProtocol::FLUSH:
+ ReturnBuffers();
+ msg->Reply(CSinkControlProtocol::ACC);
+ return;
+
+ case CSinkControlProtocol::APPFOCUSED:
+ m_extAppFocused = *(bool*)msg->data;
+ SetSilenceTimer();
+ m_extTimeout = 0ms;
+ return;
+
+ case CSinkControlProtocol::STREAMING:
+ m_extStreaming = *(bool*)msg->data;
+ return;
+
+ case CSinkControlProtocol::SETSILENCETIMEOUT:
+ m_silenceTimeOut = std::chrono::minutes(*reinterpret_cast<int*>(msg->data));
+ return;
+
+ case CSinkControlProtocol::SETNOISETYPE:
+ m_streamNoise = *(bool*)msg->data;
+ return;
+
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::DRAIN:
+ msg->Reply(CSinkDataProtocol::ACC);
+ m_state = S_TOP_UNCONFIGURED;
+ m_extTimeout = 0ms;
+ return;
+ default:
+ break;
+ }
+ }
+ {
+ std::string portName = port == nullptr ? "timer" : port->portName;
+ CLog::Log(LOGWARNING,
+ "CActiveAESink::{} - signal: {} form port: {} not handled for state: {}",
+ __FUNCTION__, signal, portName, m_state);
+ }
+ return;
+
+ case S_TOP_UNCONFIGURED:
+ if (port == nullptr) // timeout
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::TIMEOUT:
+ m_extTimeout = 1000ms;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::SAMPLE:
+ CSampleBuffer *samples;
+ samples = *((CSampleBuffer**)msg->data);
+ CThread::Sleep(std::chrono::milliseconds(1000 * samples->pkt->nb_samples /
+ samples->pkt->config.sample_rate));
+ msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*));
+ m_extTimeout = 0ms;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case S_TOP_CONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::STREAMING:
+ m_extStreaming = *(bool*)msg->data;
+ SetSilenceTimer();
+ if (!m_extSilenceTimer.IsTimePast())
+ {
+ m_state = S_TOP_CONFIGURED_SILENCE;
+ }
+ m_extTimeout = 0ms;
+ return;
+ case CSinkControlProtocol::VOLUME:
+ m_volume = *(float*)msg->data;
+ m_sink->SetVolume(m_volume);
+ return;
+
+ case CSinkControlProtocol::SETNOISETYPE:
+ {
+ bool streamNoise = *(bool*)msg->data;
+ if (streamNoise != m_streamNoise)
+ {
+ m_streamNoise = streamNoise;
+ GenerateNoise();
+ }
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::DRAIN:
+ m_sink->Drain();
+ msg->Reply(CSinkDataProtocol::ACC);
+ m_state = S_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 10s;
+ return;
+ case CSinkDataProtocol::SAMPLE:
+ CSampleBuffer *samples;
+ unsigned int delay;
+ samples = *((CSampleBuffer**)msg->data);
+ delay = OutputSamples(samples);
+ msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*));
+ if (m_extError)
+ {
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ m_state = S_TOP_CONFIGURED_SUSPEND;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_state = S_TOP_CONFIGURED_PLAY;
+ m_extTimeout = std::chrono::milliseconds(delay / 2);
+ m_extSilenceTimer.Set(m_extSilenceTimeout);
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case S_TOP_CONFIGURED_SUSPEND:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::STREAMING:
+ m_extStreaming = *(bool*)msg->data;
+ SetSilenceTimer();
+ m_extTimeout = 0ms;
+ return;
+ case CSinkControlProtocol::VOLUME:
+ m_volume = *(float*)msg->data;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::SAMPLE:
+ m_extError = false;
+ OpenSink();
+ if (!m_extError)
+ {
+ OutputSamples(&m_sampleOfSilence);
+ m_state = S_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ m_bStateMachineSelfTrigger = true;
+ }
+ else
+ {
+ m_state = S_TOP_UNCONFIGURED;
+ }
+ return;
+ case CSinkDataProtocol::DRAIN:
+ msg->Reply(CSinkDataProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == nullptr) // timeout
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::TIMEOUT:
+ m_extTimeout = 10s;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case S_TOP_CONFIGURED_IDLE:
+ if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CSinkDataProtocol::SAMPLE:
+ OutputSamples(&m_sampleOfSilence);
+ m_state = S_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ m_bStateMachineSelfTrigger = true;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == nullptr) // timeout
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::TIMEOUT:
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ m_state = S_TOP_CONFIGURED_SUSPEND;
+ m_extTimeout = 10s;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case S_TOP_CONFIGURED_PLAY:
+ if (port == nullptr) // timeout
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::TIMEOUT:
+ if (!m_extSilenceTimer.IsTimePast())
+ {
+ m_state = S_TOP_CONFIGURED_SILENCE;
+ m_extTimeout = 0ms;
+ }
+ else
+ {
+ m_sink->Drain();
+ m_state = S_TOP_CONFIGURED_IDLE;
+ if (m_extAppFocused)
+ m_extTimeout = 10s;
+ else
+ m_extTimeout = 0ms;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case S_TOP_CONFIGURED_SILENCE:
+ if (port == nullptr) // timeout
+ {
+ switch (signal)
+ {
+ case CSinkControlProtocol::TIMEOUT:
+ OutputSamples(&m_sampleOfSilence);
+ if (m_extError)
+ {
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ m_state = S_TOP_CONFIGURED_SUSPEND;
+ }
+ else
+ m_state = S_TOP_CONFIGURED_PLAY;
+ m_extTimeout = 0ms;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default: // we are in no state, should not happen
+ CLog::Log(LOGERROR, "CActiveAESink::{} - no valid state: {}", __FUNCTION__, m_state);
+ return;
+ }
+ } // for
+}
+
+void CActiveAESink::Process()
+{
+ Message *msg = nullptr;
+ Protocol *port = nullptr;
+ bool gotMsg;
+ XbmcThreads::EndTime<> timer;
+
+ m_state = S_TOP_UNCONFIGURED;
+ m_extTimeout = 1000ms;
+ m_bStateMachineSelfTrigger = false;
+ m_extAppFocused = true;
+
+ while (!m_bStop)
+ {
+ gotMsg = false;
+ timer.Set(m_extTimeout);
+
+ if (m_bStateMachineSelfTrigger)
+ {
+ m_bStateMachineSelfTrigger = false;
+ // self trigger state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = nullptr;
+ }
+ continue;
+ }
+ // check control port
+ else if (m_controlPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_controlPort;
+ }
+ // check data port
+ else if (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ }
+
+ if (gotMsg)
+ {
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = nullptr;
+ }
+ continue;
+ }
+
+ // wait for message
+ else if (m_outMsgEvent.Wait(m_extTimeout))
+ {
+ m_extTimeout = timer.GetTimeLeft();
+ continue;
+ }
+ // time out
+ else
+ {
+ msg = m_controlPort.GetMessage();
+ msg->signal = CSinkControlProtocol::TIMEOUT;
+ port = 0;
+ // signal timeout to state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = nullptr;
+ }
+ }
+ }
+}
+
+void CActiveAESink::EnumerateSinkList(bool force, std::string driver)
+{
+ if (!m_sinkInfoList.empty() && !force)
+ return;
+
+ if (!CAESinkFactory::HasSinks())
+ return;
+
+ std::vector<AE::AESinkInfo> tmpList(m_sinkInfoList);
+
+ unsigned int c_retry = 4;
+ m_sinkInfoList.clear();
+
+ if (!driver.empty())
+ {
+ for (auto const& info : tmpList)
+ {
+ if (info.m_sinkName != driver)
+ m_sinkInfoList.push_back(info);
+ }
+ }
+
+ CAESinkFactory::EnumerateEx(m_sinkInfoList, false, driver);
+ while (m_sinkInfoList.empty() && c_retry > 0)
+ {
+ CLog::Log(LOGINFO, "No Devices found - retry: {}", c_retry);
+ CThread::Sleep(1500ms);
+ c_retry--;
+ // retry the enumeration
+ CAESinkFactory::EnumerateEx(m_sinkInfoList, true, driver);
+ }
+ CLog::Log(LOGINFO, "Found {} Lists of Devices", m_sinkInfoList.size());
+ PrintSinks(driver);
+}
+
+void CActiveAESink::PrintSinks(std::string& driver)
+{
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ if (!driver.empty() && itt->m_sinkName != driver)
+ continue;
+
+ CLog::Log(LOGINFO, "Enumerated {} devices:", itt->m_sinkName);
+ int count = 0;
+ for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
+ {
+ CLog::Log(LOGINFO, " Device {}", ++count);
+ CAEDeviceInfo& info = *itt2;
+ std::stringstream ss((std::string)info);
+ std::string line;
+ while(std::getline(ss, line, '\n'))
+ CLog::Log(LOGINFO, " {}", line);
+ }
+ }
+}
+
+void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
+{
+ EnumerateSinkList(false, "");
+
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ AESinkInfo sinkInfo = *itt;
+ for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo devInfo = *itt2;
+ if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM)
+ continue;
+
+ // filters devices that should not be shown in the list
+ // of AUDIO DEVICES or AUDIO PASSTHROUGH DEVICES
+ // according to the capabilities informed by each sink
+ if (devInfo.m_onlyPassthrough && !passthrough)
+ continue;
+
+ if (devInfo.m_onlyPCM && passthrough)
+ continue;
+
+ std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName;
+
+ std::stringstream ss;
+
+ /* add the sink name if we have more then one sink type */
+ if (m_sinkInfoList.size() > 1)
+ ss << sinkInfo.m_sinkName << ": ";
+
+ ss << devInfo.m_displayName;
+ if (!devInfo.m_displayNameExtra.empty())
+ ss << ", " << devInfo.m_displayNameExtra;
+
+ devices.push_back(AEDevice(ss.str(), device));
+ }
+ }
+}
+
+void CActiveAESink::GetDeviceFriendlyName(const std::string& device)
+{
+ m_deviceFriendlyName = "Device not found";
+ /* Match the device and find its friendly name */
+ for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
+ {
+ AESinkInfo sinkInfo = *itt;
+ for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
+ {
+ CAEDeviceInfo& devInfo = *itt2;
+ if (devInfo.m_deviceName == device)
+ {
+ m_deviceFriendlyName = devInfo.m_displayName;
+ break;
+ }
+ }
+ }
+}
+
+void CActiveAESink::OpenSink()
+{
+ // we need a copy of m_device here because ParseDevice and CreateDevice write back
+ // into this variable
+ std::string device = m_device;
+ std::string driver;
+ bool passthrough = (m_requestedFormat.m_dataFormat == AE_FMT_RAW);
+
+ CAESinkFactory::ParseDevice(device, driver);
+ if (driver.empty() && m_sink)
+ driver = m_sink->GetName();
+
+ // iec packing or raw
+ if (passthrough)
+ {
+ m_needIecPack = NeedIECPacking();
+ if (m_needIecPack)
+ {
+ m_packer = new CAEBitstreamPacker();
+ m_requestedFormat.m_sampleRate = CAEBitstreamPacker::GetOutputRate(m_requestedFormat.m_streamInfo);
+ m_requestedFormat.m_channelLayout = CAEBitstreamPacker::GetOutputChannelMap(m_requestedFormat.m_streamInfo);
+ }
+ }
+
+ CLog::Log(LOGINFO, "CActiveAESink::OpenSink - initialize sink");
+
+ if (m_sink)
+ {
+ m_sink->Drain();
+ m_sink->Deinitialize();
+ delete m_sink;
+ m_sink = nullptr;
+ }
+
+ // get the display name of the device
+ GetDeviceFriendlyName(device);
+
+ // if we already have a driver, prepend it to the device string
+ if (!driver.empty())
+ device = driver + ":" + device;
+
+ // WARNING: this changes format and does not use passthrough
+ m_sinkFormat = m_requestedFormat;
+ CLog::Log(LOGDEBUG, "CActiveAESink::OpenSink - trying to open device {}", device);
+ m_sink = CAESinkFactory::Create(device, m_sinkFormat);
+
+ // try first device in out list
+ if (!m_sink && !m_sinkInfoList.empty())
+ {
+ driver = m_sinkInfoList.front().m_sinkName;
+ device = m_sinkInfoList.front().m_deviceInfoList.front().m_deviceName;
+ GetDeviceFriendlyName(device);
+ if (!driver.empty())
+ device = driver + ":" + device;
+ m_sinkFormat = m_requestedFormat;
+ CLog::Log(LOGDEBUG, "CActiveAESink::OpenSink - trying to open device {}", device);
+ m_sink = CAESinkFactory::Create(device, m_sinkFormat);
+ }
+
+ if (!m_sink)
+ {
+ CLog::Log(LOGERROR, "CActiveAESink::OpenSink - no sink was returned");
+ m_extError = true;
+ return;
+ }
+
+ m_sink->SetVolume(m_volume);
+
+#ifdef WORDS_BIGENDIAN
+ if (m_sinkFormat.m_dataFormat == AE_FMT_S16BE)
+ m_sinkFormat.m_dataFormat = AE_FMT_S16NE;
+ else if (m_sinkFormat.m_dataFormat == AE_FMT_S32BE)
+ m_sinkFormat.m_dataFormat = AE_FMT_S32NE;
+#else
+ if (m_sinkFormat.m_dataFormat == AE_FMT_S16LE)
+ m_sinkFormat.m_dataFormat = AE_FMT_S16NE;
+ else if (m_sinkFormat.m_dataFormat == AE_FMT_S32LE)
+ m_sinkFormat.m_dataFormat = AE_FMT_S32NE;
+#endif
+
+ CLog::Log(LOGDEBUG, "CActiveAESink::OpenSink - {} Initialized:", m_sink->GetName());
+ CLog::Log(LOGDEBUG, " Output Device : {}", m_deviceFriendlyName);
+ CLog::Log(LOGDEBUG, " Sample Rate : {}", m_sinkFormat.m_sampleRate);
+ CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(m_sinkFormat.m_dataFormat));
+ CLog::Log(LOGDEBUG, " Channel Count : {}", m_sinkFormat.m_channelLayout.Count());
+ CLog::Log(LOGDEBUG, " Channel Layout: {}", ((std::string)m_sinkFormat.m_channelLayout));
+ CLog::Log(LOGDEBUG, " Frames : {}", m_sinkFormat.m_frames);
+ CLog::Log(LOGDEBUG, " Frame Size : {}", m_sinkFormat.m_frameSize);
+
+ // init sample of silence
+ SampleConfig config;
+ config.fmt = CAEUtil::GetAVSampleFormat(m_sinkFormat.m_dataFormat);
+ config.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_sinkFormat.m_dataFormat);
+ config.dither_bits = CAEUtil::DataFormatToDitherBits(m_sinkFormat.m_dataFormat);
+ config.channel_layout = CAEUtil::GetAVChannelLayout(m_sinkFormat.m_channelLayout);
+ config.channels = m_sinkFormat.m_channelLayout.Count();
+ config.sample_rate = m_sinkFormat.m_sampleRate;
+
+ // init sample of silence/noise
+ delete m_sampleOfSilence.pkt;
+ m_sampleOfSilence.pkt = new CSoundPacket(config, m_sinkFormat.m_frames);
+ m_sampleOfSilence.pkt->nb_samples = m_sampleOfSilence.pkt->max_nb_samples;
+ if (!passthrough)
+ GenerateNoise();
+ else
+ {
+ m_sampleOfSilence.pkt->nb_samples = 0;
+ m_sampleOfSilence.pkt->pause_burst_ms = m_sinkFormat.m_streamInfo.GetDuration();
+ }
+
+ m_swapState = CHECK_SWAP;
+}
+
+void CActiveAESink::ReturnBuffers()
+{
+ Message *msg = nullptr;
+ CSampleBuffer *samples;
+ while (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ if (msg->signal == CSinkDataProtocol::SAMPLE)
+ {
+ samples = *((CSampleBuffer**)msg->data);
+ msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*));
+ }
+ msg->Release();
+ }
+}
+
+unsigned int CActiveAESink::OutputSamples(CSampleBuffer* samples)
+{
+ uint8_t **buffer = samples->pkt->data;
+ uint8_t *packBuffer;
+ unsigned int frames = samples->pkt->nb_samples;
+ unsigned int totalFrames = frames;
+ unsigned int maxFrames;
+ int retry = 0;
+ unsigned int written = 0;
+ uint8_t* p_mergeBuffer = nullptr;
+ AEDelayStatus status;
+
+ if (m_requestedFormat.m_dataFormat == AE_FMT_RAW)
+ {
+ bool skipSwap = false;
+ if (m_needIecPack)
+ {
+ if (frames > 0)
+ {
+ m_packer->Reset();
+ if (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ if (frames == 61440)
+ {
+ for (int i = 0, of = 0; i < 12; i++)
+ {
+ // calculates length of each audio unit using raw data of stream
+ const uint16_t len = ((*(buffer[0] + of) & 0x0F) << 8 | *(buffer[0] + of + 1)) << 1;
+
+ m_packer->Pack(m_sinkFormat.m_streamInfo, buffer[0] + of, len);
+ of += len;
+ }
+ }
+ else
+ {
+ m_extError = true;
+ CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - incomplete TrueHD buffer");
+ return 0;
+ }
+ }
+ else
+ m_packer->Pack(m_sinkFormat.m_streamInfo, buffer[0], frames);
+ }
+ else if (samples->pkt->pause_burst_ms > 0)
+ {
+ // construct a pause burst if we have already output valid audio
+ bool burst = m_extStreaming && (m_packer->GetBuffer()[0] != 0);
+ if (!m_packer->PackPause(m_sinkFormat.m_streamInfo, samples->pkt->pause_burst_ms, burst))
+ skipSwap = true;
+ }
+ else
+ m_packer->Reset();
+
+ unsigned int size = m_packer->GetSize();
+ packBuffer = m_packer->GetBuffer();
+ buffer = &packBuffer;
+ totalFrames = size / m_sinkFormat.m_frameSize;
+ frames = totalFrames;
+
+ switch(m_swapState)
+ {
+ case SKIP_SWAP:
+ break;
+ case NEED_BYTESWAP:
+ if (!skipSwap)
+ Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], size / 2);
+ break;
+ case CHECK_SWAP:
+ SwapInit(samples);
+ if (m_swapState == NEED_BYTESWAP)
+ Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], size / 2);
+ break;
+ default:
+ break;
+ }
+ }
+ else // Android IEC packer (RAW)
+ {
+ if (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD && frames == 61440)
+ {
+ if (m_mergeBuffer.empty())
+ m_mergeBuffer.resize(MAX_IEC61937_PACKET);
+
+ p_mergeBuffer = m_mergeBuffer.data();
+ unsigned int size = 0;
+
+ for (int i = 0, of = 0; i < 24; i++)
+ {
+ // calculates length of each audio unit using raw data of stream
+ const uint16_t len = ((*(buffer[0] + of) & 0x0F) << 8 | *(buffer[0] + of + 1)) << 1;
+
+ memcpy(m_mergeBuffer.data() + of, buffer[0] + of, len);
+ size += len;
+ of += len;
+ }
+
+ buffer = &p_mergeBuffer;
+ totalFrames = size / m_sinkFormat.m_frameSize; // m_frameSize = 1
+ frames = totalFrames;
+ }
+ if (samples->pkt->pause_burst_ms > 0)
+ {
+ m_sink->AddPause(samples->pkt->pause_burst_ms);
+ m_sink->GetDelay(status);
+ m_stats->UpdateSinkDelay(status, samples->pool ? 1 : 0);
+ return status.delay * 1000;
+ }
+ }
+ }
+
+ int framesOrPackets;
+
+ while (frames > 0)
+ {
+ maxFrames = std::min(frames, m_sinkFormat.m_frames);
+ written = m_sink->AddPackets(buffer, maxFrames, totalFrames - frames);
+ if (written == 0)
+ {
+ CThread::Sleep(
+ std::chrono::milliseconds(500 * m_sinkFormat.m_frames / m_sinkFormat.m_sampleRate));
+ retry++;
+ if (retry > 4)
+ {
+ m_extError = true;
+ CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - failed");
+ status.SetDelay(0);
+ framesOrPackets = frames;
+ if (m_requestedFormat.m_dataFormat == AE_FMT_RAW)
+ framesOrPackets = 1;
+ m_stats->UpdateSinkDelay(status, samples->pool ? framesOrPackets : 0);
+ return 0;
+ }
+ else
+ continue;
+ }
+ else if (written > maxFrames)
+ {
+ m_extError = true;
+ CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - sink returned error");
+ status.SetDelay(0);
+ framesOrPackets = frames;
+ if (m_requestedFormat.m_dataFormat == AE_FMT_RAW)
+ framesOrPackets = 1;
+ m_stats->UpdateSinkDelay(status, samples->pool ? framesOrPackets : 0);
+ return 0;
+ }
+ frames -= written;
+
+ m_sink->GetDelay(status);
+
+ if (m_requestedFormat.m_dataFormat != AE_FMT_RAW)
+ m_stats->UpdateSinkDelay(status, samples->pool ? written : 0);
+ }
+
+ if (m_requestedFormat.m_dataFormat == AE_FMT_RAW)
+ m_stats->UpdateSinkDelay(status, samples->pool ? 1 : 0);
+
+ return status.delay * 1000;
+}
+
+void CActiveAESink::SwapInit(CSampleBuffer* samples)
+{
+ if ((m_requestedFormat.m_dataFormat == AE_FMT_RAW) && CAEUtil::S16NeedsByteSwap(AE_FMT_S16NE, m_sinkFormat.m_dataFormat))
+ {
+ m_swapState = NEED_BYTESWAP;
+ }
+ else
+ m_swapState = SKIP_SWAP;
+}
+
+#define PI 3.1415926536f
+
+void CActiveAESink::GenerateNoise()
+{
+ int nb_floats = m_sampleOfSilence.pkt->max_nb_samples;
+ nb_floats *= m_sampleOfSilence.pkt->config.channels;
+ size_t size = nb_floats*sizeof(float);
+
+ float *noise = static_cast<float*>(KODI::MEMORY::AlignedMalloc(size, 32));
+ if (!noise)
+ throw std::bad_alloc();
+
+ if (!m_streamNoise)
+ memset(noise, 0, size);
+ else
+ {
+ float R1, R2;
+ for(int i = 0; i < nb_floats; i++)
+ {
+ do
+ {
+ R1 = (float) rand() / (float) RAND_MAX;
+ R2 = (float) rand() / (float) RAND_MAX;
+ }
+ while(R1 == 0.0f);
+
+ noise[i] = sqrt( -2.0f * log( R1 )) * cos( 2.0f * PI * R2 ) * 0.00001f;
+ }
+ }
+
+ SampleConfig config = m_sampleOfSilence.pkt->config;
+ IAEResample *resampler = CAEResampleFactory::Create(AERESAMPLEFACTORY_QUICK_RESAMPLE);
+
+ SampleConfig dstConfig, srcConfig;
+ dstConfig.channel_layout = config.channel_layout;
+ dstConfig.channels = config.channels;
+ dstConfig.sample_rate = config.sample_rate;
+ dstConfig.fmt = config.fmt;
+ dstConfig.bits_per_sample = config.bits_per_sample;
+ dstConfig.dither_bits = config.dither_bits;
+
+ srcConfig.channel_layout = config.channel_layout;
+ srcConfig.channels = config.channels;
+ srcConfig.sample_rate = config.sample_rate;
+ srcConfig.fmt = AV_SAMPLE_FMT_FLT;
+ srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_sinkFormat.m_dataFormat);
+ srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_sinkFormat.m_dataFormat);
+
+ resampler->Init(dstConfig, srcConfig,
+ false, false, M_SQRT1_2, nullptr, AE_QUALITY_UNKNOWN, false);
+
+ resampler->Resample(m_sampleOfSilence.pkt->data, m_sampleOfSilence.pkt->max_nb_samples,
+ (uint8_t**)&noise, m_sampleOfSilence.pkt->max_nb_samples, 1.0);
+
+ KODI::MEMORY::AlignedFree(noise);
+ delete resampler;
+}
+
+void CActiveAESink::SetSilenceTimer()
+{
+ if (m_extStreaming)
+ m_extSilenceTimeout = XbmcThreads::EndTime<decltype(m_extSilenceTimeout)>::Max();
+ else if (m_extAppFocused) // handles no playback/GUI and playback in pause and seek
+ {
+ // only true with AudioTrack RAW + passthrough + TrueHD or EAC3 (DD+)
+ const bool noSilenceOnPause =
+ !m_needIecPack && m_requestedFormat.m_dataFormat == AE_FMT_RAW &&
+ (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD ||
+ m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3);
+
+ m_extSilenceTimeout = (noSilenceOnPause) ? 0ms : m_silenceTimeOut;
+ }
+ else
+ {
+ m_extSilenceTimeout = 0ms;
+ }
+
+ m_extSilenceTimer.Set(m_extSilenceTimeout);
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h
new file mode 100644
index 0000000..ee1253b
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "threads/Event.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+#include "utils/ActorProtocol.h"
+
+#include <utility>
+
+class CAEBitstreamPacker;
+
+namespace ActiveAE
+{
+using namespace Actor;
+
+class CEngineStats;
+
+struct SinkConfig
+{
+ AEAudioFormat format;
+ CEngineStats *stats;
+ const std::string *device;
+};
+
+struct SinkReply
+{
+ AEAudioFormat format;
+ float cacheTotal;
+ float latency;
+ bool hasVolume;
+};
+
+class CSinkControlProtocol : public Protocol
+{
+public:
+ CSinkControlProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ CONFIGURE,
+ UNCONFIGURE,
+ STREAMING,
+ APPFOCUSED,
+ VOLUME,
+ FLUSH,
+ TIMEOUT,
+ SETSILENCETIMEOUT,
+ SETNOISETYPE,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERR,
+ STATS,
+ };
+};
+
+class CSinkDataProtocol : public Protocol
+{
+public:
+ CSinkDataProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ SAMPLE = 0,
+ DRAIN,
+ };
+ enum InSignal
+ {
+ RETURNSAMPLE,
+ ACC,
+ };
+};
+
+class CActiveAESink : private CThread
+{
+public:
+ explicit CActiveAESink(CEvent *inMsgEvent);
+ void EnumerateSinkList(bool force, std::string driver);
+ void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough);
+ void Start();
+ void Dispose();
+ AEDeviceType GetDeviceType(const std::string &device);
+ bool HasPassthroughDevice();
+ bool SupportsFormat(const std::string &device, AEAudioFormat &format);
+ bool DeviceExist(std::string driver, const std::string& device);
+ bool NeedIecPack() const { return m_needIecPack; }
+ CSinkControlProtocol m_controlPort;
+ CSinkDataProtocol m_dataPort;
+
+protected:
+ void Process() override;
+ void StateMachine(int signal, Protocol *port, Message *msg);
+ void PrintSinks(std::string& driver);
+ void GetDeviceFriendlyName(const std::string& device);
+ void OpenSink();
+ void ReturnBuffers();
+ void SetSilenceTimer();
+ bool NeedIECPacking();
+
+ unsigned int OutputSamples(CSampleBuffer* samples);
+ void SwapInit(CSampleBuffer* samples);
+
+ void GenerateNoise();
+
+ CEvent m_outMsgEvent;
+ CEvent *m_inMsgEvent;
+ int m_state;
+ bool m_bStateMachineSelfTrigger;
+ std::chrono::milliseconds m_extTimeout;
+ std::chrono::minutes m_silenceTimeOut{std::chrono::minutes::zero()};
+ bool m_extError;
+ std::chrono::milliseconds m_extSilenceTimeout;
+ bool m_extAppFocused;
+ bool m_extStreaming;
+ XbmcThreads::EndTime<> m_extSilenceTimer;
+
+ CSampleBuffer m_sampleOfSilence;
+ enum
+ {
+ CHECK_SWAP,
+ NEED_CONVERT,
+ NEED_BYTESWAP,
+ SKIP_SWAP,
+ } m_swapState;
+
+ std::vector<uint8_t> m_mergeBuffer;
+
+ std::string m_deviceFriendlyName;
+ std::string m_device;
+ std::vector<AE::AESinkInfo> m_sinkInfoList;
+ IAESink *m_sink;
+ AEAudioFormat m_sinkFormat, m_requestedFormat;
+ CEngineStats *m_stats;
+ float m_volume;
+ int m_sinkLatency;
+ CAEBitstreamPacker *m_packer;
+ bool m_needIecPack{false};
+ bool m_streamNoise;
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp
new file mode 100644
index 0000000..ee1ed70
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAESound.h"
+
+#include "ActiveAE.h"
+#include "cores/AudioEngine/Interfaces/AESound.h"
+#include "filesystem/File.h"
+#include "utils/log.h"
+
+extern "C" {
+#include <libavutil/avutil.h>
+}
+
+using namespace ActiveAE;
+using namespace XFILE;
+
+CActiveAESound::CActiveAESound(const std::string &filename, CActiveAE *ae) :
+ IAESound (filename),
+ m_filename (filename),
+ m_volume (1.0f ),
+ m_channel (AE_CH_NULL)
+{
+ m_orig_sound = NULL;
+ m_dst_sound = NULL;
+ m_pFile = NULL;
+ m_isSeekPossible = false;
+ m_fileSize = 0;
+ m_isConverted = false;
+ m_activeAE = ae;
+}
+
+CActiveAESound::~CActiveAESound()
+{
+ delete m_orig_sound;
+ delete m_dst_sound;
+ Finish();
+}
+
+void CActiveAESound::Play()
+{
+ m_activeAE->PlaySound(this);
+
+}
+
+void CActiveAESound::Stop()
+{
+ m_activeAE->StopSound(this);
+}
+
+bool CActiveAESound::IsPlaying()
+{
+ //! @todo implement
+ return false;
+}
+
+uint8_t** CActiveAESound::InitSound(bool orig, SampleConfig config, int nb_samples)
+{
+ CSoundPacket **info;
+ if (orig)
+ info = &m_orig_sound;
+ else
+ info = &m_dst_sound;
+
+ delete *info;
+ *info = new CSoundPacket(config, nb_samples);
+
+ (*info)->nb_samples = 0;
+ m_isConverted = false;
+ return (*info)->data;
+}
+
+bool CActiveAESound::StoreSound(bool orig, uint8_t **buffer, int samples, int linesize)
+{
+ CSoundPacket **info;
+ if (orig)
+ info = &m_orig_sound;
+ else
+ info = &m_dst_sound;
+
+ if ((*info)->nb_samples + samples > (*info)->max_nb_samples)
+ {
+ CLog::Log(LOGERROR, "CActiveAESound::StoreSound - exceeded max samples");
+ return false;
+ }
+
+ int bytes_to_copy = samples * (*info)->bytes_per_sample * (*info)->config.channels;
+ bytes_to_copy /= (*info)->planes;
+ int start = (*info)->nb_samples * (*info)->bytes_per_sample * (*info)->config.channels;
+ start /= (*info)->planes;
+
+ for (int i=0; i<(*info)->planes; i++)
+ {
+ memcpy((*info)->data[i]+start, buffer[i], bytes_to_copy);
+ }
+ (*info)->nb_samples += samples;
+
+ return true;
+}
+
+CSoundPacket *CActiveAESound::GetSound(bool orig)
+{
+ if (orig)
+ return m_orig_sound;
+ else
+ return m_dst_sound;
+}
+
+bool CActiveAESound::Prepare()
+{
+ unsigned int flags = READ_TRUNCATED | READ_CHUNKED;
+ m_pFile = new CFile();
+
+ if (!m_pFile->Open(m_filename, flags))
+ {
+ delete m_pFile;
+ m_pFile = NULL;
+ return false;
+ }
+ m_isSeekPossible = m_pFile->IoControl(IOCTRL_SEEK_POSSIBLE, NULL) != 0;
+ m_fileSize = m_pFile->GetLength();
+ return true;
+}
+
+void CActiveAESound::Finish()
+{
+ delete m_pFile;
+ m_pFile = NULL;
+}
+
+int CActiveAESound::GetChunkSize()
+{
+ return m_pFile->GetChunkSize();
+}
+
+int CActiveAESound::Read(void *h, uint8_t* buf, int size)
+{
+ CFile *pFile = static_cast<CActiveAESound*>(h)->m_pFile;
+ int len = pFile->Read(buf, size);
+ if (len == 0)
+ return AVERROR_EOF;
+ else
+ return len;
+}
+
+int64_t CActiveAESound::Seek(void *h, int64_t pos, int whence)
+{
+ CFile* pFile = static_cast<CActiveAESound*>(h)->m_pFile;
+ if(whence == AVSEEK_SIZE)
+ return pFile->GetLength();
+ else
+ return pFile->Seek(pos, whence & ~AVSEEK_FORCE);
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h
new file mode 100644
index 0000000..0bde021
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h"
+#include "cores/AudioEngine/Interfaces/AESound.h"
+
+class DllAvUtil;
+
+namespace XFILE
+{
+class CFile;
+}
+
+namespace ActiveAE
+{
+
+class CActiveAE;
+
+class CActiveAESound : public IAESound
+{
+public:
+ CActiveAESound (const std::string &filename, CActiveAE *ae);
+ ~CActiveAESound() override;
+
+ void Play() override;
+ void Stop() override;
+ bool IsPlaying() override;
+
+ void SetChannel(AEChannel channel) override { m_channel = channel; }
+ AEChannel GetChannel() override { return m_channel; }
+ void SetVolume(float volume) override { m_volume = std::max(0.0f, std::min(1.0f, volume)); }
+ float GetVolume() override { return m_volume; }
+
+ uint8_t** InitSound(bool orig, SampleConfig config, int nb_samples);
+ bool StoreSound(bool orig, uint8_t **buffer, int samples, int linesize);
+ CSoundPacket *GetSound(bool orig);
+
+ bool IsConverted() { return m_isConverted; }
+ void SetConverted(bool state) { m_isConverted = state; }
+
+ bool Prepare();
+ void Finish();
+ int GetChunkSize();
+ int GetFileSize() { return m_fileSize; }
+ bool IsSeekPossible() { return m_isSeekPossible; }
+
+ static int Read(void *h, uint8_t* buf, int size);
+ static int64_t Seek(void *h, int64_t pos, int whence);
+
+protected:
+ CActiveAE *m_activeAE;
+ std::string m_filename;
+ XFILE::CFile *m_pFile;
+ bool m_isSeekPossible;
+ int m_fileSize;
+ float m_volume;
+ AEChannel m_channel;
+
+ CSoundPacket *m_orig_sound;
+ CSoundPacket *m_dst_sound;
+
+ bool m_isConverted;
+};
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp
new file mode 100644
index 0000000..858b0f2
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ActiveAEStream.h"
+
+#include "ActiveAE.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace ActiveAE;
+using namespace std::chrono_literals;
+
+CActiveAEStream::CActiveAEStream(AEAudioFormat* format, unsigned int streamid, CActiveAE* ae)
+ : m_format(*format)
+{
+ m_activeAE = ae;
+ m_id = streamid;
+ m_bufferedTime = 0;
+ m_currentBuffer = NULL;
+ m_drain = false;
+ m_paused = false;
+ m_rgain = 1.0;
+ m_volume = 1.0;
+ SetVolume(1.0);
+ m_amplify = 1.0;
+ m_streamSpace = m_format.m_frameSize * m_format.m_frames;
+ m_streamDraining = false;
+ m_streamDrained = false;
+ m_streamFading = false;
+ m_streamFreeBuffers = 0;
+ m_streamIsBuffering = false;
+ m_streamIsFlushed = false;
+ m_streamSlave = NULL;
+ m_leftoverBuffer = new uint8_t[m_format.m_frameSize];
+ m_leftoverBytes = 0;
+ m_forceResampler = false;
+ m_remapper = NULL;
+ m_remapBuffer = NULL;
+ m_streamResampleRatio = 1.0;
+ m_streamResampleMode = 0;
+ m_profile = 0;
+ m_matrixEncoding = AV_MATRIX_ENCODING_NONE;
+ m_audioServiceType = AV_AUDIO_SERVICE_TYPE_MAIN;
+ m_pClock = NULL;
+ m_lastPts = 0;
+ m_lastPtsJump = 0;
+ m_clockSpeed = 1.0;
+}
+
+CActiveAEStream::~CActiveAEStream()
+{
+ delete [] m_leftoverBuffer;
+ delete m_remapper;
+ delete m_remapBuffer;
+}
+
+void CActiveAEStream::IncFreeBuffers()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ m_streamFreeBuffers++;
+}
+
+void CActiveAEStream::DecFreeBuffers()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ m_streamFreeBuffers--;
+}
+
+void CActiveAEStream::ResetFreeBuffers()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ m_streamFreeBuffers = 0;
+}
+
+void CActiveAEStream::InitRemapper()
+{
+ // check if input format follows ffmpeg channel mask
+ bool needRemap = false;
+ unsigned int avLast, avCur = 0;
+ for(unsigned int i=0; i<m_format.m_channelLayout.Count(); i++)
+ {
+ avLast = avCur;
+ avCur = CAEUtil::GetAVChannel(m_format.m_channelLayout[i]);
+ if(avCur < avLast)
+ {
+ needRemap = true;
+ break;
+ }
+ }
+
+ if(needRemap)
+ {
+ CLog::Log(LOGDEBUG, "CActiveAEStream::{} - initialize remapper", __FUNCTION__);
+
+ m_remapper = CAEResampleFactory::Create();
+ uint64_t avLayout = CAEUtil::GetAVChannelLayout(m_format.m_channelLayout);
+
+ // build layout according to ffmpeg channel order
+ // we need this for reference
+ CAEChannelInfo ffmpegLayout;
+ ffmpegLayout.Reset();
+ int idx = 0;
+ for(unsigned int i=0; i<m_format.m_channelLayout.Count(); i++)
+ {
+ for(unsigned int j=0; j<m_format.m_channelLayout.Count(); j++)
+ {
+ idx = CAEUtil::GetAVChannelIndex(m_format.m_channelLayout[j], avLayout);
+ if (idx == (int)i)
+ {
+ ffmpegLayout += m_format.m_channelLayout[j];
+ break;
+ }
+ }
+ }
+
+ // build remap layout we can pass to resampler as destination layout
+ CAEChannelInfo remapLayout;
+ remapLayout.Reset();
+ for(unsigned int i=0; i<m_format.m_channelLayout.Count(); i++)
+ {
+ for(unsigned int j=0; j<m_format.m_channelLayout.Count(); j++)
+ {
+ idx = CAEUtil::GetAVChannelIndex(m_format.m_channelLayout[j], avLayout);
+ if (idx == (int)i)
+ {
+ remapLayout += ffmpegLayout[j];
+ break;
+ }
+ }
+ }
+
+ // initialize resampler for only doing remapping
+ SampleConfig dstConfig, srcConfig;
+ dstConfig.channel_layout = avLayout;
+ dstConfig.channels = m_format.m_channelLayout.Count();
+ dstConfig.sample_rate = m_format.m_sampleRate;
+ dstConfig.fmt = CAEUtil::GetAVSampleFormat(m_format.m_dataFormat);
+ dstConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat);
+ dstConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat);
+
+ srcConfig.channel_layout = avLayout;
+ srcConfig.channels = m_format.m_channelLayout.Count();
+ srcConfig.sample_rate = m_format.m_sampleRate;
+ srcConfig.fmt = CAEUtil::GetAVSampleFormat(m_format.m_dataFormat);
+ srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat);
+ srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat);
+
+ m_remapper->Init(dstConfig, srcConfig,
+ false,
+ false,
+ M_SQRT1_2,
+ &remapLayout,
+ AE_QUALITY_LOW, // not used for remapping
+ false);
+
+ // extra sound packet, we can't resample to the same buffer
+ m_remapBuffer = new CSoundPacket(m_inputBuffers->m_allSamples[0]->pkt->config, m_inputBuffers->m_allSamples[0]->pkt->max_nb_samples);
+ }
+}
+
+void CActiveAEStream::RemapBuffer()
+{
+ if(m_remapper)
+ {
+ int samples = m_remapper->Resample(m_remapBuffer->data, m_remapBuffer->max_nb_samples,
+ m_currentBuffer->pkt->data, m_currentBuffer->pkt->nb_samples,
+ 1.0);
+
+ if (samples != m_currentBuffer->pkt->nb_samples)
+ {
+ CLog::Log(LOGERROR, "CActiveAEStream::{} - error remapping", __FUNCTION__);
+ }
+
+ // swap sound packets
+ CSoundPacket *tmp = m_currentBuffer->pkt;
+ m_currentBuffer->pkt = m_remapBuffer;
+ m_remapBuffer = tmp;
+ }
+}
+
+double CActiveAEStream::CalcResampleRatio(double error)
+{
+ //reset the integral on big errors, failsafe
+ if (fabs(error) > 1000)
+ m_resampleIntegral = 0;
+ else if (fabs(error) > 5)
+ m_resampleIntegral += error / 1000 / 50;
+
+ double proportional = 0.0;
+
+ double proportionaldiv = 2.0;
+ proportional = error / GetErrorInterval().count() / proportionaldiv;
+
+ double clockspeed = 1.0;
+ if (m_pClock)
+ {
+ clockspeed = m_pClock->GetClockSpeed();
+ if (m_clockSpeed != clockspeed)
+ m_resampleIntegral = 0;
+ m_clockSpeed = clockspeed;
+ }
+
+ double ret = 1.0 / clockspeed + proportional + m_resampleIntegral;
+ //CLog::Log(LOGINFO,"----- error: {:f}, rr: {:f}, prop: {:f}, int: {:f}",
+ // error, ret, proportional, m_resampleIntegral);
+ return ret;
+}
+
+std::chrono::milliseconds CActiveAEStream::GetErrorInterval()
+{
+ std::chrono::milliseconds ret = m_errorInterval;
+ double rr = m_processingBuffers->GetRR();
+ if (rr > 1.02 || rr < 0.98)
+ ret *= 3;
+ return ret;
+}
+
+unsigned int CActiveAEStream::GetSpace()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ if (m_format.m_dataFormat == AE_FMT_RAW)
+ return m_streamFreeBuffers;
+ else
+ return m_streamFreeBuffers * m_streamSpace;
+}
+
+unsigned int CActiveAEStream::AddData(const uint8_t* const *data, unsigned int offset, unsigned int frames, ExtData *extData)
+{
+ Message *msg;
+ unsigned int copied = 0;
+ int sourceFrames = frames;
+ const uint8_t* const *buf = data;
+ double pts = 0;
+
+ if (extData)
+ {
+ pts = extData->pts;
+ }
+
+ m_streamIsFlushed = false;
+
+ while(copied < frames)
+ {
+ sourceFrames = frames - copied;
+
+ if (m_currentBuffer)
+ {
+ int start = m_currentBuffer->pkt->nb_samples *
+ m_currentBuffer->pkt->bytes_per_sample *
+ m_currentBuffer->pkt->config.channels /
+ m_currentBuffer->pkt->planes;
+
+ int freeSpace = m_currentBuffer->pkt->max_nb_samples - m_currentBuffer->pkt->nb_samples;
+ int minFrames = std::min(freeSpace, sourceFrames);
+ int planes = m_currentBuffer->pkt->planes;
+ int bufOffset = (offset + copied)*m_format.m_frameSize/planes;
+
+ if (!copied)
+ {
+ if (pts < m_lastPts)
+ {
+ if (m_lastPtsJump != 0)
+ {
+ auto diff = std::chrono::milliseconds(static_cast<int>(pts - m_lastPtsJump));
+ if (diff > m_errorInterval)
+ {
+ diff += 1s;
+ diff = std::min(diff, 6000ms);
+ CLog::Log(LOGINFO,
+ "CActiveAEStream::AddData - messy timestamps, increasing interval for "
+ "measuring average error to {} ms",
+ diff.count());
+ m_errorInterval = diff;
+ }
+ }
+ m_lastPtsJump = pts;
+ }
+ m_lastPts = pts;
+ m_currentBuffer->timestamp = pts;
+ m_currentBuffer->pkt_start_offset = m_currentBuffer->pkt->nb_samples;
+ }
+
+ for (int i=0; i<planes; i++)
+ {
+ memcpy(m_currentBuffer->pkt->data[i]+start, buf[i]+bufOffset, minFrames*m_format.m_frameSize/planes);
+ }
+ copied += minFrames;
+
+ if (extData && extData->hasDownmix)
+ m_currentBuffer->centerMixLevel = extData->centerMixLevel;
+
+ bool rawPktComplete = false;
+ {
+ std::unique_lock<CCriticalSection> lock(m_statsLock);
+ if (m_format.m_dataFormat != AE_FMT_RAW)
+ {
+ m_currentBuffer->pkt->nb_samples += minFrames;
+ m_bufferedTime +=
+ static_cast<float>(minFrames) / m_currentBuffer->pkt->config.sample_rate;
+ }
+ else
+ {
+ m_bufferedTime += static_cast<float>(m_format.m_streamInfo.GetDuration()) / 1000;
+ m_currentBuffer->pkt->nb_samples += minFrames;
+ rawPktComplete = true;
+ }
+ }
+
+ if (m_currentBuffer->pkt->nb_samples == m_currentBuffer->pkt->max_nb_samples || rawPktComplete)
+ {
+ MsgStreamSample msgData;
+ msgData.buffer = m_currentBuffer;
+ msgData.stream = this;
+ RemapBuffer();
+ m_streamPort->SendOutMessage(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample));
+ m_currentBuffer = nullptr;
+ }
+ continue;
+ }
+ else if (m_streamPort->ReceiveInMessage(&msg))
+ {
+ if (msg->signal == CActiveAEDataProtocol::STREAMBUFFER)
+ {
+ m_currentBuffer = *((CSampleBuffer**)msg->data);
+ m_currentBuffer->timestamp = 0;
+ m_currentBuffer->pkt->nb_samples = 0;
+ m_currentBuffer->pkt->pause_burst_ms = 0;
+ msg->Release();
+ DecFreeBuffers();
+ continue;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CActiveAEStream::AddData - unknown signal");
+ msg->Release();
+ break;
+ }
+ }
+ if (!m_inMsgEvent.Wait(200ms))
+ break;
+ }
+ return copied;
+}
+
+double CActiveAEStream::GetDelay()
+{
+ AEDelayStatus status;
+ m_activeAE->GetDelay(status, this);
+ return status.GetDelay();
+}
+
+CAESyncInfo CActiveAEStream::GetSyncInfo()
+{
+ CAESyncInfo info;
+ m_activeAE->GetSyncInfo(info, this);
+ return info;
+}
+
+bool CActiveAEStream::IsBuffering()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ return m_streamIsBuffering;
+}
+
+double CActiveAEStream::GetCacheTime()
+{
+ return static_cast<double>(m_activeAE->GetCacheTime(this));
+}
+
+double CActiveAEStream::GetCacheTotal()
+{
+ return static_cast<double>(m_activeAE->GetCacheTotal());
+}
+
+double CActiveAEStream::GetMaxDelay()
+{
+ return static_cast<double>(m_activeAE->GetMaxDelay());
+}
+
+void CActiveAEStream::Pause()
+{
+ m_activeAE->PauseStream(this, true);
+}
+
+void CActiveAEStream::Resume()
+{
+ m_activeAE->PauseStream(this, false);
+}
+
+void CActiveAEStream::Drain(bool wait)
+{
+ Message *msg;
+ CActiveAEStream *stream = this;
+
+ m_streamDraining = true;
+ m_streamDrained = false;
+
+ Message *reply;
+ if (m_streamPort->SendOutMessageSync(CActiveAEDataProtocol::DRAINSTREAM,
+ &reply,2000,
+ &stream, sizeof(CActiveAEStream*)))
+ {
+ bool success = reply->signal == CActiveAEDataProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "CActiveAEStream::Drain - no acc");
+ }
+ }
+
+ if (m_currentBuffer)
+ {
+ MsgStreamSample msgData;
+ msgData.buffer = m_currentBuffer;
+ msgData.stream = this;
+ RemapBuffer();
+ m_streamPort->SendOutMessage(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample));
+ m_currentBuffer = NULL;
+ }
+
+ if (wait)
+ Resume();
+
+ XbmcThreads::EndTime<> timer(2000ms);
+ while (!timer.IsTimePast())
+ {
+ if (m_streamPort->ReceiveInMessage(&msg))
+ {
+ if (msg->signal == CActiveAEDataProtocol::STREAMBUFFER)
+ {
+ MsgStreamSample msgData;
+ msgData.stream = this;
+ msgData.buffer = *((CSampleBuffer**)msg->data);
+ msg->Reply(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample));
+ DecFreeBuffers();
+ continue;
+ }
+ else if (msg->signal == CActiveAEDataProtocol::STREAMDRAINED)
+ {
+ msg->Release();
+ return;
+ }
+ }
+ else if (!wait)
+ return;
+
+ m_inMsgEvent.Wait(timer.GetTimeLeft());
+ }
+ CLog::Log(LOGERROR, "CActiveAEStream::Drain - timeout out");
+}
+
+bool CActiveAEStream::IsDraining()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ return m_streamDraining;
+}
+
+bool CActiveAEStream::IsDrained()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ return m_streamDrained;
+}
+
+void CActiveAEStream::Flush()
+{
+ if (!m_streamIsFlushed)
+ {
+ m_currentBuffer = NULL;
+ m_leftoverBytes = 0;
+ m_activeAE->FlushStream(this);
+ m_streamIsFlushed = true;
+ }
+}
+
+float CActiveAEStream::GetAmplification()
+{
+ return m_streamAmplify;
+}
+
+void CActiveAEStream::SetAmplification(float amplify)
+{
+ m_streamAmplify = amplify;
+ m_activeAE->SetStreamAmplification(this, m_streamAmplify);
+}
+
+float CActiveAEStream::GetReplayGain()
+{
+ return m_streamRgain;
+}
+
+void CActiveAEStream::SetReplayGain(float factor)
+{
+ m_streamRgain = std::max( 0.0f, factor);
+ m_activeAE->SetStreamReplaygain(this, m_streamRgain);
+}
+
+float CActiveAEStream::GetVolume()
+{
+ return m_streamVolume;
+}
+
+void CActiveAEStream::SetVolume(float volume)
+{
+ m_streamVolume = std::max( 0.0f, std::min(1.0f, volume));
+ m_activeAE->SetStreamVolume(this, m_streamVolume);
+}
+
+double CActiveAEStream::GetResampleRatio()
+{
+ return m_streamResampleRatio;
+}
+
+void CActiveAEStream::SetResampleRatio(double ratio)
+{
+ if (ratio != m_streamResampleRatio)
+ m_activeAE->SetStreamResampleRatio(this, ratio);
+ m_streamResampleRatio = ratio;
+}
+
+void CActiveAEStream::SetResampleMode(int mode)
+{
+ if (mode != m_streamResampleMode)
+ m_activeAE->SetStreamResampleMode(this, mode);
+ m_streamResampleMode = mode;
+}
+
+void CActiveAEStream::SetFFmpegInfo(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type)
+{
+ m_activeAE->SetStreamFFmpegInfo(this, profile, matrix_encoding, audio_service_type);
+}
+
+void CActiveAEStream::FadeVolume(float from, float target, unsigned int time)
+{
+ if (time == 0 || (m_format.m_dataFormat == AE_FMT_RAW))
+ return;
+
+ m_streamFading = true;
+ m_activeAE->SetStreamFade(this, from, target, time);
+}
+
+bool CActiveAEStream::IsFading()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ return m_streamFading;
+}
+
+unsigned int CActiveAEStream::GetFrameSize() const
+{
+ return m_format.m_frameSize;
+}
+
+unsigned int CActiveAEStream::GetChannelCount() const
+{
+ return m_format.m_channelLayout.Count();
+}
+
+unsigned int CActiveAEStream::GetSampleRate() const
+{
+ return m_format.m_sampleRate;
+}
+
+enum AEDataFormat CActiveAEStream::GetDataFormat() const
+{
+ return m_format.m_dataFormat;
+}
+
+void CActiveAEStream::RegisterAudioCallback(IAudioCallback* pCallback)
+{
+}
+
+void CActiveAEStream::UnRegisterAudioCallback()
+{
+}
+
+void CActiveAEStream::RegisterSlave(IAEStream *slave)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamLock);
+ m_streamSlave = slave;
+}
+
+//------------------------------------------------------------------------------
+// CActiveAEStreamBuffers
+//------------------------------------------------------------------------------
+
+CActiveAEStreamBuffers::CActiveAEStreamBuffers(const AEAudioFormat& inputFormat,
+ const AEAudioFormat& outputFormat,
+ AEQuality quality)
+ : m_inputFormat(inputFormat)
+{
+ m_resampleBuffers = new CActiveAEBufferPoolResample(inputFormat, outputFormat, quality);
+ m_atempoBuffers = new CActiveAEBufferPoolAtempo(outputFormat);
+}
+
+CActiveAEStreamBuffers::~CActiveAEStreamBuffers()
+{
+ delete m_resampleBuffers;
+ delete m_atempoBuffers;
+}
+
+bool CActiveAEStreamBuffers::HasInputLevel(int level)
+{
+ if ((m_inputSamples.size() + m_resampleBuffers->m_inputSamples.size()) >
+ (m_resampleBuffers->m_allSamples.size() * level / 100))
+ return true;
+ else
+ return false;
+}
+
+bool CActiveAEStreamBuffers::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize)
+{
+ if (!m_resampleBuffers->Create(totaltime, remap, upmix, normalize))
+ return false;
+
+ if (!m_atempoBuffers->Create(totaltime))
+ return false;
+
+ return true;
+}
+
+void CActiveAEStreamBuffers::SetExtraData(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type)
+{
+ /*! @todo Implement set dsp config with new AudioDSP buffer implementation */
+}
+
+bool CActiveAEStreamBuffers::ProcessBuffers()
+{
+ bool busy = false;
+ CSampleBuffer *buf;
+
+ while (!m_inputSamples.empty())
+ {
+ buf = m_inputSamples.front();
+ m_inputSamples.pop_front();
+ m_resampleBuffers->m_inputSamples.push_back(buf);
+ busy = true;
+ }
+
+ busy |= m_resampleBuffers->ResampleBuffers();
+
+ while (!m_resampleBuffers->m_outputSamples.empty())
+ {
+ buf = m_resampleBuffers->m_outputSamples.front();
+ m_resampleBuffers->m_outputSamples.pop_front();
+ m_atempoBuffers->m_inputSamples.push_back(buf);
+ busy = true;
+ }
+
+ busy |= m_atempoBuffers->ProcessBuffers();
+
+ while (!m_atempoBuffers->m_outputSamples.empty())
+ {
+ buf = m_atempoBuffers->m_outputSamples.front();
+ m_atempoBuffers->m_outputSamples.pop_front();
+ m_outputSamples.push_back(buf);
+ busy = true;
+ }
+
+ return busy;
+}
+
+void CActiveAEStreamBuffers::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality)
+{
+ m_resampleBuffers->ConfigureResampler(normalizelevels, stereoupmix, quality);
+}
+
+float CActiveAEStreamBuffers::GetDelay()
+{
+ float delay = 0;
+
+ for (auto &buf : m_inputSamples)
+ {
+ delay += (float)buf->pkt->nb_samples / buf->pkt->config.sample_rate;
+ }
+
+ delay += m_resampleBuffers->GetDelay();
+ delay += m_atempoBuffers->GetDelay();
+
+ for (auto &buf : m_outputSamples)
+ {
+ delay += (float)buf->pkt->nb_samples / buf->pkt->config.sample_rate;
+ }
+
+ return delay;
+}
+
+void CActiveAEStreamBuffers::Flush()
+{
+ m_resampleBuffers->Flush();
+ m_atempoBuffers->Flush();
+
+ while (!m_inputSamples.empty())
+ {
+ m_inputSamples.front()->Return();
+ m_inputSamples.pop_front();
+ }
+ while (!m_outputSamples.empty())
+ {
+ m_outputSamples.front()->Return();
+ m_outputSamples.pop_front();
+ }
+}
+
+void CActiveAEStreamBuffers::SetDrain(bool drain)
+{
+ m_resampleBuffers->SetDrain(drain);
+ m_atempoBuffers->SetDrain(drain);
+}
+
+bool CActiveAEStreamBuffers::IsDrained()
+{
+ if (m_resampleBuffers->m_inputSamples.empty() &&
+ m_resampleBuffers->m_outputSamples.empty() &&
+ m_atempoBuffers->m_inputSamples.empty() &&
+ m_atempoBuffers->m_outputSamples.empty() &&
+ m_inputSamples.empty() &&
+ m_outputSamples.empty())
+ return true;
+ else
+ return false;
+}
+
+void CActiveAEStreamBuffers::SetRR(double rr, double atempoThreshold)
+{
+ if (fabs(rr - 1.0) < atempoThreshold)
+ {
+ m_resampleBuffers->SetRR(rr);
+ m_atempoBuffers->SetTempo(1.0);
+ }
+ else
+ {
+ m_resampleBuffers->SetRR(1.0);
+ m_atempoBuffers->SetTempo(1.0/rr);
+ }
+}
+
+double CActiveAEStreamBuffers::GetRR()
+{
+ double tempo = m_resampleBuffers->GetRR();
+ tempo /= static_cast<double>(m_atempoBuffers->GetTempo());
+ return tempo;
+}
+
+void CActiveAEStreamBuffers::FillBuffer()
+{
+ m_resampleBuffers->FillBuffer();
+ m_atempoBuffers->FillBuffer();
+}
+
+bool CActiveAEStreamBuffers::DoesNormalize()
+{
+ return m_resampleBuffers->DoesNormalize();
+}
+
+void CActiveAEStreamBuffers::ForceResampler(bool force)
+{
+ m_resampleBuffers->ForceResampler(force);
+}
+
+CActiveAEBufferPool* CActiveAEStreamBuffers::GetResampleBuffers()
+{
+ CActiveAEBufferPool *ret = m_resampleBuffers;
+ m_resampleBuffers = nullptr;
+ return ret;
+}
+
+CActiveAEBufferPool* CActiveAEStreamBuffers::GetAtempoBuffers()
+{
+ CActiveAEBufferPool *ret = m_atempoBuffers;
+ m_atempoBuffers = nullptr;
+ return ret;
+}
+
+bool CActiveAEStreamBuffers::HasWork()
+{
+ if (!m_inputSamples.empty())
+ return true;
+ if (!m_outputSamples.empty())
+ return true;
+ if (!m_resampleBuffers->m_inputSamples.empty())
+ return true;
+ if (!m_resampleBuffers->m_outputSamples.empty())
+ return true;
+ if (!m_atempoBuffers->m_inputSamples.empty())
+ return true;
+ if (!m_atempoBuffers->m_outputSamples.empty())
+ return true;
+
+ return false;
+}
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h
new file mode 100644
index 0000000..b7502d5
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Utils/AELimiter.h"
+#include "threads/Event.h"
+
+#include <atomic>
+#include <deque>
+
+namespace ActiveAE
+{
+class CActiveAE;
+
+class CSyncError
+{
+public:
+ CSyncError()
+ {
+ Flush();
+ }
+ void Add(double error)
+ {
+ m_buffer += error;
+ m_count++;
+ }
+
+ void Flush(std::chrono::milliseconds interval = std::chrono::milliseconds(100))
+ {
+ m_buffer = 0.0;
+ m_lastError = 0.0;
+ m_count = 0;
+ m_timer.Set(interval);
+ }
+
+ void SetErrorInterval(std::chrono::milliseconds interval = std::chrono::milliseconds(100))
+ {
+ m_buffer = 0.0;
+ m_count = 0;
+ m_timer.Set(interval);
+ }
+
+ bool Get(double& error, std::chrono::milliseconds interval = std::chrono::milliseconds(100))
+ {
+ if(m_timer.IsTimePast())
+ {
+ error = Get();
+ Flush(interval);
+ m_lastError = error;
+ return true;
+ }
+ else
+ {
+ error = m_lastError;
+ return false;
+ }
+ }
+
+ double GetLastError(unsigned int &time)
+ {
+ time = m_timer.GetStartTime().time_since_epoch().count();
+ return m_lastError;
+ }
+
+ void Correction(double correction)
+ {
+ m_lastError += correction;
+ }
+
+protected:
+ double Get() const
+ {
+ if(m_count)
+ return m_buffer / m_count;
+ else
+ return 0.0;
+ }
+ double m_buffer;
+ double m_lastError;
+ int m_count;
+ XbmcThreads::EndTime<> m_timer;
+};
+
+class CActiveAEStreamBuffers
+{
+public:
+ CActiveAEStreamBuffers(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality);
+ virtual ~CActiveAEStreamBuffers();
+ bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true);
+ void SetExtraData(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type);
+ bool ProcessBuffers();
+ void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality);
+ bool HasInputLevel(int level);
+ float GetDelay();
+ void Flush();
+ void SetDrain(bool drain);
+ bool IsDrained();
+ void SetRR(double rr, double atempoThreshold);
+ double GetRR();
+ void FillBuffer();
+ bool DoesNormalize();
+ void ForceResampler(bool force);
+ bool HasWork();
+ CActiveAEBufferPool *GetResampleBuffers();
+ CActiveAEBufferPool *GetAtempoBuffers();
+
+ AEAudioFormat m_inputFormat;
+ std::deque<CSampleBuffer*> m_outputSamples;
+ std::deque<CSampleBuffer*> m_inputSamples;
+
+protected:
+ CActiveAEBufferPoolResample *m_resampleBuffers;
+ CActiveAEBufferPoolAtempo *m_atempoBuffers;
+
+private:
+ CActiveAEStreamBuffers(const CActiveAEStreamBuffers&) = delete;
+ CActiveAEStreamBuffers& operator=(const CActiveAEStreamBuffers&) = delete;
+};
+
+class CActiveAEStream : public IAEStream
+{
+protected:
+ friend class CActiveAE;
+ friend class CEngineStats;
+ CActiveAEStream(AEAudioFormat *format, unsigned int streamid, CActiveAE *ae);
+ ~CActiveAEStream() override;
+ void FadingFinished();
+ void IncFreeBuffers();
+ void DecFreeBuffers();
+ void ResetFreeBuffers();
+ void InitRemapper();
+ void RemapBuffer();
+ double CalcResampleRatio(double error);
+ std::chrono::milliseconds GetErrorInterval();
+
+public:
+ unsigned int GetSpace() override;
+ unsigned int AddData(const uint8_t* const *data, unsigned int offset, unsigned int frames, ExtData *extData) override;
+ double GetDelay() override;
+ CAESyncInfo GetSyncInfo() override;
+ bool IsBuffering() override;
+ double GetCacheTime() override;
+ double GetCacheTotal() override;
+ double GetMaxDelay() override;
+
+ void Pause() override;
+ void Resume() override;
+ void Drain(bool wait) override;
+ bool IsDraining() override;
+ bool IsDrained() override;
+ void Flush() override;
+
+ float GetVolume() override;
+ float GetReplayGain() override;
+ float GetAmplification() override;
+ void SetVolume(float volume) override;
+ void SetReplayGain(float factor) override;
+ void SetAmplification(float amplify) override;
+ void SetFFmpegInfo(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type) override;
+
+ unsigned int GetFrameSize() const override;
+ unsigned int GetChannelCount() const override;
+
+ unsigned int GetSampleRate() const override ;
+ enum AEDataFormat GetDataFormat() const override;
+
+ double GetResampleRatio() override;
+ void SetResampleRatio(double ratio) override;
+ void SetResampleMode(int mode) override;
+ void RegisterAudioCallback(IAudioCallback* pCallback) override;
+ void UnRegisterAudioCallback() override;
+ void FadeVolume(float from, float to, unsigned int time) override;
+ bool IsFading() override;
+ void RegisterSlave(IAEStream *stream) override;
+
+protected:
+
+ CActiveAE *m_activeAE;
+ unsigned int m_id;
+ AEAudioFormat m_format;
+ float m_streamVolume;
+ float m_streamRgain;
+ float m_streamAmplify;
+ double m_streamResampleRatio;
+ int m_streamResampleMode;
+ unsigned int m_streamSpace;
+ bool m_streamDraining;
+ bool m_streamDrained;
+ bool m_streamFading;
+ int m_streamFreeBuffers;
+ bool m_streamIsBuffering;
+ bool m_streamIsFlushed;
+ IAEStream *m_streamSlave;
+ CCriticalSection m_streamLock;
+ CCriticalSection m_statsLock;
+ uint8_t *m_leftoverBuffer;
+ int m_leftoverBytes;
+ CSampleBuffer *m_currentBuffer;
+ CSoundPacket *m_remapBuffer;
+ IAEResample *m_remapper;
+ double m_lastPts;
+ double m_lastPtsJump;
+ std::chrono::milliseconds m_errorInterval{1000};
+
+ // only accessed by engine
+ CActiveAEBufferPool *m_inputBuffers;
+ CActiveAEStreamBuffers *m_processingBuffers;
+ std::deque<CSampleBuffer*> m_processingSamples;
+ CActiveAEDataProtocol *m_streamPort;
+ CEvent m_inMsgEvent;
+ bool m_drain;
+ bool m_paused;
+ bool m_started;
+ CAELimiter m_limiter;
+ float m_volume;
+ float m_rgain;
+ float m_amplify;
+ float m_bufferedTime;
+ int m_fadingSamples;
+ float m_fadingBase;
+ float m_fadingTarget;
+ int m_fadingTime;
+ int m_profile;
+ int m_resampleMode;
+ double m_resampleIntegral;
+ double m_clockSpeed;
+ enum AVMatrixEncoding m_matrixEncoding;
+ enum AVAudioServiceType m_audioServiceType;
+ bool m_forceResampler;
+ IAEClockCallback *m_pClock;
+ CSyncError m_syncError;
+ double m_lastSyncError;
+ CAESyncInfo::AESyncState m_syncState;
+};
+}
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/AE.h b/xbmc/cores/AudioEngine/Interfaces/AE.h
new file mode 100644
index 0000000..a8562ea
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AE.h
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+
+#include <cassert>
+#include <list>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+extern "C" {
+#include <libavutil/samplefmt.h>
+}
+
+typedef std::pair<std::string, std::string> AEDevice;
+typedef std::vector<AEDevice> AEDeviceList;
+
+/* forward declarations */
+class IAEStream;
+class IAEStreamDeleter;
+class IAESound;
+class IAESoundDeleter;
+class IAEPacketizer;
+class IAudioCallback;
+class IAEClockCallback;
+class CAEStreamInfo;
+
+namespace ADDON
+{
+struct Interface_AudioEngine;
+}
+
+/* sound options */
+#define AE_SOUND_OFF 0 /*! disable sounds */
+#define AE_SOUND_IDLE 1 /*! only play sounds while no streams are running */
+#define AE_SOUND_ALWAYS 2 /*! always play sounds */
+
+/* config options */
+#define AE_CONFIG_FIXED 1
+#define AE_CONFIG_AUTO 2
+#define AE_CONFIG_MATCH 3
+
+enum AEQuality
+{
+ AE_QUALITY_UNKNOWN = -1, /*! Unset, unknown or incorrect quality level */
+ AE_QUALITY_DEFAULT = 0, /*! Engine's default quality level */
+
+ /* Basic quality levels */
+ AE_QUALITY_LOW = 20, /*! Low quality level */
+ AE_QUALITY_MID = 30, /*! Standard quality level */
+ AE_QUALITY_HIGH = 50, /*! Best sound processing quality */
+
+ /* Optional quality levels */
+ AE_QUALITY_REALLYHIGH = 100, /*! Uncompromised optional quality level,
+ usually with unmeasurable and unnoticeable improvement */
+ AE_QUALITY_GPU = 101, /*! GPU acceleration */
+};
+
+struct SampleConfig
+{
+ AVSampleFormat fmt;
+ uint64_t channel_layout;
+ int channels;
+ int sample_rate;
+ int bits_per_sample;
+ int dither_bits;
+};
+
+/*!
+ * \brief IAE Interface
+ */
+class IAE
+{
+protected:
+
+ IAE() = default;
+ virtual ~IAE() = default;
+
+ /*!
+ * \brief Initializes the AudioEngine, called by CFactory when it is time to initialize the audio engine.
+ *
+ * Do not call this directly, CApplication will call this when it is ready
+ */
+ virtual void Start() = 0;
+public:
+ using StreamPtr = std::unique_ptr<IAEStream, IAEStreamDeleter>;
+ using SoundPtr = std::unique_ptr<IAESound, IAESoundDeleter>;
+
+ /*!
+ * \brief Called when the application needs to terminate the engine
+ */
+ virtual void Shutdown() { }
+
+ /*!
+ * \brief Suspends output and de-initializes sink
+ *
+ * Used to avoid conflicts with external players or to reduce power consumption
+ *
+ * \return True if successful
+ */
+ virtual bool Suspend() = 0;
+
+ /*!
+ * \brief Resumes output and re-initializes sink
+ *
+ * Used to resume output from Suspend() state above
+ *
+ * \return True if successful
+ */
+ virtual bool Resume() = 0;
+
+ /*!
+ * \brief Get the current Suspend() state
+ *
+ * Used by players to determine if audio is being processed
+ * Default is true so players drop audio or pause if engine unloaded
+ *
+ * \return True if processing suspended
+ */
+ virtual bool IsSuspended() {return true;}
+
+ /*!
+ * \brief Returns the current master volume level of the AudioEngine
+ *
+ * \return The volume level between 0.0 and 1.0
+ */
+ virtual float GetVolume() = 0;
+
+ /*!
+ * \brief Sets the master volume level of the AudioEngine
+ *
+ * \param volume The new volume level between 0.0 and 1.0
+ */
+ virtual void SetVolume(const float volume) = 0;
+
+ /*!
+ * \brief Set the mute state (does not affect volume level value)
+ *
+ * \param enabled The mute state
+ */
+ virtual void SetMute(const bool enabled) = 0;
+
+ /*!
+ * \brief Get the current mute state
+ *
+ * \return The current mute state
+ */
+ virtual bool IsMuted() = 0;
+
+ /*!
+ * \brief Creates and returns a new IAEStream in the format specified, this function should never fail
+ *
+ * The cleanup behaviour can be modified with the IAEStreamDeleter::setFinish method.
+ * Per default the behaviour is the same as calling FreeStream with true.
+ *
+ * \param audioFormat
+ * \param options A bit field of stream options (see: enum AEStreamOptions)
+ * \return a new IAEStream that will accept data in the requested format
+ */
+ virtual StreamPtr MakeStream(AEAudioFormat& audioFormat,
+ unsigned int options = 0,
+ IAEClockCallback* clock = NULL) = 0;
+
+ /*!
+ * \brief Creates a new IAESound that is ready to play the specified file
+ *
+ * \param file The WAV file to load, this supports XBMC's VFS
+ * \return A new IAESound if the file could be loaded, otherwise NULL
+ */
+ virtual SoundPtr MakeSound(const std::string& file) = 0;
+
+ /*!
+ * \brief Enumerate the supported audio output devices
+ *
+ * \param devices The device list to append supported devices to
+ * \param passthrough True if only passthrough devices are wanted
+ */
+ virtual void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) = 0;
+
+ /*!
+ * \brief Returns true if the AudioEngine supports AE_FMT_RAW streams for use with formats such as IEC61937
+ *
+ * \see CAEPackIEC61937::CAEPackIEC61937()
+ *
+ * \returns true if the AudioEngine is capable of RAW output
+ */
+ virtual bool SupportsRaw(AEAudioFormat &format) { return false; }
+
+ /*!
+ * \brief Returns true if the AudioEngine supports drain mode which is not streaming silence when idle
+ *
+ * \returns true if the AudioEngine is capable of drain mode
+ */
+ virtual bool SupportsSilenceTimeout() { return false; }
+
+ /*!
+ * \brief Returns true if the AudioEngine is currently configured to extract the DTS Core from DTS-HD streams
+ *
+ * \returns true if the AudioEngine is currently configured to extract the DTS Core from DTS-HD streams
+ */
+ virtual bool UsesDtsCoreFallback() { return false; }
+
+ /*!
+ * \brief Returns true if the AudioEngine is currently configured for stereo audio
+ *
+ * \returns true if the AudioEngine is currently configured for stereo audio
+ */
+ virtual bool HasStereoAudioChannelCount() { return false; }
+
+ /*!
+ * \brief Returns true if the AudioEngine is currently configured for HD audio (more than 5.1)
+ *
+ * \returns true if the AudioEngine is currently configured for HD audio (more than 5.1)
+ */
+ virtual bool HasHDAudioChannelCount() { return true; }
+
+ virtual void RegisterAudioCallback(IAudioCallback* pCallback) {}
+
+ virtual void UnregisterAudioCallback(IAudioCallback* pCallback) {}
+
+ /*!
+ * \brief Returns true if AudioEngine supports specified quality level
+ *
+ * \return true if specified quality level is supported, otherwise false
+ */
+ virtual bool SupportsQualityLevel(enum AEQuality level) { return false; }
+
+ /*!
+ * \brief AE decides whether this settings should be displayed
+ *
+ * \return true if AudioEngine wants to display this setting
+ */
+ virtual bool IsSettingVisible(const std::string &settingId) {return false; }
+
+ /*!
+ * \brief Instruct AE to keep configuration for a specified time
+ *
+ * \param millis time for which old configuration should be kept
+ */
+ virtual void KeepConfiguration(unsigned int millis) {}
+
+ /*!
+ * \brief Instruct AE to re-initialize, e.g. after ELD change event
+ */
+ virtual void DeviceChange() {}
+
+ /*!
+ * \brief Instruct AE to re-initialize, e.g. after ELD change event
+ */
+ virtual void DeviceCountChange(const std::string& driver) {}
+
+ /*!
+ * \brief Get the current sink data format
+ *
+ * \param Current sink data format. For more details see AEAudioFormat.
+ * \return Returns true on success, else false.
+ */
+ virtual bool GetCurrentSinkFormat(AEAudioFormat &SinkFormat) { return false; }
+
+private:
+ friend class IAEStreamDeleter;
+ friend class IAESoundDeleter;
+ friend struct ADDON::Interface_AudioEngine;
+
+ /*!
+ * \brief This method will remove the specified stream from the engine.
+ *
+ * For OSX/IOS this is essential to reconfigure the audio output.
+ *
+ * \param stream The stream to be altered
+ * \param finish if true AE will switch back to gui sound mode (if this is last stream)
+ * \return true on success, else false.
+ */
+ virtual bool FreeStream(IAEStream* stream, bool finish) = 0;
+
+ /*!
+ * \brief Free the supplied IAESound object
+ *
+ * \param sound The IAESound object to free
+ */
+ virtual void FreeSound(IAESound* sound) = 0;
+};
+
+class IAEStreamDeleter
+{
+private:
+ IAE* m_iae;
+ bool m_finish;
+
+public:
+ IAEStreamDeleter() : m_iae(nullptr), m_finish(true) {}
+ explicit IAEStreamDeleter(IAE& iae) : m_iae(&iae), m_finish{true} {}
+ void setFinish(bool finish) { m_finish = finish; }
+ void operator()(IAEStream* stream)
+ {
+ assert(m_iae);
+ m_iae->FreeStream(stream, m_finish);
+ }
+};
+
+class IAESoundDeleter
+{
+private:
+ IAE* m_iae;
+
+public:
+ IAESoundDeleter() : m_iae(nullptr) {}
+ explicit IAESoundDeleter(IAE& iae) : m_iae(&iae) {}
+ void operator()(IAESound* sound)
+ {
+ assert(m_iae);
+ m_iae->FreeSound(sound);
+ }
+};
diff --git a/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h b/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h
new file mode 100644
index 0000000..5c207be
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+/**
+ * IAEEncoder interface for on the fly audio compression
+ */
+class IAEEncoder
+{
+public:
+ /**
+ * Constructor
+ */
+ IAEEncoder() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~IAEEncoder() = default;
+
+ /**
+ * Return true if the supplied format is compatible with the current open encoder.
+ * @param format the format to compare
+ * @return true if compatible, false if not
+ */
+ virtual bool IsCompatible(const AEAudioFormat& format) = 0;
+
+ /**
+ * Called to setup the encoder to accept data in the specified format
+ * @param format the desired audio format, may be changed to suit the encoder
+ * @param allow_planar_input allow engine to use with planar formats
+ * @return true on success, false on failure
+ */
+ virtual bool Initialize(AEAudioFormat &format, bool allow_planar_input = false) = 0;
+
+ /**
+ * Reset the encoder for new data
+ */
+ virtual void Reset() = 0;
+
+ /**
+ * Returns the bitrate of the encoder
+ * @return bit rate in bits per second
+ */
+ virtual unsigned int GetBitRate() = 0;
+
+ /**
+ * Returns the AVCodecID of the encoder
+ * @return the ffmpeg codec id
+ */
+ virtual AVCodecID GetCodecID() = 0;
+
+ /**
+ * Return the number of frames needed to encode
+ * @return number of frames (frames * channels = samples * bits per sample = bytes)
+ */
+ virtual unsigned int GetFrames() = 0;
+
+ /**
+ * Encodes the supplied samples into a provided buffer
+ * @param in the PCM samples encoder requested format
+ * @param in_size input buffer size
+ * @param output buffer
+ * @param out_size output buffer size
+ * @return size of encoded data
+ */
+ virtual int Encode (uint8_t *in, int in_size, uint8_t *out, int out_size) = 0;
+
+ /**
+ * Get the encoded data
+ * @param data return pointer to the buffer with the current encoded block
+ * @return the size in bytes of *data
+ */
+ virtual int GetData(uint8_t **data) = 0;
+
+ /**
+ * Get the delay in seconds
+ * @param bufferSize how much encoded data the caller has buffered to add to the delay
+ * @return the delay in seconds including any un-fetched encoded data
+ */
+ virtual double GetDelay(unsigned int bufferSize) = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/AEResample.h b/xbmc/cores/AudioEngine/Interfaces/AEResample.h
new file mode 100644
index 0000000..8b27b32
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AEResample.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AE.h"
+
+namespace ActiveAE
+{
+
+class IAEResample
+{
+public:
+ // return the name of this sync for logging
+ virtual const char *GetName() = 0;
+ IAEResample() = default;
+ virtual ~IAEResample() = default;
+ virtual bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
+ CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) = 0;
+ virtual int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) = 0;
+ virtual int64_t GetDelay(int64_t base) = 0;
+ virtual int GetBufferedSamples() = 0;
+ virtual bool WantsNewSamples(int samples) = 0;
+ virtual int CalcDstSampleCount(int src_samples, int dst_rate, int src_rate) = 0;
+ virtual int GetSrcBufferSize(int samples) = 0;
+ virtual int GetDstBufferSize(int samples) = 0;
+};
+
+}
diff --git a/xbmc/cores/AudioEngine/Interfaces/AESink.h b/xbmc/cores/AudioEngine/Interfaces/AESink.h
new file mode 100644
index 0000000..5637c69
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AESink.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AE.h" // for typedef's used in derived classes
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+
+#include <stdint.h>
+#include <string>
+
+class IAESink
+{
+public:
+ /* return the name of this sync for logging */
+ virtual const char *GetName() = 0;
+
+ IAESink() = default;
+ virtual ~IAESink() = default;
+
+ /*
+ The sink does NOT have to honour anything in the format struct or the device
+ if however it does not honour what is requested, it MUST update device/format
+ with what it does support.
+ */
+ virtual bool Initialize (AEAudioFormat &format, std::string &device) = 0;
+
+ /*
+ Deinitialize the sink for destruction
+ */
+ virtual void Deinitialize() = 0;
+
+ /*
+ This method returns the total time in seconds of the cache.
+ */
+ virtual double GetCacheTotal() = 0;
+
+ /*
+ This method returns latency of hardware.
+ */
+ virtual double GetLatency() { return 0.0; }
+
+ /*!
+ * @brief Adds packets to be sent out, this routine MUST block or sleep.
+ * @param data array of pointers to planes holding audio data
+ * @param frames number of audio frames in data
+ * @param offset offset in frames where audio data starts
+ * @return number of frames consumed by the sink
+ */
+ virtual unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) = 0;
+
+ /*!
+ * @brief instruct the sink to add a pause
+ * @param millis ms to pause
+ */
+ virtual void AddPause(unsigned int millis) {}
+
+ /*!
+ * @brief Return a timestamped status structure with delay and sink info
+ * @param status structure filled with sink status
+ */
+ virtual void GetDelay(AEDelayStatus& status) = 0;
+
+ /*
+ Drain the sink
+ */
+ virtual void Drain() {}
+
+ /*
+ Indicates if sink can handle volume control.
+ */
+ virtual bool HasVolume() { return false; }
+
+ /*
+ This method sets the volume control, volume ranges from 0.0 to 1.0.
+ */
+ virtual void SetVolume(float volume) {}
+};
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/AESound.h b/xbmc/cores/AudioEngine/Interfaces/AESound.h
new file mode 100644
index 0000000..6066fa1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AESound.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+
+#include <string>
+
+class IAESound
+{
+protected:
+ friend class IAE;
+ explicit IAESound(const std::string &filename) {}
+ virtual ~IAESound() = default;
+
+public:
+ /* play the sound this object represents */
+ virtual void Play() = 0;
+
+ /* stop playing the sound this object represents */
+ virtual void Stop() = 0;
+
+ /* return true if the sound is currently playing */
+ virtual bool IsPlaying() = 0;
+
+ /* set the playback channel of this sound, AE_CH_NULL for all */
+ virtual void SetChannel(AEChannel channel) = 0;
+
+ /* get the current playback channel of this sound, AE_CH_NULL for all */
+ virtual AEChannel GetChannel() = 0;
+
+ /* set the playback volume of this sound */
+ virtual void SetVolume(float volume) = 0;
+
+ /* get the current playback volume of this sound */
+ virtual float GetVolume() = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/AEStream.h b/xbmc/cores/AudioEngine/Interfaces/AEStream.h
new file mode 100644
index 0000000..c0f20f6
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/AEStream.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include <stdint.h>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+class IAudioCallback;
+
+/**
+ * Callback interface for VideoPlayer clock needed by AE for sync
+ */
+class IAEClockCallback
+{
+public:
+ virtual ~IAEClockCallback() = default;
+ virtual double GetClock() = 0;
+ virtual double GetClockSpeed() { return 1.0; }
+};
+
+class CAESyncInfo
+{
+public:
+ double delay;
+ double error;
+ double rr;
+ unsigned int errortime;
+ enum AESyncState
+ {
+ SYNC_OFF,
+ SYNC_INSYNC,
+ SYNC_START,
+ SYNC_MUTE,
+ SYNC_ADJUST
+ };
+ AESyncState state;
+};
+
+/**
+ * IAEStream Stream Interface for streaming audio
+ */
+class IAEStream
+{
+protected:
+ friend class IAE;
+ IAEStream() = default;
+ virtual ~IAEStream() = default;
+
+public:
+ struct ExtData
+ {
+ double pts = 0;
+ bool hasDownmix = false;
+ double centerMixLevel = 1;
+ };
+
+public:
+ /**
+ * Returns the amount of space available in the stream
+ * @return The number of bytes AddData will consume
+ */
+ virtual unsigned int GetSpace() = 0;
+
+ /**
+ * Add planar or interleaved PCM data to the stream
+ * @param data array of pointers to the planes
+ * @param offset to frame in frames
+ * @param frames number of frames
+ * @param pts timestamp
+ * @return The number of frames consumed
+ */
+ virtual unsigned int AddData(const uint8_t* const *data, unsigned int offset, unsigned int frames, ExtData *extData) = 0;
+
+ /**
+ * Returns the time in seconds that it will take
+ * for the next added packet to be heard from the speakers.
+ * @return seconds
+ */
+ virtual double GetDelay() = 0;
+
+ /**
+ * Returns info about audio to clock synchronization
+ * @return CAESyncInfo
+ */
+ virtual CAESyncInfo GetSyncInfo() = 0;
+
+ /**
+ * Returns if the stream is buffering
+ * @return True if the stream is buffering
+ */
+ virtual bool IsBuffering() = 0;
+
+ /**
+ * Returns the time in seconds of the stream's
+ * cached audio samples. Engine buffers excluded.
+ * @return seconds
+ */
+ virtual double GetCacheTime() = 0;
+
+ /**
+ * Returns the total time in seconds of the cache
+ * @return seconds
+ */
+ virtual double GetCacheTotal() = 0;
+
+ /**
+ * Returns the total time in seconds of maximum delay
+ * @return seconds
+ */
+ virtual double GetMaxDelay() = 0;
+
+ /**
+ * Pauses the stream playback
+ */
+ virtual void Pause() = 0;
+
+ /**
+ * Resumes the stream after pausing
+ */
+ virtual void Resume() = 0;
+
+ /**
+ * Start draining the stream
+ * @note Once called AddData will not consume more data.
+ */
+ virtual void Drain(bool wait) = 0;
+
+ /**
+ * Returns true if the is stream draining
+ */
+ virtual bool IsDraining() = 0;
+
+ /**
+ * Returns true if the is stream has finished draining
+ */
+ virtual bool IsDrained() = 0;
+
+ /**
+ * Flush all buffers dropping the audio data
+ */
+ virtual void Flush() = 0;
+
+ /**
+ * Return the stream's current volume level
+ * @return The volume level between 0.0 and 1.0
+ */
+ virtual float GetVolume() = 0;
+
+ /**
+ * Set the stream's volume level
+ * @param volume The new volume level between 0.0 and 1.0
+ */
+ virtual void SetVolume(float volume) = 0;
+
+ /**
+ * Returns the stream's current replay gain factor
+ * @return The replay gain factor between 0.0 and 1.0
+ */
+ virtual float GetReplayGain() = 0;
+
+ /**
+ * Sets the stream's replay gain factor, this is used by formats such as MP3 that have attenuation information in their streams
+ * @param factor The replay gain factor
+ */
+ virtual void SetReplayGain(float factor) = 0;
+
+ /**
+ * Gets the stream's volume amplification in linear units.
+ * @return The volume amplification factor between 1.0 and 1000.0
+ */
+ virtual float GetAmplification() = 0;
+
+ /**
+ * Sets the stream's volume amplification in linear units.
+ * @param The volume amplification factor between 1.0 and 1000.0
+ */
+ virtual void SetAmplification(float amplify) = 0;
+
+ /**
+ * Sets the stream ffmpeg information if present.
+ + @param profile
+ * @param matrix_encoding
+ * @param audio_service_type
+ */
+ virtual void SetFFmpegInfo(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type) = 0;
+
+ /**
+ * Returns the size of one audio frame in bytes (channelCount * resolution)
+ * @return The size in bytes of one frame
+ */
+ virtual unsigned int GetFrameSize() const = 0;
+
+ /**
+ * Returns the number of channels the stream is configured to accept
+ * @return The channel count
+ */
+ virtual unsigned int GetChannelCount() const = 0;
+
+ /**
+ * Returns the stream's sample rate, if the stream is using a dynamic sample rate, this value will NOT reflect any changes made by calls to SetResampleRatio()
+ * @return The stream's sample rate (eg, 48000)
+ */
+ virtual unsigned int GetSampleRate() const = 0;
+
+ /**
+ * Return the data format the stream has been configured with
+ * @return The stream's data format (eg, AE_FMT_S16LE)
+ */
+ virtual enum AEDataFormat GetDataFormat() const = 0;
+
+ /**
+ * Return the resample ratio
+ * @note This will return an undefined value if the stream is not resampling
+ * @return the current resample ratio or undefined if the stream is not resampling
+ */
+ virtual double GetResampleRatio() = 0;
+
+ /**
+ * Sets the resample ratio
+ * @note This function may return false if the stream is not resampling, if you wish to use this be sure to set the AESTREAM_FORCE_RESAMPLE option
+ * @param ratio the new sample rate ratio, calculated by ((double)desiredRate / (double)GetSampleRate())
+ */
+ virtual void SetResampleRatio(double ratio) = 0;
+
+ /**
+ * Sets the resamplling on/ff
+ */
+ virtual void SetResampleMode(int mode) = 0;
+
+ /**
+ * Registers the audio callback to call with each block of data, this is used by Audio Visualizations
+ * @warning Currently the callbacks require stereo float data in blocks of 512 samples, any deviation from this may crash XBMC, or cause junk to be rendered
+ * @param pCallback The callback
+ */
+ virtual void RegisterAudioCallback(IAudioCallback* pCallback) = 0;
+
+ /**
+ * Unregisters the current audio callback
+ */
+ virtual void UnRegisterAudioCallback() = 0;
+
+ /**
+ * Fade the volume level over the specified time
+ * @param from The volume level to fade from (0.0f-1.0f) - See notes
+ * @param target The volume level to fade to (0.0f-1.0f)
+ * @param time The amount of time in milliseconds for the fade to occur
+ * @note The from parameter does not set the streams volume, it is only used to calculate the fade time properly
+ */
+ virtual void FadeVolume(float from, float target, unsigned int time) {} /* FIXME: once all the engines have these new methods */
+
+ /**
+ * Returns if a fade is still running
+ * @return true if a fade is in progress, otherwise false
+ */
+ virtual bool IsFading() { return false; }
+
+ /**
+ * Slave a stream to resume when this stream has drained
+ */
+ virtual void RegisterSlave(IAEStream *stream) = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/IAudioCallback.h b/xbmc/cores/AudioEngine/Interfaces/IAudioCallback.h
new file mode 100644
index 0000000..d8759d5
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/IAudioCallback.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// IAudioCallback.h: interface for the IAudioCallback class.
+//
+//////////////////////////////////////////////////////////////////////
+
+class IAudioCallback
+{
+public:
+ IAudioCallback() = default;
+ virtual ~IAudioCallback() = default;
+ virtual void OnInitialize(int iChannels, int iSamplesPerSec, int iBitsPerSample) = 0;
+ virtual void OnAudioData(const float* pAudioData, unsigned int iAudioDataLength) = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Interfaces/ThreadedAE.h b/xbmc/cores/AudioEngine/Interfaces/ThreadedAE.h
new file mode 100644
index 0000000..336b200
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Interfaces/ThreadedAE.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AE.h"
+#include "threads/IRunnable.h"
+
+class IThreadedAE : public IAE, public IRunnable
+{
+public:
+ virtual void Run () = 0;
+ virtual void Stop() = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
new file mode 100644
index 0000000..3e7b7b2
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
@@ -0,0 +1,1691 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkALSA.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h"
+#ifndef HAVE_X11
+#include "cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h"
+#endif
+#include "cores/AudioEngine/Utils/AEELDParser.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "platform/Platform.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <limits.h>
+#include <set>
+#include <sstream>
+#include <stdint.h>
+#include <string>
+
+#include <sys/utsname.h>
+
+using namespace std::chrono_literals;
+
+#define ALSA_OPTIONS (SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_RESAMPLE)
+
+#define ALSA_MAX_CHANNELS 16
+static enum AEChannel LegacyALSAChannelMap[ALSA_MAX_CHANNELS + 1] = {
+ AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
+ AE_CH_UNKNOWN1, AE_CH_UNKNOWN2, AE_CH_UNKNOWN3, AE_CH_UNKNOWN4, AE_CH_UNKNOWN5, AE_CH_UNKNOWN6, AE_CH_UNKNOWN7, AE_CH_UNKNOWN8, /* for p16v devices */
+ AE_CH_NULL
+};
+
+static enum AEChannel LegacyALSAChannelMap51Wide[ALSA_MAX_CHANNELS + 1] = {
+ AE_CH_FL , AE_CH_FR , AE_CH_SL , AE_CH_SR , AE_CH_FC , AE_CH_LFE , AE_CH_BL , AE_CH_BR ,
+ AE_CH_UNKNOWN1, AE_CH_UNKNOWN2, AE_CH_UNKNOWN3, AE_CH_UNKNOWN4, AE_CH_UNKNOWN5, AE_CH_UNKNOWN6, AE_CH_UNKNOWN7, AE_CH_UNKNOWN8, /* for p16v devices */
+ AE_CH_NULL
+};
+
+static enum AEChannel ALSAChannelMapPassthrough[ALSA_MAX_CHANNELS + 1] = {
+ AE_CH_RAW , AE_CH_RAW , AE_CH_RAW , AE_CH_RAW , AE_CH_RAW , AE_CH_RAW , AE_CH_RAW , AE_CH_RAW ,
+ AE_CH_UNKNOWN1, AE_CH_UNKNOWN2, AE_CH_UNKNOWN3, AE_CH_UNKNOWN4, AE_CH_UNKNOWN5, AE_CH_UNKNOWN6, AE_CH_UNKNOWN7, AE_CH_UNKNOWN8, /* for p16v devices */
+ AE_CH_NULL
+};
+
+static unsigned int ALSASampleRateList[] =
+{
+ 5512,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000,
+ 0
+};
+
+namespace
+{
+struct SndConfigDeleter
+{
+ void operator()(snd_config_t* p) { snd_config_delete(p); }
+};
+
+inline std::unique_ptr<snd_config_t, SndConfigDeleter> SndConfigCopy(snd_config_t* original)
+{
+ snd_config_t* config;
+ snd_config_copy(&config, original);
+ return std::unique_ptr<snd_config_t, SndConfigDeleter>(config, SndConfigDeleter());
+}
+} // namespace
+
+CAESinkALSA::CAESinkALSA() :
+ m_pcm(NULL)
+{
+ /* ensure that ALSA has been initialized */
+ if (!snd_config)
+ snd_config_update();
+}
+
+CAESinkALSA::~CAESinkALSA()
+{
+ Deinitialize();
+}
+
+void CAESinkALSA::Register()
+{
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "ALSA";
+ entry.createFunc = CAESinkALSA::Create;
+ entry.enumerateFunc = CAESinkALSA::EnumerateDevicesEx;
+ entry.cleanupFunc = CAESinkALSA::Cleanup;
+ AE::CAESinkFactory::RegisterSink(entry);
+}
+
+IAESink* CAESinkALSA::Create(std::string &device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkALSA();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+inline CAEChannelInfo CAESinkALSA::GetChannelLayoutRaw(const AEAudioFormat& format)
+{
+ unsigned int count = 0;
+
+ switch (format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ count = 8;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ count = 2;
+ break;
+ default:
+ count = 0;
+ break;
+ }
+
+ CAEChannelInfo info;
+ for (unsigned int i = 0; i < count; ++i)
+ info += ALSAChannelMapPassthrough[i];
+
+ return info;
+}
+
+inline CAEChannelInfo CAESinkALSA::GetChannelLayoutLegacy(const AEAudioFormat& format, unsigned int minChannels, unsigned int maxChannels)
+{
+ enum AEChannel* channelMap = LegacyALSAChannelMap;
+ unsigned int count = 0;
+
+ if (format.m_dataFormat == AE_FMT_RAW)
+ return GetChannelLayoutRaw(format);
+
+ // According to CEA-861-D only RL and RR are known. In case of a format having SL and SR channels
+ // but no BR BL channels, we use the wide map in order to open only the num of channels really
+ // needed.
+ if (format.m_channelLayout.HasChannel(AE_CH_SL) && !format.m_channelLayout.HasChannel(AE_CH_BL))
+ {
+ channelMap = LegacyALSAChannelMap51Wide;
+ }
+ for (unsigned int c = 0; c < 8; ++c)
+ {
+ for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
+ {
+ if (format.m_channelLayout[i] == channelMap[c])
+ {
+ count = c + 1;
+ break;
+ }
+ }
+ }
+ count = std::max(count, minChannels);
+ count = std::min(count, maxChannels);
+
+ CAEChannelInfo info;
+ for (unsigned int i = 0; i < count; ++i)
+ info += channelMap[i];
+
+ return info;
+}
+
+inline CAEChannelInfo CAESinkALSA::GetChannelLayout(const AEAudioFormat& format, unsigned int channels)
+{
+ CAEChannelInfo info;
+ std::string alsaMapStr("none");
+
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ info = GetChannelLayoutRaw(format);
+ }
+ else
+ {
+ /* ask for the actual map */
+ snd_pcm_chmap_t* actualMap = snd_pcm_get_chmap(m_pcm);
+ if (actualMap)
+ {
+ alsaMapStr = ALSAchmapToString(actualMap);
+
+ info = ALSAchmapToAEChannelMap(actualMap);
+
+ /* "fake" a compatible map if it is more suitable for AE */
+ if (!info.ContainsChannels(format.m_channelLayout))
+ {
+ CAEChannelInfo infoAlternate = GetAlternateLayoutForm(info);
+ if (infoAlternate.Count())
+ {
+ std::vector<CAEChannelInfo> alts;
+ alts.push_back(info);
+ alts.push_back(infoAlternate);
+ if (format.m_channelLayout.BestMatch(alts) == 1)
+ info = infoAlternate;
+ }
+ }
+
+ /* add empty channels as needed (with e.g. FL,FR,LFE in 4ch) */
+ while (info.Count() < channels)
+ info += AE_CH_UNKNOWN1;
+
+ free(actualMap);
+ }
+ else
+ {
+ info = GetChannelLayoutLegacy(format, channels, channels);
+ }
+ }
+
+ CLog::Log(LOGDEBUG,
+ "CAESinkALSA::GetChannelLayout - Input Channel Count: {} Output Channel Count: {}",
+ format.m_channelLayout.Count(), info.Count());
+ CLog::Log(LOGDEBUG, "CAESinkALSA::GetChannelLayout - Requested Layout: {}",
+ std::string(format.m_channelLayout));
+ CLog::Log(LOGDEBUG, "CAESinkALSA::GetChannelLayout - Got Layout: {} (ALSA: {})",
+ std::string(info), alsaMapStr);
+
+ return info;
+}
+
+AEChannel CAESinkALSA::ALSAChannelToAEChannel(unsigned int alsaChannel)
+{
+ AEChannel aeChannel;
+ switch (alsaChannel)
+ {
+ case SND_CHMAP_FL: aeChannel = AE_CH_FL; break;
+ case SND_CHMAP_FR: aeChannel = AE_CH_FR; break;
+ case SND_CHMAP_FC: aeChannel = AE_CH_FC; break;
+ case SND_CHMAP_LFE: aeChannel = AE_CH_LFE; break;
+ case SND_CHMAP_RL: aeChannel = AE_CH_BL; break;
+ case SND_CHMAP_RR: aeChannel = AE_CH_BR; break;
+ case SND_CHMAP_FLC: aeChannel = AE_CH_FLOC; break;
+ case SND_CHMAP_FRC: aeChannel = AE_CH_FROC; break;
+ case SND_CHMAP_RC: aeChannel = AE_CH_BC; break;
+ case SND_CHMAP_SL: aeChannel = AE_CH_SL; break;
+ case SND_CHMAP_SR: aeChannel = AE_CH_SR; break;
+ case SND_CHMAP_TFL: aeChannel = AE_CH_TFL; break;
+ case SND_CHMAP_TFR: aeChannel = AE_CH_TFR; break;
+ case SND_CHMAP_TFC: aeChannel = AE_CH_TFC; break;
+ case SND_CHMAP_TC: aeChannel = AE_CH_TC; break;
+ case SND_CHMAP_TRL: aeChannel = AE_CH_TBL; break;
+ case SND_CHMAP_TRR: aeChannel = AE_CH_TBR; break;
+ case SND_CHMAP_TRC: aeChannel = AE_CH_TBC; break;
+ case SND_CHMAP_RLC: aeChannel = AE_CH_BLOC; break;
+ case SND_CHMAP_RRC: aeChannel = AE_CH_BROC; break;
+ default: aeChannel = AE_CH_UNKNOWN1; break;
+ }
+ return aeChannel;
+}
+
+unsigned int CAESinkALSA::AEChannelToALSAChannel(AEChannel aeChannel)
+{
+ unsigned int alsaChannel;
+ switch (aeChannel)
+ {
+ case AE_CH_FL: alsaChannel = SND_CHMAP_FL; break;
+ case AE_CH_FR: alsaChannel = SND_CHMAP_FR; break;
+ case AE_CH_FC: alsaChannel = SND_CHMAP_FC; break;
+ case AE_CH_LFE: alsaChannel = SND_CHMAP_LFE; break;
+ case AE_CH_BL: alsaChannel = SND_CHMAP_RL; break;
+ case AE_CH_BR: alsaChannel = SND_CHMAP_RR; break;
+ case AE_CH_FLOC: alsaChannel = SND_CHMAP_FLC; break;
+ case AE_CH_FROC: alsaChannel = SND_CHMAP_FRC; break;
+ case AE_CH_BC: alsaChannel = SND_CHMAP_RC; break;
+ case AE_CH_SL: alsaChannel = SND_CHMAP_SL; break;
+ case AE_CH_SR: alsaChannel = SND_CHMAP_SR; break;
+ case AE_CH_TFL: alsaChannel = SND_CHMAP_TFL; break;
+ case AE_CH_TFR: alsaChannel = SND_CHMAP_TFR; break;
+ case AE_CH_TFC: alsaChannel = SND_CHMAP_TFC; break;
+ case AE_CH_TC: alsaChannel = SND_CHMAP_TC; break;
+ case AE_CH_TBL: alsaChannel = SND_CHMAP_TRL; break;
+ case AE_CH_TBR: alsaChannel = SND_CHMAP_TRR; break;
+ case AE_CH_TBC: alsaChannel = SND_CHMAP_TRC; break;
+ case AE_CH_BLOC: alsaChannel = SND_CHMAP_RLC; break;
+ case AE_CH_BROC: alsaChannel = SND_CHMAP_RRC; break;
+ default: alsaChannel = SND_CHMAP_UNKNOWN; break;
+ }
+ return alsaChannel;
+}
+
+CAEChannelInfo CAESinkALSA::ALSAchmapToAEChannelMap(snd_pcm_chmap_t* alsaMap)
+{
+ CAEChannelInfo info;
+
+ for (unsigned int i = 0; i < alsaMap->channels; i++)
+ info += ALSAChannelToAEChannel(alsaMap->pos[i]);
+
+ return info;
+}
+
+snd_pcm_chmap_t* CAESinkALSA::AEChannelMapToALSAchmap(const CAEChannelInfo& info)
+{
+ int AECount = info.Count();
+ snd_pcm_chmap_t* alsaMap = (snd_pcm_chmap_t*)malloc(sizeof(snd_pcm_chmap_t) + AECount * sizeof(int));
+
+ alsaMap->channels = AECount;
+
+ for (int i = 0; i < AECount; i++)
+ alsaMap->pos[i] = AEChannelToALSAChannel(info[i]);
+
+ return alsaMap;
+}
+
+snd_pcm_chmap_t* CAESinkALSA::CopyALSAchmap(snd_pcm_chmap_t* alsaMap)
+{
+ snd_pcm_chmap_t* copyMap = (snd_pcm_chmap_t*)malloc(sizeof(snd_pcm_chmap_t) + alsaMap->channels * sizeof(int));
+
+ copyMap->channels = alsaMap->channels;
+ memcpy(copyMap->pos, alsaMap->pos, alsaMap->channels * sizeof(int));
+
+ return copyMap;
+}
+
+std::string CAESinkALSA::ALSAchmapToString(snd_pcm_chmap_t* alsaMap)
+{
+ char buf[128] = {};
+ //! @bug ALSA bug - buffer overflow by a factor of 2 is possible
+ //! http://mailman.alsa-project.org/pipermail/alsa-devel/2014-December/085815.html
+ int err = snd_pcm_chmap_print(alsaMap, sizeof(buf) / 2, buf);
+ if (err < 0)
+ return "Error";
+ return std::string(buf);
+}
+
+CAEChannelInfo CAESinkALSA::GetAlternateLayoutForm(const CAEChannelInfo& info)
+{
+ CAEChannelInfo altLayout;
+
+ /* only handle symmetrical layouts */
+ if (info.HasChannel(AE_CH_BL) == info.HasChannel(AE_CH_BR) &&
+ info.HasChannel(AE_CH_SL) == info.HasChannel(AE_CH_SR) &&
+ info.HasChannel(AE_CH_BLOC) == info.HasChannel(AE_CH_BROC))
+ {
+ /* CEA-861-D used by HDMI 1.x has 7.1 as back+back-x-of-center, not
+ * side+back. Mangle it here. */
+ if (info.HasChannel(AE_CH_SL) && info.HasChannel(AE_CH_BL) && !info.HasChannel(AE_CH_BLOC))
+ {
+ altLayout = info;
+ altLayout.ReplaceChannel(AE_CH_BL, AE_CH_BLOC);
+ altLayout.ReplaceChannel(AE_CH_BR, AE_CH_BROC);
+ altLayout.ReplaceChannel(AE_CH_SL, AE_CH_BL);
+ altLayout.ReplaceChannel(AE_CH_SR, AE_CH_BR);
+ }
+ /* same in reverse */
+ else if (!info.HasChannel(AE_CH_SL) && info.HasChannel(AE_CH_BL) && info.HasChannel(AE_CH_BLOC))
+ {
+ altLayout = info;
+ altLayout.ReplaceChannel(AE_CH_BL, AE_CH_SL);
+ altLayout.ReplaceChannel(AE_CH_BR, AE_CH_SR);
+ altLayout.ReplaceChannel(AE_CH_BLOC, AE_CH_BL);
+ altLayout.ReplaceChannel(AE_CH_BROC, AE_CH_BR);
+ }
+ /* We have side speakers but no back speakers, allow map to back
+ * speakers. */
+ else if (info.HasChannel(AE_CH_SL) && !info.HasChannel(AE_CH_BL))
+ {
+ altLayout = info;
+ altLayout.ReplaceChannel(AE_CH_SL, AE_CH_BL);
+ altLayout.ReplaceChannel(AE_CH_SR, AE_CH_BR);
+ }
+ /* reverse */
+ else if (!info.HasChannel(AE_CH_SL) && info.HasChannel(AE_CH_BL))
+ {
+ altLayout = info;
+ altLayout.ReplaceChannel(AE_CH_BL, AE_CH_SL);
+ altLayout.ReplaceChannel(AE_CH_BR, AE_CH_SR);
+ }
+ }
+ return altLayout;
+}
+
+snd_pcm_chmap_t* CAESinkALSA::SelectALSAChannelMap(const CAEChannelInfo& info)
+{
+ snd_pcm_chmap_t* chmap = NULL;
+ snd_pcm_chmap_query_t** supportedMaps;
+
+ supportedMaps = snd_pcm_query_chmaps(m_pcm);
+
+ if (!supportedMaps)
+ return NULL;
+
+ CAEChannelInfo infoAlternate = GetAlternateLayoutForm(info);
+
+ /* for efficiency, first try to find an exact match, and only then fallback
+ * to searching for less perfect matches */
+ int i = 0;
+ for (snd_pcm_chmap_query_t* supportedMap = supportedMaps[i++];
+ supportedMap; supportedMap = supportedMaps[i++])
+ {
+ if (supportedMap->map.channels == info.Count())
+ {
+ CAEChannelInfo candidate = ALSAchmapToAEChannelMap(&supportedMap->map);
+ const CAEChannelInfo* selectedInfo = &info;
+
+ if (!candidate.ContainsChannels(info) || !info.ContainsChannels(candidate))
+ {
+ selectedInfo = &infoAlternate;
+ if (!candidate.ContainsChannels(infoAlternate) || !infoAlternate.ContainsChannels(candidate))
+ continue;
+ }
+
+ if (supportedMap->type == SND_CHMAP_TYPE_VAR)
+ {
+ /* device supports the AE map directly */
+ chmap = AEChannelMapToALSAchmap(*selectedInfo);
+ break;
+ }
+ else
+ {
+ /* device needs 1:1 remapping */
+ chmap = CopyALSAchmap(&supportedMap->map);
+ break;
+ }
+ }
+ }
+
+ /* if no exact chmap was found, fallback to best-effort */
+ if (!chmap)
+ {
+ CAEChannelInfo allChannels;
+ std::vector<CAEChannelInfo> supportedMapsAE;
+
+ /* Convert the ALSA maps to AE maps. */
+ int i = 0;
+ for (snd_pcm_chmap_query_t* supportedMap = supportedMaps[i++];
+ supportedMap; supportedMap = supportedMaps[i++])
+ supportedMapsAE.push_back(ALSAchmapToAEChannelMap(&supportedMap->map));
+
+ int score = 0;
+ int best = info.BestMatch(supportedMapsAE, &score);
+
+ /* see if we find a better result with the alternate form */
+ if (infoAlternate.Count() && score < 0)
+ {
+ int scoreAlt = 0;
+ int bestAlt = infoAlternate.BestMatch(supportedMapsAE, &scoreAlt);
+ if (scoreAlt > score)
+ best = bestAlt;
+ }
+
+ if (best > 0)
+ chmap = CopyALSAchmap(&supportedMaps[best]->map);
+ }
+
+ if (chmap && CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
+ CLog::Log(LOGDEBUG, "CAESinkALSA::SelectALSAChannelMap - Selected ALSA map \"{}\"",
+ ALSAchmapToString(chmap));
+
+ snd_pcm_free_chmaps(supportedMaps);
+ return chmap;
+}
+
+void CAESinkALSA::GetAESParams(const AEAudioFormat& format, std::string& params)
+{
+ if (m_passthrough)
+ params = "AES0=0x06";
+ else
+ params = "AES0=0x04";
+
+ params += ",AES1=0x82,AES2=0x00";
+
+ if (m_passthrough && format.m_channelLayout.Count() == 8) params += ",AES3=0x09";
+ else if (format.m_sampleRate == 192000) params += ",AES3=0x0e";
+ else if (format.m_sampleRate == 176400) params += ",AES3=0x0c";
+ else if (format.m_sampleRate == 96000) params += ",AES3=0x0a";
+ else if (format.m_sampleRate == 88200) params += ",AES3=0x08";
+ else if (format.m_sampleRate == 48000) params += ",AES3=0x02";
+ else if (format.m_sampleRate == 44100) params += ",AES3=0x00";
+ else if (format.m_sampleRate == 32000) params += ",AES3=0x03";
+ else params += ",AES3=0x01";
+}
+
+bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device)
+{
+ m_initDevice = device;
+ m_initFormat = format;
+ ALSAConfig inconfig, outconfig;
+ inconfig.format = format.m_dataFormat;
+ inconfig.sampleRate = format.m_sampleRate;
+
+ /*
+ * We can't use the better GetChannelLayout() at this point as the device
+ * is not opened yet, and we need inconfig.channels to select the correct
+ * device... Legacy layouts should be accurate enough for device selection
+ * in all cases, though.
+ */
+ inconfig.channels = GetChannelLayoutLegacy(format, 2, 8).Count();
+
+ /* if we are raw, correct the data format */
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ inconfig.format = AE_FMT_S16NE;
+ m_passthrough = true;
+ }
+ else
+ {
+ m_passthrough = false;
+ }
+
+ if (inconfig.channels == 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::Initialize - Unable to open the requested channel layout");
+ return false;
+ }
+
+ AEDeviceType devType = AEDeviceTypeFromName(device);
+
+ std::string AESParams;
+ /* digital interfaces should have AESx set, though in practice most
+ * receivers don't care */
+ if (m_passthrough || devType == AE_DEVTYPE_HDMI || devType == AE_DEVTYPE_IEC958)
+ GetAESParams(format, AESParams);
+
+ CLog::Log(LOGINFO, "CAESinkALSA::Initialize - Attempting to open device \"{}\"", device);
+
+ /* get the sound config */
+ std::unique_ptr<snd_config_t, SndConfigDeleter> config = SndConfigCopy(snd_config);
+
+ if (!OpenPCMDevice(device, AESParams, inconfig.channels, &m_pcm, config.get()))
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::Initialize - failed to initialize device \"{}\"", device);
+ return false;
+ }
+
+ /* get the actual device name that was used */
+ device = snd_pcm_name(m_pcm);
+ m_device = device;
+
+ CLog::Log(LOGINFO, "CAESinkALSA::Initialize - Opened device \"{}\"", device);
+
+ snd_pcm_chmap_t* selectedChmap = NULL;
+ if (!m_passthrough)
+ {
+ selectedChmap = SelectALSAChannelMap(format.m_channelLayout);
+ if (selectedChmap)
+ {
+ /* update wanted channel count according to the selected map */
+ inconfig.channels = selectedChmap->channels;
+ }
+ }
+
+ if (!InitializeHW(inconfig, outconfig) || !InitializeSW(outconfig))
+ {
+ free(selectedChmap);
+ return false;
+ }
+
+ if (selectedChmap)
+ {
+ /* failure is OK, that likely just means the selected chmap is fixed already */
+ snd_pcm_set_chmap(m_pcm, selectedChmap);
+ free(selectedChmap);
+ }
+
+ // we want it blocking
+ snd_pcm_nonblock(m_pcm, 0);
+ snd_pcm_prepare (m_pcm);
+
+ if (m_passthrough && inconfig.channels != outconfig.channels)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA::Initialize - could not open required number of channels");
+ return false;
+ }
+ // adjust format to the configuration we got
+ format.m_channelLayout = GetChannelLayout(format, outconfig.channels);
+ // we might end up with an unusable channel layout that contains only UNKNOWN
+ // channels, let's do a sanity check.
+ if (!format.m_channelLayout.IsLayoutValid())
+ return false;
+
+ format.m_sampleRate = outconfig.sampleRate;
+ format.m_frames = outconfig.periodSize;
+ format.m_frameSize = outconfig.frameSize;
+ format.m_dataFormat = outconfig.format;
+
+ m_format = format;
+ m_formatSampleRateMul = 1.0 / (double)m_format.m_sampleRate;
+
+ return true;
+}
+
+snd_pcm_format_t CAESinkALSA::AEFormatToALSAFormat(const enum AEDataFormat format)
+{
+ if (format == AE_FMT_RAW)
+ return SND_PCM_FORMAT_S16;
+
+ switch (format)
+ {
+ case AE_FMT_U8 : return SND_PCM_FORMAT_U8;
+ case AE_FMT_S16NE : return SND_PCM_FORMAT_S16;
+ case AE_FMT_S16LE : return SND_PCM_FORMAT_S16_LE;
+ case AE_FMT_S16BE : return SND_PCM_FORMAT_S16_BE;
+ case AE_FMT_S24NE4: return SND_PCM_FORMAT_S24;
+#ifdef __BIG_ENDIAN__
+ case AE_FMT_S24NE3: return SND_PCM_FORMAT_S24_3BE;
+#else
+ case AE_FMT_S24NE3: return SND_PCM_FORMAT_S24_3LE;
+#endif
+ case AE_FMT_S32NE : return SND_PCM_FORMAT_S32;
+ case AE_FMT_FLOAT : return SND_PCM_FORMAT_FLOAT;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+bool CAESinkALSA::InitializeHW(const ALSAConfig &inconfig, ALSAConfig &outconfig)
+{
+ snd_pcm_hw_params_t *hw_params;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+ memset(hw_params, 0, snd_pcm_hw_params_sizeof());
+
+ snd_pcm_hw_params_any(m_pcm, hw_params);
+ snd_pcm_hw_params_set_access(m_pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+
+ unsigned int sampleRate = inconfig.sampleRate;
+ snd_pcm_hw_params_set_rate_near (m_pcm, hw_params, &sampleRate, NULL);
+
+ unsigned int channelCount = inconfig.channels;
+ /* select a channel count >=wanted, or otherwise the highest available */
+ if (snd_pcm_hw_params_set_channels_min(m_pcm, hw_params, &channelCount) == 0)
+ snd_pcm_hw_params_set_channels_first(m_pcm, hw_params, &channelCount);
+ else
+ snd_pcm_hw_params_set_channels_last(m_pcm, hw_params, &channelCount);
+
+ /* ensure we opened X channels or more */
+ if (inconfig.channels > channelCount)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Unable to open the required number of channels");
+ }
+
+ /* update outconfig */
+ outconfig.channels = channelCount;
+
+ snd_pcm_format_t fmt = AEFormatToALSAFormat(inconfig.format);
+ outconfig.format = inconfig.format;
+
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ {
+ /* if we dont support the requested format, fallback to float */
+ fmt = SND_PCM_FORMAT_FLOAT;
+ outconfig.format = AE_FMT_FLOAT;
+ }
+
+ snd_pcm_hw_params_t *hw_params_copy;
+ snd_pcm_hw_params_alloca(&hw_params_copy);
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // copy what we have
+
+ /* try the data format */
+ if (snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0)
+ {
+ /* if the chosen format is not supported, try each one in descending order */
+ CLog::Log(LOGINFO,
+ "CAESinkALSA::InitializeHW - Your hardware does not support {}, trying other formats",
+ CAEUtil::DataFormatToStr(outconfig.format));
+ for (enum AEDataFormat i = AE_FMT_MAX; i > AE_FMT_INVALID; i = (enum AEDataFormat)((int)i - 1))
+ {
+ if (i == AE_FMT_RAW || i == AE_FMT_MAX)
+ continue;
+
+ if (m_passthrough && i != AE_FMT_S16BE && i != AE_FMT_S16LE)
+ continue;
+
+ fmt = AEFormatToALSAFormat(i);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ continue;
+
+ snd_pcm_hw_params_copy(hw_params, hw_params_copy); // restore from copy
+ if (snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0)
+ {
+ fmt = SND_PCM_FORMAT_UNKNOWN;
+ continue;
+ }
+
+ int fmtBits = CAEUtil::DataFormatToBits(i);
+ int bits = snd_pcm_hw_params_get_sbits(hw_params);
+
+ // skip bits check when alsa reports invalid sbits value
+ if (bits > 0 && bits != fmtBits)
+ {
+ /* if we opened in 32bit and only have 24bits, signal it accordingly */
+ if (fmt == SND_PCM_FORMAT_S32 && bits == 24)
+ i = AE_FMT_S24NE4MSB;
+ else if (fmt == SND_PCM_FORMAT_S24 && bits == 24)
+ i = AE_FMT_S24NE4;
+ else
+ continue;
+ }
+
+ /* record that the format fell back to X */
+ outconfig.format = i;
+ CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Using data format {}",
+ CAEUtil::DataFormatToStr(outconfig.format));
+ break;
+ }
+
+ /* if we failed to find a valid output format */
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeHW - Unable to find a suitable output format");
+ return false;
+ }
+ }
+
+ snd_pcm_uframes_t periodSize, bufferSize;
+ snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufferSize);
+ snd_pcm_hw_params_get_period_size_max(hw_params, &periodSize, NULL);
+
+ /*
+ We want to make sure, that we have max 200 ms Buffer with
+ a periodSize of approx 50 ms. Choosing a higher bufferSize
+ will cause problems with menu sounds. Buffer will be increased
+ after those are fixed.
+ */
+ periodSize = std::min(periodSize, (snd_pcm_uframes_t) sampleRate / 20);
+ bufferSize = std::min(bufferSize, (snd_pcm_uframes_t) sampleRate / 5);
+
+ /*
+ According to upstream we should set buffer size first - so make sure it is always at least
+ 4x period size to not get underruns (some systems seem to have issues with only 2 periods)
+ */
+ periodSize = std::min(periodSize, bufferSize / 4);
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Request: periodSize {}, bufferSize {}",
+ periodSize, bufferSize);
+
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // copy what we have and is already working
+
+ // Make sure to not initialize too large to not cause underruns
+ snd_pcm_uframes_t periodSizeMax = bufferSize / 3;
+ if(snd_pcm_hw_params_set_period_size_max(m_pcm, hw_params_copy, &periodSizeMax, NULL) != 0)
+ {
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Request: Failed to limit periodSize to {}",
+ periodSizeMax);
+ }
+
+ // first trying bufferSize, PeriodSize
+ // for more info see here:
+ // http://mailman.alsa-project.org/pipermail/alsa-devel/2009-September/021069.html
+ // the last three tries are done as within pulseaudio
+
+ // backup periodSize and bufferSize first. Restore them after every failed try
+ snd_pcm_uframes_t periodSizeTemp, bufferSizeTemp;
+ periodSizeTemp = periodSize;
+ bufferSizeTemp = bufferSize;
+ if (snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0
+ || snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0
+ || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ bufferSize = bufferSizeTemp;
+ periodSize = periodSizeTemp;
+ // retry with PeriodSize, bufferSize
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy
+ if (snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0
+ || snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0
+ || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ // try only periodSize
+ periodSize = periodSizeTemp;
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy
+ if(snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0
+ || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ // try only BufferSize
+ bufferSize = bufferSizeTemp;
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy
+ if (snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0
+ || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ // set default that Alsa would choose
+ CLog::Log(LOGWARNING, "CAESinkAlsa::InitializeHW - Using default alsa values - set failed");
+ if (snd_pcm_hw_params(m_pcm, hw_params) != 0)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Could not init a valid sink");
+ return false;
+ }
+ }
+ }
+ // reread values when alsa default was kept
+ snd_pcm_get_params(m_pcm, &bufferSize, &periodSize);
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Got: periodSize {}, bufferSize {}", periodSize,
+ bufferSize);
+
+ /* set the format parameters */
+ outconfig.sampleRate = sampleRate;
+
+ /* if periodSize is too small Audio Engine might starve */
+ m_fragmented = false;
+ unsigned int fragments = 1;
+ if (periodSize < AE_MIN_PERIODSIZE)
+ {
+ fragments = std::ceil((double) AE_MIN_PERIODSIZE / periodSize);
+ CLog::Log(LOGDEBUG, "Audio Driver reports too low periodSize {} - will use {} fragments",
+ (int)periodSize, (int)fragments);
+ m_fragmented = true;
+ }
+
+ m_originalPeriodSize = periodSize;
+ outconfig.periodSize = fragments * periodSize;
+ outconfig.frameSize = snd_pcm_frames_to_bytes(m_pcm, 1);
+
+ m_bufferSize = (unsigned int)bufferSize;
+ m_timeout = std::ceil((double)(bufferSize * 1000) / (double)sampleRate);
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Setting timeout to {} ms", m_timeout);
+
+ return true;
+}
+
+bool CAESinkALSA::InitializeSW(const ALSAConfig &inconfig)
+{
+ snd_pcm_sw_params_t *sw_params;
+ snd_pcm_uframes_t boundary;
+
+ snd_pcm_sw_params_alloca(&sw_params);
+ memset(sw_params, 0, snd_pcm_sw_params_sizeof());
+
+ snd_pcm_sw_params_current (m_pcm, sw_params);
+ snd_pcm_sw_params_set_start_threshold (m_pcm, sw_params, INT_MAX);
+ snd_pcm_sw_params_set_silence_threshold(m_pcm, sw_params, 0);
+ snd_pcm_sw_params_get_boundary (sw_params, &boundary);
+ snd_pcm_sw_params_set_silence_size (m_pcm, sw_params, boundary);
+ snd_pcm_sw_params_set_avail_min (m_pcm, sw_params, inconfig.periodSize);
+
+ if (snd_pcm_sw_params(m_pcm, sw_params) < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeSW - Failed to set the parameters");
+ return false;
+ }
+
+ return true;
+}
+
+void CAESinkALSA::Deinitialize()
+{
+ if (m_pcm)
+ {
+ Stop();
+ snd_pcm_close(m_pcm);
+ m_pcm = NULL;
+ }
+}
+
+void CAESinkALSA::Stop()
+{
+ if (!m_pcm)
+ return;
+ snd_pcm_drop(m_pcm);
+}
+
+void CAESinkALSA::GetDelay(AEDelayStatus& status)
+{
+ if (!m_pcm)
+ {
+ status.SetDelay(0);
+ return;
+ }
+ snd_pcm_sframes_t frames = 0;
+ snd_pcm_delay(m_pcm, &frames);
+
+ if (frames < 0)
+ {
+ snd_pcm_forward(m_pcm, -frames);
+ frames = 0;
+ }
+
+ status.SetDelay((double)frames * m_formatSampleRateMul);
+}
+
+double CAESinkALSA::GetCacheTotal()
+{
+ return (double)m_bufferSize * m_formatSampleRateMul;
+}
+
+unsigned int CAESinkALSA::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_pcm)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA - Tried to add packets without a sink");
+ return INT_MAX;
+ }
+
+ void *buffer = data[0]+offset*m_format.m_frameSize;
+ unsigned int amount = 0;
+ int64_t data_left = (int64_t) frames;
+ int frames_written = 0;
+
+ while (data_left > 0)
+ {
+ if (m_fragmented)
+ amount = std::min((unsigned int) data_left, m_originalPeriodSize);
+ else // take care as we can come here a second time if the sink does not eat all data
+ amount = (unsigned int) data_left;
+
+ int ret = snd_pcm_writei(m_pcm, buffer, amount);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA - snd_pcm_writei({}) {} - trying to recover", ret,
+ snd_strerror(ret));
+ ret = snd_pcm_recover(m_pcm, ret, 1);
+ if(ret < 0)
+ {
+ HandleError("snd_pcm_writei(1)", ret);
+ ret = snd_pcm_writei(m_pcm, buffer, amount);
+ if (ret < 0)
+ {
+ HandleError("snd_pcm_writei(2)", ret);
+ ret = 0;
+ }
+ }
+ }
+
+ if ( ret > 0 && snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
+ snd_pcm_start(m_pcm);
+
+ if (ret <= 0)
+ break;
+
+ frames_written += ret;
+ data_left -= ret;
+ buffer = data[0]+offset*m_format.m_frameSize + frames_written*m_format.m_frameSize;
+ }
+ return frames_written;
+}
+
+void CAESinkALSA::HandleError(const char* name, int err)
+{
+ switch(err)
+ {
+ case -EPIPE:
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError({}) - underrun", name);
+ if ((err = snd_pcm_prepare(m_pcm)) < 0)
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError({}) - snd_pcm_prepare returned {} ({})", name,
+ err, snd_strerror(err));
+ break;
+
+ case -ESTRPIPE:
+ CLog::Log(LOGINFO, "CAESinkALSA::HandleError({}) - Resuming after suspend", name);
+
+ /* try to resume the stream */
+ while((err = snd_pcm_resume(m_pcm)) == -EAGAIN)
+ KODI::TIME::Sleep(1ms);
+
+ /* if the hardware doesn't support resume, prepare the stream */
+ if (err == -ENOSYS)
+ if ((err = snd_pcm_prepare(m_pcm)) < 0)
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError({}) - snd_pcm_prepare returned {} ({})",
+ name, err, snd_strerror(err));
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError({}) - snd_pcm_writei returned {} ({})", name,
+ err, snd_strerror(err));
+ break;
+ }
+}
+
+void CAESinkALSA::Drain()
+{
+ if (!m_pcm)
+ return;
+
+ snd_pcm_drain(m_pcm);
+ snd_pcm_prepare(m_pcm);
+}
+
+void CAESinkALSA::AppendParams(std::string &device, const std::string &params)
+{
+ /* Note: escaping, e.g. "plug:'something:X=y'" isn't handled,
+ * but it is not normally encountered at this point. */
+
+ device += (device.find(':') == std::string::npos) ? ':' : ',';
+ device += params;
+}
+
+bool CAESinkALSA::TryDevice(const std::string &name, snd_pcm_t **pcmp, snd_config_t *lconf)
+{
+ /* Check if this device was already open (e.g. when checking for supported
+ * channel count in EnumerateDevice()) */
+ if (*pcmp)
+ {
+ if (name == snd_pcm_name(*pcmp))
+ return true;
+
+ snd_pcm_close(*pcmp);
+ *pcmp = NULL;
+ }
+
+ int err = snd_pcm_open_lconf(pcmp, name.c_str(), SND_PCM_STREAM_PLAYBACK, ALSA_OPTIONS, lconf);
+ if (err < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA - Unable to open device \"{}\" for playback", name);
+ }
+
+ return err == 0;
+}
+
+bool CAESinkALSA::TryDeviceWithParams(const std::string &name, const std::string &params, snd_pcm_t **pcmp, snd_config_t *lconf)
+{
+ if (!params.empty())
+ {
+ std::string nameWithParams = name;
+ AppendParams(nameWithParams, params);
+ if (TryDevice(nameWithParams, pcmp, lconf))
+ return true;
+ }
+
+ /* Try the variant without extra parameters.
+ * Custom devices often do not take the AESx parameters, for example.
+ */
+ return TryDevice(name, pcmp, lconf);
+}
+
+bool CAESinkALSA::OpenPCMDevice(const std::string &name, const std::string &params, int channels, snd_pcm_t **pcmp, snd_config_t *lconf)
+{
+ /* Special name denoting surroundXX mangling. This is needed for some
+ * devices for multichannel to work. */
+ if (name == "@" || name.substr(0, 2) == "@:")
+ {
+ std::string openName = name.substr(1);
+
+ /* These device names allow alsa-lib to perform special routing if needed
+ * for multichannel to work with the audio hardware.
+ * Fall through in switch() so that devices with more channels are
+ * added as fallback. */
+ switch (channels)
+ {
+ case 3:
+ case 4:
+ if (TryDeviceWithParams("surround40" + openName, params, pcmp, lconf))
+ return true;
+ [[fallthrough]];
+ case 5:
+ case 6:
+ if (TryDeviceWithParams("surround51" + openName, params, pcmp, lconf))
+ return true;
+ [[fallthrough]];
+ case 7:
+ case 8:
+ if (TryDeviceWithParams("surround71" + openName, params, pcmp, lconf))
+ return true;
+ }
+
+ /* Try "sysdefault" and "default" (they provide dmix if needed, and route
+ * audio to all extra channels on subdeviced cards),
+ * unless the selected devices is not DEV=0 of the card, in which case
+ * "sysdefault" and "default" would point to another device.
+ * "sysdefault" is a newish device name that won't be overwritten in case
+ * system configuration redefines "default". "default" is still tried
+ * because "sysdefault" is rather new. */
+ size_t devPos = openName.find(",DEV=");
+ if (devPos == std::string::npos || (devPos + 5 < openName.size() && openName[devPos+5] == '0'))
+ {
+ /* "sysdefault" and "default" do not have "DEV=0", drop it */
+ std::string nameWithoutDev = openName;
+ if (devPos != std::string::npos)
+ nameWithoutDev.erase(nameWithoutDev.begin() + devPos, nameWithoutDev.begin() + devPos + 6);
+
+ if (TryDeviceWithParams("sysdefault" + nameWithoutDev, params, pcmp, lconf)
+ || TryDeviceWithParams("default" + nameWithoutDev, params, pcmp, lconf))
+ return true;
+ }
+
+ /* Try "front" (no dmix, no audio in other channels on subdeviced cards) */
+ if (TryDeviceWithParams("front" + openName, params, pcmp, lconf))
+ return true;
+
+ }
+ else
+ {
+ /* Non-surroundXX device, just add it */
+ if (TryDeviceWithParams(name, params, pcmp, lconf))
+ return true;
+ }
+
+ return false;
+}
+
+void CAESinkALSA::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+#if defined(HAVE_LIBUDEV)
+ const auto deviceMonitor = CServiceBroker::GetPlatform().GetService<CALSADeviceMonitor>();
+ deviceMonitor->Start();
+#endif
+
+ /* ensure that ALSA has been initialized */
+ snd_lib_error_set_handler(sndLibErrorHandler);
+ if(!snd_config || force)
+ {
+ if(force)
+ snd_config_update_free_global();
+
+ snd_config_update();
+ }
+
+ std::unique_ptr<snd_config_t, SndConfigDeleter> config = SndConfigCopy(snd_config);
+
+#if !defined(HAVE_X11)
+ const auto controlMonitor = CServiceBroker::GetPlatform().GetService<CALSAHControlMonitor>();
+ controlMonitor->Clear();
+#endif
+
+ /* Always enumerate the default device.
+ * Note: If "default" is a stereo device, EnumerateDevice()
+ * will automatically add "@" instead to enable surroundXX mangling.
+ * We don't want to do that if "default" can handle multichannel
+ * itself (e.g. in case of a pulseaudio server). */
+ EnumerateDevice(list, "default", "", config.get());
+
+ void **hints;
+
+ if (snd_device_name_hint(-1, "pcm", &hints) < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA - Unable to get a list of devices");
+ return;
+ }
+
+ std::string defaultDescription;
+
+ for (void** hint = hints; *hint != NULL; ++hint)
+ {
+ char *io = snd_device_name_get_hint(*hint, "IOID");
+ char *name = snd_device_name_get_hint(*hint, "NAME");
+ char *desc = snd_device_name_get_hint(*hint, "DESC");
+ if ((!io || strcmp(io, "Output") == 0) && name
+ && strcmp(name, "null") != 0)
+ {
+ std::string baseName = std::string(name);
+ baseName = baseName.substr(0, baseName.find(':'));
+
+ if (strcmp(name, "default") == 0)
+ {
+ /* added already, but lets get the description if we have one */
+ if (desc)
+ defaultDescription = desc;
+ }
+ else if (baseName == "front")
+ {
+ /* Enumerate using the surroundXX mangling */
+ /* do not enumerate basic "front", it is already handled
+ * by the default "@" entry added in the very beginning */
+ if (strcmp(name, "front") != 0)
+ EnumerateDevice(list, std::string("@") + (name + 5), desc ? desc : name, config.get());
+ }
+
+ /* Do not enumerate "default", it is already enumerated above. */
+
+ /* Do not enumerate the surroundXX devices, those are always accompanied
+ * with a "front" device and it is handled above as "@". The below
+ * devices plus sysdefault will be automatically used if available
+ * for a "@" device.
+ * sysdefault devices are enumerated as not all cards have front/surround
+ * devices. For cards with front/surround devices the sysdefault
+ * entry will be removed in a second pass after enumeration.
+ */
+
+ /* Ubuntu has patched their alsa-lib so that "defaults.namehint.extended"
+ * defaults to "on" instead of upstream "off", causing lots of unwanted
+ * extra devices (many of which are not actually routed properly) to be
+ * found by the enumeration process. Skip them as well ("hw", "dmix",
+ * "plughw", "dsnoop"). */
+
+ else if (baseName != "default"
+ && baseName != "surround40"
+ && baseName != "surround41"
+ && baseName != "surround50"
+ && baseName != "surround51"
+ && baseName != "surround71"
+ && baseName != "hw"
+ && baseName != "dmix"
+ && baseName != "plughw"
+ && baseName != "dsnoop")
+ {
+ EnumerateDevice(list, name, desc ? desc : name, config.get());
+ }
+ }
+ free(io);
+ free(name);
+ free(desc);
+ }
+ snd_device_name_free_hint(hints);
+
+#if !defined(HAVE_X11)
+ controlMonitor->Start();
+#endif
+
+ /* set the displayname for default device */
+ if (!list.empty() && list[0].m_deviceName == "default")
+ {
+ /* If we have one from a hint (DESC), use it */
+ if (!defaultDescription.empty())
+ list[0].m_displayName = defaultDescription;
+ /* Otherwise use the discovered name or (unlikely) "Default" */
+ else if (list[0].m_displayName.empty())
+ list[0].m_displayName = "Default";
+ }
+
+ /* cards with surround entries where sysdefault should be removed */
+ std::set<std::string> cardsWithSurround;
+
+ for (AEDeviceInfoList::iterator it1 = list.begin(); it1 != list.end(); ++it1)
+ {
+ std::string baseName = it1->m_deviceName.substr(0, it1->m_deviceName.find(':'));
+ std::string card = GetParamFromName(it1->m_deviceName, "CARD");
+ if (baseName == "@" && !card.empty())
+ cardsWithSurround.insert(card);
+ }
+
+ if (!cardsWithSurround.empty())
+ {
+ /* remove sysdefault entries where we already have a surround entry */
+ AEDeviceInfoList::iterator iter = list.begin();
+ while (iter != list.end())
+ {
+ std::string baseName = iter->m_deviceName.substr(0, iter->m_deviceName.find(':'));
+ std::string card = GetParamFromName(iter->m_deviceName, "CARD");
+ if (baseName == "sysdefault" && cardsWithSurround.find(card) != cardsWithSurround.end())
+ iter = list.erase(iter);
+ else
+ ++iter;
+ }
+ }
+
+ /* lets check uniqueness, we may need to append DEV or CARD to DisplayName */
+ /* If even a single device of card/dev X clashes with Y, add suffixes to
+ * all devices of both them, for clarity. */
+
+ /* clashing card names, e.g. "NVidia", "NVidia_2" */
+ std::set<std::string> cardsToAppend;
+
+ /* clashing basename + cardname combinations, e.g. ("hdmi","Nvidia") */
+ std::set<std::pair<std::string, std::string> > devsToAppend;
+
+ for (AEDeviceInfoList::iterator it1 = list.begin(); it1 != list.end(); ++it1)
+ {
+ for (AEDeviceInfoList::iterator it2 = it1+1; it2 != list.end(); ++it2)
+ {
+ if (it1->m_displayName == it2->m_displayName
+ && it1->m_displayNameExtra == it2->m_displayNameExtra)
+ {
+ /* something needs to be done */
+ std::string cardString1 = GetParamFromName(it1->m_deviceName, "CARD");
+ std::string cardString2 = GetParamFromName(it2->m_deviceName, "CARD");
+
+ if (cardString1 != cardString2)
+ {
+ /* card name differs, add identifiers to all devices */
+ cardsToAppend.insert(cardString1);
+ cardsToAppend.insert(cardString2);
+ continue;
+ }
+
+ std::string devString1 = GetParamFromName(it1->m_deviceName, "DEV");
+ std::string devString2 = GetParamFromName(it2->m_deviceName, "DEV");
+
+ if (devString1 != devString2)
+ {
+ /* device number differs, add identifiers to all such devices */
+ devsToAppend.insert(std::make_pair(it1->m_deviceName.substr(0, it1->m_deviceName.find(':')), cardString1));
+ devsToAppend.insert(std::make_pair(it2->m_deviceName.substr(0, it2->m_deviceName.find(':')), cardString2));
+ continue;
+ }
+
+ /* if we got here, the configuration is really weird, just append the whole device string */
+ it1->m_displayName += " (" + it1->m_deviceName + ")";
+ it2->m_displayName += " (" + it2->m_deviceName + ")";
+ }
+ }
+ }
+
+ for (std::set<std::string>::iterator it = cardsToAppend.begin();
+ it != cardsToAppend.end(); ++it)
+ {
+ for (AEDeviceInfoList::iterator itl = list.begin(); itl != list.end(); ++itl)
+ {
+ std::string cardString = GetParamFromName(itl->m_deviceName, "CARD");
+ if (cardString == *it)
+ /* "HDA NVidia (NVidia)", "HDA NVidia (NVidia_2)", ... */
+ itl->m_displayName += " (" + cardString + ")";
+ }
+ }
+
+ for (std::set<std::pair<std::string, std::string> >::iterator it = devsToAppend.begin();
+ it != devsToAppend.end(); ++it)
+ {
+ for (AEDeviceInfoList::iterator itl = list.begin(); itl != list.end(); ++itl)
+ {
+ std::string baseName = itl->m_deviceName.substr(0, itl->m_deviceName.find(':'));
+ std::string cardString = GetParamFromName(itl->m_deviceName, "CARD");
+ if (baseName == it->first && cardString == it->second)
+ {
+ std::string devString = GetParamFromName(itl->m_deviceName, "DEV");
+ /* "HDMI #0", "HDMI #1" ... */
+ itl->m_displayNameExtra += " #" + devString;
+ }
+ }
+ }
+}
+
+AEDeviceType CAESinkALSA::AEDeviceTypeFromName(const std::string &name)
+{
+ if (name.substr(0, 4) == "hdmi")
+ return AE_DEVTYPE_HDMI;
+ else if (name.substr(0, 6) == "iec958" || name.substr(0, 5) == "spdif")
+ return AE_DEVTYPE_IEC958;
+
+ return AE_DEVTYPE_PCM;
+}
+
+std::string CAESinkALSA::GetParamFromName(const std::string &name, const std::string &param)
+{
+ /* name = "hdmi:CARD=x,DEV=y" param = "CARD" => return "x" */
+ size_t parPos = name.find(param + '=');
+ if (parPos != std::string::npos)
+ {
+ parPos += param.size() + 1;
+ return name.substr(parPos, name.find_first_of(",'\"", parPos)-parPos);
+ }
+
+ return "";
+}
+
+void CAESinkALSA::EnumerateDevice(AEDeviceInfoList &list, const std::string &device, const std::string &description, snd_config_t *config)
+{
+ snd_pcm_t *pcmhandle = NULL;
+ if (!OpenPCMDevice(device, "", ALSA_MAX_CHANNELS, &pcmhandle, config))
+ return;
+
+ snd_pcm_info_t *pcminfo;
+ snd_pcm_info_alloca(&pcminfo);
+ memset(pcminfo, 0, snd_pcm_info_sizeof());
+
+ int err = snd_pcm_info(pcmhandle, pcminfo);
+ if (err < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA - Unable to get pcm_info for \"{}\"", device);
+ snd_pcm_close(pcmhandle);
+ }
+
+ int cardNr = snd_pcm_info_get_card(pcminfo);
+
+ CAEDeviceInfo info;
+ info.m_deviceName = device;
+ info.m_deviceType = AEDeviceTypeFromName(device);
+
+ if (cardNr >= 0)
+ {
+ /* "HDA NVidia", "HDA Intel", "HDA ATI HDMI", "SB Live! 24-bit External", ... */
+ char *cardName;
+ if (snd_card_get_name(cardNr, &cardName) == 0)
+ {
+ info.m_displayName = cardName;
+ free(cardName);
+ }
+
+ if (info.m_deviceType == AE_DEVTYPE_HDMI && info.m_displayName.size() > 5 &&
+ info.m_displayName.substr(info.m_displayName.size()-5) == " HDMI")
+ {
+ /* We already know this is HDMI, strip it */
+ info.m_displayName.erase(info.m_displayName.size()-5);
+ }
+
+ /* "CONEXANT Analog", "USB Audio", "HDMI 0", "ALC889 Digital" ... */
+ std::string pcminfoName = snd_pcm_info_get_name(pcminfo);
+
+ /*
+ * Filter "USB Audio", in those cases snd_card_get_name() is more
+ * meaningful already
+ */
+ if (pcminfoName != "USB Audio")
+ info.m_displayNameExtra = pcminfoName;
+
+ if (info.m_deviceType == AE_DEVTYPE_HDMI)
+ {
+ /* replace, this was likely "HDMI 0" */
+ info.m_displayNameExtra = "HDMI";
+
+ int dev = snd_pcm_info_get_device(pcminfo);
+
+ if (dev >= 0)
+ {
+ /* lets see if we can get ELD info */
+
+ snd_ctl_t *ctlhandle;
+ std::stringstream sstr;
+ sstr << "hw:" << cardNr;
+ std::string strHwName = sstr.str();
+
+ if (snd_ctl_open_lconf(&ctlhandle, strHwName.c_str(), 0, config) == 0)
+ {
+ snd_hctl_t *hctl;
+ if (snd_hctl_open_ctl(&hctl, ctlhandle) == 0)
+ {
+ snd_hctl_load(hctl);
+ bool badHDMI = false;
+
+#if !defined(HAVE_X11)
+ /* add ELD to monitoring */
+ const auto controlMonitor =
+ CServiceBroker::GetPlatform().GetService<CALSAHControlMonitor>();
+ controlMonitor->Add(strHwName, SND_CTL_ELEM_IFACE_PCM, dev, "ELD");
+#endif
+
+ if (!GetELD(hctl, dev, info, badHDMI))
+ CLog::Log(LOGDEBUG,
+ "CAESinkALSA - Unable to obtain ELD information for device \"{}\" (not "
+ "supported by device, or kernel older than 3.2)",
+ device);
+
+ /* snd_hctl_close also closes ctlhandle */
+ snd_hctl_close(hctl);
+
+ if (badHDMI)
+ {
+ /*
+ * Warn about disconnected devices, but keep them enabled
+ * Detection can go wrong on Intel, Nvidia and on all
+ * AMD (fglrx) hardware, so it is not safe to close those
+ * handles
+ */
+ CLog::Log(LOGDEBUG,
+ "CAESinkALSA - HDMI device \"{}\" may be unconnected (no ELD data)",
+ device);
+ }
+ }
+ else
+ {
+ snd_ctl_close(ctlhandle);
+ }
+ }
+ }
+ }
+ else if (info.m_deviceType == AE_DEVTYPE_IEC958)
+ {
+ /* append instead of replace, pcminfoName is useful for S/PDIF */
+ if (!info.m_displayNameExtra.empty())
+ info.m_displayNameExtra += ' ';
+ info.m_displayNameExtra += "S/PDIF";
+
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ info.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ else if (info.m_displayNameExtra.empty())
+ {
+ /* for USB audio, it gets a bit confusing as there is
+ * - "SB Live! 24-bit External"
+ * - "SB Live! 24-bit External, S/PDIF"
+ * so add "Analog" qualifier to the first one */
+ info.m_displayNameExtra = "Analog";
+ }
+
+ /* "default" is a device that will be used for all inputs, while
+ * "@" will be mangled to front/default/surroundXX as necessary */
+ if (device == "@" || device == "default")
+ {
+ /* Make it "Default (whatever)" */
+ info.m_displayName = "Default (" + info.m_displayName + (info.m_displayNameExtra.empty() ? "" : " " + info.m_displayNameExtra + ")");
+ info.m_displayNameExtra = "";
+ }
+
+ }
+ else
+ {
+ /* virtual devices: "default", "pulse", ... */
+ /* description can be e.g. "PulseAudio Sound Server" - for hw devices it is
+ * normally uninteresting, like "HDMI Audio Output" or "Default Audio Device",
+ * so we only use it for virtual devices that have no better display name */
+ info.m_displayName = description;
+ }
+
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+ memset(hwparams, 0, snd_pcm_hw_params_sizeof());
+
+ /* ensure we can get a playback configuration for the device */
+ if (snd_pcm_hw_params_any(pcmhandle, hwparams) < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA - No playback configurations available for device \"{}\"",
+ device);
+ snd_pcm_close(pcmhandle);
+ return;
+ }
+
+ /* detect the available sample rates */
+ for (unsigned int *rate = ALSASampleRateList; *rate != 0; ++rate)
+ if (snd_pcm_hw_params_test_rate(pcmhandle, hwparams, *rate, 0) >= 0)
+ info.m_sampleRates.push_back(*rate);
+
+ /* detect the channels available */
+ int channels = 0;
+ for (int i = ALSA_MAX_CHANNELS; i >= 1; --i)
+ {
+ /* Reopen the device if needed on the special "surroundXX" cases */
+ if (info.m_deviceType == AE_DEVTYPE_PCM && (i == 8 || i == 6 || i == 4))
+ OpenPCMDevice(device, "", i, &pcmhandle, config);
+
+ if (snd_pcm_hw_params_test_channels(pcmhandle, hwparams, i) >= 0)
+ {
+ channels = i;
+ break;
+ }
+ }
+
+ if (device == "default" && channels == 2)
+ {
+ /* This looks like the ALSA standard default stereo dmix device, we
+ * probably want to use "@" instead to get surroundXX. */
+ snd_pcm_close(pcmhandle);
+ EnumerateDevice(list, "@", description, config);
+ return;
+ }
+
+ CAEChannelInfo alsaChannels;
+ snd_pcm_chmap_query_t** alsaMaps = snd_pcm_query_chmaps(pcmhandle);
+ bool useEldChannels = (info.m_channels.Count() > 0);
+ if (alsaMaps)
+ {
+ int i = 0;
+ for (snd_pcm_chmap_query_t* alsaMap = alsaMaps[i++];
+ alsaMap; alsaMap = alsaMaps[i++])
+ {
+ CAEChannelInfo AEmap = ALSAchmapToAEChannelMap(&alsaMap->map);
+ alsaChannels.AddMissingChannels(AEmap);
+ if (!useEldChannels)
+ info.m_channels.AddMissingChannels(AEmap);
+ }
+ snd_pcm_free_chmaps(alsaMaps);
+ }
+ else
+ {
+ for (int i = 0; i < channels; ++i)
+ {
+ if (!info.m_channels.HasChannel(LegacyALSAChannelMap[i]))
+ info.m_channels += LegacyALSAChannelMap[i];
+ alsaChannels += LegacyALSAChannelMap[i];
+ }
+ }
+
+ /* remove the channels from m_channels that we cant use */
+ info.m_channels.ResolveChannels(alsaChannels);
+
+ /* detect the PCM sample formats that are available */
+ for (enum AEDataFormat i = AE_FMT_MAX; i > AE_FMT_INVALID; i = (enum AEDataFormat)((int)i - 1))
+ {
+ if (i == AE_FMT_RAW || i == AE_FMT_MAX)
+ continue;
+ snd_pcm_format_t fmt = AEFormatToALSAFormat(i);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ continue;
+
+ if (snd_pcm_hw_params_test_format(pcmhandle, hwparams, fmt) >= 0)
+ info.m_dataFormats.push_back(i);
+ }
+
+ if (info.m_deviceType == AE_DEVTYPE_HDMI)
+ {
+ // we don't trust ELD information and push back our supported formats explicitly
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+
+ // indicate that we can do AE_FMT_RAW
+ info.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+
+ snd_pcm_close(pcmhandle);
+ info.m_wantsIECPassthrough = true;
+ list.push_back(info);
+}
+
+bool CAESinkALSA::GetELD(snd_hctl_t *hctl, int device, CAEDeviceInfo& info, bool& badHDMI)
+{
+ badHDMI = false;
+
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_info_t *einfo;
+ snd_ctl_elem_value_t *control;
+ snd_hctl_elem_t *elem;
+
+ snd_ctl_elem_id_alloca(&id);
+ memset(id, 0, snd_ctl_elem_id_sizeof());
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
+ snd_ctl_elem_id_set_name (id, "ELD" );
+ snd_ctl_elem_id_set_device (id, device);
+ elem = snd_hctl_find_elem(hctl, id);
+ if (!elem)
+ return false;
+
+ snd_ctl_elem_info_alloca(&einfo);
+ memset(einfo, 0, snd_ctl_elem_info_sizeof());
+
+ if (snd_hctl_elem_info(elem, einfo) < 0)
+ return false;
+
+ if (!snd_ctl_elem_info_is_readable(einfo))
+ return false;
+
+ if (snd_ctl_elem_info_get_type(einfo) != SND_CTL_ELEM_TYPE_BYTES)
+ return false;
+
+ snd_ctl_elem_value_alloca(&control);
+ memset(control, 0, snd_ctl_elem_value_sizeof());
+
+ if (snd_hctl_elem_read(elem, control) < 0)
+ return false;
+
+ int dataLength = snd_ctl_elem_info_get_count(einfo);
+ /* if there is no ELD data, then its a bad HDMI device, either nothing attached OR an invalid nVidia HDMI device
+ * OR the driver doesn't properly support ELD (notably ATI/AMD, 2012-05) */
+ if (!dataLength)
+ badHDMI = true;
+ else
+ CAEELDParser::Parse(
+ (const uint8_t*)snd_ctl_elem_value_get_bytes(control),
+ dataLength,
+ info
+ );
+
+ info.m_deviceType = AE_DEVTYPE_HDMI;
+ return true;
+}
+
+void CAESinkALSA::sndLibErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...)
+{
+ if (!CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
+ return;
+
+ va_list arg;
+ va_start(arg, fmt);
+ char *errorStr;
+ if (vasprintf(&errorStr, fmt, arg) >= 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA - ALSA: {}:{}:({}) {}{}{}", file, line, function, errorStr,
+ err ? ": " : "", err ? snd_strerror(err) : "");
+ free(errorStr);
+ }
+ va_end(arg);
+}
+
+void CAESinkALSA::Cleanup()
+{
+#if HAVE_LIBUDEV
+ const auto deviceMonitor = CServiceBroker::GetPlatform().GetService<CALSADeviceMonitor>();
+ deviceMonitor->Stop();
+#endif
+
+#if !defined(HAVE_X11)
+ const auto controlMonitor = CServiceBroker::GetPlatform().GetService<CALSAHControlMonitor>();
+ controlMonitor->Clear();
+#endif
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h
new file mode 100644
index 0000000..3ab9bd3
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "threads/CriticalSection.h"
+
+#include <stdint.h>
+
+#include <alsa/asoundlib.h>
+
+#define AE_MIN_PERIODSIZE 256
+
+class CAESinkALSA : public IAESink
+{
+public:
+ const char *GetName() override { return "ALSA"; }
+
+ CAESinkALSA();
+ ~CAESinkALSA() override;
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
+ static void Cleanup();
+
+ bool Initialize(AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+
+ virtual void Stop ();
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+private:
+ CAEChannelInfo GetChannelLayoutRaw(const AEAudioFormat& format);
+ CAEChannelInfo GetChannelLayoutLegacy(const AEAudioFormat& format, unsigned int minChannels, unsigned int maxChannels);
+ CAEChannelInfo GetChannelLayout(const AEAudioFormat& format, unsigned int channels);
+
+ static AEChannel ALSAChannelToAEChannel(unsigned int alsaChannel);
+ static unsigned int AEChannelToALSAChannel(AEChannel aeChannel);
+ static CAEChannelInfo ALSAchmapToAEChannelMap(snd_pcm_chmap_t* alsaMap);
+ static snd_pcm_chmap_t* AEChannelMapToALSAchmap(const CAEChannelInfo& info);
+ static snd_pcm_chmap_t* CopyALSAchmap(snd_pcm_chmap_t* alsaMap);
+ static std::string ALSAchmapToString(snd_pcm_chmap_t* alsaMap);
+ static CAEChannelInfo GetAlternateLayoutForm(const CAEChannelInfo& info);
+ snd_pcm_chmap_t* SelectALSAChannelMap(const CAEChannelInfo& info);
+
+ void GetAESParams(const AEAudioFormat& format, std::string& params);
+ void HandleError(const char* name, int err);
+
+ std::string m_initDevice;
+ AEAudioFormat m_initFormat;
+ AEAudioFormat m_format;
+ unsigned int m_bufferSize = 0;
+ double m_formatSampleRateMul = 0.0;
+ bool m_passthrough = false;
+ std::string m_device;
+ snd_pcm_t *m_pcm;
+ int m_timeout = 0;
+ // support fragmentation, e.g. looping in the sink to get a certain amount of data onto the device
+ bool m_fragmented = false;
+ unsigned int m_originalPeriodSize = AE_MIN_PERIODSIZE;
+
+ struct ALSAConfig
+ {
+ unsigned int sampleRate;
+ unsigned int periodSize;
+ unsigned int frameSize;
+ unsigned int channels;
+ AEDataFormat format;
+ };
+
+ static snd_pcm_format_t AEFormatToALSAFormat(const enum AEDataFormat format);
+
+ bool InitializeHW(const ALSAConfig &inconfig, ALSAConfig &outconfig);
+ bool InitializeSW(const ALSAConfig &inconfig);
+
+ static void AppendParams(std::string &device, const std::string &params);
+ static bool TryDevice(const std::string &name, snd_pcm_t **pcmp, snd_config_t *lconf);
+ static bool TryDeviceWithParams(const std::string &name, const std::string &params, snd_pcm_t **pcmp, snd_config_t *lconf);
+ static bool OpenPCMDevice(const std::string &name, const std::string &params, int channels, snd_pcm_t **pcmp, snd_config_t *lconf);
+
+ static AEDeviceType AEDeviceTypeFromName(const std::string &name);
+ static std::string GetParamFromName(const std::string &name, const std::string &param);
+ static void EnumerateDevice(AEDeviceInfoList &list, const std::string &device, const std::string &description, snd_config_t *config);
+ static bool SoundDeviceExists(const std::string& device);
+ static bool GetELD(snd_hctl_t *hctl, int device, CAEDeviceInfo& info, bool& badHDMI);
+
+ static void sndLibErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...);
+};
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
new file mode 100644
index 0000000..a7fb55c
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkAUDIOTRACK.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "settings/Settings.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+#include <androidjni/AudioFormat.h>
+#include <androidjni/AudioManager.h>
+#include <androidjni/AudioTrack.h>
+#include <androidjni/Build.h>
+#include <unistd.h>
+
+// This is an alternative to the linear weighted delay smoothing
+// advantages: only one history value needs to be stored
+// in tests the linear weighted average smoother yield better results
+//#define AT_USE_EXPONENTIAL_AVERAGING 1
+
+using namespace jni;
+
+using namespace std::chrono_literals;
+
+// those are empirical values while the HD buffer
+// is the max TrueHD package
+const unsigned int MAX_RAW_AUDIO_BUFFER_HD = 61440;
+const unsigned int MAX_RAW_AUDIO_BUFFER = 16384;
+const unsigned int MOVING_AVERAGE_MAX_MEMBERS = 3;
+const uint64_t UINT64_LOWER_BYTES = 0x00000000FFFFFFFF;
+const uint64_t UINT64_UPPER_BYTES = 0xFFFFFFFF00000000;
+
+static const AEChannel KnownChannels[] = {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE,
+ AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR,
+ AE_CH_BC, AE_CH_BLOC, AE_CH_BROC, AE_CH_NULL};
+
+static int AEStreamFormatToATFormat(const CAEStreamInfo::DataType& dt)
+{
+ switch (dt)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ return CJNIAudioFormat::ENCODING_AC3;
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ return CJNIAudioFormat::ENCODING_DTS;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ return CJNIAudioFormat::ENCODING_DTS_HD;
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ return CJNIAudioFormat::ENCODING_E_AC3;
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ return CJNIAudioFormat::ENCODING_DOLBY_TRUEHD;
+ default:
+ return CJNIAudioFormat::ENCODING_PCM_16BIT;
+ }
+}
+
+static AEChannel AUDIOTRACKChannelToAEChannel(int atChannel)
+{
+ AEChannel aeChannel;
+
+ /* cannot use switch since CJNIAudioFormat is populated at runtime */
+
+ if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT) aeChannel = AE_CH_FL;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT) aeChannel = AE_CH_FR;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER) aeChannel = AE_CH_FC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY) aeChannel = AE_CH_LFE;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT) aeChannel = AE_CH_BL;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT) aeChannel = AE_CH_BR;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_SIDE_LEFT) aeChannel = AE_CH_SL;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_SIDE_RIGHT) aeChannel = AE_CH_SR;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER) aeChannel = AE_CH_FLOC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) aeChannel = AE_CH_FROC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER) aeChannel = AE_CH_BC;
+ else aeChannel = AE_CH_UNKNOWN1;
+
+ return aeChannel;
+}
+
+static int AEChannelToAUDIOTRACKChannel(AEChannel aeChannel)
+{
+ int atChannel;
+ switch (aeChannel)
+ {
+ case AE_CH_FL: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT; break;
+ case AE_CH_FR: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT; break;
+ case AE_CH_FC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER; break;
+ case AE_CH_LFE: atChannel = CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY; break;
+ case AE_CH_BL: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT; break;
+ case AE_CH_BR: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT; break;
+ case AE_CH_SL: atChannel = CJNIAudioFormat::CHANNEL_OUT_SIDE_LEFT; break;
+ case AE_CH_SR: atChannel = CJNIAudioFormat::CHANNEL_OUT_SIDE_RIGHT; break;
+ case AE_CH_BC: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER; break;
+ case AE_CH_FLOC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER; break;
+ case AE_CH_FROC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER; break;
+ default: atChannel = CJNIAudioFormat::CHANNEL_INVALID; break;
+ }
+ return atChannel;
+}
+
+static CAEChannelInfo AUDIOTRACKChannelMaskToAEChannelMap(int atMask)
+{
+ CAEChannelInfo info;
+
+ int mask = 0x1;
+ for (unsigned int i = 0; i < sizeof(int32_t) * 8; i++)
+ {
+ if (atMask & mask)
+ info += AUDIOTRACKChannelToAEChannel(mask);
+ mask <<= 1;
+ }
+
+ return info;
+}
+
+static int AEChannelMapToAUDIOTRACKChannelMask(CAEChannelInfo info)
+{
+ info.ResolveChannels(CAEChannelInfo(KnownChannels));
+
+ // Detect layouts with 6 channels including one LFE channel
+ // We currently support the following layouts:
+ // 5.1 FL+FR+FC+LFE+BL+BR
+ // 5.1(side) FL+FR+FC+LFE+SL+SR
+ // According to CEA-861-D only RR and RL are defined
+ // Therefore we let Android decide about the 5.1 mapping
+ // For 8 channel layouts including one LFE channel
+ // we leave the same decision to Android
+ if (info.Count() == 6 && info.HasChannel(AE_CH_LFE))
+ return CJNIAudioFormat::CHANNEL_OUT_5POINT1;
+
+ if (info.Count() == 8 && info.HasChannel(AE_CH_LFE))
+ return CJNIAudioFormat::CHANNEL_OUT_7POINT1_SURROUND;
+
+ int atMask = 0;
+
+ for (unsigned int i = 0; i < info.Count(); i++)
+ atMask |= AEChannelToAUDIOTRACKChannel(info[i]);
+
+ return atMask;
+}
+
+jni::CJNIAudioTrack *CAESinkAUDIOTRACK::CreateAudioTrack(int stream, int sampleRate, int channelMask, int encoding, int bufferSize)
+{
+ jni::CJNIAudioTrack *jniAt = NULL;
+
+ try
+ {
+ CJNIAudioAttributesBuilder attrBuilder;
+ attrBuilder.setUsage(CJNIAudioAttributes::USAGE_MEDIA);
+ attrBuilder.setContentType(CJNIAudioAttributes::CONTENT_TYPE_MUSIC);
+
+ CJNIAudioFormatBuilder fmtBuilder;
+ fmtBuilder.setChannelMask(channelMask);
+ fmtBuilder.setEncoding(encoding);
+ fmtBuilder.setSampleRate(sampleRate);
+
+ jniAt = new CJNIAudioTrack(attrBuilder.build(),
+ fmtBuilder.build(),
+ bufferSize,
+ CJNIAudioTrack::MODE_STREAM,
+ CJNIAudioManager::AUDIO_SESSION_ID_GENERATE);
+ }
+ catch (const std::invalid_argument& e)
+ {
+ CLog::Log(LOGINFO, "AESinkAUDIOTRACK - AudioTrack creation (channelMask {:#08x}): {}",
+ channelMask, e.what());
+ }
+
+ return jniAt;
+}
+
+int CAESinkAUDIOTRACK::AudioTrackWrite(char* audioData, int offsetInBytes, int sizeInBytes)
+{
+ int written = 0;
+ if (m_jniAudioFormat == CJNIAudioFormat::ENCODING_PCM_FLOAT)
+ {
+ if (m_floatbuf.size() != (sizeInBytes - offsetInBytes) / sizeof(float))
+ m_floatbuf.resize((sizeInBytes - offsetInBytes) / sizeof(float));
+ memcpy(m_floatbuf.data(), audioData + offsetInBytes, sizeInBytes - offsetInBytes);
+ written = m_at_jni->write(m_floatbuf, 0, (sizeInBytes - offsetInBytes) / sizeof(float), CJNIAudioTrack::WRITE_BLOCKING);
+ written *= sizeof(float);
+ }
+ else if (m_jniAudioFormat == CJNIAudioFormat::ENCODING_IEC61937)
+ {
+ if (m_shortbuf.size() != (sizeInBytes - offsetInBytes) / sizeof(int16_t))
+ m_shortbuf.resize((sizeInBytes - offsetInBytes) / sizeof(int16_t));
+ memcpy(m_shortbuf.data(), audioData + offsetInBytes, sizeInBytes - offsetInBytes);
+ if (CJNIBase::GetSDKVersion() >= 23)
+ written = m_at_jni->write(m_shortbuf, 0, (sizeInBytes - offsetInBytes) / sizeof(int16_t), CJNIAudioTrack::WRITE_BLOCKING);
+ else
+ written = m_at_jni->write(m_shortbuf, 0, (sizeInBytes - offsetInBytes) / sizeof(int16_t));
+ written *= sizeof(uint16_t);
+ }
+ else
+ {
+ if (static_cast<int>(m_charbuf.size()) != (sizeInBytes - offsetInBytes))
+ m_charbuf.resize(sizeInBytes - offsetInBytes);
+ memcpy(m_charbuf.data(), audioData + offsetInBytes, sizeInBytes - offsetInBytes);
+ if (CJNIBase::GetSDKVersion() >= 23)
+ written = m_at_jni->write(m_charbuf, 0, sizeInBytes - offsetInBytes, CJNIAudioTrack::WRITE_BLOCKING);
+ else
+ written = m_at_jni->write(m_charbuf, 0, sizeInBytes - offsetInBytes);
+ }
+
+ return written;
+}
+
+int CAESinkAUDIOTRACK::AudioTrackWrite(char* audioData, int sizeInBytes, int64_t timestamp)
+{
+ int written = 0;
+ std::vector<char> buf;
+ buf.reserve(sizeInBytes);
+ memcpy(buf.data(), audioData, sizeInBytes);
+
+ CJNIByteBuffer bytebuf = CJNIByteBuffer::wrap(buf);
+ written = m_at_jni->write(bytebuf.get_raw(), sizeInBytes, CJNIAudioTrack::WRITE_BLOCKING, timestamp);
+
+ return written;
+}
+
+CAEDeviceInfo CAESinkAUDIOTRACK::m_info;
+CAEDeviceInfo CAESinkAUDIOTRACK::m_info_iec;
+CAEDeviceInfo CAESinkAUDIOTRACK::m_info_raw;
+bool CAESinkAUDIOTRACK::m_hasIEC = false;
+std::set<unsigned int> CAESinkAUDIOTRACK::m_sink_sampleRates;
+bool CAESinkAUDIOTRACK::m_sinkSupportsFloat = false;
+bool CAESinkAUDIOTRACK::m_sinkSupportsMultiChannelFloat = false;
+bool CAESinkAUDIOTRACK::m_passthrough_use_eac3 = false;
+
+////////////////////////////////////////////////////////////////////////////////////////////
+CAESinkAUDIOTRACK::CAESinkAUDIOTRACK()
+{
+ m_alignedS16 = NULL;
+ m_sink_frameSize = 0;
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
+ m_audiotrackbuffer_sec = 0.0;
+ m_audiotrackbuffer_sec_orig = 0.0;
+ m_at_jni = NULL;
+ m_duration_written = 0;
+ m_headPos = 0;
+ m_stuckCounter = 0;
+ m_headPosOld = 0;
+ m_timestampPos = 0;
+ m_sink_sampleRate = 0;
+ m_passthrough = false;
+ m_min_buffer_size = 0;
+}
+
+CAESinkAUDIOTRACK::~CAESinkAUDIOTRACK()
+{
+ Deinitialize();
+}
+
+bool CAESinkAUDIOTRACK::VerifySinkConfiguration(int sampleRate,
+ int channelMask,
+ int encoding,
+ bool isRaw)
+{
+ int minBufferSize = CJNIAudioTrack::getMinBufferSize(sampleRate, channelMask, encoding);
+ bool supported = (minBufferSize > 0);
+
+ // make sure to have enough buffer as minimum might not be enough to open
+ if (!isRaw)
+ minBufferSize *= 2;
+
+ if (supported)
+ {
+ jni::CJNIAudioTrack* jniAt = CreateAudioTrack(CJNIAudioManager::STREAM_MUSIC, sampleRate,
+ channelMask, encoding, minBufferSize);
+ supported = (jniAt && jniAt->getState() == CJNIAudioTrack::STATE_INITIALIZED);
+ if (supported)
+ {
+ jniAt->pause();
+ jniAt->flush();
+ }
+
+ if (jniAt)
+ {
+ jniAt->release();
+ delete jniAt;
+ }
+ }
+ CLog::Log(LOGDEBUG, "VerifySinkConfiguration samplerate: {} mask: {} encoding: {} success: {}",
+ sampleRate, channelMask, encoding, supported ? "true" : "false");
+ return supported;
+}
+
+
+bool CAESinkAUDIOTRACK::IsSupported(int sampleRateInHz, int channelConfig, int encoding)
+{
+ int ret = CJNIAudioTrack::getMinBufferSize( sampleRateInHz, channelConfig, encoding);
+ return (ret > 0);
+}
+
+bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
+{
+
+ // try to recover used device
+ if (!m_hasIEC)
+ m_info = m_info_raw;
+ else if (device == "Default" && !m_info.m_wantsIECPassthrough)
+ m_info = m_info_raw;
+ else if (device == "AudioTrack (RAW)")
+ m_info = m_info_raw;
+ else
+ m_info = m_info_iec;
+
+ m_format = format;
+ m_headPos = 0;
+ m_stuckCounter = 0;
+ m_headPosOld = 0;
+ m_timestampPos = 0;
+ m_linearmovingaverage.clear();
+ m_pause_ms = 0.0;
+ CLog::Log(LOGDEBUG,
+ "CAESinkAUDIOTRACK::Initialize requested: sampleRate {}; format: {}; channels: {}",
+ format.m_sampleRate, CAEUtil::DataFormatToStr(format.m_dataFormat),
+ format.m_channelLayout.Count());
+
+ int stream = CJNIAudioManager::STREAM_MUSIC;
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
+
+ // If the device supports EAC3 passthrough, but not basic AC3 patthrough, send it as EAC3 (which is AC3 compatible) instead
+ if (!m_info.m_wantsIECPassthrough)
+ {
+ if ((m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_AC3) && m_passthrough_use_eac3)
+ m_format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ }
+
+ uint32_t distance = UINT32_MAX; // max upper distance, update at least ones to use one of our samplerates
+ for (auto& s : m_sink_sampleRates)
+ {
+ // prefer best match or alternatively something that divides nicely and
+ // is not too far away
+ uint32_t d = std::abs((int)m_format.m_sampleRate - (int)s) + 8 * (s > m_format.m_sampleRate ? (s % m_format.m_sampleRate) : (m_format.m_sampleRate % s));
+ if (d < distance)
+ {
+ m_sink_sampleRate = s;
+ distance = d;
+ CLog::Log(LOGDEBUG, "Updated SampleRate: {} Distance: {}", m_sink_sampleRate, d);
+ }
+ }
+
+ if (m_format.m_dataFormat == AE_FMT_RAW)
+ {
+ m_passthrough = true;
+ m_encoding = AEStreamFormatToATFormat(m_format.m_streamInfo.m_type);
+ m_format.m_channelLayout = AE_CH_LAYOUT_2_0;
+
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA ||
+ m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ m_format.m_channelLayout = AE_CH_LAYOUT_7_1;
+ }
+
+ // EAC3 needs real samplerate not the modulation
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3)
+ m_sink_sampleRate = m_format.m_streamInfo.m_sampleRate;
+
+ if (m_info.m_wantsIECPassthrough)
+ {
+ m_format.m_dataFormat = AE_FMT_S16LE;
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD ||
+ m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA ||
+ m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ m_sink_sampleRate = 192000;
+
+ // new Android N format
+ if (CJNIAudioFormat::ENCODING_IEC61937 != -1)
+ {
+ m_encoding = CJNIAudioFormat::ENCODING_IEC61937;
+ // this will be sent tunneled, therefore the IEC path needs e.g.
+ // 4 * m_format.m_streamInfo.m_sampleRate
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3)
+ m_sink_sampleRate = m_format.m_sampleRate;
+ }
+
+ // we are running on an old android version
+ // that does neither know AC3, DTS or whatever
+ // we will fallback to 16BIT passthrough
+ if (m_encoding == -1)
+ {
+ m_format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ m_format.m_sampleRate = m_sink_sampleRate;
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
+ CLog::Log(LOGDEBUG, "Fallback to PCM passthrough mode - this might not work!");
+ }
+ }
+ }
+ else
+ {
+ m_passthrough = false;
+ m_format.m_sampleRate = m_sink_sampleRate;
+ if (m_sinkSupportsMultiChannelFloat)
+ {
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_FLOAT;
+ m_format.m_dataFormat = AE_FMT_FLOAT;
+ }
+ else if (m_sinkSupportsFloat && m_format.m_channelLayout.Count() == 2)
+ {
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_FLOAT;
+ m_format.m_dataFormat = AE_FMT_FLOAT;
+ }
+ else
+ {
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
+ m_format.m_dataFormat = AE_FMT_S16LE;
+ }
+ }
+
+ int atChannelMask = AEChannelMapToAUDIOTRACKChannelMask(m_format.m_channelLayout);
+ m_format.m_channelLayout = AUDIOTRACKChannelMaskToAEChannelMap(atChannelMask);
+ if (m_encoding == CJNIAudioFormat::ENCODING_IEC61937)
+ {
+ // keep above channel output if we do IEC61937 and got DTSHD or TrueHD by AudioEngine
+ if (m_format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_DTSHD_MA && m_format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ atChannelMask = CJNIAudioFormat::CHANNEL_OUT_STEREO;
+ }
+
+ bool retried = false;
+ while (!m_at_jni)
+ {
+ CLog::Log(LOGINFO, "Trying to open: samplerate: {}, channelMask: {}, encoding: {}",
+ m_sink_sampleRate, atChannelMask, m_encoding);
+ int min_buffer = CJNIAudioTrack::getMinBufferSize(m_sink_sampleRate,
+ atChannelMask,
+ m_encoding);
+
+ if (min_buffer < 0)
+ {
+ CLog::Log(LOGERROR,
+ "Minimum Buffer Size was: {} - disable passthrough (?) your hw does not support it",
+ min_buffer);
+ return false;
+ }
+
+ m_min_buffer_size = (unsigned int) min_buffer;
+ CLog::Log(LOGINFO, "Minimum size we need for stream: {} Bytes", m_min_buffer_size);
+ double rawlength_in_seconds = 0.0;
+ int multiplier = 1;
+ unsigned int ac3FrameSize = 1;
+ if (m_passthrough && !m_info.m_wantsIECPassthrough)
+ {
+ switch (m_format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ m_min_buffer_size = MAX_RAW_AUDIO_BUFFER_HD;
+ m_format.m_frames = m_min_buffer_size;
+ rawlength_in_seconds = 8 * m_format.m_streamInfo.GetDuration() / 1000; // on average
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ // normal frame is max 2012 bytes + 2764 sub frame
+ m_min_buffer_size = 66432; //according to the buffer model of ISO/IEC13818-1
+ m_format.m_frames = m_min_buffer_size;
+ rawlength_in_seconds = 8 * m_format.m_streamInfo.GetDuration() / 1000; // average value
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ // max 2012 bytes
+ // depending on sample rate between 156 ms and 312 ms
+ m_min_buffer_size = 16 * 2012;
+ m_format.m_frames = m_min_buffer_size;
+ rawlength_in_seconds = 16 * m_format.m_streamInfo.GetDuration() / 1000;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ m_min_buffer_size = 8 * 5462;
+ m_format.m_frames = m_min_buffer_size;
+ rawlength_in_seconds = 8 * m_format.m_streamInfo.GetDuration() / 1000;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ ac3FrameSize = m_format.m_streamInfo.m_ac3FrameSize;
+ if (ac3FrameSize == 0)
+ ac3FrameSize = 1536; // fallback if not set, e.g. Transcoding
+ m_min_buffer_size = std::max(m_min_buffer_size * 3, ac3FrameSize * 8);
+ m_format.m_frames = m_min_buffer_size;
+ multiplier = m_min_buffer_size / ac3FrameSize; // int division is wanted
+ rawlength_in_seconds = multiplier * m_format.m_streamInfo.GetDuration() / 1000;
+ break;
+ // EAC3 is currently not supported
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ m_min_buffer_size = 2 * 10752; // least common multiple of 1792 and 1536
+ m_format.m_frames = m_min_buffer_size; // needs testing
+ rawlength_in_seconds = 8 * m_format.m_streamInfo.GetDuration() / 1000;
+ break;
+ default:
+ m_min_buffer_size = MAX_RAW_AUDIO_BUFFER;
+ m_format.m_frames = m_min_buffer_size;
+ rawlength_in_seconds = 0.4;
+ break;
+ }
+
+ CLog::Log(LOGDEBUG, "Opening Passthrough RAW Format: {} Sink SampleRate: {}",
+ CAEUtil::StreamTypeToStr(m_format.m_streamInfo.m_type), m_sink_sampleRate);
+ m_format.m_frameSize = 1;
+ m_sink_frameSize = m_format.m_frameSize;
+ }
+ else
+ {
+ m_format.m_frameSize = m_format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(m_format.m_dataFormat) / 8);
+ m_sink_frameSize = m_format.m_frameSize;
+ // aim at max 200 ms buffer and 50 ms periods but at least two periods of min_buffer
+ // make sure periods are actually not smaller than 32 ms (32, cause 32 * 2 = 64)
+ // but also lower than 64 ms
+ // which is large enough to not cause CPU hogging in case 32 ms periods are used
+ m_audiotrackbuffer_sec =
+ static_cast<double>(m_min_buffer_size) / (m_sink_frameSize * m_sink_sampleRate);
+ // the period calculation starts
+ // after the buffer division to get even division results
+ int c = 1;
+ if (m_audiotrackbuffer_sec > 0.25)
+ {
+ CLog::Log(LOGWARNING,
+ "Audiobuffer is already very large {:f} ms - Reducing to a sensible value",
+ 1000.0 * m_audiotrackbuffer_sec);
+ int buffer_frames = m_sink_sampleRate / 4; // 250 ms
+ m_min_buffer_size = buffer_frames * m_sink_frameSize;
+ c = 5; // 50 ms
+ }
+ // update potential new buffertime
+ m_audiotrackbuffer_sec =
+ static_cast<double>(m_min_buffer_size) / (m_sink_frameSize * m_sink_sampleRate);
+ constexpr double max_time = 0.064;
+ constexpr double min_time = 0.032;
+ constexpr double target_duration = 0.128;
+
+ while (m_audiotrackbuffer_sec < target_duration)
+ {
+ m_min_buffer_size += min_buffer;
+ c++;
+ m_audiotrackbuffer_sec =
+ static_cast<double>(m_min_buffer_size) / (m_sink_frameSize * m_sink_sampleRate);
+ }
+ unsigned int period_size = m_min_buffer_size / c;
+ double period_time =
+ static_cast<double>(period_size) / (m_sink_frameSize * m_sink_sampleRate);
+
+ // This will result in minimum 32 ms
+ while (period_time >= max_time)
+ {
+ period_time /= 2;
+ period_size /= 2;
+ }
+ // If the audio track API gave us very low values increase them
+ // In this case the first loop would not have been run at all
+ while (period_time < min_time)
+ {
+ period_size *= 2;
+ period_time *= 2;
+ }
+ m_format.m_frames = static_cast<int>(period_size / m_format.m_frameSize);
+
+ CLog::Log(LOGINFO,
+ "Audiotrack buffer params are: period time = {:.3f} ms, period size = "
+ "{} bytes, num periods = {}",
+ period_time * 1000, period_size, m_min_buffer_size / period_size);
+ }
+
+ if (m_passthrough && !m_info.m_wantsIECPassthrough)
+ m_audiotrackbuffer_sec = rawlength_in_seconds;
+
+ CLog::Log(LOGINFO,
+ "Created Audiotrackbuffer with playing time of {:f} ms min buffer size: {} bytes",
+ m_audiotrackbuffer_sec * 1000, m_min_buffer_size);
+
+ m_audiotrackbuffer_sec_orig = m_audiotrackbuffer_sec;
+
+ m_jniAudioFormat = m_encoding;
+ m_at_jni = CreateAudioTrack(stream, m_sink_sampleRate, atChannelMask,
+ m_encoding, m_min_buffer_size);
+
+ if (!IsInitialized())
+ {
+ if (!m_passthrough)
+ {
+ if (atChannelMask != CJNIAudioFormat::CHANNEL_OUT_STEREO &&
+ atChannelMask != CJNIAudioFormat::CHANNEL_OUT_5POINT1)
+ {
+ atChannelMask = CJNIAudioFormat::CHANNEL_OUT_5POINT1;
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTRACK - Retrying multichannel playback with a 5.1 layout");
+ continue;
+ }
+ else if (atChannelMask != CJNIAudioFormat::CHANNEL_OUT_STEREO)
+ {
+ atChannelMask = CJNIAudioFormat::CHANNEL_OUT_STEREO;
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTRACK - Retrying with a stereo layout");
+ continue;
+ }
+ }
+ else
+ {
+ if (!retried)
+ {
+ retried = true;
+ CLog::Log(LOGWARNING, "AESinkAUDIOTRACK - Unable to open PT device - will retry once");
+ // Seems that some devices don't properly implement pause + flush, which during seek
+ // might open the device too fast - let's retry
+ usleep(200 * 1000);
+ continue;
+ }
+ }
+ CLog::Log(LOGERROR, "AESinkAUDIOTRACK - Unable to create AudioTrack");
+ Deinitialize();
+ return false;
+ }
+ const char* method = m_passthrough ? (m_info.m_wantsIECPassthrough ? "IEC (PT)" : "RAW (PT)") : "PCM";
+ CLog::Log(LOGINFO,
+ "CAESinkAUDIOTRACK::Initializing with: m_sampleRate: {} format: {} (AE) method: {} "
+ "stream-type: {} min_buffer_size: {} m_frames: {} m_frameSize: {} channels: {}",
+ m_sink_sampleRate, CAEUtil::DataFormatToStr(m_format.m_dataFormat), method,
+ m_passthrough ? CAEUtil::StreamTypeToStr(m_format.m_streamInfo.m_type) : "PCM-STREAM",
+ m_min_buffer_size, m_format.m_frames, m_format.m_frameSize,
+ m_format.m_channelLayout.Count());
+ }
+ format = m_format;
+
+ return true;
+}
+
+void CAESinkAUDIOTRACK::Deinitialize()
+{
+ CLog::Log(LOGDEBUG, "CAESinkAUDIOTRACK::Deinitialize");
+
+ if (!m_at_jni)
+ return;
+
+ if (IsInitialized())
+ {
+ m_at_jni->pause();
+ m_at_jni->flush();
+ }
+ m_at_jni->release();
+
+ m_duration_written = 0;
+ m_headPos = 0;
+ m_headPosOld = 0;
+ m_timestampPos = 0;
+ m_stampTimer.SetExpired();
+
+ m_linearmovingaverage.clear();
+
+ delete m_at_jni;
+ m_at_jni = NULL;
+ m_delay = 0.0;
+ m_hw_delay = 0.0;
+}
+
+bool CAESinkAUDIOTRACK::IsInitialized()
+{
+ return (m_at_jni && m_at_jni->getState() == CJNIAudioTrack::STATE_INITIALIZED);
+}
+
+void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status)
+{
+ if (!m_at_jni)
+ {
+ status.SetDelay(0);
+ return;
+ }
+
+ bool usesAdvancedLogging = CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO);
+ // In their infinite wisdom, Google decided to make getPlaybackHeadPosition
+ // return a 32bit "int" that you should "interpret as unsigned." As such,
+ // for wrap safety, we need to do all ops on it in 32bit integer math.
+
+ uint32_t head_pos = (uint32_t)m_at_jni->getPlaybackHeadPosition();
+
+ // Wraparound
+ if ((uint32_t)(m_headPos & UINT64_LOWER_BYTES) > head_pos) // need to compute wraparound
+ m_headPos += (1ULL << 32); // add wraparound, e.g. 0x0000 FFFF FFFF -> 0x0001 FFFF FFFF
+ // clear lower 32 bit values, e.g. 0x0001 FFFF FFFF -> 0x0001 0000 0000
+ // and add head_pos which wrapped around, e.g. 0x0001 0000 0000 -> 0x0001 0000 0004
+ m_headPos = (m_headPos & UINT64_UPPER_BYTES) | (uint64_t)head_pos;
+ // check if sink is stuck
+ if (m_headPos == m_headPosOld)
+ m_stuckCounter++;
+ else
+ {
+ m_stuckCounter = 0;
+ m_headPosOld = m_headPos;
+ }
+
+ double gone = static_cast<double>(m_headPos) / m_sink_sampleRate;
+
+ // if sink is run dry without buffer time written anymore
+ if (gone > m_duration_written)
+ gone = m_duration_written;
+
+ double delay = m_duration_written - gone;
+
+ if (m_stampTimer.IsTimePast())
+ {
+ if (!m_at_jni->getTimestamp(m_timestamp))
+ {
+ CLog::Log(LOGDEBUG, "Could not acquire timestamp");
+ m_stampTimer.Set(100ms);
+ }
+ else
+ {
+ // check if frameposition is valid and nano timer less than 50 ms outdated
+ if (m_timestamp.get_framePosition() > 0 &&
+ (CurrentHostCounter() - m_timestamp.get_nanoTime()) < 50 * 1000 * 1000)
+ m_stampTimer.Set(1000ms);
+ else
+ m_stampTimer.Set(100ms);
+ }
+ }
+ // check if last value was received less than 2 seconds ago
+ if (m_timestamp.get_framePosition() > 0 &&
+ (CurrentHostCounter() - m_timestamp.get_nanoTime()) < 2 * 1000 * 1000 * 1000)
+ {
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO, "Framecounter: {} Time: {} Current-Time: {}",
+ (m_timestamp.get_framePosition() & UINT64_LOWER_BYTES), m_timestamp.get_nanoTime(),
+ CurrentHostCounter());
+ }
+ uint64_t delta = static_cast<uint64_t>(CurrentHostCounter() - m_timestamp.get_nanoTime());
+ uint64_t stamphead =
+ static_cast<uint64_t>(m_timestamp.get_framePosition() & UINT64_LOWER_BYTES) +
+ delta * m_sink_sampleRate / 1000000000.0;
+ // wrap around
+ // e.g. 0xFFFFFFFFFFFF0123 -> 0x0000000000002478
+ // because we only query each second the simple smaller comparison won't suffice
+ // as delay can fluctuate minimally
+ if (stamphead < m_timestampPos && (m_timestampPos - stamphead) > 0x7FFFFFFFFFFFFFFFULL)
+ {
+ uint64_t stamp = m_timestampPos;
+ stamp += (1ULL << 32);
+ stamphead = (stamp & UINT64_UPPER_BYTES) | stamphead;
+ CLog::Log(LOGDEBUG, "Wraparound happened old: {} new: {}", m_timestampPos, stamphead);
+ }
+ m_timestampPos = stamphead;
+
+ double playtime = m_timestampPos / static_cast<double>(m_sink_sampleRate);
+
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO,
+ "Delay - Timestamp: {} (ms) delta: {} (ms) playtime: {} (ms) Duration: {} ms",
+ 1000.0 * (m_duration_written - playtime), delta / 1000000.0, playtime * 1000,
+ m_duration_written * 1000);
+ CLog::Log(LOGINFO, "Head-Position {} Timestamp Position {} Delay-Offset: {} ms", m_headPos,
+ m_timestampPos,
+ 1000.0 * (static_cast<int64_t>(m_headPos - m_timestampPos)) / m_sink_sampleRate);
+ }
+ double hw_delay = m_duration_written - playtime;
+ // correct by subtracting above measured delay, if lower delay gets automatically reduced
+ hw_delay -= delay;
+ // sometimes at the beginning of the stream m_timestampPos is more accurate and ahead of
+ // m_headPos - don't use the computed value then and wait
+ if (hw_delay > -1.0 && hw_delay < 1.0)
+ m_hw_delay = hw_delay;
+ else
+ m_hw_delay = 0.0;
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO, "HW-Delay (1): {} ms", hw_delay * 1000);
+ }
+ }
+
+ delay += m_hw_delay;
+
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO, "Combined Delay: {} ms", delay * 1000);
+ }
+ if (delay < 0.0)
+ delay = 0.0;
+
+ // the RAW hack for simulating pause bursts should not come
+ // into the way of hw delay
+ if (m_pause_ms > 0.0)
+ {
+ double difference = (m_audiotrackbuffer_sec - delay) * 1000;
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO, "Faking Pause-Bursts in Delay - returning smoothed {} ms Original {} ms",
+ m_audiotrackbuffer_sec * 1000, delay * 1000);
+ CLog::Log(LOGINFO, "Difference: {} ms m_pause_ms {}", difference, m_pause_ms);
+ }
+ // buffer not yet reached
+ if (difference > 0.0)
+ delay = m_audiotrackbuffer_sec;
+ else
+ {
+ CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (2)");
+ m_pause_ms = 0.0;
+ }
+ }
+
+ const double d = GetMovingAverageDelay(delay);
+
+ // Audiotrack is caching more than we thought it would
+ if (d > m_audiotrackbuffer_sec)
+ m_audiotrackbuffer_sec = d;
+
+ // track delay in local member
+ m_delay = d;
+ if (usesAdvancedLogging)
+ {
+ CLog::Log(LOGINFO, "Delay Current: {:f} ms", d * 1000);
+ }
+ status.SetDelay(d);
+}
+
+double CAESinkAUDIOTRACK::GetLatency()
+{
+ return 0.0;
+}
+
+double CAESinkAUDIOTRACK::GetCacheTotal()
+{
+ // total amount that the audio sink can buffer in units of seconds
+ return m_audiotrackbuffer_sec;
+}
+
+// this method is supposed to block until all frames are written to the device buffer
+// when it returns ActiveAESink will take the next buffer out of a queue
+unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!IsInitialized())
+ return INT_MAX;
+
+ // If the sink did not move twice the buffer size in time it was opened
+ // take action. Some sinks open with e.g. 128 ms nicely but under the
+ // hood need a bit more samples to start moving on sink start.
+ // Simple equation: N x stime packages in ms > 2 configured audiotrack_buffer in ms
+ // will result in the error condition triggering.
+
+ const bool isRawPt = m_passthrough && !m_info.m_wantsIECPassthrough;
+ if (!isRawPt)
+ {
+ const double max_stuck_delay_ms = m_audiotrackbuffer_sec_orig * 2000.0;
+ const double stime_ms = 1000.0 * frames / m_format.m_sampleRate;
+
+ if (m_stuckCounter * stime_ms > max_stuck_delay_ms)
+ {
+ CLog::Log(LOGERROR, "Sink got stuck with {:f} ms - ask AE for reopening", max_stuck_delay_ms);
+ usleep(max_stuck_delay_ms * 1000);
+ return INT_MAX;
+ }
+ }
+
+ // for debugging only - can be removed if everything is really stable
+ uint64_t startTime = CurrentHostCounter();
+
+ uint8_t *buffer = data[0]+offset*m_format.m_frameSize;
+ uint8_t *out_buf = buffer;
+ int size = frames * m_format.m_frameSize;
+
+ // write as many frames of audio as we can fit into our internal buffer.
+ int written = 0;
+ int loop_written = 0;
+ if (frames)
+ {
+ if (m_at_jni->getPlayState() != CJNIAudioTrack::PLAYSTATE_PLAYING)
+ m_at_jni->play();
+
+ bool retried = false;
+ int size_left = size;
+ while (written < size)
+ {
+ loop_written = AudioTrackWrite((char*)out_buf, 0, size_left);
+ written += loop_written;
+ size_left -= loop_written;
+
+ if (loop_written < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkAUDIOTRACK::AddPackets write returned error: {}",
+ loop_written);
+ return INT_MAX;
+ }
+
+ // if we could not add any data - sleep a bit and retry
+ if (loop_written == 0)
+ {
+ if (!retried)
+ {
+ retried = true;
+ double sleep_time = 0;
+ if (m_passthrough && !m_info.m_wantsIECPassthrough)
+ {
+ sleep_time = m_format.m_streamInfo.GetDuration();
+ usleep(sleep_time * 1000);
+ }
+ else
+ {
+ sleep_time = 1000.0 * m_format.m_frames / m_format.m_sampleRate;
+ usleep(sleep_time * 1000);
+ }
+ bool playing = m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PLAYING;
+ CLog::Log(LOGDEBUG, "Retried to write onto the sink - slept: {:f} playing: {}",
+ sleep_time, playing ? "yes" : "no");
+ continue;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Repeatedly tried to write onto the sink - giving up");
+ break;
+ }
+ }
+ retried = false; // at least one time there was more than zero data written
+ if (m_passthrough && !m_info.m_wantsIECPassthrough)
+ {
+ if (written == size)
+ m_duration_written += m_format.m_streamInfo.GetDuration() / 1000;
+ else
+ {
+ CLog::Log(LOGDEBUG, "Error writing full package to sink, left: {}", size_left);
+ // Let AE wait some ms to come back
+ unsigned int written_frames = (unsigned int) (written/m_format.m_frameSize);
+ return written_frames;
+ }
+ }
+ else
+ {
+ double duration =
+ (static_cast<double>(loop_written) / m_format.m_frameSize) / m_format.m_sampleRate;
+ m_duration_written += duration;
+ }
+
+ // just try again to care for fragmentation
+ if (written < size)
+ out_buf = out_buf + loop_written;
+
+ loop_written = 0;
+ }
+ }
+ unsigned int written_frames = static_cast<unsigned int>(written / m_format.m_frameSize);
+ double time_to_add_ms = 1000.0 * (CurrentHostCounter() - startTime) / CurrentHostFrequency();
+ if (m_passthrough && !m_info.m_wantsIECPassthrough)
+ {
+ // AT does not consume in a blocking way - it runs ahead and blocks
+ // exactly once with the last package for some 100 ms
+ double extra_sleep = 0.0;
+ if (time_to_add_ms < m_format.m_streamInfo.GetDuration())
+ extra_sleep = (m_format.m_streamInfo.GetDuration() - time_to_add_ms) / 2;
+
+ // if there is still place, just add it without blocking
+ if (m_delay < (m_audiotrackbuffer_sec - (m_format.m_streamInfo.GetDuration() / 1000.0)))
+ extra_sleep = 0;
+
+ if (m_pause_ms > 0.0)
+ {
+ extra_sleep = 0;
+ m_pause_ms -= m_format.m_streamInfo.GetDuration();
+ if (m_pause_ms <= 0.0)
+ {
+ m_pause_ms = 0.0;
+ CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (1)");
+ }
+ }
+
+ usleep(extra_sleep * 1000);
+ }
+ else
+ {
+ // waiting should only be done if sink is not run dry
+ double period_time = m_format.m_frames / static_cast<double>(m_sink_sampleRate);
+ if (m_delay >= (m_audiotrackbuffer_sec - period_time))
+ {
+ double time_should_ms = 1000.0 * written_frames / m_format.m_sampleRate;
+ double time_off = time_should_ms - time_to_add_ms;
+ if (time_off > 0)
+ usleep(time_off * 500); // sleep half the error on average away
+ }
+ }
+
+ return written_frames;
+}
+
+void CAESinkAUDIOTRACK::AddPause(unsigned int millis)
+{
+ if (!m_at_jni)
+ return;
+
+ // just sleep out the frames
+ if (m_at_jni->getPlayState() != CJNIAudioTrack::PLAYSTATE_PAUSED)
+ m_at_jni->pause();
+
+ // This is a mixture to get it right between
+ // blocking, sleeping roughly and GetDelay smoothing
+ // In short: Shit in, shit out
+ usleep(millis * 1000);
+ m_pause_ms += millis;
+}
+
+void CAESinkAUDIOTRACK::Drain()
+{
+ if (!m_at_jni)
+ return;
+
+ CLog::Log(LOGDEBUG, "Draining Audio");
+ if (IsInitialized())
+ {
+ m_at_jni->stop();
+ // stay ready
+ m_at_jni->pause();
+ }
+ m_duration_written = 0;
+ m_headPos = 0;
+ m_stuckCounter = 0;
+ m_timestampPos = 0;
+ m_linearmovingaverage.clear();
+ m_stampTimer.SetExpired();
+ m_pause_ms = 0.0;
+}
+
+void CAESinkAUDIOTRACK::Register()
+{
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "AUDIOTRACK";
+ entry.createFunc = CAESinkAUDIOTRACK::Create;
+ entry.enumerateFunc = CAESinkAUDIOTRACK::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(entry);
+}
+
+IAESink* CAESinkAUDIOTRACK::Create(std::string &device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkAUDIOTRACK();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+void CAESinkAUDIOTRACK::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ // Clear everything
+ m_info.m_channels.Reset();
+ m_info.m_dataFormats.clear();
+ m_info.m_sampleRates.clear();
+ m_info.m_streamTypes.clear();
+ m_sink_sampleRates.clear();
+
+ m_info.m_deviceType = AE_DEVTYPE_PCM;
+ m_info.m_deviceName = "AudioTrack (IEC)";
+ m_info.m_displayName = "AudioTrack (IEC)";
+ m_info.m_displayNameExtra = "Kodi IEC packer (recommended)";
+
+ // Query IEC capabilities
+ bool isRaw = false;
+ if (CJNIAudioFormat::ENCODING_IEC61937 != -1)
+ {
+ UpdateAvailablePCMCapabilities();
+ UpdateAvailablePassthroughCapabilities(isRaw);
+
+ if (!m_info.m_streamTypes.empty())
+ {
+ m_info_iec = m_info;
+ list.push_back(m_info_iec);
+ m_hasIEC = true;
+ }
+ }
+
+ // Query RAW capabilities
+ isRaw = true;
+ m_info.m_deviceName = "AudioTrack (RAW)";
+ m_info.m_displayName = "AudioTrack (RAW)";
+ m_info.m_displayNameExtra = "Android IEC packer";
+ UpdateAvailablePCMCapabilities();
+ UpdateAvailablePassthroughCapabilities(isRaw);
+ m_info_raw = m_info;
+
+ // no need to display two PCM sinks - as they are the same
+ if (!list.empty())
+ m_info_raw.m_onlyPassthrough = true;
+
+ list.push_back(m_info_raw);
+}
+
+void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw)
+{
+ m_info.m_deviceType = AE_DEVTYPE_HDMI;
+ m_info.m_wantsIECPassthrough = false;
+ m_info.m_dataFormats.push_back(AE_FMT_RAW);
+ m_info.m_streamTypes.clear();
+ if (isRaw)
+ {
+ bool canDoAC3 = false;
+
+ if (CJNIAudioFormat::ENCODING_AC3 != -1)
+ {
+ if (VerifySinkConfiguration(48000, CJNIAudioFormat::CHANNEL_OUT_STEREO,
+ CJNIAudioFormat::ENCODING_AC3, true))
+ {
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ CLog::Log(LOGDEBUG, "Firmware implements AC3 RAW");
+
+ canDoAC3 = true;
+ }
+ }
+
+ // EAC3 working on shield, broken on FireTV
+ if (CJNIAudioFormat::ENCODING_E_AC3 != -1)
+ {
+ if (VerifySinkConfiguration(48000, CJNIAudioFormat::CHANNEL_OUT_STEREO,
+ CJNIAudioFormat::ENCODING_E_AC3, true))
+ {
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ CLog::Log(LOGDEBUG, "Firmware implements EAC3 RAW");
+
+ if (!canDoAC3)
+ m_passthrough_use_eac3 = true;
+ }
+ }
+
+ if (CJNIAudioFormat::ENCODING_DTS != -1)
+ {
+ if (VerifySinkConfiguration(48000, CJNIAudioFormat::CHANNEL_OUT_STEREO,
+ CJNIAudioFormat::ENCODING_DTS, true))
+ {
+ CLog::Log(LOGDEBUG, "Firmware implements DTS RAW");
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ }
+ }
+
+ if (CJNIAudioManager::GetSDKVersion() >= 23)
+ {
+ if (CJNIAudioFormat::ENCODING_DTS_HD != -1)
+ {
+ if (VerifySinkConfiguration(48000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1),
+ CJNIAudioFormat::ENCODING_DTS_HD, true))
+ {
+ CLog::Log(LOGDEBUG, "Firmware implements DTS-HD RAW");
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ }
+ }
+ if (CJNIAudioFormat::ENCODING_DOLBY_TRUEHD != -1)
+ {
+ if (VerifySinkConfiguration(48000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1),
+ CJNIAudioFormat::ENCODING_DOLBY_TRUEHD, true))
+ {
+ CLog::Log(LOGDEBUG, "Firmware implements TrueHD RAW");
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+ }
+ }
+ }
+ }
+ else
+ {
+ // Android v24 and backports can do real IEC API
+ if (CJNIAudioFormat::ENCODING_IEC61937 != -1)
+ {
+ // check if we support opening an IEC sink at all:
+ bool supports_iec = VerifySinkConfiguration(48000, CJNIAudioFormat::CHANNEL_OUT_STEREO,
+ CJNIAudioFormat::ENCODING_IEC61937);
+ if (supports_iec)
+ {
+ bool supports_192khz = m_sink_sampleRates.find(192000) != m_sink_sampleRates.end();
+ m_info.m_wantsIECPassthrough = true;
+ m_info.m_streamTypes.clear();
+ m_info.m_dataFormats.push_back(AE_FMT_RAW);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTrack: Using IEC PT mode: {}",
+ CJNIAudioFormat::ENCODING_IEC61937);
+ CLog::Log(LOGDEBUG, "AC3 and DTS via IEC61937 is supported");
+ if (supports_192khz)
+ {
+ // Check for IEC 2 channel 192 khz PT DTS-HD-HR and E-AC3
+ if (VerifySinkConfiguration(192000, CJNIAudioFormat::CHANNEL_OUT_STEREO,
+ CJNIAudioFormat::ENCODING_IEC61937))
+ {
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ CLog::Log(LOGDEBUG, "E-AC3 and DTSHD-HR via IEC61937 is supported");
+ }
+ // Check for IEC 8 channel 192 khz PT DTS-HD-MA and TrueHD
+ int atChannelMask = AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1);
+ if (VerifySinkConfiguration(192000, atChannelMask, CJNIAudioFormat::ENCODING_IEC61937))
+ {
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+ CLog::Log(LOGDEBUG, "DTSHD-MA and TrueHD via IEC61937 is supported");
+ }
+ }
+ }
+ }
+ }
+}
+
+void CAESinkAUDIOTRACK::UpdateAvailablePCMCapabilities()
+{
+ m_info.m_channels = KnownChannels;
+
+ // default fallback format
+ m_info.m_dataFormats.push_back(AE_FMT_S16LE);
+ unsigned int native_sampleRate = CJNIAudioTrack::getNativeOutputSampleRate(CJNIAudioManager::STREAM_MUSIC);
+ m_sink_sampleRates.insert(native_sampleRate);
+
+ int encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
+ m_sinkSupportsFloat = VerifySinkConfiguration(native_sampleRate, CJNIAudioFormat::CHANNEL_OUT_STEREO, CJNIAudioFormat::ENCODING_PCM_FLOAT);
+ // Only try for Android 7 or later - there are a lot of old devices that open successfully
+ // but won't work correctly under the hood (famouse example: old FireTV)
+ // As even newish devices like Android Chromecast don't do it properly - just disable it ... and use 16 bit Integer
+ //if (CJNIAudioManager::GetSDKVersion() > 23)
+ // m_sinkSupportsMultiChannelFloat = VerifySinkConfiguration(native_sampleRate, CJNIAudioFormat::CHANNEL_OUT_7POINT1_SURROUND, CJNIAudioFormat::ENCODING_PCM_FLOAT);
+
+ if (m_sinkSupportsFloat)
+ {
+ encoding = CJNIAudioFormat::ENCODING_PCM_FLOAT;
+ m_info.m_dataFormats.push_back(AE_FMT_FLOAT);
+ CLog::Log(LOGINFO, "Float is supported");
+ }
+ if (m_sinkSupportsMultiChannelFloat)
+ {
+ CLog::Log(LOGINFO, "Multi channel Float is supported");
+ }
+
+ int test_sample[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
+ int test_sample_sz = sizeof(test_sample) / sizeof(int);
+
+ for (int i = 0; i < test_sample_sz; ++i)
+ {
+ if (IsSupported(test_sample[i], CJNIAudioFormat::CHANNEL_OUT_STEREO, encoding))
+ {
+ m_sink_sampleRates.insert(test_sample[i]);
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTRACK - {} supported", test_sample[i]);
+ }
+ }
+ std::copy(m_sink_sampleRates.begin(), m_sink_sampleRates.end(), std::back_inserter(m_info.m_sampleRates));
+}
+
+double CAESinkAUDIOTRACK::GetMovingAverageDelay(double newestdelay)
+{
+#if defined AT_USE_EXPONENTIAL_AVERAGING
+ double old = 0.0;
+ if (m_linearmovingaverage.empty()) // just for creating one space in list
+ m_linearmovingaverage.push_back(newestdelay);
+ else
+ old = m_linearmovingaverage.front();
+
+ const double alpha = 0.3;
+ const double beta = 0.7;
+
+ double d = alpha * newestdelay + beta * old;
+ m_linearmovingaverage.at(0) = d;
+
+ return d;
+#endif
+
+ m_linearmovingaverage.push_back(newestdelay);
+
+ // new values are in the back, old values are in the front
+ // oldest value is removed if elements > MOVING_AVERAGE_MAX_MEMBERS
+ // removing first element of a vector sucks - I know that
+ // but hey - 10 elements - not 1 million
+ size_t size = m_linearmovingaverage.size();
+ if (size > MOVING_AVERAGE_MAX_MEMBERS)
+ {
+ m_linearmovingaverage.pop_front();
+ size--;
+ }
+ // m_{LWMA}^{(n)}(t) = \frac{2}{n (n+1)} \sum_{i=1}^n i \; x(t-n+i)
+ const double denom = 2.0 / (size * (size + 1));
+ double sum = 0.0;
+ for (size_t i = 0; i < m_linearmovingaverage.size(); i++)
+ sum += (i + 1) * m_linearmovingaverage.at(i);
+
+ return sum * denom;
+}
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h
new file mode 100644
index 0000000..6e9ec76
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+
+#include <deque>
+#include <set>
+
+#include <androidjni/AudioTrack.h>
+
+class CAESinkAUDIOTRACK : public IAESink
+{
+public:
+ const char* GetName() override { return "AUDIOTRACK"; }
+
+ CAESinkAUDIOTRACK();
+ ~CAESinkAUDIOTRACK() override;
+
+ bool Initialize(AEAudioFormat& format, std::string& device) override;
+ void Deinitialize() override;
+ bool IsInitialized();
+
+ void GetDelay(AEDelayStatus& status) override;
+ double GetLatency() override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t** data, unsigned int frames, unsigned int offset) override;
+ void AddPause(unsigned int millis) override;
+ void Drain() override;
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+
+protected:
+ static jni::CJNIAudioTrack *CreateAudioTrack(int stream, int sampleRate, int channelMask, int encoding, int bufferSize);
+ static bool IsSupported(int sampleRateInHz, int channelConfig, int audioFormat);
+ static bool VerifySinkConfiguration(int sampleRate,
+ int channelMask,
+ int encoding,
+ bool isRaw = false);
+ static void UpdateAvailablePCMCapabilities();
+ static void UpdateAvailablePassthroughCapabilities(bool isRaw = false);
+
+ int AudioTrackWrite(char* audioData, int offsetInBytes, int sizeInBytes);
+ int AudioTrackWrite(char* audioData, int sizeInBytes, int64_t timestamp);
+
+private:
+ jni::CJNIAudioTrack *m_at_jni;
+ int m_jniAudioFormat;
+
+ double m_duration_written;
+ unsigned int m_min_buffer_size;
+ uint64_t m_headPos;
+ uint64_t m_headPosOld;
+ uint32_t m_stuckCounter;
+ uint64_t m_timestampPos = 0;
+ // Moving Average computes the weighted average delay over
+ // a fixed size of delay values - current size: 20 values
+ double GetMovingAverageDelay(double newestdelay);
+
+ // We maintain our linear weighted average delay counter in here
+ // The n-th value (timely oldest value) is weighted with 1/n
+ // the newest value gets a weight of 1
+ std::deque<double> m_linearmovingaverage;
+
+ static CAEDeviceInfo m_info;
+ static CAEDeviceInfo m_info_raw;
+ static CAEDeviceInfo m_info_iec;
+ static bool m_hasIEC;
+ static std::set<unsigned int> m_sink_sampleRates;
+ static bool m_sinkSupportsFloat;
+ static bool m_sinkSupportsMultiChannelFloat;
+ static bool m_passthrough_use_eac3;
+
+ AEAudioFormat m_format;
+ int16_t *m_alignedS16;
+ unsigned int m_sink_frameSize;
+ unsigned int m_sink_sampleRate;
+ bool m_passthrough;
+ double m_audiotrackbuffer_sec;
+ double m_audiotrackbuffer_sec_orig;
+ int m_encoding;
+ double m_pause_ms = 0.0;
+ double m_delay = 0.0;
+ double m_hw_delay = 0.0;
+ CJNIAudioTimestamp m_timestamp;
+ XbmcThreads::EndTime<> m_stampTimer;
+
+ std::vector<float> m_floatbuf;
+ std::vector<int16_t> m_shortbuf;
+ std::vector<char> m_charbuf;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.h b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.h
new file mode 100644
index 0000000..708b0f6
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+#define DO_440HZ_TONE_TEST 0
+
+#if DO_440HZ_TONE_TEST
+typedef struct {
+ float currentPhase;
+ float phaseIncrement;
+} SineWaveGenerator;
+#endif
+
+class AERingBuffer;
+class CAAudioUnitSink;
+
+class CAESinkDARWINIOS : public IAESink
+{
+public:
+ const char* GetName() override { return "DARWINIOS"; }
+
+ CAESinkDARWINIOS();
+ ~CAESinkDARWINIOS() override = default;
+
+ static void Register();
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force);
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+
+ bool Initialize(AEAudioFormat& format, std::string& device) override;
+ void Deinitialize() override;
+
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t** data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+ bool HasVolume() override;
+
+private:
+ static AEDeviceInfoList m_devices;
+ CAEDeviceInfo m_info;
+ AEAudioFormat m_format;
+
+ CAAudioUnitSink *m_audioSink;
+#if DO_440HZ_TONE_TEST
+ SineWaveGenerator m_SineWaveGenerator;
+#endif
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.mm b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.mm
new file mode 100644
index 0000000..34b52fa
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINIOS.mm
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "cores/AudioEngine/Sinks/AESinkDARWINIOS.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "cores/AudioEngine/Utils/AERingBuffer.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "threads/Condition.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+#include <sstream>
+
+#include <AudioToolbox/AudioToolbox.h>
+
+using namespace std::chrono_literals;
+
+#define CA_MAX_CHANNELS 8
+static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
+ AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
+ AE_CH_NULL
+};
+
+/***************************************************************************************/
+/***************************************************************************************/
+#if DO_440HZ_TONE_TEST
+static void SineWaveGeneratorInitWithFrequency(SineWaveGenerator *ctx, double frequency, double samplerate)
+{
+ // Given:
+ // frequency in cycles per second
+ // 2*PI radians per sine wave cycle
+ // sample rate in samples per second
+ //
+ // Then:
+ // cycles radians seconds radians
+ // ------ * ------- * ------- = -------
+ // second cycle sample sample
+ ctx->currentPhase = 0.0;
+ ctx->phaseIncrement = frequency * 2*M_PI / samplerate;
+}
+
+static int16_t SineWaveGeneratorNextSampleInt16(SineWaveGenerator *ctx)
+{
+ int16_t sample = INT16_MAX * sinf(ctx->currentPhase);
+
+ ctx->currentPhase += ctx->phaseIncrement;
+ // Keep the value between 0 and 2*M_PI
+ while (ctx->currentPhase > 2*M_PI)
+ ctx->currentPhase -= 2*M_PI;
+
+ return sample / 4;
+}
+static float SineWaveGeneratorNextSampleFloat(SineWaveGenerator *ctx)
+{
+ float sample = MAXFLOAT * sinf(ctx->currentPhase);
+
+ ctx->currentPhase += ctx->phaseIncrement;
+ // Keep the value between 0 and 2*M_PI
+ while (ctx->currentPhase > 2*M_PI)
+ ctx->currentPhase -= 2*M_PI;
+
+ return sample / 4;
+}
+#endif
+
+/***************************************************************************************/
+/***************************************************************************************/
+class CAAudioUnitSink
+{
+ public:
+ CAAudioUnitSink() = default;
+ ~CAAudioUnitSink();
+
+ bool open(AudioStreamBasicDescription outputFormat);
+ bool close();
+ bool play(bool mute);
+ bool mute(bool mute);
+ bool pause();
+ void drain();
+ void getDelay(AEDelayStatus& status);
+ double cacheSize();
+ unsigned int write(uint8_t *data, unsigned int byte_count);
+ unsigned int chunkSize() { return m_bufferDuration * m_sampleRate; }
+ unsigned int getRealisedSampleRate() { return m_outputFormat.mSampleRate; }
+ static Float64 getCoreAudioRealisedSampleRate();
+
+ private:
+ void setCoreAudioBuffersize();
+ bool setCoreAudioInputFormat();
+ void setCoreAudioPreferredSampleRate();
+ bool setupAudio();
+ bool checkAudioRoute();
+ bool checkSessionProperties();
+ bool activateAudioSession();
+ void deactivateAudioSession();
+
+ // callbacks
+ static void sessionPropertyCallback(void *inClientData,
+ AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData);
+
+ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames,
+ AudioBufferList *ioData);
+
+ bool m_setup;
+ bool m_activated = false;
+ AudioUnit m_audioUnit;
+ AudioStreamBasicDescription m_outputFormat;
+ AERingBuffer* m_buffer = nullptr;
+
+ bool m_mute;
+ Float32 m_outputVolume;
+ Float32 m_outputLatency;
+ Float32 m_bufferDuration;
+
+ unsigned int m_sampleRate;
+ unsigned int m_frameSize;
+
+ bool m_playing = false;
+ volatile bool m_started = false;
+
+ CAESpinSection m_render_section;
+ volatile int64_t m_render_timestamp = 0;
+ volatile uint32_t m_render_frames = 0;
+};
+
+CAAudioUnitSink::~CAAudioUnitSink()
+{
+ close();
+}
+
+bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat)
+{
+ m_mute = false;
+ m_setup = false;
+ m_outputFormat = outputFormat;
+ m_outputLatency = 0.0;
+ m_bufferDuration= 0.0;
+ m_outputVolume = 1.0;
+ m_sampleRate = (unsigned int)outputFormat.mSampleRate;
+ m_frameSize = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
+
+ /* TODO: Reduce the size of this buffer, pre-calculate the size based on how large
+ the buffers are that CA calls us with in the renderCallback - perhaps call
+ the checkSessionProperties() before running this? */
+ m_buffer = new AERingBuffer(16384);
+
+ return setupAudio();
+}
+
+bool CAAudioUnitSink::close()
+{
+ deactivateAudioSession();
+
+ delete m_buffer;
+ m_buffer = NULL;
+
+ m_started = false;
+ return true;
+}
+
+bool CAAudioUnitSink::play(bool mute)
+{
+ if (!m_playing)
+ {
+ if (activateAudioSession())
+ {
+ CAAudioUnitSink::mute(mute);
+ m_playing = !AudioOutputUnitStart(m_audioUnit);
+ }
+ }
+
+ return m_playing;
+}
+
+bool CAAudioUnitSink::mute(bool mute)
+{
+ m_mute = mute;
+
+ return true;
+}
+
+bool CAAudioUnitSink::pause()
+{
+ if (m_playing)
+ m_playing = AudioOutputUnitStop(m_audioUnit);
+
+ return m_playing;
+}
+
+void CAAudioUnitSink::getDelay(AEDelayStatus& status)
+{
+ CAESpinLock lock(m_render_section);
+ do
+ {
+ status.delay = (double)m_buffer->GetReadSize() / m_frameSize;
+ status.delay += (double)m_render_frames;
+ status.tick = m_render_timestamp;
+ } while(lock.retry());
+
+ status.delay /= m_sampleRate;
+ status.delay += static_cast<double>(m_bufferDuration + m_outputLatency);
+}
+
+double CAAudioUnitSink::cacheSize()
+{
+ return (double)m_buffer->GetMaxSize() / (double)(m_frameSize * m_sampleRate);
+}
+
+CCriticalSection mutex;
+XbmcThreads::ConditionVariable condVar;
+
+unsigned int CAAudioUnitSink::write(uint8_t *data, unsigned int frames)
+{
+ if (m_buffer->GetWriteSize() < frames * m_frameSize)
+ { // no space to write - wait for a bit
+ std::unique_lock<CCriticalSection> lock(mutex);
+ auto timeout = std::chrono::milliseconds(900 * frames / m_sampleRate);
+ if (!m_started)
+ timeout = 4500ms;
+
+ // we are using a timer here for being sure for timeouts
+ // condvar can be woken spuriously as signaled
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+ if (!m_started && timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "{} engine didn't start in {} ms!", __FUNCTION__, timeout.count());
+ return INT_MAX;
+ }
+ }
+
+ unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_frameSize);
+ if (write_frames)
+ m_buffer->Write(data, write_frames * m_frameSize);
+
+ return write_frames;
+}
+
+void CAAudioUnitSink::drain()
+{
+ unsigned int bytes = m_buffer->GetReadSize();
+ unsigned int totalBytes = bytes;
+ int maxNumTimeouts = 3;
+ auto timeout = std::chrono::milliseconds(900 * bytes / (m_sampleRate * m_frameSize));
+ while (bytes && maxNumTimeouts > 0)
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+
+ bytes = m_buffer->GetReadSize();
+ // if we timeout and don't
+ // consume bytes - decrease maxNumTimeouts
+ if (timer.IsTimePast() && bytes == totalBytes)
+ maxNumTimeouts--;
+ totalBytes = bytes;
+ }
+}
+
+void CAAudioUnitSink::setCoreAudioBuffersize()
+{
+#if !TARGET_IPHONE_SIMULATOR
+ // set the buffer size, this affects the number of samples
+ // that get rendered every time the audio callback is fired.
+ Float32 preferredBufferSize = 512 * m_outputFormat.mChannelsPerFrame / m_outputFormat.mSampleRate;
+ CLog::Log(LOGINFO, "{} setting buffer duration to {:f}", __PRETTY_FUNCTION__,
+ preferredBufferSize);
+ OSStatus status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
+ sizeof(preferredBufferSize), &preferredBufferSize);
+ if (status != noErr)
+ CLog::Log(LOGWARNING, "{} preferredBufferSize couldn't be set (error: {})", __PRETTY_FUNCTION__,
+ (int)status);
+#endif
+}
+
+bool CAAudioUnitSink::setCoreAudioInputFormat()
+{
+ // Set the output stream format
+ UInt32 ioDataSize = sizeof(AudioStreamBasicDescription);
+ OSStatus status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0, &m_outputFormat, ioDataSize);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error setting stream format on audioUnit (error: {})",
+ __PRETTY_FUNCTION__, (int)status);
+ return false;
+ }
+ return true;
+}
+
+void CAAudioUnitSink::setCoreAudioPreferredSampleRate()
+{
+ Float64 preferredSampleRate = m_outputFormat.mSampleRate;
+ CLog::Log(LOGINFO, "{} requesting hw samplerate {:f}", __PRETTY_FUNCTION__, preferredSampleRate);
+ OSStatus status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate,
+ sizeof(preferredSampleRate), &preferredSampleRate);
+ if (status != noErr)
+ CLog::Log(LOGWARNING, "{} preferredSampleRate couldn't be set (error: {})", __PRETTY_FUNCTION__,
+ (int)status);
+}
+
+Float64 CAAudioUnitSink::getCoreAudioRealisedSampleRate()
+{
+ Float64 outputSampleRate = 0.0;
+ UInt32 ioDataSize = sizeof(outputSampleRate);
+ if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
+ &ioDataSize, &outputSampleRate) != noErr)
+ CLog::Log(LOGERROR, "{}: error getting CurrentHardwareSampleRate", __FUNCTION__);
+ return outputSampleRate;
+}
+
+bool CAAudioUnitSink::setupAudio()
+{
+ OSStatus status = noErr;
+ if (m_setup && m_audioUnit)
+ return true;
+
+ AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
+ sessionPropertyCallback, this);
+
+ AudioSessionAddPropertyListener(kAudioSessionProperty_CurrentHardwareOutputVolume,
+ sessionPropertyCallback, this);
+
+ // Audio Unit Setup
+ // Describe a default output unit.
+ AudioComponentDescription description = {};
+ description.componentType = kAudioUnitType_Output;
+ description.componentSubType = kAudioUnitSubType_RemoteIO;
+ description.componentManufacturer = kAudioUnitManufacturer_Apple;
+
+ // Get component
+ AudioComponent component;
+ component = AudioComponentFindNext(NULL, &description);
+ status = AudioComponentInstanceNew(component, &m_audioUnit);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error creating audioUnit (error: {})", __PRETTY_FUNCTION__,
+ (int)status);
+ return false;
+ }
+
+ setCoreAudioPreferredSampleRate();
+
+ // Get the output samplerate for knowing what was setup in reality
+ Float64 realisedSampleRate = getCoreAudioRealisedSampleRate();
+ if (m_outputFormat.mSampleRate != realisedSampleRate)
+ {
+ CLog::Log(LOGINFO,
+ "{} couldn't set requested samplerate {}, coreaudio will resample to {} instead",
+ __PRETTY_FUNCTION__, (int)m_outputFormat.mSampleRate, (int)realisedSampleRate);
+ // if we don't ca to resample - but instead let activeae resample -
+ // reflect the realised samplerate to the outputformat here
+ // well maybe it is handy in the future - as of writing this
+ // ca was about 6 times faster then activeae ;)
+ //m_outputFormat.mSampleRate = realisedSampleRate;
+ //m_sampleRate = realisedSampleRate;
+ }
+
+ setCoreAudioBuffersize();
+ if (!setCoreAudioInputFormat())
+ return false;
+
+ // Attach a render callback on the unit
+ AURenderCallbackStruct callbackStruct = {};
+ callbackStruct.inputProc = renderCallback;
+ callbackStruct.inputProcRefCon = this;
+ status = AudioUnitSetProperty(m_audioUnit,
+ kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
+ 0, &callbackStruct, sizeof(callbackStruct));
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error setting render callback for audioUnit (error: {})",
+ __PRETTY_FUNCTION__, (int)status);
+ return false;
+ }
+
+ status = AudioUnitInitialize(m_audioUnit);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error initializing audioUnit (error: {})", __PRETTY_FUNCTION__,
+ (int)status);
+ return false;
+ }
+
+ checkSessionProperties();
+
+ m_setup = true;
+ std::string formatString;
+ CLog::Log(LOGINFO, "{} setup audio format: {}", __PRETTY_FUNCTION__,
+ StreamDescriptionToString(m_outputFormat, formatString));
+
+ return m_setup;
+}
+
+bool CAAudioUnitSink::checkAudioRoute()
+{
+ // why do we need to know the audio route ?
+ CFStringRef route;
+ UInt32 propertySize = sizeof(CFStringRef);
+ if (AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route) != noErr)
+ return false;
+
+ return true;
+}
+
+bool CAAudioUnitSink::checkSessionProperties()
+{
+ checkAudioRoute();
+
+ UInt32 ioDataSize;
+ ioDataSize = sizeof(m_outputVolume);
+ if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume,
+ &ioDataSize, &m_outputVolume) != noErr)
+ CLog::Log(LOGERROR, "{}: error getting CurrentHardwareOutputVolume", __FUNCTION__);
+
+ ioDataSize = sizeof(m_outputLatency);
+ if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputLatency,
+ &ioDataSize, &m_outputLatency) != noErr)
+ CLog::Log(LOGERROR, "{}: error getting CurrentHardwareOutputLatency", __FUNCTION__);
+
+ ioDataSize = sizeof(m_bufferDuration);
+ if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration,
+ &ioDataSize, &m_bufferDuration) != noErr)
+ CLog::Log(LOGERROR, "{}: error getting CurrentHardwareIOBufferDuration", __FUNCTION__);
+
+ CLog::Log(LOGDEBUG, "{}: volume = {:f}, latency = {:f}, buffer = {:f}", __FUNCTION__,
+ m_outputVolume, m_outputLatency, m_bufferDuration);
+ return true;
+}
+
+bool CAAudioUnitSink::activateAudioSession()
+{
+ if (!m_activated)
+ {
+ if (checkAudioRoute() && setupAudio())
+ m_activated = true;
+ }
+
+ return m_activated;
+}
+
+void CAAudioUnitSink::deactivateAudioSession()
+{
+ if (m_activated)
+ {
+ pause();
+ AudioUnitUninitialize(m_audioUnit);
+ AudioComponentInstanceDispose(m_audioUnit), m_audioUnit = NULL;
+ AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioRouteChange,
+ sessionPropertyCallback, this);
+ AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_CurrentHardwareOutputVolume,
+ sessionPropertyCallback, this);
+
+ m_setup = false;
+ m_activated = false;
+ }
+}
+
+void CAAudioUnitSink::sessionPropertyCallback(void *inClientData,
+ AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData)
+{
+ CAAudioUnitSink *sink = (CAAudioUnitSink*)inClientData;
+
+ if (inID == kAudioSessionProperty_AudioRouteChange)
+ {
+ if (sink->checkAudioRoute())
+ sink->checkSessionProperties();
+ }
+ else if (inID == kAudioSessionProperty_CurrentHardwareOutputVolume)
+ {
+ if (inData && inDataSize == 4)
+ sink->m_outputVolume = *(float*)inData;
+ }
+}
+
+inline void LogLevel(unsigned int got, unsigned int wanted)
+{
+ static unsigned int lastReported = INT_MAX;
+ if (got != wanted)
+ {
+ if (got != lastReported)
+ {
+ CLog::Log(LOGWARNING, "DARWINIOS: {}flow ({} vs {} bytes)", got > wanted ? "over" : "under",
+ got, wanted);
+ lastReported = got;
+ }
+ }
+ else
+ lastReported = INT_MAX; // indicate we were good at least once
+}
+
+OSStatus CAAudioUnitSink::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
+{
+ CAAudioUnitSink *sink = (CAAudioUnitSink*)inRefCon;
+
+ sink->m_render_section.enter();
+ sink->m_started = true;
+
+ for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
+ {
+ // buffers come from CA already zero'd, so just copy what is wanted
+ unsigned int wanted = ioData->mBuffers[i].mDataByteSize;
+ unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
+ sink->m_buffer->Read((unsigned char*)ioData->mBuffers[i].mData, bytes);
+ LogLevel(bytes, wanted);
+
+ if (bytes == 0)
+ *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
+ }
+
+ sink->m_render_timestamp = inTimeStamp->mHostTime;
+ sink->m_render_frames = inNumberFrames;
+ sink->m_render_section.leave();
+ // tell the sink we're good for more data
+ condVar.notifyAll();
+
+ return noErr;
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+static void EnumerateDevices(AEDeviceInfoList &list)
+{
+ CAEDeviceInfo device;
+
+ device.m_deviceName = "default";
+ device.m_displayName = "Default";
+ device.m_displayNameExtra = "";
+ // TODO screen changing on ios needs to call
+ // devices changed once this is available in active
+ if (false)
+ {
+ device.m_deviceType = AE_DEVTYPE_IEC958; //allow passthrough for tvout
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ device.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ else
+ device.m_deviceType = AE_DEVTYPE_PCM;
+
+ // add channel info
+ CAEChannelInfo channel_info;
+ for (UInt32 chan = 0; chan < 2; ++chan)
+ {
+ if (!device.m_channels.HasChannel(CAChannelMap[chan]))
+ device.m_channels += CAChannelMap[chan];
+ channel_info += CAChannelMap[chan];
+ }
+
+ // there are more supported ( one of those 2 gets resampled
+ // by coreaudio anyway) - but for keeping it save ignore
+ // the others...
+ device.m_sampleRates.push_back(44100);
+ device.m_sampleRates.push_back(48000);
+
+ device.m_dataFormats.push_back(AE_FMT_S16LE);
+ //device.m_dataFormats.push_back(AE_FMT_S24LE3);
+ //device.m_dataFormats.push_back(AE_FMT_S32LE);
+ device.m_dataFormats.push_back(AE_FMT_FLOAT);
+ device.m_wantsIECPassthrough = true;
+
+ CLog::Log(LOGDEBUG, "EnumerateDevices:Device({})", device.m_deviceName);
+
+ list.push_back(device);
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+AEDeviceInfoList CAESinkDARWINIOS::m_devices;
+
+CAESinkDARWINIOS::CAESinkDARWINIOS()
+: m_audioSink(NULL)
+{
+}
+
+void CAESinkDARWINIOS::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "DARWINIOS";
+ reg.createFunc = CAESinkDARWINIOS::Create;
+ reg.enumerateFunc = CAESinkDARWINIOS::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkDARWINIOS::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ IAESink *sink = new CAESinkDARWINIOS();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkDARWINIOS::Initialize(AEAudioFormat &format, std::string &device)
+{
+ bool found = false;
+ bool forceRaw = false;
+
+ std::string devicelower = device;
+ StringUtils::ToLower(devicelower);
+ for (size_t i = 0; i < m_devices.size(); i++)
+ {
+ if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
+ {
+ m_info = m_devices[i];
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return false;
+
+ AudioStreamBasicDescription audioFormat = {};
+
+ if (format.m_dataFormat == AE_FMT_FLOAT)
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
+ else// this will be selected when AE wants AC3 or DTS or anything other then float
+ {
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
+ if (format.m_dataFormat == AE_FMT_RAW)
+ forceRaw = true;
+ format.m_dataFormat = AE_FMT_S16LE;
+ }
+
+ format.m_channelLayout = m_info.m_channels;
+ format.m_frameSize = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
+
+
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ switch(format.m_sampleRate)
+ {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ case 176400:
+ audioFormat.mSampleRate = 44100;
+ break;
+ default:
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 96000:
+ case 192000:
+ case 384000:
+ audioFormat.mSampleRate = 48000;
+ break;
+ }
+
+ if (forceRaw)//make sure input and output samplerate match for preventing resampling
+ audioFormat.mSampleRate = CAAudioUnitSink::getCoreAudioRealisedSampleRate();
+
+ audioFormat.mFramesPerPacket = 1;
+ audioFormat.mChannelsPerFrame= 2;// ios only supports 2 channels
+ audioFormat.mBitsPerChannel = CAEUtil::DataFormatToBits(format.m_dataFormat);
+ audioFormat.mBytesPerFrame = format.m_frameSize;
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+
+#if DO_440HZ_TONE_TEST
+ SineWaveGeneratorInitWithFrequency(&m_SineWaveGenerator, 440.0, audioFormat.mSampleRate);
+#endif
+
+ m_audioSink = new CAAudioUnitSink;
+ m_audioSink->open(audioFormat);
+
+ format.m_frames = m_audioSink->chunkSize();
+ // reset to the realised samplerate
+ format.m_sampleRate = m_audioSink->getRealisedSampleRate();
+ m_format = format;
+
+ m_audioSink->play(false);
+
+ return true;
+}
+
+void CAESinkDARWINIOS::Deinitialize()
+{
+ delete m_audioSink;
+ m_audioSink = NULL;
+}
+
+void CAESinkDARWINIOS::GetDelay(AEDelayStatus& status)
+{
+ if (m_audioSink)
+ m_audioSink->getDelay(status);
+ else
+ status.SetDelay(0.0);
+}
+
+double CAESinkDARWINIOS::GetCacheTotal()
+{
+ if (m_audioSink)
+ return m_audioSink->cacheSize();
+ return 0.0;
+}
+
+unsigned int CAESinkDARWINIOS::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ uint8_t *buffer = data[0]+offset*m_format.m_frameSize;
+#if DO_440HZ_TONE_TEST
+ if (m_format.m_dataFormat == AE_FMT_FLOAT)
+ {
+ float *samples = (float*)buffer;
+ for (unsigned int j = 0; j < frames ; j++)
+ {
+ float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
+ *samples++ = sample;
+ *samples++ = sample;
+ }
+
+ }
+ else
+ {
+ int16_t *samples = (int16_t*)buffer;
+ for (unsigned int j = 0; j < frames ; j++)
+ {
+ int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
+ *samples++ = sample;
+ *samples++ = sample;
+ }
+ }
+#endif
+ if (m_audioSink)
+ return m_audioSink->write(buffer, frames);
+ return 0;
+}
+
+void CAESinkDARWINIOS::Drain()
+{
+ if (m_audioSink)
+ m_audioSink->drain();
+}
+
+bool CAESinkDARWINIOS::HasVolume()
+{
+ return false;
+}
+
+void CAESinkDARWINIOS::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ m_devices.clear();
+ EnumerateDevices(m_devices);
+ list = m_devices;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.cpp
new file mode 100644
index 0000000..b2a0280
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.cpp
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h"
+#include "cores/AudioEngine/Sinks/osx/CoreAudioHardware.h"
+#include "cores/AudioEngine/Utils/AERingBuffer.h"
+#include "threads/SystemClock.h"
+#include "utils/MemUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+static void EnumerateDevices(CADeviceList &list)
+{
+ std::string defaultDeviceName;
+ CCoreAudioHardware::GetOutputDeviceName(defaultDeviceName);
+ AudioDeviceID defaultID = CCoreAudioHardware::GetDefaultOutputDevice();
+
+ CoreAudioDeviceList deviceIDList;
+ CCoreAudioHardware::GetOutputDevices(&deviceIDList);
+ while (!deviceIDList.empty())
+ {
+ AudioDeviceID deviceID = deviceIDList.front();
+
+ AEDeviceEnumerationOSX devEnum(deviceID);
+ CADeviceList listForDevice = devEnum.GetDeviceInfoList();
+ for (UInt32 devIdx = 0; devIdx < listForDevice.size(); devIdx++)
+ list.push_back(listForDevice[devIdx]);
+
+ //in the first place of the list add the default device
+ //with name "default" - if this is selected
+ //we will output to whatever osx claims to be default
+ //(allows transition from headphones to speaker and stuff
+ //like that)
+ //fixme taking the first stream device is wrong here
+ //we rather might need the concatenation of all streams *sucks*
+ if(defaultID == deviceID && defaultDeviceName == devEnum.GetMasterDeviceName())
+ {
+ struct CADeviceInstance deviceInstance;
+ deviceInstance.audioDeviceId = deviceID;
+ deviceInstance.streamIndex = INT_MAX;//don't limit streamidx for the raw device
+ deviceInstance.sourceId = INT_MAX;
+ CAEDeviceInfo firstDevice = listForDevice.front().second;
+ firstDevice.m_deviceName = "default";
+ firstDevice.m_displayName = "Default";
+ firstDevice.m_displayNameExtra = defaultDeviceName;
+ list.insert(list.begin(), std::make_pair(deviceInstance, firstDevice));
+ }
+
+ deviceIDList.pop_front();
+ }
+}
+
+/* static, threadsafe access to the device list */
+static CADeviceList s_devices;
+static CCriticalSection s_devicesLock;
+
+static void EnumerateDevices()
+{
+ CADeviceList devices;
+ EnumerateDevices(devices);
+ {
+ std::unique_lock<CCriticalSection> lock(s_devicesLock);
+ s_devices = devices;
+ }
+}
+
+static CADeviceList GetDevices()
+{
+ CADeviceList list;
+ {
+ std::unique_lock<CCriticalSection> lock(s_devicesLock);
+ list = s_devices;
+ }
+ return list;
+}
+
+OSStatus deviceChangedCB(AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void* inClientData)
+{
+ bool deviceChanged = false;
+ static AudioDeviceID oldDefaultDevice = 0;
+ AudioDeviceID currentDefaultOutputDevice = 0;
+
+ for (unsigned int i = 0; i < inNumberAddresses; i++)
+ {
+ switch (inAddresses[i].mSelector)
+ {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ currentDefaultOutputDevice = CCoreAudioHardware::GetDefaultOutputDevice();
+ // This listener is called on every change of the hardware
+ // device. So check if the default device has really changed.
+ if (oldDefaultDevice != currentDefaultOutputDevice)
+ {
+ deviceChanged = true;
+ oldDefaultDevice = currentDefaultOutputDevice;
+ }
+ break;
+ default:
+ deviceChanged = true;
+ break;
+ }
+ if (deviceChanged)
+ break;
+ }
+
+ if (deviceChanged)
+ {
+ CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - reenumerating");
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->DeviceChange();
+ CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - done");
+ }
+ return noErr;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+CAESinkDARWINOSX::CAESinkDARWINOSX()
+{
+ // By default, kAudioHardwarePropertyRunLoop points at the process's main thread on SnowLeopard,
+ // If your process lacks such a run loop, you can set kAudioHardwarePropertyRunLoop to NULL which
+ // tells the HAL to run its own thread for notifications (which was the default prior to SnowLeopard).
+ // So tell the HAL to use its own thread for similar behavior under all supported versions of OSX.
+ CFRunLoopRef theRunLoop = NULL;
+ AudioObjectPropertyAddress theAddress = {
+ kAudioHardwarePropertyRunLoop,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus theError = AudioObjectSetPropertyData(kAudioObjectSystemObject,
+ &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
+ if (theError != noErr)
+ {
+ CLog::Log(LOGERROR, "CCoreAudioAE::constructor: kAudioHardwarePropertyRunLoop error.");
+ }
+ CCoreAudioDevice::RegisterDeviceChangedCB(true, deviceChangedCB, this);
+ CCoreAudioDevice::RegisterDefaultOutputDeviceChangedCB(true, deviceChangedCB, this);
+}
+
+CAESinkDARWINOSX::~CAESinkDARWINOSX()
+{
+ CCoreAudioDevice::RegisterDeviceChangedCB(false, deviceChangedCB, this);
+ CCoreAudioDevice::RegisterDefaultOutputDeviceChangedCB(false, deviceChangedCB, this);
+}
+
+void CAESinkDARWINOSX::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "DARWINOSX";
+ reg.createFunc = CAESinkDARWINOSX::Create;
+ reg.enumerateFunc = CAESinkDARWINOSX::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkDARWINOSX::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ IAESink *sink = new CAESinkDARWINOSX();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
+{
+ AudioDeviceID deviceID = 0;
+ UInt32 requestedStreamIndex = INT_MAX;
+ UInt32 requestedSourceId = INT_MAX;
+ bool passthrough = false;
+
+ // this sink needs IEC packing for RAW data
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ format.m_dataFormat= AE_FMT_S16NE;
+ passthrough = true;
+ }
+
+ CADeviceList devices = GetDevices();
+ if (StringUtils::EqualsNoCase(device, "default"))
+ {
+ CCoreAudioHardware::GetOutputDeviceName(device);
+ deviceID = CCoreAudioHardware::GetDefaultOutputDevice();
+ CLog::Log(LOGINFO, "{}: Opening default device {}", __PRETTY_FUNCTION__, device);
+ }
+ else
+ {
+ for (size_t i = 0; i < devices.size(); i++)
+ {
+ if (device == devices[i].second.m_deviceName)
+ {
+ const struct CADeviceInstance &deviceInstance = devices[i].first;
+ deviceID = deviceInstance.audioDeviceId;
+ requestedStreamIndex = deviceInstance.streamIndex;
+ requestedSourceId = deviceInstance.sourceId;
+ if (requestedStreamIndex != INT_MAX)
+ CLog::Log(LOGINFO, "{} pseudo device - requesting stream {}", __FUNCTION__,
+ (unsigned int)requestedStreamIndex);
+ if (requestedSourceId != INT_MAX)
+ CLog::Log(LOGINFO, "{} device - requesting audiosource {}", __FUNCTION__,
+ (unsigned int)requestedSourceId);
+ break;
+ }
+ }
+ }
+
+ if (!deviceID)
+ {
+ CLog::Log(LOGERROR, "{}: Unable to find device {}", __FUNCTION__, device);
+ return false;
+ }
+
+ AEDeviceEnumerationOSX devEnum(deviceID);
+ AudioStreamBasicDescription outputFormat = {};
+ AudioStreamID outputStream = 0;
+ UInt32 numOutputChannels = 0;
+ m_planes = 1;
+ // after FindSuitableFormatForStream requestedStreamIndex will have a valid index and no INT_MAX anymore ...
+ if (devEnum.FindSuitableFormatForStream(requestedStreamIndex, format, false, outputFormat, outputStream))
+ {
+ numOutputChannels = outputFormat.mChannelsPerFrame;
+
+ if (devEnum.IsPlanar())
+ {
+ numOutputChannels = std::min((size_t)format.m_channelLayout.Count(), (size_t)devEnum.GetNumPlanes());
+ m_planes = numOutputChannels;
+ CLog::Log(LOGDEBUG, "{} Found planar audio with {} channels using {} of them.", __FUNCTION__,
+ (unsigned int)devEnum.GetNumPlanes(), (unsigned int)numOutputChannels);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{}, Unable to find suitable stream", __FUNCTION__);
+ return false;
+ }
+
+ AudioStreamBasicDescription outputFormatVirt = {};
+ AudioStreamID outputStreamVirt = 0;
+ UInt32 numOutputChannelsVirt = 0;
+ if (passthrough)
+ {
+ if (devEnum.FindSuitableFormatForStream(requestedStreamIndex, format, true, outputFormatVirt, outputStreamVirt))
+ {
+ numOutputChannelsVirt = outputFormatVirt.mChannelsPerFrame;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{}, Unable to find suitable virtual stream", __FUNCTION__);
+ //return false;
+ numOutputChannelsVirt = 0;
+ }
+ }
+
+ /* Update our AE format */
+ format.m_sampleRate = outputFormat.mSampleRate;
+
+ m_outputBufferIndex = requestedStreamIndex;
+
+ // if we are in passthrough but didn't have a matching
+ // virtual format - enable bitstream which deals with
+ // backconverting from float to 16bit
+ if (passthrough && numOutputChannelsVirt == 0)
+ {
+ m_outputBitstream = true;
+ CLog::Log(LOGDEBUG, "{}: Bitstream passthrough with float -> int16 conversion enabled",
+ __FUNCTION__);
+ }
+
+ std::string formatString;
+ CLog::Log(LOGDEBUG, "{}: Selected stream[{}] - id: {:#04X}, Physical Format: {} {}", __FUNCTION__,
+ (unsigned int)m_outputBufferIndex, (unsigned int)outputStream,
+ StreamDescriptionToString(outputFormat, formatString),
+ passthrough ? "passthrough" : "");
+
+ m_device.Open(deviceID);
+ SetHogMode(passthrough);
+
+ // Configure the output stream object
+ m_outputStream.Open(outputStream);
+
+ AudioStreamBasicDescription virtualFormat, previousPhysicalFormat;
+ m_outputStream.GetVirtualFormat(&virtualFormat);
+ m_outputStream.GetPhysicalFormat(&previousPhysicalFormat);
+ CLog::Log(LOGDEBUG, "{}: Previous Virtual Format: {}", __FUNCTION__,
+ StreamDescriptionToString(virtualFormat, formatString));
+ CLog::Log(LOGDEBUG, "{}: Previous Physical Format: {}", __FUNCTION__,
+ StreamDescriptionToString(previousPhysicalFormat, formatString));
+
+ m_outputStream.SetPhysicalFormat(&outputFormat); // Set the active format (the old one will be reverted when we close)
+ if (passthrough && numOutputChannelsVirt > 0)
+ m_outputStream.SetVirtualFormat(&outputFormatVirt);
+
+ m_outputStream.GetVirtualFormat(&virtualFormat);
+ CLog::Log(LOGDEBUG, "{}: New Virtual Format: {}", __FUNCTION__,
+ StreamDescriptionToString(virtualFormat, formatString));
+ CLog::Log(LOGDEBUG, "{}: New Physical Format: {}", __FUNCTION__,
+ StreamDescriptionToString(outputFormat, formatString));
+
+ if (requestedSourceId != INT_MAX && !m_device.SetDataSource(requestedSourceId))
+ CLog::Log(LOGERROR, "{}: Error setting requested audio source.", __FUNCTION__);
+
+ m_latentFrames = m_device.GetNumLatencyFrames();
+ m_latentFrames += m_outputStream.GetNumLatencyFrames();
+
+ // update the channel map based on the new stream format
+ devEnum.GetAEChannelMap(format.m_channelLayout, numOutputChannels);
+
+ //! @todo Should we use the virtual format to determine our data format?
+ format.m_frameSize = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
+ format.m_frames = m_device.GetBufferSize();
+
+ m_frameSizePerPlane = format.m_frameSize / m_planes;
+ m_framesPerSecond = format.m_sampleRate;
+
+ unsigned int num_buffers = 4;
+ m_buffer = new AERingBuffer(num_buffers * format.m_frames * m_frameSizePerPlane, m_planes);
+ CLog::Log(LOGDEBUG, "{}: using buffer size: {} ({:f} ms)", __FUNCTION__, m_buffer->GetMaxSize(),
+ (float)m_buffer->GetMaxSize() / (m_framesPerSecond * m_frameSizePerPlane));
+
+ if (!passthrough)
+ format.m_dataFormat = (m_planes > 1) ? AE_FMT_FLOATP : AE_FMT_FLOAT;
+
+ // Register for data request callbacks from the driver and start
+ m_device.AddIOProc(renderCallback, this);
+ m_device.Start();
+ return true;
+}
+
+void CAESinkDARWINOSX::SetHogMode(bool on)
+{
+ //! @todo Auto hogging sets this for us. Figure out how/when to turn it off or use it
+ //! It appears that leaving this set will also restore the previous stream format when the
+ //! Application exits. If auto hogging is set and we try to set hog mode, we will deadlock
+ //! From the SDK docs: "If the AudioDevice is in a non-mixable mode, the HAL will automatically take hog mode on behalf of the first process to start an IOProc."
+ //!
+ //! Lock down the device. This MUST be done PRIOR to switching to a non-mixable format, if it is done at all
+ //! If it is attempted after the format change, there is a high likelihood of a deadlock
+ //! We may need to do this sooner to enable mix-disable (i.e. before setting the stream format)
+ if (on)
+ {
+ // Auto-Hog does not always un-hog the device when changing back to a mixable mode.
+ // Handle this on our own until it is fixed.
+ CCoreAudioHardware::SetAutoHogMode(false);
+ bool autoHog = CCoreAudioHardware::GetAutoHogMode();
+ CLog::Log(LOGDEBUG,
+ " CoreAudioRenderer::InitializeEncoded: "
+ "Auto 'hog' mode is set to '{}'.",
+ autoHog ? "On" : "Off");
+ if (autoHog)
+ return;
+ }
+ m_device.SetHogStatus(on);
+ m_device.SetMixingSupport(!on);
+}
+
+void CAESinkDARWINOSX::Deinitialize()
+{
+ m_device.Stop();
+ m_device.RemoveIOProc();
+
+ m_outputStream.Close();
+ m_device.Close();
+ if (m_buffer)
+ {
+ delete m_buffer;
+ m_buffer = NULL;
+ }
+ m_outputBufferIndex = 0;
+ m_outputBitstream = false;
+ m_planes = 1;
+
+ m_started = false;
+}
+
+void CAESinkDARWINOSX::GetDelay(AEDelayStatus& status)
+{
+ /* lockless way of guaranteeing consistency of tick/delay/buffer,
+ * this work since render callback is short and quick and higher
+ * priority compared to this thread, unsigned int are assumed
+ * aligned and having atomic read/write */
+ unsigned int size;
+ CAESpinLock lock(m_render_locker);
+ do
+ {
+ status.tick = m_render_tick;
+ status.delay = m_render_delay;
+ if(m_buffer)
+ size = m_buffer->GetReadSize();
+ else
+ size = 0;
+
+ } while(lock.retry());
+
+ status.delay += (double)size / (double)m_frameSizePerPlane / (double)m_framesPerSecond;
+ status.delay += (double)m_latentFrames / (double)m_framesPerSecond;
+}
+
+double CAESinkDARWINOSX::GetCacheTotal()
+{
+ return (double)m_buffer->GetMaxSize() / (double)(m_frameSizePerPlane * m_framesPerSecond);
+}
+
+CCriticalSection mutex;
+XbmcThreads::ConditionVariable condVar;
+
+unsigned int CAESinkDARWINOSX::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (m_buffer->GetWriteSize() < frames * m_frameSizePerPlane)
+ { // no space to write - wait for a bit
+ std::unique_lock<CCriticalSection> lock(mutex);
+ auto timeout = std::chrono::milliseconds(900 * frames / m_framesPerSecond);
+ if (!m_started)
+ timeout = 4500ms;
+
+ // we are using a timer here for being sure for timeouts
+ // condvar can be woken spuriously as signaled
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+ if (!m_started && timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "{} engine didn't start in {} ms!", __FUNCTION__, timeout.count());
+ return INT_MAX;
+ }
+ }
+
+ unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_frameSizePerPlane);
+ if (write_frames)
+ {
+ for (unsigned int i = 0; i < m_buffer->NumPlanes(); i++)
+ m_buffer->Write(data[i] + offset * m_frameSizePerPlane, write_frames * m_frameSizePerPlane, i);
+ }
+ return write_frames;
+}
+
+void CAESinkDARWINOSX::Drain()
+{
+ int bytes = m_buffer->GetReadSize();
+ int totalBytes = bytes;
+ int maxNumTimeouts = 3;
+ auto timeout = std::chrono::milliseconds(900 * bytes / (m_framesPerSecond * m_frameSizePerPlane));
+ while (bytes && maxNumTimeouts > 0)
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+
+ bytes = m_buffer->GetReadSize();
+ // if we timeout and don't
+ // consume bytes - decrease maxNumTimeouts
+ if (timer.IsTimePast() && bytes == totalBytes)
+ maxNumTimeouts--;
+ totalBytes = bytes;
+ }
+}
+
+void CAESinkDARWINOSX::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ EnumerateDevices();
+ list.clear();
+ for (CADeviceList::const_iterator i = s_devices.begin(); i != s_devices.end(); ++i)
+ list.push_back(i->second);
+}
+
+inline void LogLevel(unsigned int got, unsigned int wanted)
+{
+ static unsigned int lastReported = INT_MAX;
+ if (got != wanted)
+ {
+ if (got != lastReported)
+ {
+ CLog::Log(LOGWARNING, "DARWINOSX: {}flow ({} vs {} bytes)", got > wanted ? "over" : "under",
+ got, wanted);
+ lastReported = got;
+ }
+ }
+ else
+ lastReported = INT_MAX; // indicate we were good at least once
+}
+
+OSStatus CAESinkDARWINOSX::renderCallback(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData)
+{
+ CAESinkDARWINOSX *sink = (CAESinkDARWINOSX*)inClientData;
+
+ sink->m_render_locker.enter(); /* grab lock */
+ sink->m_started = true;
+ if (outOutputData->mNumberBuffers)
+ {
+ //planar always starts at outputbuffer/streamidx 0
+ unsigned int startIdx = sink->m_buffer->NumPlanes() == 1 ? sink->m_outputBufferIndex : 0;
+ unsigned int endIdx = startIdx + sink->m_buffer->NumPlanes();
+
+ /* NOTE: We assume that the buffers are all the same size... */
+ if (sink->m_outputBitstream)
+ {
+ /* HACK for bitstreaming AC3/DTS via PCM.
+ We reverse the float->S16LE conversion done in the stream or device */
+ static const float mul = 1.0f / (INT16_MAX + 1);
+
+ size_t wanted = outOutputData->mBuffers[0].mDataByteSize / sizeof(float) * sizeof(int16_t);
+ size_t bytes = std::min((size_t)sink->m_buffer->GetReadSize(), wanted);
+ for (unsigned int j = 0; j < bytes / sizeof(int16_t); j++)
+ {
+ for (unsigned int i = startIdx; i < endIdx; i++)
+ {
+ int16_t src = 0;
+ sink->m_buffer->Read((unsigned char *)&src, sizeof(int16_t), i);
+ if (i < outOutputData->mNumberBuffers && outOutputData->mBuffers[i].mData)
+ {
+ float *dest = (float *)outOutputData->mBuffers[i].mData;
+ dest[j] = src * mul;
+ }
+ }
+ }
+ LogLevel(bytes, wanted);
+ }
+ else
+ {
+ /* buffers appear to come from CA already zero'd, so just copy what is wanted */
+ unsigned int wanted = outOutputData->mBuffers[0].mDataByteSize;
+ unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
+ for (unsigned int i = startIdx; i < endIdx; i++)
+ {
+ if (i < outOutputData->mNumberBuffers && outOutputData->mBuffers[i].mData)
+ sink->m_buffer->Read((unsigned char *)outOutputData->mBuffers[i].mData, bytes, i);
+ else
+ sink->m_buffer->Read(NULL, bytes, i);
+ }
+ LogLevel(bytes, wanted);
+ }
+
+ // tell the sink we're good for more data
+ condVar.notifyAll();
+ }
+
+ sink->m_render_delay = (double)(inOutputTime->mHostTime - inNow->mHostTime) / CurrentHostFrequency();
+ sink->m_render_tick = inNow->mHostTime;
+ sink->m_render_locker.leave();
+ return noErr;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.h b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.h
new file mode 100644
index 0000000..826ccf4
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Sinks/osx/CoreAudioDevice.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+class AERingBuffer;
+struct AEDelayStatus;
+
+class CAESinkDARWINOSX : public IAESink
+{
+public:
+ const char* GetName() override { return "DARWINOSX"; }
+
+ CAESinkDARWINOSX();
+ ~CAESinkDARWINOSX() override;
+
+ static void Register();
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force);
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+
+ bool Initialize(AEAudioFormat& format, std::string& device) override;
+ void Deinitialize() override;
+
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t** data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+private:
+ static OSStatus renderCallback(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData);
+ void SetHogMode(bool on);
+
+ CAEDeviceInfo m_info;
+
+ CCoreAudioDevice m_device;
+ CCoreAudioStream m_outputStream;
+ unsigned int m_latentFrames = 0;
+ unsigned int m_outputBufferIndex = 0;
+
+ bool m_outputBitstream =
+ false; ///< true if we're bistreaming into a LinearPCM stream rather than AC3 stream.
+ unsigned int m_planes = 1; ///< number of audio planes (1 if non-planar)
+ unsigned int m_frameSizePerPlane = 0; ///< frame size (per plane) in bytes
+ unsigned int m_framesPerSecond = 0; ///< sample rate
+
+ AERingBuffer* m_buffer = nullptr;
+ volatile bool m_started =
+ false; // set once we get a callback from CoreAudio, which can take a little while.
+
+ CAESpinSection m_render_locker;
+ volatile int64_t m_render_tick = 0;
+ volatile double m_render_delay = 0.0;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.h b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.h
new file mode 100644
index 0000000..8a29dde
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+#define DO_440HZ_TONE_TEST 0
+
+#if DO_440HZ_TONE_TEST
+typedef struct
+{
+ float currentPhase;
+ float phaseIncrement;
+} SineWaveGenerator;
+#endif
+
+class AERingBuffer;
+class CAAudioUnitSink;
+
+class CAESinkDARWINTVOS : public IAESink
+{
+public:
+ const char* GetName() override { return "DARWINTVOS"; }
+
+ CAESinkDARWINTVOS();
+ ~CAESinkDARWINTVOS() override = default;
+
+ static void Register();
+ static void EnumerateDevicesEx(AEDeviceInfoList& list, bool force);
+ static IAESink* Create(std::string& device, AEAudioFormat& desiredFormat);
+
+ bool Initialize(AEAudioFormat& format, std::string& device) override;
+ void Deinitialize() override;
+
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t** data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+ bool HasVolume() override;
+
+private:
+ static AEDeviceInfoList m_devices;
+ CAEDeviceInfo m_info;
+ AEAudioFormat m_format;
+
+ CAAudioUnitSink* m_audioSink = nullptr;
+#if DO_440HZ_TONE_TEST
+ SineWaveGenerator m_SineWaveGenerator;
+#endif
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.mm b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.mm
new file mode 100644
index 0000000..955e636
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDARWINTVOS.mm
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkDARWINTVOS.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "cores/AudioEngine/Utils/AERingBuffer.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "threads/Condition.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/darwin/DarwinUtils.h"
+
+#include <mutex>
+#include <sstream>
+
+#import <AVFoundation/AVAudioSession.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <unistd.h>
+
+using namespace std::chrono_literals;
+
+enum CAChannelIndex
+{
+ CAChannel_PCM_6CHAN = 0,
+ CAChannel_PCM_8CHAN = 1,
+ CAChannel_PCM_DD5_1 = 2,
+};
+
+static enum AEChannel CAChannelMap[3][9] = {
+ {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_FC, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FC, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL},
+};
+
+static std::string getAudioRoute()
+{
+ std::string route;
+ AVAudioSession* myAudioSession = [AVAudioSession sharedInstance];
+ AVAudioSessionRouteDescription* currentRoute = [myAudioSession currentRoute];
+ NSString* output = [[currentRoute.outputs firstObject] portType];
+ if (output)
+ route = [output UTF8String];
+
+ return route;
+}
+
+static void dumpAVAudioSessionProperties()
+{
+ std::string route = getAudioRoute();
+ CLog::Log(LOGINFO, "{} audio route = {}", __PRETTY_FUNCTION__, route.empty() ? "NONE" : route);
+
+ AVAudioSession* mySession = [AVAudioSession sharedInstance];
+
+ CLog::Log(LOGINFO, "{} sampleRate {:f}", __PRETTY_FUNCTION__, [mySession sampleRate]);
+ CLog::Log(LOGINFO, "{} outputLatency {:f}", __PRETTY_FUNCTION__, [mySession outputLatency]);
+ CLog::Log(LOGINFO, "{} IOBufferDuration {:f}", __PRETTY_FUNCTION__, [mySession IOBufferDuration]);
+ CLog::Log(LOGINFO, "{} outputNumberOfChannels {}", __PRETTY_FUNCTION__,
+ static_cast<long>([mySession outputNumberOfChannels]));
+ // maximumOutputNumberOfChannels provides hints to tvOS audio settings
+ // if 2, then audio is set to two channel stereo. iOS return this unless hdmi connected
+ // if 6, then audio is set to Digital Dolby 5.1 OR hdmi path detected sink can only handle 6 channels.
+ // if 8, then audio is set to Best Quality AND hdmi path detected sink can handle 8 channels.
+ CLog::Log(LOGINFO, "{} maximumOutputNumberOfChannels {}", __PRETTY_FUNCTION__,
+ static_cast<long>([mySession maximumOutputNumberOfChannels]));
+
+ //CDarwinUtils::DumpAudioDescriptions(__PRETTY_FUNCTION__);
+}
+
+static bool deactivateAudioSession(int count)
+{
+ if (--count < 0)
+ return false;
+
+ bool rtn = false;
+ NSError* err = nullptr;
+ // deactvivate the session
+ AVAudioSession* mySession = [AVAudioSession sharedInstance];
+ if (![mySession setActive:NO error:&err])
+ {
+ CLog::Log(LOGWARNING, "AVAudioSession setActive NO failed, count {}", count);
+ usleep(10 * 1000);
+ rtn = deactivateAudioSession(count);
+ }
+ else
+ {
+ rtn = true;
+ }
+ return rtn;
+}
+
+static void setAVAudioSessionProperties(NSTimeInterval bufferseconds,
+ double samplerate,
+ int channels)
+{
+ // darwin docs and technotes say,
+ // deavtivate the session before changing the values
+ AVAudioSession* mySession = [AVAudioSession sharedInstance];
+
+ // need to fetch maximumOutputNumberOfChannels when active
+ NSInteger maxchannels = [mySession maximumOutputNumberOfChannels];
+
+ NSError* err = nil;
+ // deactvivate the session
+ if (!deactivateAudioSession(10))
+ CLog::Log(LOGWARNING, "AVAudioSession setActive NO failed: {}", static_cast<long>(err.code));
+
+ // change the number of channels
+ if (channels > maxchannels)
+ channels = static_cast<UInt32>(maxchannels);
+ err = nil;
+ [mySession setPreferredOutputNumberOfChannels:channels error:&err];
+ if (err != nil)
+ CLog::Log(LOGWARNING, "{} setPreferredOutputNumberOfChannels failed", __PRETTY_FUNCTION__);
+
+ // change the sameple rate
+ err = nil;
+ [mySession setPreferredSampleRate:samplerate error:&err];
+ if (err != nil)
+ CLog::Log(LOGWARNING, "{} setPreferredSampleRate failed", __PRETTY_FUNCTION__);
+
+ // change the i/o buffer duration
+ err = nil;
+ [mySession setPreferredIOBufferDuration:bufferseconds error:&err];
+ if (err != nil)
+ CLog::Log(LOGWARNING, "{} setPreferredIOBufferDuration failed", __PRETTY_FUNCTION__);
+
+ // reactivate the session
+ err = nil;
+ if (![mySession setActive:YES error:&err])
+ CLog::Log(LOGWARNING, "AVAudioSession setActive YES failed: {}", static_cast<long>(err.code));
+
+ // check that we got the samperate what we asked for
+ if (samplerate != [mySession sampleRate])
+ CLog::Log(LOGWARNING, "sampleRate does not match: asked {:f}, is {:f}", samplerate,
+ [mySession sampleRate]);
+
+ // check that we got the number of channels what we asked for
+ if (channels != [mySession outputNumberOfChannels])
+ CLog::Log(LOGWARNING, "number of channels do not match: asked {}, is {}", channels,
+ static_cast<long>([mySession outputNumberOfChannels]));
+}
+
+#pragma mark - SineWaveGenerator
+/***************************************************************************************/
+/***************************************************************************************/
+#if DO_440HZ_TONE_TEST
+static void SineWaveGeneratorInitWithFrequency(SineWaveGenerator* ctx,
+ double frequency,
+ double samplerate)
+{
+ // Given:
+ // frequency in cycles per second
+ // 2*PI radians per sine wave cycle
+ // sample rate in samples per second
+ //
+ // Then:
+ // cycles radians seconds radians
+ // ------ * ------- * ------- = -------
+ // second cycle sample sample
+ ctx->currentPhase = 0.0;
+ ctx->phaseIncrement = frequency * 2 * M_PI / samplerate;
+}
+
+static int16_t SineWaveGeneratorNextSampleInt16(SineWaveGenerator* ctx)
+{
+ int16_t sample = INT16_MAX * sinf(ctx->currentPhase);
+
+ ctx->currentPhase += ctx->phaseIncrement;
+ // Keep the value between 0 and 2*M_PI
+ while (ctx->currentPhase > 2 * M_PI)
+ ctx->currentPhase -= 2 * M_PI;
+
+ return sample / 4;
+}
+static float SineWaveGeneratorNextSampleFloat(SineWaveGenerator* ctx)
+{
+ float sample = MAXFLOAT * sinf(ctx->currentPhase);
+
+ ctx->currentPhase += ctx->phaseIncrement;
+ // Keep the value between 0 and 2*M_PI
+ while (ctx->currentPhase > 2 * M_PI)
+ ctx->currentPhase -= 2 * M_PI;
+
+ return sample / 4;
+}
+#endif
+
+#pragma mark - CAAudioUnitSink
+/***************************************************************************************/
+/***************************************************************************************/
+class CAAudioUnitSink
+{
+public:
+ CAAudioUnitSink();
+ ~CAAudioUnitSink();
+
+ bool open(AudioStreamBasicDescription outputFormat, size_t buffer_size);
+ bool close();
+ bool activate();
+ bool deactivate();
+ void updatedelay(AEDelayStatus& status);
+ double buffertime();
+ unsigned int sampletrate() { return m_outputFormat.mSampleRate; };
+ unsigned int write(uint8_t* data, unsigned int frames, unsigned int framesize);
+ void drain();
+
+private:
+ bool setupAudio();
+
+ // callbacks
+ static OSStatus renderCallback(void* inRefCon,
+ AudioUnitRenderActionFlags* ioActionFlags,
+ const AudioTimeStamp* inTimeStamp,
+ UInt32 inOutputBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList* ioData);
+
+ bool m_setup;
+ bool m_activated = false;
+ AudioUnit m_audioUnit;
+ AudioStreamBasicDescription m_outputFormat;
+ AERingBuffer* m_buffer = nullptr;
+
+ Float32 m_totalLatency;
+ Float32 m_inputLatency;
+ Float32 m_outputLatency;
+ Float32 m_bufferDuration;
+
+ unsigned int m_sampleRate;
+ unsigned int m_frameSize;
+
+ std::atomic<bool> m_started;
+
+ CAESpinSection m_render_section;
+ std::atomic<int64_t> m_render_timestamp;
+};
+
+CAAudioUnitSink::CAAudioUnitSink() : m_started(false), m_render_timestamp(0)
+{
+}
+
+CAAudioUnitSink::~CAAudioUnitSink()
+{
+ close();
+}
+
+bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat, size_t buffer_size)
+{
+ m_setup = false;
+ m_outputFormat = outputFormat;
+ m_outputLatency = 0.0;
+ m_bufferDuration = 0.0;
+ m_sampleRate = static_cast<unsigned int>(outputFormat.mSampleRate);
+ m_frameSize = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
+
+ m_buffer = new AERingBuffer(buffer_size);
+
+ return setupAudio();
+}
+
+bool CAAudioUnitSink::close()
+{
+ deactivate();
+ delete m_buffer;
+ m_buffer = NULL;
+
+ m_started = false;
+ return true;
+}
+
+bool CAAudioUnitSink::activate()
+{
+ if (!m_activated)
+ {
+ if (setupAudio())
+ {
+ AudioOutputUnitStart(m_audioUnit);
+ m_activated = true;
+ }
+ }
+
+ return m_activated;
+}
+
+bool CAAudioUnitSink::deactivate()
+{
+ if (m_activated)
+ {
+ AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0);
+
+ // this is a delayed call, the OS will block here
+ // until the autio unit actually is stopped.
+ AudioOutputUnitStop(m_audioUnit);
+
+ // detach the render callback on the unit
+ AURenderCallbackStruct callbackStruct = {};
+ AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
+ 0, &callbackStruct, sizeof(callbackStruct));
+
+ AudioUnitUninitialize(m_audioUnit);
+ AudioComponentInstanceDispose(m_audioUnit), m_audioUnit = nullptr;
+
+ m_setup = false;
+ m_activated = false;
+ }
+
+ return m_activated;
+}
+
+void CAAudioUnitSink::updatedelay(AEDelayStatus& status)
+{
+ // return the number of audio frames in buffer, in seconds
+ // use internal framesize, once written,
+ // bytes in buffer are owned by CAAudioUnitSink.
+ unsigned int size;
+ CAESpinLock lock(m_render_section);
+ do
+ {
+ status.tick = m_render_timestamp;
+ status.delay = 0;
+ if (m_buffer)
+ size = m_buffer->GetReadSize();
+ else
+ size = 0;
+ } while (lock.retry());
+
+ // bytes to seconds
+ status.delay += static_cast<double>(size) / static_cast<double>(m_frameSize) /
+ static_cast<double>(m_sampleRate);
+ // add in hw delay and total latency (in seconds)
+ status.delay += static_cast<double>(m_totalLatency);
+}
+
+double CAAudioUnitSink::buffertime()
+{
+ // return the number of audio frames for the total buffer size, in seconds
+ // use internal framesize, buffer is owned by CAAudioUnitSink.
+ double buffertime;
+ buffertime =
+ static_cast<double>(m_buffer->GetMaxSize()) / static_cast<double>(m_frameSize * m_sampleRate);
+ return buffertime;
+}
+
+CCriticalSection mutex;
+XbmcThreads::ConditionVariable condVar;
+
+unsigned int CAAudioUnitSink::write(uint8_t* data, unsigned int frames, unsigned int framesize)
+{
+ // use the passed in framesize instead of internal,
+ // writes are relative to AE formats. once written,
+ // CAAudioUnitSink owns them.
+ if (m_buffer->GetWriteSize() < frames * framesize)
+ { // no space to write - wait for a bit
+ std::unique_lock<CCriticalSection> lock(mutex);
+ auto timeout = std::chrono::milliseconds(900 * frames / m_sampleRate);
+ if (!m_started)
+ timeout = 4500ms;
+
+ // we are using a timer here for being sure for timeouts
+ // condvar can be woken spuriously as signaled
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+ if (!m_started && timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "{} engine didn't start in {} ms!", __FUNCTION__, timeout.count());
+ return INT_MAX;
+ }
+ }
+
+ unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / framesize);
+ if (write_frames)
+ m_buffer->Write(data, write_frames * framesize);
+
+ return write_frames;
+}
+
+void CAAudioUnitSink::drain()
+{
+ unsigned int bytes = m_buffer->GetReadSize();
+ unsigned int totalBytes = bytes;
+ int maxNumTimeouts = 3;
+ auto timeout = std::chrono::milliseconds(static_cast<int>(buffertime()));
+
+ while (bytes && maxNumTimeouts > 0)
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ XbmcThreads::EndTime<> timer(timeout);
+ condVar.wait(mutex, timeout);
+
+ bytes = m_buffer->GetReadSize();
+ // if we timeout and do not consume bytes,
+ // decrease maxNumTimeouts and try again.
+ if (timer.IsTimePast() && bytes == totalBytes)
+ maxNumTimeouts--;
+ totalBytes = bytes;
+ }
+}
+
+bool CAAudioUnitSink::setupAudio()
+{
+ if (m_setup && m_audioUnit)
+ return true;
+
+ // Audio Unit Setup
+ // Describe a default output unit.
+ AudioComponentDescription description = {};
+ description.componentType = kAudioUnitType_Output;
+ description.componentSubType = kAudioUnitSubType_RemoteIO;
+ description.componentManufacturer = kAudioUnitManufacturer_Apple;
+
+ // Get component
+ AudioComponent component;
+ component = AudioComponentFindNext(nullptr, &description);
+ OSStatus status = AudioComponentInstanceNew(component, &m_audioUnit);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error creating audioUnit (error: {})", __PRETTY_FUNCTION__,
+ static_cast<int>(status));
+ return false;
+ }
+
+ // set the hw buffer size (in seconds), this affects the number of samples
+ // that get rendered every time the audio callback is fired.
+ double samplerate = m_outputFormat.mSampleRate;
+ int channels = m_outputFormat.mChannelsPerFrame;
+ NSTimeInterval bufferseconds =
+ 1024 * m_outputFormat.mChannelsPerFrame / m_outputFormat.mSampleRate;
+ CLog::Log(LOGINFO, "{} setting channels {}", __PRETTY_FUNCTION__, channels);
+ CLog::Log(LOGINFO, "{} setting samplerate {:f}", __PRETTY_FUNCTION__, samplerate);
+ CLog::Log(LOGINFO, "{} setting buffer duration to {:f}", __PRETTY_FUNCTION__, bufferseconds);
+ setAVAudioSessionProperties(bufferseconds, samplerate, channels);
+
+ // Get the real output samplerate, the requested might not available
+ Float64 realisedSampleRate = [[AVAudioSession sharedInstance] sampleRate];
+ if (m_outputFormat.mSampleRate != realisedSampleRate)
+ {
+ CLog::Log(LOGINFO,
+ "{} couldn't set requested samplerate {}, AudioUnit will resample to {} instead",
+ __PRETTY_FUNCTION__, static_cast<int>(m_outputFormat.mSampleRate),
+ static_cast<int>(realisedSampleRate));
+ // if we don't want AudioUnit to resample - but instead let activeae resample -
+ // reflect the realised samplerate to the output format here
+ // well maybe it is handy in the future - as of writing this
+ // AudioUnit was about 6 times faster then activeae ;)
+ //m_outputFormat.mSampleRate = realisedSampleRate;
+ //m_sampleRate = realisedSampleRate;
+ }
+
+ // Set the output stream format
+ UInt32 ioDataSize = sizeof(AudioStreamBasicDescription);
+ status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
+ 0, &m_outputFormat, ioDataSize);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error setting stream format on audioUnit (error: {})",
+ __PRETTY_FUNCTION__, static_cast<int>(status));
+ return false;
+ }
+
+ // Attach a render callback on the unit
+ AURenderCallbackStruct callbackStruct = {};
+ callbackStruct.inputProc = renderCallback;
+ callbackStruct.inputProcRefCon = this;
+ status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error setting render callback for AudioUnit (error: {})",
+ __PRETTY_FUNCTION__, static_cast<int>(status));
+ return false;
+ }
+
+ status = AudioUnitInitialize(m_audioUnit);
+ if (status != noErr)
+ {
+ CLog::Log(LOGERROR, "{} error initializing AudioUnit (error: {})", __PRETTY_FUNCTION__,
+ static_cast<int>(status));
+ return false;
+ }
+
+ AVAudioSession* mySession = [AVAudioSession sharedInstance];
+ m_inputLatency = [mySession inputLatency];
+ m_outputLatency = [mySession outputLatency];
+ m_bufferDuration = [mySession IOBufferDuration];
+ m_totalLatency = m_outputLatency + m_bufferDuration;
+ CLog::Log(LOGINFO, "{} total latency = {:f}", __PRETTY_FUNCTION__, m_totalLatency);
+
+ m_setup = true;
+ std::string formatString;
+ CLog::Log(LOGINFO, "{} setup audio format: {}", __PRETTY_FUNCTION__,
+ StreamDescriptionToString(m_outputFormat, formatString));
+
+ dumpAVAudioSessionProperties();
+
+ return m_setup;
+}
+
+inline void LogLevel(unsigned int got, unsigned int wanted)
+{
+ static unsigned int lastReported = INT_MAX;
+ if (got != wanted)
+ {
+ if (got != lastReported)
+ {
+ CLog::Log(LOGWARNING, "DARWINIOS: {}flow ({} vs {} bytes)", got > wanted ? "over" : "under",
+ got, wanted);
+ lastReported = got;
+ }
+ }
+ else
+ lastReported = INT_MAX; // indicate we were good at least once
+}
+
+OSStatus CAAudioUnitSink::renderCallback(void* inRefCon,
+ AudioUnitRenderActionFlags* ioActionFlags,
+ const AudioTimeStamp* inTimeStamp,
+ UInt32 inOutputBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList* ioData)
+{
+ CAAudioUnitSink* sink = (CAAudioUnitSink*)inRefCon;
+
+ sink->m_render_section.enter();
+ sink->m_started = true;
+
+ for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
+ {
+ unsigned int wanted = ioData->mBuffers[i].mDataByteSize;
+ unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
+ sink->m_buffer->Read(static_cast<unsigned char*>(ioData->mBuffers[i].mData), bytes);
+ LogLevel(bytes, wanted);
+
+ if (bytes == 0)
+ {
+ // Apple iOS docs say kAudioUnitRenderAction_OutputIsSilence provides a hint to
+ // the audio unit that there is no audio to process. and you must also explicitly
+ // set the buffers contents pointed at by the ioData parameter to 0.
+ memset(ioData->mBuffers[i].mData, 0x00, ioData->mBuffers[i].mDataByteSize);
+ *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
+ }
+ else if (bytes < wanted)
+ {
+ // zero out what we did not copy over (underflow)
+ uint8_t* empty = static_cast<uint8_t*>(ioData->mBuffers[i].mData) + bytes;
+ memset(empty, 0x00, wanted - bytes);
+ }
+ }
+
+ sink->m_render_timestamp = inTimeStamp->mHostTime;
+ sink->m_render_section.leave();
+ // tell the sink we're good for more data
+ condVar.notifyAll();
+
+ return noErr;
+}
+
+#pragma mark - EnumerateDevices
+/***************************************************************************************/
+/***************************************************************************************/
+static void EnumerateDevices(AEDeviceInfoList& list)
+{
+ CAEDeviceInfo device;
+
+ device.m_deviceName = "default";
+ device.m_displayName = "Default";
+ device.m_displayNameExtra = "";
+
+ // if not hdmi, CAESinkDARWINIOS::Initialize will kick back to 2 channel PCM
+ device.m_deviceType = AE_DEVTYPE_HDMI;
+ device.m_wantsIECPassthrough = true;
+
+ // Passthrough only working < tvos 11.2??
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+
+ device.m_sampleRates.push_back(44100);
+ device.m_sampleRates.push_back(48000);
+
+ device.m_dataFormats.push_back(AE_FMT_RAW);
+ device.m_dataFormats.push_back(AE_FMT_S16LE);
+ device.m_dataFormats.push_back(AE_FMT_FLOAT);
+
+ // add channel info
+ NSInteger maxChannels = [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels];
+ if (maxChannels > 6)
+ device.m_channels = AE_CH_LAYOUT_7_1;
+ else
+ device.m_channels = AE_CH_LAYOUT_5_1;
+
+ CLog::Log(LOGDEBUG, "EnumerateDevices:Device({})", device.m_deviceName);
+
+ list.push_back(device);
+}
+
+#pragma mark - AEDeviceInfoList
+/***************************************************************************************/
+/***************************************************************************************/
+AEDeviceInfoList CAESinkDARWINTVOS::m_devices;
+
+CAESinkDARWINTVOS::CAESinkDARWINTVOS()
+{
+}
+
+void CAESinkDARWINTVOS::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "DARWINTVOS";
+ reg.createFunc = CAESinkDARWINTVOS::Create;
+ reg.enumerateFunc = CAESinkDARWINTVOS::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkDARWINTVOS::Create(std::string& device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkDARWINTVOS();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkDARWINTVOS::Initialize(AEAudioFormat& format, std::string& device)
+{
+ std::string route = getAudioRoute();
+ // no route, no audio. bail and let AE kick back to NULL device
+ if (route.empty())
+ return false;
+
+ // no device, bail and let AE kick back to NULL device
+ bool found = false;
+ std::string devicelower = device;
+ StringUtils::ToLower(devicelower);
+ for (size_t i = 0; i < m_devices.size(); i++)
+ {
+ if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
+ {
+ m_info = m_devices[i];
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+
+ AudioStreamBasicDescription audioFormat = {};
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+
+ // check if are we dealing with raw formats or pcm
+ bool passthrough = false;
+ switch (format.m_dataFormat)
+ {
+ case AE_FMT_RAW:
+ // this will be selected when AE wants AC3 or DTS or anything other then float
+ format.m_dataFormat = AE_FMT_S16LE;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
+ if (route.find("HDMI") != std::string::npos)
+ passthrough = true;
+ else
+ {
+ // this should never happen but we cover it just in case
+ // for iOS/tvOS, if we are not hdmi, we cannot do raw
+ // so kick back to pcm.
+ format.m_dataFormat = AE_FMT_FLOAT;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
+ }
+ break;
+ default:
+ // AE lies, even when we register formats we can handle,
+ // it shoves everything down and it is up to the sink
+ // to check/verify and kick back to what the sink supports
+ format.m_dataFormat = AE_FMT_FLOAT;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
+ break;
+ }
+
+ // check and correct sample rates to what we support,
+ // remember, AE is a lier and we need to check/verify
+ // and kick back to what the sink supports
+ switch (format.m_sampleRate)
+ {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ case 176400:
+ if (route.find("HDMI") != std::string::npos)
+ audioFormat.mSampleRate = 48000;
+ else
+ audioFormat.mSampleRate = 44100;
+ break;
+ default:
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 96000:
+ case 192000:
+ case 384000:
+ audioFormat.mSampleRate = 48000;
+ break;
+ }
+
+ if (passthrough)
+ {
+ // passthrough is special, PCM encapsulated IEC61937 packets.
+ // make sure input and output samplerate match for preventing resampling
+ audioFormat.mSampleRate = [[AVAudioSession sharedInstance] sampleRate];
+ audioFormat.mFramesPerPacket = 1; // must be 1
+ audioFormat.mChannelsPerFrame = 2; // passthrough needs 2 channels
+ audioFormat.mBitsPerChannel = 16;
+ audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel >> 3);
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+ }
+ else
+ {
+ NSInteger maxChannels = [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels];
+ audioFormat.mFramesPerPacket = 1; // must be 1
+
+ // tvos supports up to 8 channels
+ audioFormat.mChannelsPerFrame = format.m_channelLayout.Count();
+ // clamp number of channels to what tvOS reports
+ if (maxChannels == 2)
+ audioFormat.mChannelsPerFrame = (UInt32)maxChannels;
+
+ audioFormat.mBitsPerChannel = CAEUtil::DataFormatToBits(format.m_dataFormat);
+ audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel >> 3);
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+ audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+
+ CAEChannelInfo channel_info;
+ CAChannelIndex channel_index = CAChannel_PCM_6CHAN;
+ if (maxChannels == 6 && format.m_channelLayout.Count() == 6)
+ {
+ // if 6, then audio is set to Digital Dolby 5.1, need to use DD mapping
+ channel_index = CAChannel_PCM_DD5_1;
+ }
+ else if (format.m_channelLayout.Count() == 5)
+ {
+ // if 5, then audio is set to Digital Dolby 5.0, need to use DD mapping
+ channel_index = CAChannel_PCM_DD5_1;
+ }
+ else
+ {
+ if (format.m_channelLayout.Count() > 6)
+ channel_index = CAChannel_PCM_8CHAN;
+ }
+ for (size_t chan = 0; chan < format.m_channelLayout.Count(); ++chan)
+ {
+ if (chan < maxChannels)
+ channel_info += CAChannelMap[channel_index][chan];
+ }
+ format.m_channelLayout = channel_info;
+ }
+
+ std::string formatString;
+ CLog::Log(LOGDEBUG, "{}: AudioStreamBasicDescription: {} {}", __PRETTY_FUNCTION__,
+ StreamDescriptionToString(audioFormat, formatString),
+ passthrough ? "passthrough" : "pcm");
+
+#if DO_440HZ_TONE_TEST
+ SineWaveGeneratorInitWithFrequency(&m_SineWaveGenerator, 440.0, audioFormat.mSampleRate);
+#endif
+
+ size_t buffer_size;
+ switch (format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ if (!format.m_streamInfo.m_ac3FrameSize)
+ format.m_streamInfo.m_ac3FrameSize = 1536;
+ format.m_frames = format.m_streamInfo.m_ac3FrameSize;
+ buffer_size = format.m_frames * 8;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ if (!format.m_streamInfo.m_ac3FrameSize)
+ format.m_streamInfo.m_ac3FrameSize = 1536;
+ format.m_frames = format.m_streamInfo.m_ac3FrameSize;
+ buffer_size = format.m_frames * 8;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ format.m_frames = 512;
+ buffer_size = 16384;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ format.m_frames = 1024;
+ buffer_size = 16384;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ format.m_frames = 2048;
+ buffer_size = 16384;
+ break;
+ default:
+ format.m_frames = 1024;
+ buffer_size = (512 * audioFormat.mBytesPerFrame) * 8;
+ break;
+ }
+ m_audioSink = new CAAudioUnitSink;
+ m_audioSink->open(audioFormat, buffer_size);
+ // reset to the realised samplerate
+ format.m_sampleRate = m_audioSink->sampletrate();
+ format.m_frameSize =
+ format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
+
+ m_format = format;
+
+ if (!m_audioSink->activate())
+ return false;
+
+ return true;
+}
+
+void CAESinkDARWINTVOS::Deinitialize()
+{
+ delete m_audioSink;
+ m_audioSink = nullptr;
+}
+
+void CAESinkDARWINTVOS::GetDelay(AEDelayStatus& status)
+{
+ if (m_audioSink)
+ m_audioSink->updatedelay(status);
+ else
+ status.SetDelay(0.0);
+}
+
+double CAESinkDARWINTVOS::GetCacheTotal()
+{
+ if (m_audioSink)
+ return m_audioSink->buffertime();
+ return 0.0;
+}
+
+unsigned int CAESinkDARWINTVOS::AddPackets(uint8_t** data, unsigned int frames, unsigned int offset)
+{
+ uint8_t* buffer = data[0] + (offset * m_format.m_frameSize);
+#if DO_440HZ_TONE_TEST
+ if (m_format.m_dataFormat == AE_FMT_FLOAT)
+ {
+ float* samples = static_cast<float*>(buffer);
+ for (unsigned int j = 0; j < frames; j++)
+ {
+ float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
+ *samples++ = sample;
+ *samples++ = sample;
+ }
+ }
+ else
+ {
+ int16_t* samples = (int16_t*)buffer;
+ for (unsigned int j = 0; j < frames; j++)
+ {
+ int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
+ *samples++ = sample;
+ *samples++ = sample;
+ }
+ }
+#endif
+ if (m_audioSink)
+ return m_audioSink->write(buffer, frames, m_format.m_frameSize);
+ return 0;
+}
+
+void CAESinkDARWINTVOS::Drain()
+{
+ if (m_audioSink)
+ m_audioSink->drain();
+}
+
+bool CAESinkDARWINTVOS::HasVolume()
+{
+ return false;
+}
+
+void CAESinkDARWINTVOS::EnumerateDevicesEx(AEDeviceInfoList& list, bool force)
+{
+ m_devices.clear();
+ EnumerateDevices(m_devices);
+ list = m_devices;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp
new file mode 100644
index 0000000..653e5c4
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#define INITGUID
+
+
+#include "AESinkDirectSound.h"
+
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include "platform/win32/CharsetConverter.h"
+
+#include <algorithm>
+#include <list>
+#include <mutex>
+
+#include <Audioclient.h>
+#include <Mmreg.h>
+#include <Rpc.h>
+#include <initguid.h>
+
+// include order is important here
+// clang-format off
+#include <mmdeviceapi.h>
+#include <Functiondiscoverykeys_devpkey.h>
+// clang-format on
+
+#pragma comment(lib, "Rpcrt4.lib")
+
+extern HWND g_hWnd;
+
+DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
+DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
+
+extern const char *WASAPIErrToStr(HRESULT err);
+#define EXIT_ON_FAILURE(hr, reason) \
+ if (FAILED(hr)) \
+ { \
+ CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
+ goto failed; \
+ }
+
+#define DS_SPEAKER_COUNT 8
+static const unsigned int DSChannelOrder[] = {SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT};
+static const enum AEChannel AEChannelNamesDS[] = {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR, AE_CH_NULL};
+
+using namespace Microsoft::WRL;
+
+struct DSDevice
+{
+ std::string name;
+ LPGUID lpGuid;
+};
+
+static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext)
+{
+ DSDevice dev;
+ std::list<DSDevice> &enumerator = *static_cast<std::list<DSDevice>*>(lpContext);
+
+ dev.name = KODI::PLATFORM::WINDOWS::FromW(lpcstrDescription);
+
+ dev.lpGuid = lpGuid;
+
+ if (lpGuid)
+ enumerator.push_back(dev);
+
+ return TRUE;
+}
+
+CAESinkDirectSound::CAESinkDirectSound() :
+ m_pBuffer (nullptr),
+ m_pDSound (nullptr),
+ m_encodedFormat (AE_FMT_INVALID),
+ m_AvgBytesPerSec(0 ),
+ m_dwChunkSize (0 ),
+ m_dwFrameSize (0 ),
+ m_dwBufferLen (0 ),
+ m_BufferOffset (0 ),
+ m_CacheLen (0 ),
+ m_BufferTimeouts(0 ),
+ m_running (false),
+ m_initialized (false),
+ m_isDirtyDS (false)
+{
+ m_channelLayout.Reset();
+}
+
+CAESinkDirectSound::~CAESinkDirectSound()
+{
+ Deinitialize();
+}
+
+void CAESinkDirectSound::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "DIRECTSOUND";
+ reg.createFunc = CAESinkDirectSound::Create;
+ reg.enumerateFunc = CAESinkDirectSound::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkDirectSound::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ IAESink *sink = new CAESinkDirectSound();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
+{
+ if (m_initialized)
+ return false;
+
+ LPGUID deviceGUID = nullptr;
+ RPC_WSTR wszUuid = nullptr;
+ HRESULT hr = E_FAIL;
+ std::string strDeviceGUID = device;
+ std::list<DSDevice> DSDeviceList;
+ std::string deviceFriendlyName;
+ DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
+
+ if(StringUtils::EndsWithNoCase(device, std::string("default")))
+ strDeviceGUID = GetDefaultDevice();
+
+ for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
+ {
+ if ((*itt).lpGuid)
+ {
+ hr = (UuidToString((*itt).lpGuid, &wszUuid));
+ std::string sztmp = KODI::PLATFORM::WINDOWS::FromW(reinterpret_cast<wchar_t*>(wszUuid));
+ std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
+ if (StringUtils::CompareNoCase(szGUID, strDeviceGUID) == 0)
+ {
+ deviceGUID = (*itt).lpGuid;
+ deviceFriendlyName = (*itt).name.c_str();
+ break;
+ }
+ }
+ if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
+ }
+
+ hr = DirectSoundCreate(deviceGUID, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(
+ LOGERROR,
+ "Failed to create the DirectSound device {} with error {}, trying the default device.",
+ deviceFriendlyName, dserr2str(hr));
+
+ hr = DirectSoundCreate(nullptr, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Failed to create the default DirectSound device with error {}.",
+ dserr2str(hr));
+ return false;
+ }
+ }
+
+ /* Dodge the null handle on first init by using desktop handle */
+ HWND tmp_hWnd = g_hWnd == nullptr ? GetDesktopWindow() : g_hWnd;
+ CLog::LogF(LOGDEBUG, "Using Window handle: {}", fmt::ptr(tmp_hWnd));
+
+ hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Failed to create the DirectSound device cooperative level.");
+ CLog::LogF(LOGERROR, "DSErr: {}", dserr2str(hr));
+ m_pDSound = nullptr;
+ return false;
+ }
+
+ // clamp samplerate between 44100 and 192000
+ if (format.m_sampleRate < 44100)
+ format.m_sampleRate = 44100;
+
+ if (format.m_sampleRate > 192000)
+ format.m_sampleRate = 192000;
+
+ // fill waveformatex
+ WAVEFORMATEXTENSIBLE wfxex = {};
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
+ wfxex.Format.nChannels = format.m_channelLayout.Count();
+ wfxex.Format.nSamplesPerSec = format.m_sampleRate;
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
+ wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Format.nChannels = 2;
+ }
+ else
+ {
+ wfxex.dwChannelMask = SpeakerMaskFromAEChannels(format.m_channelLayout);
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ wfxex.Format.wBitsPerSample = 32;
+ }
+
+ wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
+
+ unsigned int uiFrameCount = (int)(format.m_sampleRate * 0.015); //default to 15ms chunks
+ m_dwFrameSize = wfxex.Format.nBlockAlign;
+ m_dwChunkSize = m_dwFrameSize * uiFrameCount;
+ m_dwBufferLen = m_dwChunkSize * 12; //180ms total buffer
+
+ // fill in the secondary sound buffer descriptor
+ DSBUFFERDESC dsbdesc = {};
+ dsbdesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
+ | DSBCAPS_TRUEPLAYPOSITION /** Vista+ accurate position */
+ | DSBCAPS_GLOBALFOCUS; /** Allows background playing */
+
+ dsbdesc.dwBufferBytes = m_dwBufferLen;
+ dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
+
+ // now create the stream buffer
+ HRESULT res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
+ if (res != DS_OK)
+ {
+ if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
+ {
+ CLog::LogF(LOGDEBUG, "Couldn't create secondary buffer ({}). Trying without LOCHARDWARE.",
+ dserr2str(res));
+ // Try without DSBCAPS_LOCHARDWARE
+ dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
+ res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
+ }
+ if (res != DS_OK)
+ {
+ m_pBuffer = nullptr;
+ CLog::LogF(LOGERROR, "cannot create secondary buffer ({})", dserr2str(res));
+ return false;
+ }
+ }
+ CLog::LogF(LOGDEBUG, "secondary buffer created");
+
+ m_pBuffer->Stop();
+
+ AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
+ format.m_channelLayout = m_channelLayout;
+ m_encodedFormat = format.m_dataFormat;
+ format.m_frames = uiFrameCount;
+ format.m_frameSize = ((format.m_dataFormat == AE_FMT_RAW) ? (wfxex.Format.wBitsPerSample >> 3) : sizeof(float)) * format.m_channelLayout.Count();
+ format.m_dataFormat = (format.m_dataFormat == AE_FMT_RAW) ? AE_FMT_S16NE : AE_FMT_FLOAT;
+
+ m_format = format;
+ m_device = device;
+
+ m_BufferOffset = 0;
+ m_CacheLen = 0;
+ m_initialized = true;
+ m_isDirtyDS = false;
+
+ CLog::LogF(LOGDEBUG, "Initializing DirectSound with the following parameters:");
+ CLog::Log(LOGDEBUG, " Audio Device : {}", ((std::string)deviceFriendlyName));
+ CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
+ CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
+ CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
+ CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
+ CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
+ CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
+ CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
+ CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
+ CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
+ CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
+ CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames);
+ CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize);
+
+ return true;
+}
+
+void CAESinkDirectSound::Deinitialize()
+{
+ if (!m_initialized)
+ return;
+
+ CLog::LogF(LOGDEBUG, "Cleaning up");
+
+ if (m_pBuffer)
+ {
+ m_pBuffer->Stop();
+ }
+
+ m_initialized = false;
+ m_pBuffer = nullptr;
+ m_pDSound = nullptr;
+ m_BufferOffset = 0;
+ m_CacheLen = 0;
+ m_dwChunkSize = 0;
+ m_dwBufferLen = 0;
+}
+
+unsigned int CAESinkDirectSound::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_initialized)
+ return 0;
+
+ DWORD total = m_dwFrameSize * frames;
+ DWORD len = total;
+ unsigned char* pBuffer = (unsigned char*)data[0]+offset*m_format.m_frameSize;
+
+ DWORD bufferStatus = 0;
+ if (m_pBuffer->GetStatus(&bufferStatus) != DS_OK)
+ {
+ CLog::LogF(LOGERROR, "GetStatus() failed");
+ return 0;
+ }
+ if (bufferStatus & DSBSTATUS_BUFFERLOST)
+ {
+ CLog::LogF(LOGDEBUG, "Buffer allocation was lost. Restoring buffer.");
+ m_pBuffer->Restore();
+ }
+
+ while (GetSpace() < total)
+ {
+ if(m_isDirtyDS)
+ return INT_MAX;
+ else
+ {
+ KODI::TIME::Sleep(
+ std::chrono::milliseconds(static_cast<int>(total * 1000 / m_AvgBytesPerSec)));
+ }
+ }
+
+ while (len)
+ {
+ void* start = nullptr, *startWrap = nullptr;
+ DWORD size = 0, sizeWrap = 0;
+ if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
+ m_BufferOffset = 0;
+ DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len);
+ HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0);
+ if (DS_OK != res)
+ {
+ CLog::LogF(LOGERROR, "Unable to lock buffer at offset {}. HRESULT: {:#08x}", m_BufferOffset,
+ res);
+ m_isDirtyDS = true;
+ return INT_MAX;
+ }
+
+ memcpy(start, pBuffer, size);
+
+ pBuffer += size;
+ len -= size;
+
+ m_BufferOffset += size;
+ if (startWrap) // Write-region wraps to beginning of buffer
+ {
+ memcpy(startWrap, pBuffer, sizeWrap);
+ m_BufferOffset = sizeWrap;
+
+ pBuffer += sizeWrap;
+ len -= sizeWrap;
+ }
+
+ m_CacheLen += size + sizeWrap; // This data is now in the cache
+ m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
+ }
+
+ CheckPlayStatus();
+
+ return (total - len) / m_dwFrameSize; // Frames used
+}
+
+void CAESinkDirectSound::Stop()
+{
+ if (m_pBuffer)
+ m_pBuffer->Stop();
+}
+
+void CAESinkDirectSound::Drain()
+{
+ if (!m_initialized || m_isDirtyDS)
+ return;
+
+ m_pBuffer->Stop();
+ HRESULT res = m_pBuffer->SetCurrentPosition(0);
+ if (DS_OK != res)
+ {
+ CLog::LogF(LOGERROR,
+ "SetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
+ res);
+ m_isDirtyDS = true;
+ return;
+ }
+ m_BufferOffset = 0;
+ UpdateCacheStatus();
+}
+
+void CAESinkDirectSound::GetDelay(AEDelayStatus& status)
+{
+ if (!m_initialized)
+ {
+ status.SetDelay(0);
+ return;
+ }
+
+ /* Make sure we know how much data is in the cache */
+ if (!UpdateCacheStatus())
+ m_isDirtyDS = true;
+
+ /** returns current cached data duration in seconds */
+ status.SetDelay((double)m_CacheLen / (double)m_AvgBytesPerSec);
+}
+
+double CAESinkDirectSound::GetCacheTotal()
+{
+ /** returns total cache capacity in seconds */
+ return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
+}
+
+void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
+{
+ CAEDeviceInfo deviceInfo;
+
+ ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
+ UINT uiCount = 0;
+
+ HRESULT hr;
+
+ std::string strDD = GetDefaultDevice();
+
+ /* Windows Vista or later - supporting WASAPI device probing */
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
+
+ hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
+
+ hr = pEnumDevices->GetCount(&uiCount);
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
+
+ for (UINT i = 0; i < uiCount; i++)
+ {
+ ComPtr<IMMDevice> pDevice = nullptr;
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+ PropVariantInit(&varName);
+
+ deviceInfo.m_channels.Reset();
+ deviceInfo.m_dataFormats.clear();
+ deviceInfo.m_sampleRates.clear();
+
+ hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint failed.");
+ goto failed;
+ }
+
+ hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint properties failed.");
+ goto failed;
+ }
+
+ hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint device name failed.");
+ goto failed;
+ }
+
+ std::string strFriendlyName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint GUID failed.");
+ goto failed;
+ }
+
+ std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint form factor failed.");
+ goto failed;
+ }
+ std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
+ AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
+
+ PropVariantClear(&varName);
+
+ /* In shared mode Windows tells us what format the audio must be in. */
+ ComPtr<IAudioClient> pClient;
+ hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast<void**>(pClient.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Activate device failed.")
+
+ //hr = pClient->GetMixFormat(&pwfxex);
+ hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
+ if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
+ {
+ WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
+ deviceInfo.m_channels = layoutsByChCount[std::max(std::min(smpwfxex->nChannels, (WORD) DS_SPEAKER_COUNT), (WORD) 2)];
+ deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
+ if (aeDeviceType != AE_DEVTYPE_PCM)
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ // DTS is played with the same infrastructure as AC3
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ // signal that we can doe AE_FMT_RAW
+ deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 192000));
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Getting DeviceFormat failed ({})", WASAPIErrToStr(hr));
+ }
+
+ deviceInfo.m_deviceName = strDevName;
+ deviceInfo.m_displayName = strWinDevType.append(strFriendlyName);
+ deviceInfo.m_displayNameExtra = std::string("DIRECTSOUND: ").append(strFriendlyName);
+ deviceInfo.m_deviceType = aeDeviceType;
+
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfoList.push_back(deviceInfo);
+
+ // add the default device with m_deviceName = default
+ if(strDD == strDevName)
+ {
+ deviceInfo.m_deviceName = std::string("default");
+ deviceInfo.m_displayName = std::string("default");
+ deviceInfo.m_displayNameExtra = std::string("");
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfoList.push_back(deviceInfo);
+ }
+ }
+
+ return;
+
+failed:
+
+ if (FAILED(hr))
+ CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CAESinkDirectSound::CheckPlayStatus()
+{
+ DWORD status = 0;
+ if (m_pBuffer->GetStatus(&status) != DS_OK)
+ {
+ CLog::LogF(LOGERROR, "GetStatus() failed");
+ return;
+ }
+
+ if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
+ {
+ HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
+ CLog::LogF(LOGDEBUG, "Resuming Playback");
+ if (FAILED(hr))
+ CLog::LogF(LOGERROR, "Failed to play the DirectSound buffer: {}", dserr2str(hr));
+ }
+}
+
+bool CAESinkDirectSound::UpdateCacheStatus()
+{
+ std::unique_lock<CCriticalSection> lock(m_runLock);
+
+ DWORD playCursor = 0, writeCursor = 0;
+ HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
+ if (DS_OK != res)
+ {
+ CLog::LogF(LOGERROR,
+ "GetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
+ res);
+ m_isDirtyDS = true;
+ return false;
+ }
+
+ // Check the state of the ring buffer (P->O->W == underrun)
+ // These are the logical situations that can occur
+ // O: CurrentOffset W: WriteCursor P: PlayCursor
+ // | | | | | | | | | |
+ // ***O----W----P***** < underrun P > W && O < W (1)
+ // | | | | | | | | | |
+ // ---P****O----W----- < underrun O > P && O < W (2)
+ // | | | | | | | | | |
+ // ---W----P****O----- < underrun P > W && P < O (3)
+ // | | | | | | | | | |
+ // ***W****O----P***** P > W && P > O (4)
+ // | | | | | | | | | |
+ // ---P****W****O----- P < W && O > W (5)
+ // | | | | | | | | | |
+ // ***O----P****W***** P < W && O < P (6)
+
+ // Check for underruns
+ if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
+ (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
+ (playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
+ {
+ CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:{}, P:{}, O:{}.",
+ writeCursor, playCursor, m_BufferOffset);
+ m_BufferOffset = writeCursor; // Catch up
+ //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
+ m_BufferTimeouts++;
+ if (m_BufferTimeouts > 10)
+ {
+ m_isDirtyDS = true;
+ return false;
+ }
+ }
+ else
+ m_BufferTimeouts = 0;
+
+ // Calculate available space in the ring buffer
+ if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
+ m_CacheLen = 0;
+ else if (m_BufferOffset > playCursor)
+ m_CacheLen = m_BufferOffset - playCursor;
+ else
+ m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
+
+ return true;
+}
+
+unsigned int CAESinkDirectSound::GetSpace()
+{
+ std::unique_lock<CCriticalSection> lock(m_runLock);
+ if (!UpdateCacheStatus())
+ m_isDirtyDS = true;
+ unsigned int space = m_dwBufferLen - m_CacheLen;
+
+ // We can never allow the internal buffers to fill up complete
+ // as we get confused between if the buffer is full or empty
+ // so never allow the last chunk to be added
+ if (space > m_dwChunkSize)
+ return space - m_dwChunkSize;
+ else
+ return 0;
+}
+
+void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
+{
+ m_channelLayout.Reset();
+
+ for (int i = 0; i < DS_SPEAKER_COUNT; i++)
+ {
+ if (speakers & DSChannelOrder[i])
+ m_channelLayout += AEChannelNamesDS[i];
+ }
+}
+
+DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
+{
+ DWORD mask = 0;
+
+ for (unsigned int i = 0; i < channels.Count(); i++)
+ {
+ for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
+ if (channels[i] == AEChannelNamesDS[j])
+ mask |= DSChannelOrder[j];
+ }
+
+ return mask;
+}
+
+const char *CAESinkDirectSound::dserr2str(int err)
+{
+ switch (err)
+ {
+ case DS_OK: return "DS_OK";
+ case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
+ case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
+ case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
+ case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
+ case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
+ case DSERR_GENERIC: return "DSERR_GENERIC";
+ case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
+ case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
+ case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
+ case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
+ case DSERR_NODRIVER: return "DSERR_NODRIVER";
+ case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
+ case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
+ case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
+ case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
+ case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
+ case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
+ case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
+ case DSERR_BUFFERTOOSMALL: return "DSERR_BUFFERTOOSMALL";
+ case DSERR_DS8_REQUIRED: return "DSERR_DS8_REQUIRED";
+ case DSERR_SENDLOOP: return "DSERR_SENDLOOP";
+ case DSERR_BADSENDBUFFERGUID: return "DSERR_BADSENDBUFFERGUID";
+ case DSERR_OBJECTNOTFOUND: return "DSERR_OBJECTNOTFOUND";
+ case DSERR_FXUNAVAILABLE: return "DSERR_FXUNAVAILABLE";
+ default: return "unknown";
+ }
+}
+
+std::string CAESinkDirectSound::GetDefaultDevice()
+{
+ HRESULT hr;
+ ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ ComPtr<IMMDevice> pDevice = nullptr;
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+ std::string strDevName = "default";
+ AEDeviceType aeDeviceType;
+
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator")
+
+ hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, pDevice.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
+
+ hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint properties failed.")
+
+ PropVariantInit(&varName);
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
+ EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint form factor failed.")
+
+ aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
+ EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint GUID failed")
+
+ strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+failed:
+
+ return strDevName;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h
new file mode 100644
index 0000000..6eabca4
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "threads/CriticalSection.h"
+
+#include <stdint.h>
+
+#include <mmsystem.h> /* Microsoft can't write standalone headers */
+#include <DSound.h> /* Microsoft can't write standalone headers */
+#include <wrl/client.h>
+
+class CAESinkDirectSound : public IAESink
+{
+public:
+ virtual const char *GetName() { return "DIRECTSOUND"; }
+
+ CAESinkDirectSound();
+ virtual ~CAESinkDirectSound();
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+
+ virtual bool Initialize(AEAudioFormat &format, std::string &device);
+ virtual void Deinitialize();
+
+ virtual void Stop();
+ virtual void Drain();
+ virtual void GetDelay(AEDelayStatus& status);
+ virtual double GetCacheTotal();
+ virtual unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset);
+
+ static std::string GetDefaultDevice();
+ static void EnumerateDevicesEx (AEDeviceInfoList &deviceInfoList, bool force = false);
+private:
+ void AEChannelsFromSpeakerMask(DWORD speakers);
+ DWORD SpeakerMaskFromAEChannels(const CAEChannelInfo &channels);
+ void CheckPlayStatus();
+ bool UpdateCacheStatus();
+ unsigned int GetSpace();
+ const char *dserr2str(int err);
+
+ Microsoft::WRL::ComPtr<IDirectSoundBuffer> m_pBuffer;
+ Microsoft::WRL::ComPtr<IDirectSound> m_pDSound;
+
+ AEAudioFormat m_format;
+ enum AEDataFormat m_encodedFormat;
+ CAEChannelInfo m_channelLayout;
+ std::string m_device;
+
+ unsigned int m_AvgBytesPerSec;
+
+ unsigned int m_dwChunkSize;
+ unsigned int m_dwFrameSize;
+ unsigned int m_dwBufferLen;
+
+ unsigned int m_BufferOffset;
+ unsigned int m_CacheLen;
+ unsigned int m_BufferTimeouts;
+
+ bool m_running;
+ bool m_initialized;
+ bool m_isDirtyDS;
+ CCriticalSection m_runLock;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp
new file mode 100644
index 0000000..b319317
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkOSS.h"
+#include <stdint.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/log.h"
+#include "threads/SingleLock.h"
+#include <sstream>
+
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+
+#if defined(OSS4) || defined(TARGET_FREEBSD)
+ #include <sys/soundcard.h>
+#else
+ #include <linux/soundcard.h>
+#endif
+
+#define OSS_FRAMES 256
+
+static enum AEChannel OSSChannelMap[9] =
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_NULL};
+
+#if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
+static int OSSSampleRateList[] =
+{
+ 5512,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000,
+ 0
+};
+#endif
+
+CAESinkOSS::CAESinkOSS()
+{
+ m_fd = 0;
+}
+
+CAESinkOSS::~CAESinkOSS()
+{
+ Deinitialize();
+}
+
+void CAESinkOSS::Register()
+{
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "OSS";
+ entry.createFunc = CAESinkOSS::Create;
+ entry.enumerateFunc = CAESinkOSS::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(entry);
+}
+
+IAESink* CAESinkOSS::Create(std::string &device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkOSS();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+std::string CAESinkOSS::GetDeviceUse(const AEAudioFormat& format, const std::string &device)
+{
+#ifdef OSS4
+ if (AE_IS_RAW(format.m_dataFormat))
+ {
+ if (device.find_first_of('/') != 0)
+ return "/dev/dsp_ac3";
+ return device;
+ }
+
+ if (device.find_first_of('/') != 0)
+ return "/dev/dsp_multich";
+#else
+ if (device.find_first_of('/') != 0)
+ return "/dev/dsp";
+#endif
+
+ return device;
+}
+
+bool CAESinkOSS::Initialize(AEAudioFormat &format, std::string &device)
+{
+ m_initFormat = format;
+ format.m_channelLayout = GetChannelLayout(format);
+ device = GetDeviceUse(format, device);
+
+#ifdef __linux__
+ /* try to open in exclusive mode first (no software mixing) */
+ m_fd = open(device.c_str(), O_WRONLY | O_EXCL, 0);
+ if (m_fd == -1)
+#endif
+ m_fd = open(device.c_str(), O_WRONLY, 0);
+ if (m_fd == -1)
+ {
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to open the audio device: {}", device);
+ return false;
+ }
+
+ int format_mask;
+ if (ioctl(m_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1)
+ {
+ close(m_fd);
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get supported formats, assuming AFMT_S16_NE");
+ return false;
+ }
+
+#ifdef OSS4
+ bool useCooked = true;
+#endif
+
+ int oss_fmt = 0;
+#ifdef AFMT_FLOAT
+ if ((format.m_dataFormat == AE_FMT_FLOAT) && (format_mask & AFMT_FLOAT ))
+ oss_fmt = AFMT_FLOAT;
+ else
+#endif
+#ifdef AFMT_S32_NE
+ if ((format.m_dataFormat == AE_FMT_S32NE) && (format_mask & AFMT_S32_NE))
+ oss_fmt = AFMT_S32_NE;
+ else if ((format.m_dataFormat == AE_FMT_S32BE) && (format_mask & AFMT_S32_BE))
+ oss_fmt = AFMT_S32_BE;
+ else if ((format.m_dataFormat == AE_FMT_S32LE) && (format_mask & AFMT_S32_LE))
+ oss_fmt = AFMT_S32_LE;
+ else
+#endif
+ if ((format.m_dataFormat == AE_FMT_S16NE) && (format_mask & AFMT_S16_NE))
+ oss_fmt = AFMT_S16_NE;
+ else if ((format.m_dataFormat == AE_FMT_S16BE) && (format_mask & AFMT_S16_BE))
+ oss_fmt = AFMT_S16_BE;
+ else if ((format.m_dataFormat == AE_FMT_S16LE) && (format_mask & AFMT_S16_LE))
+ oss_fmt = AFMT_S16_LE;
+ else if ((format.m_dataFormat == AE_FMT_U8 ) && (format_mask & AFMT_U8 ))
+ oss_fmt = AFMT_U8;
+ else if (((format.m_dataFormat == AE_FMT_RAW) ) && (format_mask & AFMT_AC3 ))
+ {
+ oss_fmt = AFMT_AC3;
+ format.m_dataFormat = AE_FMT_S16NE;
+ }
+ else if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ close(m_fd);
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to find a suitable RAW output format");
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGINFO,
+ "CAESinkOSS::Initialize - Your hardware does not support {}, trying other formats",
+ CAEUtil::DataFormatToStr(format.m_dataFormat));
+
+ /* fallback to the best supported format */
+#ifdef AFMT_FLOAT
+ if (format_mask & AFMT_FLOAT )
+ {
+ oss_fmt = AFMT_FLOAT;
+ format.m_dataFormat = AE_FMT_FLOAT;
+ }
+ else
+#endif
+#ifdef AFMT_S32_NE
+ if (format_mask & AFMT_S32_NE)
+ {
+ oss_fmt = AFMT_S32_NE;
+ format.m_dataFormat = AE_FMT_S32NE;
+ }
+ else if (format_mask & AFMT_S32_BE)
+ {
+ oss_fmt = AFMT_S32_BE;
+ format.m_dataFormat = AE_FMT_S32BE;
+ }
+ else if (format_mask & AFMT_S32_LE)
+ {
+ oss_fmt = AFMT_S32_LE;
+ format.m_dataFormat = AE_FMT_S32LE;
+ }
+ else
+#endif
+ if (format_mask & AFMT_S16_NE)
+ {
+ oss_fmt = AFMT_S16_NE;
+ format.m_dataFormat = AE_FMT_S16NE;
+ }
+ else if (format_mask & AFMT_S16_BE)
+ {
+ oss_fmt = AFMT_S16_BE;
+ format.m_dataFormat = AE_FMT_S16BE;
+ }
+ else if (format_mask & AFMT_S16_LE)
+ {
+ oss_fmt = AFMT_S16_LE;
+ format.m_dataFormat = AE_FMT_S16LE;
+ }
+ else if (format_mask & AFMT_U8 )
+ {
+ oss_fmt = AFMT_U8;
+ format.m_dataFormat = AE_FMT_U8;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to find a suitable native output format, will try to use AE_FMT_S16NE anyway");
+ oss_fmt = AFMT_S16_NE;
+ format.m_dataFormat = AE_FMT_S16NE;
+#ifdef OSS4
+ /* dont use cooked if we did not find a native format, OSS might be able to convert */
+ useCooked = false;
+#endif
+ }
+ }
+
+#ifdef OSS4
+ if (useCooked)
+ {
+ int oss_cooked = 1;
+ if (ioctl(m_fd, SNDCTL_DSP_COOKEDMODE, &oss_cooked) == -1)
+ CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set cooked mode");
+ }
+#endif
+
+ if (ioctl(m_fd, SNDCTL_DSP_SETFMT, &oss_fmt) == -1)
+ {
+ close(m_fd);
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the data format ({})",
+ CAEUtil::DataFormatToStr(format.m_dataFormat));
+ return false;
+ }
+
+ /* find the number we need to open to access the channels we need */
+ bool found = false;
+ int oss_ch = 0;
+ for (int ch = format.m_channelLayout.Count(); ch < 9; ++ch)
+ {
+ oss_ch = ch;
+ if (ioctl(m_fd, SNDCTL_DSP_CHANNELS, &oss_ch) != -1 && oss_ch >= (int)format.m_channelLayout.Count())
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to access the number of channels required, falling back");
+
+#if defined(TARGET_FREEBSD)
+ /* fix hdmi 8 channels order */
+ if ((oss_fmt != AFMT_AC3) && 8 == oss_ch)
+ {
+ unsigned long long order = 0x0000000087346521ULL;
+
+ if (ioctl(m_fd, SNDCTL_DSP_SET_CHNORDER, &order) == -1)
+ CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set the channel order");
+ }
+#elif defined(OSS4)
+ unsigned long long order = 0;
+
+ for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
+ switch (format.m_channelLayout[i])
+ {
+ case AE_CH_FL : order = (order << 4) | CHID_L ; break;
+ case AE_CH_FR : order = (order << 4) | CHID_R ; break;
+ case AE_CH_FC : order = (order << 4) | CHID_C ; break;
+ case AE_CH_LFE: order = (order << 4) | CHID_LFE; break;
+ case AE_CH_SL : order = (order << 4) | CHID_LS ; break;
+ case AE_CH_SR : order = (order << 4) | CHID_RS ; break;
+ case AE_CH_BL : order = (order << 4) | CHID_LR ; break;
+ case AE_CH_BR : order = (order << 4) | CHID_RR ; break;
+
+ default:
+ continue;
+ }
+
+ if (ioctl(m_fd, SNDCTL_DSP_SET_CHNORDER, &order) == -1)
+ {
+ if (ioctl(m_fd, SNDCTL_DSP_GET_CHNORDER, &order) == -1)
+ {
+ CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to get the channel order, assuming CHNORDER_NORMAL");
+ }
+ }
+#endif
+
+ int tmp = (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3) * format.m_channelLayout.Count() * OSS_FRAMES;
+ int pos = 0;
+ while ((tmp & 0x1) == 0x0)
+ {
+ tmp = tmp >> 1;
+ ++pos;
+ }
+
+ int oss_frag = (4 << 16) | pos;
+ if (ioctl(m_fd, SNDCTL_DSP_SETFRAGMENT, &oss_frag) == -1)
+ CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set the fragment size");
+
+ int oss_sr = format.m_sampleRate;
+ if (ioctl(m_fd, SNDCTL_DSP_SPEED, &oss_sr) == -1)
+ {
+ close(m_fd);
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the sample rate");
+ return false;
+ }
+
+ audio_buf_info bi;
+ if (ioctl(m_fd, SNDCTL_DSP_GETOSPACE, &bi) == -1)
+ {
+ close(m_fd);
+ CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get the output buffer size");
+ return false;
+ }
+
+ format.m_sampleRate = oss_sr;
+ format.m_frameSize = (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3) * format.m_channelLayout.Count();
+ format.m_frames = bi.fragsize / format.m_frameSize;
+
+ m_device = device;
+ m_format = format;
+ return true;
+}
+
+void CAESinkOSS::Deinitialize()
+{
+ Stop();
+
+ if (m_fd != -1)
+ close(m_fd);
+}
+
+inline CAEChannelInfo CAESinkOSS::GetChannelLayout(const AEAudioFormat& format)
+{
+ unsigned int count = 0;
+
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ switch (format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ count = 8;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ count = 2;
+ break;
+ default:
+ count = 0;
+ break;
+ }
+ }
+ else
+ {
+ for (unsigned int c = 0; c < 8; ++c)
+ for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
+ if (format.m_channelLayout[i] == OSSChannelMap[c])
+ {
+ count = c + 1;
+ break;
+ }
+ }
+
+ CAEChannelInfo info;
+ for (unsigned int i = 0; i < count; ++i)
+ info += OSSChannelMap[i];
+
+ return info;
+}
+
+void CAESinkOSS::Stop()
+{
+#ifdef SNDCTL_DSP_RESET
+ if (m_fd != -1)
+ ioctl(m_fd, SNDCTL_DSP_RESET, NULL);
+#endif
+}
+
+void CAESinkOSS::GetDelay(AEDelayStatus& status)
+{
+ if (m_fd == -1)
+ {
+ status.SetDelay(0);
+ return;
+ }
+
+ int delay;
+ if (ioctl(m_fd, SNDCTL_DSP_GETODELAY, &delay) == -1)
+ {
+ status.SetDelay(0);
+ return;
+ }
+
+ status.SetDelay((double)delay / (m_format.m_frameSize * m_format.m_sampleRate));
+}
+
+unsigned int CAESinkOSS::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ int size = frames * m_format.m_frameSize;
+ if (m_fd == -1)
+ {
+ CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
+ return INT_MAX;
+ }
+
+ void *buffer = data[0]+offset*m_format.m_frameSize;
+ int wrote = write(m_fd, buffer, size);
+ if (wrote < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
+ return INT_MAX;
+ }
+
+ return wrote / m_format.m_frameSize;
+}
+
+void CAESinkOSS::Drain()
+{
+ if (m_fd == -1)
+ return;
+
+ if(ioctl(m_fd, SNDCTL_DSP_SYNC, NULL) == -1)
+ {
+ CLog::Log(LOGERROR, "CAESinkOSS::Drain - Draining the Sink failed");
+ }
+}
+
+void CAESinkOSS::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ int mixerfd;
+ const char * mixerdev = "/dev/mixer";
+
+ if ((mixerfd = open(mixerdev, O_RDWR, 0)) == -1)
+ {
+ CLog::Log(LOGINFO, "CAESinkOSS::EnumerateDevicesEx - No OSS mixer device present: {}",
+ mixerdev);
+ return;
+ }
+
+#if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
+ oss_sysinfo sysinfo;
+ if (ioctl(mixerfd, SNDCTL_SYSINFO, &sysinfo) == -1)
+ {
+ // hardware not supported
+ // OSSv4 required ?
+ close(mixerfd);
+ return;
+ }
+
+ for (int i = 0; i < sysinfo.numcards; ++i)
+ {
+ std::stringstream devicepath;
+ std::stringstream devicename;
+ CAEDeviceInfo info;
+ oss_card_info cardinfo;
+
+ devicepath << "/dev/dsp" << i;
+ info.m_deviceName = devicepath.str();
+
+ cardinfo.card = i;
+ if (ioctl(mixerfd, SNDCTL_CARDINFO, &cardinfo) == -1)
+ break;
+
+ devicename << cardinfo.shortname << " " << cardinfo.longname;
+ info.m_displayName = devicename.str();
+
+ info.m_dataFormats.push_back(AE_FMT_S16NE);
+ info.m_dataFormats.push_back(AE_FMT_S32NE);
+ if (info.m_displayName.find("HDMI") != std::string::npos
+ || info.m_displayName.find("DisplayPort") != std::string::npos)
+ {
+ info.m_deviceType = AE_DEVTYPE_HDMI;
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+ info.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ else if (info.m_displayName.find("Digital") != std::string::npos)
+ {
+ info.m_deviceType = AE_DEVTYPE_IEC958;
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ info.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ else
+ {
+ info.m_deviceType = AE_DEVTYPE_PCM;
+ }
+
+ oss_audioinfo ainfo = {};
+ ainfo.dev = i;
+ if (ioctl(mixerfd, SNDCTL_AUDIOINFO, &ainfo) != -1) {
+#if 0
+ if (ainfo.oformats & AFMT_S32_LE)
+ info.m_dataFormats.push_back(AE_FMT_S32LE);
+ if (ainfo.oformats & AFMT_S16_LE)
+ info.m_dataFormats.push_back(AE_FMT_S16LE);
+#endif
+ for (int j = 0;
+ j < ainfo.max_channels && AE_CH_NULL != OSSChannelMap[j];
+ ++j)
+ info.m_channels += OSSChannelMap[j];
+
+ for (int *rate = OSSSampleRateList; *rate != 0; ++rate)
+ if (*rate >= ainfo.min_rate && *rate <= ainfo.max_rate)
+ info.m_sampleRates.push_back(*rate);
+ }
+ info.m_wantsIECPassthrough = true;
+ list.push_back(info);
+ }
+#endif
+ close(mixerfd);
+}
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h
new file mode 100644
index 0000000..98762da
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "threads/CriticalSection.h"
+
+#include <stdint.h>
+
+class CAESinkOSS : public IAESink
+{
+public:
+ const char *GetName() override { return "OSS"; }
+
+ CAESinkOSS();
+ ~CAESinkOSS() override;
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
+
+ bool Initialize(AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+
+ virtual void Stop();
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override { return 0.0; } /* FIXME */
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+private:
+ int m_fd;
+ std::string m_device;
+ AEAudioFormat m_initFormat;
+ AEAudioFormat m_format;
+
+ CAEChannelInfo GetChannelLayout(const AEAudioFormat& format);
+ std::string GetDeviceUse(const AEAudioFormat& format, const std::string &device);
+};
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp
new file mode 100644
index 0000000..77add8d
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp
@@ -0,0 +1,1297 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "AESinkPULSE.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "threads/SingleLock.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <array>
+#include <mutex>
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+class CDriverMonitor
+{
+public:
+ CDriverMonitor() = default;
+ virtual ~CDriverMonitor();
+ bool Start();
+ bool IsInitialized();
+
+ CCriticalSection m_sec;
+
+protected:
+ pa_context* m_pContext = nullptr;
+ pa_threaded_mainloop* m_pMainLoop = nullptr;
+ bool m_isInit = false;
+};
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+static const char *ContextStateToString(pa_context_state s)
+{
+ switch (s)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ return "unconnected";
+ case PA_CONTEXT_CONNECTING:
+ return "connecting";
+ case PA_CONTEXT_AUTHORIZING:
+ return "authorizing";
+ case PA_CONTEXT_SETTING_NAME:
+ return "setting name";
+ case PA_CONTEXT_READY:
+ return "ready";
+ case PA_CONTEXT_FAILED:
+ return "failed";
+ case PA_CONTEXT_TERMINATED:
+ return "terminated";
+ default:
+ return "none";
+ }
+}
+
+static const char *StreamStateToString(pa_stream_state s)
+{
+ switch(s)
+ {
+ case PA_STREAM_UNCONNECTED:
+ return "unconnected";
+ case PA_STREAM_CREATING:
+ return "creating";
+ case PA_STREAM_READY:
+ return "ready";
+ case PA_STREAM_FAILED:
+ return "failed";
+ case PA_STREAM_TERMINATED:
+ return "terminated";
+ default:
+ return "none";
+ }
+}
+
+static pa_sample_format AEStreamFormatToPulseFormat(CAEStreamInfo::DataType type)
+{
+ switch (type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ return PA_SAMPLE_S16NE;
+
+ default:
+ return PA_SAMPLE_INVALID;
+ }
+}
+
+static pa_sample_format AEFormatToPulseFormat(AEDataFormat format)
+{
+ switch (format)
+ {
+ case AE_FMT_U8 : return PA_SAMPLE_U8;
+ case AE_FMT_S16NE : return PA_SAMPLE_S16NE;
+ case AE_FMT_S24NE3 : return PA_SAMPLE_S24NE;
+ case AE_FMT_S24NE4 : return PA_SAMPLE_S24_32NE;
+ case AE_FMT_S32NE : return PA_SAMPLE_S32NE;
+ case AE_FMT_FLOAT : return PA_SAMPLE_FLOAT32;
+
+ default:
+ return PA_SAMPLE_INVALID;
+ }
+}
+
+static pa_encoding AEStreamFormatToPulseEncoding(CAEStreamInfo::DataType type)
+{
+ switch (type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ return PA_ENCODING_AC3_IEC61937;
+
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ return PA_ENCODING_DTS_IEC61937;
+
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ return PA_ENCODING_EAC3_IEC61937;
+
+ default:
+ return PA_ENCODING_INVALID;
+ }
+}
+
+static pa_encoding AEFormatToPulseEncoding(AEDataFormat format)
+{
+ switch (format)
+ {
+ case AE_FMT_RAW:
+ return PA_ENCODING_INVALID;
+
+ default:
+ return PA_ENCODING_PCM;
+ }
+}
+
+namespace
+{
+
+// clang-format off
+constexpr std::array<AEDataFormat, 6> defaultDataFormats = {
+ AE_FMT_U8,
+ AE_FMT_S16NE,
+ AE_FMT_S24NE3,
+ AE_FMT_S24NE4,
+ AE_FMT_S32NE,
+ AE_FMT_FLOAT
+};
+
+constexpr std::array<unsigned int, 14> defaultSampleRates = {
+ 5512,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000
+};
+// clang-format on
+
+} // namespace
+
+/* Static callback functions */
+
+static void ContextStateCallback(pa_context *c, void *userdata)
+{
+ pa_threaded_mainloop* m = static_cast<pa_threaded_mainloop*>(userdata);
+ switch (pa_context_get_state(c))
+ {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(m, 0);
+ break;
+ }
+}
+
+static void StreamStateCallback(pa_stream *s, void *userdata)
+{
+ pa_threaded_mainloop* m = static_cast<pa_threaded_mainloop*>(userdata);
+ switch (pa_stream_get_state(s))
+ {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(m, 0);
+ break;
+ }
+}
+
+static void StreamRequestCallback(pa_stream *s, size_t length, void *userdata)
+{
+ CAESinkPULSE* p = static_cast<CAESinkPULSE*>(userdata);
+ if (!p)
+ return;
+
+ pa_threaded_mainloop* m = p->GetInternalMainLoop();
+ // pulse always tells us the total number of bytes
+ // we can add.
+ p->m_requestedBytes = static_cast<int>(length);
+ pa_threaded_mainloop_signal(m, 0);
+}
+
+static void StreamLatencyUpdateCallback(pa_stream *s, void *userdata)
+{
+ pa_threaded_mainloop* m = static_cast<pa_threaded_mainloop*>(userdata);
+ pa_threaded_mainloop_signal(m, 0);
+}
+
+
+static void SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata)
+{
+ CAESinkPULSE *p = static_cast<CAESinkPULSE*>(userdata);
+ if (!p || !p->IsInitialized())
+ return;
+
+ if(i && i->has_volume && !i->corked)
+ p->UpdateInternalVolume(&(i->volume));
+}
+
+static void SinkCallback(pa_context* c,
+ pa_subscription_event_type_t t,
+ uint32_t idx,
+ void* userdata)
+{
+ CDriverMonitor* p = static_cast<CDriverMonitor*>(userdata);
+ if (!p)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(p->m_sec);
+ if (p->IsInitialized())
+ {
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
+ {
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
+ {
+ CLog::Log(LOGDEBUG, "Sink appeared");
+ CServiceBroker::GetActiveAE()->DeviceCountChange("PULSE");
+ }
+ else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
+ {
+ CLog::Log(LOGDEBUG, "Sink removed");
+ CServiceBroker::GetActiveAE()->DeviceCountChange("PULSE");
+ }
+ else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE)
+ {
+ CLog::Log(LOGDEBUG, "Sink changed");
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Not subscribed to Event: {}", static_cast<int>(t));
+ }
+ }
+}
+
+static void SinkChangedCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
+{
+ CAESinkPULSE* p = static_cast<CAESinkPULSE*>(userdata);
+ if(!p)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(p->m_sec);
+ if (p->IsInitialized())
+ {
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT)
+ {
+ // when we get a sink input event volume might have changed
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE)
+ {
+ if (idx != pa_stream_get_index(p->GetInternalStream()))
+ return;
+
+ // we need to leave the lock as we trigger a second callback
+ CSingleExit exitlock(p->m_sec);
+ pa_operation* op = pa_context_get_sink_input_info(c, idx, SinkInputInfoCallback, p);
+ if (op == NULL)
+ CLog::Log(LOGERROR, "PulseAudio: Failed to sync volume");
+ else
+ pa_operation_unref(op);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Not subscribed to Event: {}", static_cast<int>(t));
+ }
+ }
+}
+
+struct SinkInfoStruct
+{
+ AEDeviceInfoList *list;
+ bool isHWDevice;
+ bool isNWDevice;
+ bool isBTDevice;
+ bool device_found;
+ pa_threaded_mainloop *mainloop;
+ int samplerate;
+ pa_channel_map map;
+ SinkInfoStruct()
+ {
+ list = nullptr;
+ isHWDevice = false;
+ isNWDevice = false;
+ isBTDevice = false;
+ device_found = true;
+ mainloop = NULL; //called into C
+ samplerate = 0;
+ pa_channel_map_init(&map);
+ }
+};
+
+struct ModuleInfoStruct
+{
+ pa_threaded_mainloop *mainloop;
+ bool hasAllowPT;
+ ModuleInfoStruct()
+ {
+ mainloop = NULL; //called into C
+ hasAllowPT = false;
+ }
+};
+
+static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
+{
+ SinkInfoStruct *sinkStruct = static_cast<SinkInfoStruct*>(userdata);
+ if (!sinkStruct)
+ return;
+
+ if(i)
+ {
+ if (i->flags)
+ {
+ if (i->flags & PA_SINK_HARDWARE)
+ sinkStruct->isHWDevice = true;
+
+ if (i->flags & PA_SINK_NETWORK)
+ sinkStruct->isNWDevice = true;
+
+ sinkStruct->isBTDevice =
+ StringUtils::EndsWithNoCase(std::string(i->name), std::string("a2dp_sink"));
+ if (sinkStruct->isBTDevice)
+ CLog::Log(LOGINFO, "Found BT Device - will adjust buffers to larger values");
+
+ sinkStruct->samplerate = i->sample_spec.rate;
+ sinkStruct->device_found = true;
+ sinkStruct->map = i->channel_map;
+ }
+ }
+ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
+}
+
+static AEChannel PAChannelToAEChannel(pa_channel_position_t channel)
+{
+ AEChannel ae_channel;
+ switch (channel)
+ {
+ case PA_CHANNEL_POSITION_FRONT_LEFT: ae_channel = AE_CH_FL; break;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT: ae_channel = AE_CH_FR; break;
+ case PA_CHANNEL_POSITION_FRONT_CENTER: ae_channel = AE_CH_FC; break;
+ case PA_CHANNEL_POSITION_LFE: ae_channel = AE_CH_LFE; break;
+ case PA_CHANNEL_POSITION_REAR_LEFT: ae_channel = AE_CH_BL; break;
+ case PA_CHANNEL_POSITION_REAR_RIGHT: ae_channel = AE_CH_BR; break;
+ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: ae_channel = AE_CH_FLOC; break;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: ae_channel = AE_CH_FROC; break;
+ case PA_CHANNEL_POSITION_REAR_CENTER: ae_channel = AE_CH_BC; break;
+ case PA_CHANNEL_POSITION_SIDE_LEFT: ae_channel = AE_CH_SL; break;
+ case PA_CHANNEL_POSITION_SIDE_RIGHT: ae_channel = AE_CH_SR; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: ae_channel = AE_CH_TFL; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: ae_channel = AE_CH_TFR; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: ae_channel = AE_CH_TFC; break;
+ case PA_CHANNEL_POSITION_TOP_CENTER: ae_channel = AE_CH_TC; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_LEFT: ae_channel = AE_CH_TBL; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: ae_channel = AE_CH_TBR; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_CENTER: ae_channel = AE_CH_TBC; break;
+ default: ae_channel = AE_CH_NULL; break;
+ }
+ return ae_channel;
+}
+
+static pa_channel_position_t AEChannelToPAChannel(AEChannel ae_channel)
+{
+ pa_channel_position_t pa_channel;
+ switch (ae_channel)
+ {
+ case AE_CH_FL: pa_channel = PA_CHANNEL_POSITION_FRONT_LEFT; break;
+ case AE_CH_FR: pa_channel = PA_CHANNEL_POSITION_FRONT_RIGHT; break;
+ case AE_CH_FC: pa_channel = PA_CHANNEL_POSITION_FRONT_CENTER; break;
+ case AE_CH_LFE: pa_channel = PA_CHANNEL_POSITION_LFE; break;
+ case AE_CH_BL: pa_channel = PA_CHANNEL_POSITION_REAR_LEFT; break;
+ case AE_CH_BR: pa_channel = PA_CHANNEL_POSITION_REAR_RIGHT; break;
+ case AE_CH_FLOC: pa_channel = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; break;
+ case AE_CH_FROC: pa_channel = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break;
+ case AE_CH_BC: pa_channel = PA_CHANNEL_POSITION_REAR_CENTER; break;
+ case AE_CH_SL: pa_channel = PA_CHANNEL_POSITION_SIDE_LEFT; break;
+ case AE_CH_SR: pa_channel = PA_CHANNEL_POSITION_SIDE_RIGHT; break;
+ case AE_CH_TFL: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; break;
+ case AE_CH_TFR: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; break;
+ case AE_CH_TFC: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; break;
+ case AE_CH_TC: pa_channel = PA_CHANNEL_POSITION_TOP_CENTER; break;
+ case AE_CH_TBL: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_LEFT; break;
+ case AE_CH_TBR: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; break;
+ case AE_CH_TBC: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_CENTER; break;
+ default: pa_channel = PA_CHANNEL_POSITION_INVALID; break;
+ }
+ return pa_channel;
+}
+
+static pa_channel_map AEChannelMapToPAChannel(const CAEChannelInfo& info)
+{
+ pa_channel_map map;
+ pa_channel_map_init(&map);
+ pa_channel_position_t pos;
+ for (unsigned int i = 0; i < info.Count(); ++i)
+ {
+ pos = AEChannelToPAChannel(info[i]);
+ if(pos != PA_CHANNEL_POSITION_INVALID)
+ {
+ // remember channel name and increase channel count
+ map.map[map.channels++] = pos;
+ }
+ }
+ return map;
+}
+
+static CAEChannelInfo PAChannelToAEChannelMap(const pa_channel_map& channels)
+{
+ CAEChannelInfo info;
+ AEChannel ch;
+ info.Reset();
+ for (unsigned int i=0; i<channels.channels; i++)
+ {
+ ch = PAChannelToAEChannel(channels.map[i]);
+ if(ch != AE_CH_NULL)
+ info += ch;
+ }
+ return info;
+}
+
+static void ModuleInfoCallback(pa_context* c, const pa_module_info *i, int eol, void *userdata)
+{
+ ModuleInfoStruct *mis = static_cast<ModuleInfoStruct*>(userdata);
+ if (!mis)
+ return;
+
+ if (i)
+ {
+ if (strcmp(i->name, "module-allow-passthrough") == 0)
+ mis->hasAllowPT = true;
+ }
+ pa_threaded_mainloop_signal(mis->mainloop, 0);
+}
+
+static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
+{
+
+ SinkInfoStruct *sinkStruct = static_cast<SinkInfoStruct*>(userdata);
+ if (!sinkStruct)
+ return;
+
+ if(sinkStruct->list->empty())
+ {
+ //add a default device first
+ CAEDeviceInfo defaultDevice;
+ defaultDevice.m_deviceName = std::string("Default");
+ defaultDevice.m_displayName = std::string("Default");
+ defaultDevice.m_displayNameExtra = std::string("Default Output Device (PULSEAUDIO)");
+ defaultDevice.m_dataFormats.insert(defaultDevice.m_dataFormats.end(),
+ defaultDataFormats.begin(), defaultDataFormats.end());
+ defaultDevice.m_channels = CAEChannelInfo(AE_CH_LAYOUT_2_0);
+ defaultDevice.m_sampleRates.assign(defaultSampleRates.begin(), defaultSampleRates.end());
+ defaultDevice.m_deviceType = AE_DEVTYPE_PCM;
+ defaultDevice.m_wantsIECPassthrough = true;
+ sinkStruct->list->push_back(defaultDevice);
+ }
+ if (i && i->name)
+ {
+ CAEDeviceInfo device;
+ bool valid = true;
+ device.m_deviceName = std::string(i->name);
+ device.m_displayName = std::string(i->description);
+ if (i->active_port && i->active_port->description)
+ device.m_displayNameExtra = std::string((i->active_port->description)).append(" (PULSEAUDIO)");
+ else
+ device.m_displayNameExtra = std::string((i->description)).append(" (PULSEAUDIO)");
+ unsigned int device_type = AE_DEVTYPE_PCM; //0
+
+ device.m_channels = PAChannelToAEChannelMap(i->channel_map);
+
+ // Don't add devices that would not have a channel map
+ if(device.m_channels.Count() == 0)
+ valid = false;
+
+ device.m_sampleRates.assign(defaultSampleRates.begin(), defaultSampleRates.end());
+
+ for (unsigned int j = 0; j < i->n_formats; j++)
+ {
+ switch(i->formats[j]->encoding)
+ {
+ case PA_ENCODING_AC3_IEC61937:
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ device_type = AE_DEVTYPE_IEC958;
+ break;
+ case PA_ENCODING_DTS_IEC61937:
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ device_type = AE_DEVTYPE_IEC958;
+ break;
+ case PA_ENCODING_EAC3_IEC61937:
+ device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ device_type = AE_DEVTYPE_IEC958;
+ break;
+ case PA_ENCODING_PCM:
+ device.m_dataFormats.insert(device.m_dataFormats.end(), defaultDataFormats.begin(),
+ defaultDataFormats.end());
+ break;
+ default:
+ break;
+ }
+ }
+ // passthrough is only working when device has Stereo channel config
+ if (device_type > AE_DEVTYPE_PCM && device.m_channels.Count() == 2)
+ {
+ device.m_deviceType = AE_DEVTYPE_IEC958;
+ device.m_dataFormats.push_back(AE_FMT_RAW);
+ }
+ else
+ device.m_deviceType = AE_DEVTYPE_PCM;
+
+ device.m_wantsIECPassthrough = true;
+
+ if(valid)
+ {
+ CLog::Log(LOGDEBUG, "PulseAudio: Found {} with devicestring {}", device.m_displayName,
+ device.m_deviceName);
+ sinkStruct->list->push_back(device);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "PulseAudio: Skipped {} with devicestring {}", device.m_displayName,
+ device.m_deviceName);
+ }
+ }
+ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
+}
+
+static bool SetupContext(const char* host,
+ const char* appname,
+ pa_context** context,
+ pa_threaded_mainloop** mainloop)
+{
+ if ((*mainloop = pa_threaded_mainloop_new()) == nullptr)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
+ return false;
+ }
+
+ if (((*context) = pa_context_new(pa_threaded_mainloop_get_api(*mainloop), appname)) == nullptr)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
+ return false;
+ }
+
+ pa_context_set_state_callback(*context, ContextStateCallback, *mainloop);
+
+ if (pa_context_connect(*context, host, (pa_context_flags_t)0, nullptr) < 0)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
+ return false;
+ }
+ pa_threaded_mainloop_lock(*mainloop);
+
+ if (pa_threaded_mainloop_start(*mainloop) < 0)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
+ pa_threaded_mainloop_unlock(*mainloop);
+ return false;
+ }
+
+ /* Wait until the context is ready */
+ do
+ {
+ pa_threaded_mainloop_wait(*mainloop);
+ CLog::Log(LOGDEBUG, "PulseAudio: Context {}",
+ ContextStateToString(pa_context_get_state(*context)));
+ } while (pa_context_get_state(*context) != PA_CONTEXT_READY &&
+ pa_context_get_state(*context) != PA_CONTEXT_FAILED);
+
+ if (pa_context_get_state(*context) == PA_CONTEXT_FAILED)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
+ pa_threaded_mainloop_unlock(*mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(*mainloop);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+CDriverMonitor::~CDriverMonitor()
+{
+ m_isInit = false;
+
+ if (m_pMainLoop)
+ pa_threaded_mainloop_stop(m_pMainLoop);
+
+ if (m_pContext)
+ {
+ pa_context_disconnect(m_pContext);
+ pa_context_unref(m_pContext);
+ m_pContext = nullptr;
+ }
+
+ if (m_pMainLoop)
+ {
+ pa_threaded_mainloop_free(m_pMainLoop);
+ m_pMainLoop = nullptr;
+ }
+}
+
+bool CDriverMonitor::IsInitialized()
+{
+ return m_isInit;
+}
+
+bool CDriverMonitor::Start()
+{
+ if (!SetupContext(nullptr, "KodiDriver", &m_pContext, &m_pMainLoop))
+ {
+ CLog::Log(LOGINFO, "PulseAudio might not be running. Context was not created.");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(m_pMainLoop);
+
+ m_isInit = true;
+
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ // Register Callback for Sink changes
+ pa_context_set_subscribe_callback(m_pContext, SinkCallback, this);
+ const pa_subscription_mask_t mask = pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK);
+ pa_operation* op = pa_context_subscribe(m_pContext, mask, nullptr, this);
+ if (op != nullptr)
+ pa_operation_unref(op);
+
+ pa_threaded_mainloop_unlock(m_pMainLoop);
+
+ return true;
+}
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/* PulseAudio class memberfunctions*/
+
+std::unique_ptr<CDriverMonitor> CAESinkPULSE::m_pMonitor;
+
+bool CAESinkPULSE::Register()
+{
+ // check if pulseaudio is actually available
+ pa_simple *s;
+ pa_sample_spec ss;
+ ss.format = PA_SAMPLE_S16NE;
+ ss.channels = 2;
+ ss.rate = 44100;
+ s = pa_simple_new(NULL, "Kodi-Tester", PA_STREAM_PLAYBACK, NULL, "Test", &ss, NULL, NULL, NULL);
+ if (!s)
+ {
+ CLog::Log(LOGINFO, "PulseAudio: Server not running");
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "PulseAudio: Server found running - will try to use Pulse");
+ pa_simple_free(s);
+ }
+
+ m_pMonitor.reset(new CDriverMonitor());
+ m_pMonitor->Start();
+
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "PULSE";
+ entry.createFunc = CAESinkPULSE::Create;
+ entry.enumerateFunc = CAESinkPULSE::EnumerateDevicesEx;
+ entry.cleanupFunc = CAESinkPULSE::Cleanup;
+ AE::CAESinkFactory::RegisterSink(entry);
+ return true;
+}
+
+IAESink* CAESinkPULSE::Create(std::string &device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkPULSE();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+CAESinkPULSE::CAESinkPULSE()
+{
+ m_IsAllocated = false;
+ m_passthrough = false;
+ m_MainLoop = NULL;
+ m_BytesPerSecond = 0;
+ m_BufferSize = 0;
+ m_Channels = 0;
+ m_maxLatency = 0.0;
+ m_Stream = NULL;
+ m_Context = NULL;
+ m_IsStreamPaused = false;
+ m_volume_needs_update = false;
+ m_periodSize = 0;
+ pa_cvolume_init(&m_Volume);
+}
+
+CAESinkPULSE::~CAESinkPULSE()
+{
+ Deinitialize();
+}
+
+bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ m_IsAllocated = false;
+ }
+ m_passthrough = false;
+ m_BytesPerSecond = 0;
+ m_BufferSize = 0;
+ m_Channels = 0;
+ m_maxLatency = 0.0;
+ m_Stream = NULL;
+ m_Context = NULL;
+ m_periodSize = 0;
+
+ if (!SetupContext(NULL, "KodiSink", &m_Context, &m_MainLoop))
+ {
+ CLog::Log(LOGINFO, "PulseAudio might not be running. Context was not created.");
+ Deinitialize();
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(m_MainLoop);
+
+ struct pa_channel_map map;
+ pa_channel_map_init(&map);
+
+ // PULSE cannot cope with e.g. planar formats so we fall back to FLOAT
+ // when we receive an invalid pulse format
+ pa_sample_format pa_fmt;
+ // PA can only handle IEC packed RAW format if we get a RAW format
+ if (format.m_dataFormat == AE_FMT_RAW)
+ {
+ pa_fmt = AEStreamFormatToPulseFormat(format.m_streamInfo.m_type);
+ m_passthrough = true;
+ }
+ else
+ pa_fmt = AEFormatToPulseFormat(format.m_dataFormat);
+
+ if (pa_fmt == PA_SAMPLE_INVALID)
+ {
+ CLog::Log(LOGDEBUG, "PULSE does not support format: {} - will fallback to AE_FMT_FLOAT",
+ CAEUtil::DataFormatToStr(format.m_dataFormat));
+ format.m_dataFormat = AE_FMT_FLOAT;
+ pa_fmt = PA_SAMPLE_FLOAT32;
+ m_passthrough = false;
+ }
+ // store information about current sink
+ SinkInfoStruct sinkStruct;
+ sinkStruct.mainloop = m_MainLoop;
+ sinkStruct.device_found = false;
+
+ // get real sample rate of the device we want to open - to avoid resampling
+ bool isDefaultDevice = false;
+ if(StringUtils::EndsWithNoCase(device, std::string("default")))
+ isDefaultDevice = true;
+
+ WaitForOperation(pa_context_get_sink_info_by_name(m_Context, isDefaultDevice ? NULL : device.c_str(), SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info");
+ // only check if the device is existing - don't alter the sample rate
+ if (!sinkStruct.device_found)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Sink {} not found", device);
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ if(m_passthrough)
+ {
+ map.channels = 2;
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ }
+ else
+ {
+ map = AEChannelMapToPAChannel(format.m_channelLayout);
+ format.m_channelLayout = PAChannelToAEChannelMap(map);
+ }
+ m_Channels = format.m_channelLayout.Count();
+
+ // Pulse can resample everything between 5 khz and 384 khz (since 9.0)
+ unsigned int max_pulse_sample_rate = 384000U;
+ format.m_sampleRate = std::max(5512U, std::min(format.m_sampleRate, max_pulse_sample_rate));
+
+ pa_format_info *info[1];
+ info[0] = pa_format_info_new();
+ if (m_passthrough)
+ info[0]->encoding = AEStreamFormatToPulseEncoding(format.m_streamInfo.m_type);
+ else
+ info[0]->encoding = AEFormatToPulseEncoding(format.m_dataFormat);
+
+ if (info[0]->encoding == PA_ENCODING_INVALID)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Invalid Encoding");
+ pa_format_info_free(info[0]);
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ if(!m_passthrough)
+ {
+ pa_format_info_set_sample_format(info[0], pa_fmt);
+ pa_format_info_set_channel_map(info[0], &map);
+ }
+ pa_format_info_set_channels(info[0], m_Channels);
+
+ // PA requires the original encoded rate in order to do EAC3
+ unsigned int samplerate = format.m_sampleRate;
+ if (m_passthrough && (info[0]->encoding == PA_ENCODING_EAC3_IEC61937))
+ {
+ // this is only used internally for PA to use EAC3
+ samplerate = format.m_streamInfo.m_sampleRate;
+ }
+
+ pa_format_info_set_rate(info[0], samplerate);
+
+ if (!pa_format_info_valid(info[0]))
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Invalid format info");
+ pa_format_info_free(info[0]);
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ pa_sample_spec spec;
+ pa_format_info_to_sample_spec(info[0], &spec, NULL);
+ if (!pa_sample_spec_valid(&spec))
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec");
+ pa_format_info_free(info[0]);
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ m_BytesPerSecond = pa_bytes_per_second(&spec);
+ unsigned int frameSize = pa_frame_size(&spec);
+
+ m_Stream = pa_stream_new_extended(m_Context, "kodi audio stream", info, 1, NULL);
+ pa_format_info_free(info[0]);
+
+ if (m_Stream == NULL)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Could not create a stream");
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ pa_stream_set_state_callback(m_Stream, StreamStateCallback, m_MainLoop);
+ pa_stream_set_write_callback(m_Stream, StreamRequestCallback, this);
+ pa_stream_set_latency_update_callback(m_Stream, StreamLatencyUpdateCallback, m_MainLoop);
+
+ // default buffer construction
+ // align with AE's max buffer
+ unsigned int latency = m_BytesPerSecond / 2.5; // 400 ms
+ unsigned int process_time = latency / 4; // 100 ms
+ if (sinkStruct.isHWDevice && !sinkStruct.isNWDevice && !sinkStruct.isBTDevice)
+ {
+ // on hw devices buffers can be further reduced
+ // 200ms max latency
+ // 50ms min packet size
+ latency = m_BytesPerSecond / 5;
+ process_time = latency / 4;
+ }
+
+ pa_buffer_attr buffer_attr;
+ buffer_attr.fragsize = latency;
+ buffer_attr.maxlength = (uint32_t) -1;
+ buffer_attr.minreq = process_time;
+ buffer_attr.prebuf = (uint32_t) -1;
+ buffer_attr.tlength = latency;
+ int flags = (PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
+
+ if (m_passthrough)
+ flags |= PA_STREAM_PASSTHROUGH;
+
+ if (pa_stream_connect_playback(m_Stream, isDefaultDevice ? NULL : device.c_str(), &buffer_attr, (pa_stream_flags) flags, NULL, NULL) < 0)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output");
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ /* Wait until the stream is ready */
+ do
+ {
+ pa_threaded_mainloop_wait(m_MainLoop);
+ CLog::Log(LOGDEBUG, "PulseAudio: Stream {}",
+ StreamStateToString(pa_stream_get_state(m_Stream)));
+ }
+ while (pa_stream_get_state(m_Stream) != PA_STREAM_READY && pa_stream_get_state(m_Stream) != PA_STREAM_FAILED);
+
+ if (pa_stream_get_state(m_Stream) == PA_STREAM_FAILED)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: Waited for the stream but it failed");
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ const pa_buffer_attr *a;
+
+ if (!(a = pa_stream_get_buffer_attr(m_Stream)))
+ {
+ CLog::Log(LOGERROR, "PulseAudio: {}", pa_strerror(pa_context_errno(m_Context)));
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+ else
+ {
+ unsigned int packetSize = a->minreq;
+ m_BufferSize = a->tlength;
+ m_periodSize = a->minreq;
+
+ format.m_frames = packetSize / frameSize;
+ m_maxLatency = static_cast<double>(m_BufferSize) / m_BytesPerSecond;
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ // Register Callback for Sink changes
+ pa_context_set_subscribe_callback(m_Context, SinkChangedCallback, this);
+ const pa_subscription_mask_t mask = pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK_INPUT);
+ pa_operation *op = pa_context_subscribe(m_Context, mask, NULL, this);
+ if (op != NULL)
+ pa_operation_unref(op);
+ }
+
+ pa_threaded_mainloop_unlock(m_MainLoop);
+
+ format.m_frameSize = frameSize;
+ m_format = format;
+ format.m_dataFormat = m_passthrough ? AE_FMT_S16NE : format.m_dataFormat;
+
+ CLog::Log(LOGINFO,
+ "PulseAudio: Opened device {} in {} mode with Buffersize {} ms Periodsize {} ms",
+ device, m_passthrough ? "passthrough" : "pcm",
+ static_cast<unsigned int>(1000.0 * m_BufferSize / m_BytesPerSecond),
+ static_cast<unsigned int>(1000.0 * m_periodSize / m_BytesPerSecond));
+
+ // Cork stream will resume when adding first package
+ Pause(true);
+ {
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ m_IsAllocated = true;
+ }
+ return true;
+}
+
+void CAESinkPULSE::Deinitialize()
+{
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ m_IsAllocated = false;
+ m_passthrough = false;
+ m_periodSize = 0;
+ m_requestedBytes = 0;
+ m_maxLatency = 0.0;
+
+ if (m_Stream)
+ {
+ CSingleExit exit(m_sec);
+ pa_threaded_mainloop_lock(m_MainLoop);
+ WaitForOperation(pa_stream_flush(m_Stream, NULL, NULL), m_MainLoop, "Flush");
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ }
+
+ {
+ CSingleExit exit(m_sec);
+ if (m_MainLoop)
+ pa_threaded_mainloop_stop(m_MainLoop);
+ }
+
+ if (m_Stream)
+ {
+ pa_stream_disconnect(m_Stream);
+ pa_stream_unref(m_Stream);
+ m_Stream = NULL;
+ m_IsStreamPaused = false;
+ }
+
+ if (m_Context)
+ {
+ pa_context_disconnect(m_Context);
+ pa_context_unref(m_Context);
+ m_Context = NULL;
+ }
+
+ if (m_MainLoop)
+ {
+ pa_threaded_mainloop_free(m_MainLoop);
+ m_MainLoop = NULL;
+ }
+}
+
+void CAESinkPULSE::GetDelay(AEDelayStatus& status)
+{
+ if (!m_IsAllocated)
+ {
+ status.SetDelay(0);
+ return;
+ }
+
+ pa_threaded_mainloop_lock(m_MainLoop);
+ pa_usec_t r_usec;
+ int negative;
+
+ if (pa_stream_get_latency(m_Stream, &r_usec, &negative) < 0)
+ r_usec = 0;
+
+ double delay = r_usec / 1000000.0;
+ if (delay > m_maxLatency)
+ m_maxLatency = delay;
+
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ status.SetDelay(delay);
+}
+
+double CAESinkPULSE::GetCacheTotal()
+{
+ return m_maxLatency;
+}
+
+unsigned int CAESinkPULSE::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_IsAllocated)
+ return 0;
+
+ if (m_IsStreamPaused)
+ {
+ Pause(false);
+ }
+
+ pa_threaded_mainloop_lock(m_MainLoop);
+
+ unsigned int available = frames * m_format.m_frameSize;
+ unsigned int length = m_periodSize;
+ void *buffer = data[0]+offset*m_format.m_frameSize;
+ auto wait_time =
+ std::chrono::duration<double>(static_cast<double>(m_BufferSize) / m_BytesPerSecond);
+ XbmcThreads::EndTime<std::chrono::duration<double>> timer(wait_time);
+ // we don't want to block forever - if timer expires pa_stream_write will
+ // fail - therefore we don't care and just return 0;
+ while (!timer.IsTimePast())
+ {
+ if (m_requestedBytes > 0)
+ break;
+ pa_threaded_mainloop_wait(m_MainLoop);
+ }
+
+ if (timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "Sink Timer expired for more than buffer time: {}s", wait_time.count());
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ return 0;
+ }
+
+ length = std::min(length, available);
+ int error = pa_stream_write(m_Stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ if (error)
+ {
+ CLog::Log(LOGERROR, "CAESinkPULSE::AddPackets - pa_stream_write failed: {}", error);
+ return 0;
+ }
+
+ // subtract here, as we might come back earlier than our callback and there is
+ // still space in the buffer to write another time
+ m_requestedBytes -= length;
+
+ unsigned int res = length / m_format.m_frameSize;
+
+ return res;
+}
+
+void CAESinkPULSE::Drain()
+{
+ if (!m_IsAllocated)
+ return;
+
+ pa_threaded_mainloop_lock(m_MainLoop);
+ WaitForOperation(pa_stream_drain(m_Stream, NULL, NULL), m_MainLoop, "Drain");
+ WaitForOperation(pa_stream_cork(m_Stream, 1, NULL, NULL), m_MainLoop, "Pause");
+ m_IsStreamPaused = true;
+ pa_threaded_mainloop_unlock(m_MainLoop);
+}
+
+// This is a helper to get stream info during the PA callbacks
+// it shall never be called from real outside
+pa_stream* CAESinkPULSE::GetInternalStream()
+{
+ return m_Stream;
+}
+
+// This is a helper to use the internal mainloop from another thread, e.g. a RequestCallback
+// it is shipped via the userdata. Don't use it for other purposes than signalling
+pa_threaded_mainloop* CAESinkPULSE::GetInternalMainLoop()
+{
+ return m_MainLoop;
+}
+
+void CAESinkPULSE::UpdateInternalVolume(const pa_cvolume* nVol)
+{
+ if (!nVol)
+ return;
+
+ pa_volume_t o_vol = pa_cvolume_avg(&m_Volume);
+ pa_volume_t n_vol = pa_cvolume_avg(nVol);
+
+ if (o_vol != n_vol)
+ {
+ pa_cvolume_set(&m_Volume, m_Channels, n_vol);
+ m_volume_needs_update = true;
+ }
+}
+
+void CAESinkPULSE::SetVolume(float volume)
+{
+ if (m_IsAllocated && !m_passthrough)
+ {
+ pa_threaded_mainloop_lock(m_MainLoop);
+ // clamp possibly too large / low values
+ float per_cent_volume = std::max(0.0f, std::min(volume, 1.0f));
+
+ if (m_volume_needs_update)
+ {
+ m_volume_needs_update = false;
+ pa_volume_t n_vol = pa_cvolume_avg(&m_Volume);
+ n_vol = std::min(n_vol, PA_VOLUME_NORM);
+ per_cent_volume = static_cast<float>(n_vol) / PA_VOLUME_NORM;
+ // only update internal volume
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ appVolume->SetVolume(per_cent_volume, false);
+ return;
+ }
+
+ pa_volume_t pavolume = per_cent_volume * PA_VOLUME_NORM;
+ unsigned int sink_input_idx = pa_stream_get_index(m_Stream);
+
+ if ( pavolume <= 0 )
+ pa_cvolume_mute(&m_Volume, m_Channels);
+ else
+ pa_cvolume_set(&m_Volume, m_Channels, pavolume);
+
+ pa_operation *op = pa_context_set_sink_input_volume(m_Context, sink_input_idx, &m_Volume, NULL, NULL);
+ if (op == NULL)
+ CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
+ else
+ pa_operation_unref(op);
+
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ }
+}
+
+void CAESinkPULSE::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ pa_context *context;
+ pa_threaded_mainloop *mainloop;
+
+ if (!SetupContext(NULL, "KodiSink", &context, &mainloop))
+ {
+ CLog::Log(LOGINFO, "PulseAudio might not be running. Context was not created.");
+ return;
+ }
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ SinkInfoStruct sinkStruct;
+ sinkStruct.mainloop = mainloop;
+ sinkStruct.list = &list;
+
+ ModuleInfoStruct mis;
+ mis.mainloop = mainloop;
+
+ WaitForOperation(pa_context_get_module_info_list(context, ModuleInfoCallback, &mis), mainloop, "Check PA Modules");
+ if (!mis.hasAllowPT)
+ {
+ CLog::Log(LOGWARNING, "Pulseaudio module module-allow-passthrough not loaded - opening PT devices might fail");
+ }
+ WaitForOperation(pa_context_get_sink_info_list(context, SinkInfoRequestCallback, &sinkStruct), mainloop, "EnumerateAudioSinks");
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ if (mainloop)
+ pa_threaded_mainloop_stop(mainloop);
+
+ if (context)
+ {
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ context = NULL;
+ }
+
+ if (mainloop)
+ {
+ pa_threaded_mainloop_free(mainloop);
+ mainloop = NULL;
+ }
+}
+
+bool CAESinkPULSE::IsInitialized()
+{
+ std::unique_lock<CCriticalSection> lock(m_sec);
+ return m_IsAllocated;
+}
+
+void CAESinkPULSE::Pause(bool pause)
+{
+ pa_threaded_mainloop_lock(m_MainLoop);
+
+ if (!WaitForOperation(pa_stream_cork(m_Stream, pause ? 1 : 0, NULL, NULL), m_MainLoop, pause ? "Pause" : "Resume"))
+ pause = !pause;
+
+ m_IsStreamPaused = pause;
+ pa_threaded_mainloop_unlock(m_MainLoop);
+}
+
+inline bool CAESinkPULSE::WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "")
+{
+ if (op == NULL)
+ return false;
+
+ bool success = true;
+
+ while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mainloop);
+
+ if (pa_operation_get_state(op) != PA_OPERATION_DONE)
+ {
+ CLog::Log(LOGERROR, "PulseAudio: {} Operation failed", LogEntry);
+ success = false;
+ }
+
+ pa_operation_unref(op);
+ return success;
+}
+
+void CAESinkPULSE::Cleanup()
+{
+ m_pMonitor.reset();
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h
new file mode 100644
index 0000000..18aeb32
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+#include <memory>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+
+class CDriverMonitor;
+
+class CAESinkPULSE : public IAESink
+{
+public:
+ const char *GetName() override { return "PULSE"; }
+
+ CAESinkPULSE();
+ ~CAESinkPULSE() override;
+
+ static bool Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
+ static void Cleanup();
+
+ bool Initialize(AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+
+ virtual double GetDelay() { return 0.0; }
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+ bool HasVolume() override { return true; }
+ void SetVolume(float volume) override;
+
+ bool IsInitialized();
+ void UpdateInternalVolume(const pa_cvolume* nVol);
+ pa_stream* GetInternalStream();
+ pa_threaded_mainloop* GetInternalMainLoop();
+ CCriticalSection m_sec;
+ std::atomic<int> m_requestedBytes = 0;
+
+private:
+ void Pause(bool pause);
+ static inline bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry);
+
+ bool m_IsAllocated;
+ bool m_passthrough;
+ bool m_IsStreamPaused;
+
+ AEAudioFormat m_format;
+ unsigned int m_BytesPerSecond;
+ unsigned int m_BufferSize;
+ unsigned int m_Channels;
+ double m_maxLatency;
+
+ pa_stream *m_Stream;
+ pa_cvolume m_Volume;
+ bool m_volume_needs_update;
+ uint32_t m_periodSize;
+
+ pa_context *m_Context;
+ pa_threaded_mainloop *m_MainLoop;
+
+ static std::unique_ptr<CDriverMonitor> m_pMonitor;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.cpp
new file mode 100644
index 0000000..3ffb37f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.cpp
@@ -0,0 +1,316 @@
+/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2016-2017 Tobias Kortkamp <t@tobik.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkSNDIO.h"
+
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/log.h"
+
+#include <sys/param.h>
+
+#ifndef nitems
+#define nitems(x) (sizeof((x))/sizeof((x)[0]))
+#endif
+
+static enum AEChannel channelMap[] =
+{
+ AE_CH_FL,
+ AE_CH_FR,
+ AE_CH_BL,
+ AE_CH_BR,
+ AE_CH_FC,
+ AE_CH_LFE,
+ AE_CH_SL,
+ AE_CH_SR,
+};
+
+struct sndio_formats
+{
+ AEDataFormat fmt;
+ unsigned int bits;
+ unsigned int bps;
+ unsigned int sig;
+ unsigned int le;
+ unsigned int msb;
+};
+
+static struct sndio_formats formats[] =
+{
+ { AE_FMT_S32NE, 32, 4, 1, SIO_LE_NATIVE, 1 },
+ { AE_FMT_S32LE, 32, 4, 1, 1, 1 },
+ { AE_FMT_S32BE, 32, 4, 1, 0, 1 },
+
+ { AE_FMT_S24NE4, 24, 4, 1, SIO_LE_NATIVE, 0 },
+ { AE_FMT_S24NE4, 24, 4, 1, SIO_LE_NATIVE, 1 },
+ { AE_FMT_S24NE3, 24, 3, 1, SIO_LE_NATIVE, 0 },
+ { AE_FMT_S24NE3, 24, 3, 1, SIO_LE_NATIVE, 1 },
+
+ { AE_FMT_S16NE, 16, 2, 1, SIO_LE_NATIVE, 1 },
+ { AE_FMT_S16NE, 16, 2, 1, SIO_LE_NATIVE, 0 },
+ { AE_FMT_S16LE, 16, 2, 1, 1, 1 },
+ { AE_FMT_S16LE, 16, 2, 1, 1, 0 },
+ { AE_FMT_S16BE, 16, 2, 1, 0, 1 },
+ { AE_FMT_S16BE, 16, 2, 1, 0, 0 },
+
+ { AE_FMT_U8, 8, 1, 0, 0, 0 },
+ { AE_FMT_U8, 8, 1, 0, 0, 1 },
+ { AE_FMT_U8, 8, 1, 0, 1, 0 },
+ { AE_FMT_U8, 8, 1, 0, 1, 1 },
+};
+
+static AEDataFormat lookupDataFormat(unsigned int bits, unsigned int bps,
+ unsigned int sig, unsigned int le, unsigned int msb)
+{
+ for (const sndio_formats& format : formats)
+ {
+ if (bits == format.bits &&
+ bps == format.bps &&
+ sig == format.sig &&
+ le == format.le &&
+ msb == format.msb)
+ {
+ return format.fmt;
+ }
+ }
+ return AE_FMT_INVALID;
+}
+
+void CAESinkSNDIO::AudioFormatToPar(AEAudioFormat& format)
+{
+ sio_initpar(&m_par);
+
+ m_par.rate = format.m_sampleRate;
+ m_par.xrun = SIO_IGNORE;
+ m_par.pchan = format.m_channelLayout.Count();
+
+ for (const sndio_formats& f : formats)
+ {
+ if (f.fmt == format.m_dataFormat)
+ {
+ m_par.bits = f.bits;
+ m_par.sig = f.sig;
+ m_par.le = f.le;
+ m_par.msb = f.msb;
+ m_par.bps = f.bps;
+ return;
+ }
+ }
+
+ /* Default to AE_FMT_S16NE */
+ m_par.bits = 16;
+ m_par.bps = 2;
+ m_par.sig = 1;
+ m_par.le = SIO_LE_NATIVE;
+}
+
+bool CAESinkSNDIO::ParToAudioFormat(AEAudioFormat& format)
+{
+ AEDataFormat dataFormat = lookupDataFormat(m_par.bits, m_par.bps, m_par.sig, m_par.le, m_par.msb);
+ if (dataFormat == AE_FMT_INVALID)
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::ParToAudioFormat - invalid data format");
+ return false;
+ }
+
+ if (m_par.pchan > nitems(channelMap))
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::ParToAudioFormat - too many channels: {}", m_par.pchan);
+ return false;
+ }
+
+ CAEChannelInfo info;
+ for (unsigned int i = 0; i < m_par.pchan; i++)
+ info += channelMap[i];
+ format.m_channelLayout = info;
+ format.m_dataFormat = dataFormat;
+ format.m_sampleRate = m_par.rate;
+ format.m_frameSize = m_par.bps * m_par.pchan;
+ format.m_frames = m_par.bufsz / format.m_frameSize;
+
+ return true;
+}
+
+CAESinkSNDIO::CAESinkSNDIO()
+{
+ m_hdl = nullptr;
+}
+
+CAESinkSNDIO::~CAESinkSNDIO()
+{
+ Deinitialize();
+}
+
+void CAESinkSNDIO::Register()
+{
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "SNDIO";
+ entry.createFunc = CAESinkSNDIO::Create;
+ entry.enumerateFunc = CAESinkSNDIO::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(entry);
+}
+
+IAESink* CAESinkSNDIO::Create(std::string &device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkSNDIO();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkSNDIO::Initialize(AEAudioFormat &format, std::string &device)
+{
+ if ((m_hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == nullptr)
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::Initialize - Failed to open device");
+ return false;
+ }
+
+ AudioFormatToPar(format);
+ if (!sio_setpar(m_hdl, &m_par) ||
+ !sio_getpar(m_hdl, &m_par) ||
+ !ParToAudioFormat(format))
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::Initialize - could not negotiate parameters");
+ return false;
+ }
+
+ m_played = m_written = 0;
+
+ sio_onmove(m_hdl, CAESinkSNDIO::OnmoveCb, this);
+
+ if (!sio_start(m_hdl))
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::Initialize - sio_start failed");
+ return false;
+ }
+
+ return true;
+}
+
+void CAESinkSNDIO::Deinitialize()
+{
+ if (m_hdl != nullptr)
+ {
+ sio_close(m_hdl);
+ m_hdl = nullptr;
+ }
+}
+
+void CAESinkSNDIO::Stop()
+{
+ if (!m_hdl)
+ return;
+
+ if (!sio_stop(m_hdl))
+ CLog::Log(LOGERROR, "CAESinkSNDIO::Stop - Failed");
+
+ m_written = m_played = 0;
+}
+
+void CAESinkSNDIO::OnmoveCb(void *arg, int delta) {
+ CAESinkSNDIO* self = static_cast<CAESinkSNDIO*>(arg);
+ self->m_played += delta;
+}
+
+void CAESinkSNDIO::GetDelay(AEDelayStatus& status)
+{
+ unsigned int frameSize = m_par.bps * m_par.pchan;
+ double delay = 1.0 * ((m_written / frameSize) - m_played) / m_par.rate;
+ status.SetDelay(delay);
+}
+
+unsigned int CAESinkSNDIO::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_hdl)
+ return INT_MAX;
+
+ unsigned int frameSize = m_par.bps * m_par.pchan;
+ size_t size = frames * frameSize;
+ void *buffer = data[0] + offset * frameSize;
+ size_t wrote = sio_write(m_hdl, buffer, size);
+ m_written += wrote;
+ return wrote / frameSize;
+}
+
+void CAESinkSNDIO::Drain()
+{
+ if(!m_hdl)
+ return;
+
+ if (!sio_stop(m_hdl) || !sio_start(m_hdl))
+ CLog::Log(LOGERROR, "CAESinkSNDIO::Drain - failed");
+
+ m_written = m_played = 0;
+}
+
+void CAESinkSNDIO::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
+{
+ struct sio_hdl *hdl;
+ struct sio_cap cap;
+
+ if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == nullptr)
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::EnumerateDevicesEx - sio_open");
+ return;
+ }
+
+ if (!sio_getcap(hdl, &cap))
+ {
+ CLog::Log(LOGERROR, "CAESinkSNDIO::EnumerateDevicesEx - sio_getcap");
+ return;
+ }
+
+ sio_close(hdl);
+ hdl = nullptr;
+
+ for (unsigned int i = 0; i < cap.nconf; i++)
+ {
+ CAEDeviceInfo info;
+ sio_cap::sio_conf conf = cap.confs[i];
+
+ info.m_deviceName = SIO_DEVANY;
+ info.m_displayName = "sndio";
+ info.m_displayNameExtra = "#" + std::to_string(i);
+ info.m_deviceType = AE_DEVTYPE_PCM;
+ info.m_wantsIECPassthrough = false;
+
+ unsigned int maxchan = 0;
+ for (unsigned int j = 0; j < SIO_NCHAN; j++)
+ {
+ if (conf.pchan & (1 << j))
+ maxchan = MAX(maxchan, cap.pchan[j]);
+ }
+
+ maxchan = MIN(maxchan, nitems(channelMap));
+ for (unsigned int j = 0; j < maxchan; j++)
+ info.m_channels += channelMap[j];
+
+ for (unsigned int j = 0; j < SIO_NRATE; j++)
+ {
+ if (conf.rate & (1 << j))
+ {
+ info.m_sampleRates.push_back(cap.rate[j]);
+ }
+ }
+
+ for (unsigned int j = 0; j < SIO_NENC; j++)
+ {
+ if (conf.enc & (1 << j))
+ {
+ AEDataFormat format = lookupDataFormat(cap.enc[j].bits, cap.enc[j].bps, cap.enc[j].sig, cap.enc[j].le, cap.enc[j].msb);
+ if (format != AE_FMT_INVALID)
+ info.m_dataFormats.push_back(format);
+ }
+ }
+
+ list.push_back(info);
+ }
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.h b/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.h
new file mode 100644
index 0000000..668a3b7
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkSNDIO.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "threads/CriticalSection.h"
+
+#include <stdint.h>
+
+#include <sndio.h>
+
+class CAESinkSNDIO : public IAESink
+{
+public:
+ const char *GetName() override { return "sndio"; }
+
+ CAESinkSNDIO();
+ ~CAESinkSNDIO() override;
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
+
+ bool Initialize(AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+
+ virtual void Stop();
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override { return 0.0; }
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+private:
+ void AudioFormatToPar(AEAudioFormat& format);
+ bool ParToAudioFormat(AEAudioFormat& format);
+ static void OnmoveCb(void *arg, int delta);
+
+ struct sio_hdl *m_hdl;
+ struct sio_par m_par;
+ ssize_t m_played;
+ ssize_t m_written;
+};
+
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
new file mode 100644
index 0000000..cbbf084
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
@@ -0,0 +1,1002 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkWASAPI.h"
+
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/TimeUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <stdint.h>
+
+#include <Audioclient.h>
+#include <Mmreg.h>
+
+#ifdef TARGET_WINDOWS_DESKTOP
+# pragma comment(lib, "Avrt.lib")
+#endif // TARGET_WINDOWS_DESKTOP
+
+const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
+const IID IID_IAudioClock = __uuidof(IAudioClock);
+DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
+DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
+
+extern const char *WASAPIErrToStr(HRESULT err);
+#define EXIT_ON_FAILURE(hr, reason) \
+ if (FAILED(hr)) \
+ { \
+ CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
+ goto failed; \
+ }
+
+template<class T>
+inline void SafeRelease(T **ppT)
+{
+ if (*ppT)
+ {
+ (*ppT)->Release();
+ *ppT = nullptr;
+ }
+}
+
+using namespace Microsoft::WRL;
+
+CAESinkWASAPI::CAESinkWASAPI()
+{
+ m_channelLayout.Reset();
+}
+
+CAESinkWASAPI::~CAESinkWASAPI()
+{
+}
+
+void CAESinkWASAPI::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "WASAPI";
+ reg.createFunc = CAESinkWASAPI::Create;
+ reg.enumerateFunc = CAESinkWASAPI::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkWASAPI::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ IAESink *sink = new CAESinkWASAPI();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device)
+{
+ if (m_initialized)
+ return false;
+
+ m_device = device;
+ bool bdefault = false;
+ HRESULT hr = S_FALSE;
+
+ /* Save requested format */
+ /* Clear returned format */
+ sinkReqFormat = format.m_dataFormat;
+ sinkRetFormat = AE_FMT_INVALID;
+
+ if(StringUtils::EndsWithNoCase(device, std::string("default")))
+ bdefault = true;
+
+ if(!bdefault)
+ {
+ hr = CAESinkFactoryWin::ActivateWASAPIDevice(device, &m_pDevice);
+ EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.")
+ }
+
+ if (!m_pDevice)
+ {
+ if(!bdefault)
+ {
+ CLog::LogF(LOGINFO,
+ "Could not locate the device named \"{}\" in the list of WASAPI endpoint devices. "
+ " Trying the default device...",
+ device);
+ }
+
+ std::string defaultId = CAESinkFactoryWin::GetDefaultDeviceId();
+ if (defaultId.empty())
+ {
+ CLog::LogF(LOGINFO, "Could not locate the default device id in the list of WASAPI endpoint devices.");
+ goto failed;
+ }
+
+ hr = CAESinkFactoryWin::ActivateWASAPIDevice(defaultId, &m_pDevice);
+ EXIT_ON_FAILURE(hr, "Could not retrieve the default WASAPI audio endpoint.")
+
+ device = defaultId;
+ }
+
+ hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf());
+ EXIT_ON_FAILURE(hr, "Activating the WASAPI endpoint device failed.")
+
+ if (!InitializeExclusive(format))
+ {
+ CLog::LogF(LOGINFO, "Could not Initialize Exclusive with that format");
+ goto failed;
+ }
+
+ /* get the buffer size and calculate the frames for AE */
+ m_pAudioClient->GetBufferSize(&m_uiBufferLen);
+
+ format.m_frames = m_uiBufferLen;
+ m_format = format;
+ sinkRetFormat = format.m_dataFormat;
+
+ hr = m_pAudioClient->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(m_pRenderClient.ReleaseAndGetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI render client interface.")
+
+ hr = m_pAudioClient->GetService(IID_IAudioClock, reinterpret_cast<void**>(m_pAudioClock.ReleaseAndGetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI audio clock interface.")
+
+ hr = m_pAudioClock->GetFrequency(&m_clockFreq);
+ EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetFrequency failed.")
+
+ m_needDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ hr = m_pAudioClient->SetEventHandle(m_needDataEvent);
+ EXIT_ON_FAILURE(hr, "Could not set the WASAPI event handler.");
+
+ m_initialized = true;
+ m_isDirty = false;
+
+ // allow feeding less samples than buffer size
+ // if the device is opened exclusive and event driven, provided samples must match buffersize
+ // ActiveAE tries to align provided samples with buffer size but cannot guarantee (e.g. transcoding)
+ // this can be avoided by dropping the event mode which has not much benefit; SoftAE polls anyway
+ m_buffer.resize(format.m_frames * format.m_frameSize);
+ m_bufferPtr = 0;
+
+ return true;
+
+failed:
+ CLog::LogF(LOGERROR, "WASAPI initialization failed.");
+ SafeRelease(&m_pDevice);
+ if(m_needDataEvent)
+ {
+ CloseHandle(m_needDataEvent);
+ m_needDataEvent = 0;
+ }
+
+ return false;
+}
+
+void CAESinkWASAPI::Deinitialize()
+{
+ if (!m_initialized && !m_isDirty)
+ return;
+
+ if (m_running)
+ {
+ try
+ {
+ m_pAudioClient->Stop(); //stop the audio output
+ m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
+ m_sinkFrames = 0;
+ }
+ catch (...)
+ {
+ CLog::LogF(LOGDEBUG, "Invalidated AudioClient - Releasing");
+ }
+ }
+ m_running = false;
+
+ CloseHandle(m_needDataEvent);
+
+ m_pRenderClient = nullptr;
+ m_pAudioClient = nullptr;
+ m_pAudioClock = nullptr;
+ SafeRelease(&m_pDevice);
+
+ m_initialized = false;
+
+ m_bufferPtr = 0;
+}
+
+/**
+ * @brief rescale uint64_t without overflowing on large values
+ */
+static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den)
+{
+ return ((val / den) * num) + (((val % den) * num) / den);
+}
+
+
+void CAESinkWASAPI::GetDelay(AEDelayStatus& status)
+{
+ HRESULT hr;
+ uint64_t pos, tick;
+ int retries = 0;
+
+ if (!m_initialized)
+ goto failed;
+
+ do {
+ hr = m_pAudioClock->GetPosition(&pos, &tick);
+ } while (hr != S_OK && ++retries < 100);
+ EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetPosition failed.")
+
+ status.delay = (double)(m_sinkFrames + m_bufferPtr) / m_format.m_sampleRate - (double)pos / m_clockFreq;
+ status.tick = rescale_u64(tick, CurrentHostFrequency(), 10000000); /* convert from 100ns back to qpc ticks */
+ return;
+failed:
+ status.SetDelay(0);
+}
+
+double CAESinkWASAPI::GetCacheTotal()
+{
+ if (!m_initialized)
+ return 0.0;
+
+ return m_sinkLatency;
+}
+
+unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_initialized)
+ return 0;
+
+ HRESULT hr;
+ BYTE *buf;
+ DWORD flags = 0;
+
+#ifndef _DEBUG
+ LARGE_INTEGER timerStart;
+ LARGE_INTEGER timerStop;
+ LARGE_INTEGER timerFreq;
+#endif
+
+ unsigned int NumFramesRequested = m_format.m_frames;
+ unsigned int FramesToCopy = std::min(m_format.m_frames - m_bufferPtr, frames);
+ uint8_t *buffer = data[0]+offset*m_format.m_frameSize;
+ if (m_bufferPtr != 0 || frames != m_format.m_frames)
+ {
+ memcpy(m_buffer.data() + m_bufferPtr * m_format.m_frameSize, buffer,
+ FramesToCopy * m_format.m_frameSize);
+ m_bufferPtr += FramesToCopy;
+ if (m_bufferPtr != m_format.m_frames)
+ return frames;
+ }
+
+ if (!m_running) //first time called, pre-fill buffer then start audio client
+ {
+ hr = m_pAudioClient->Reset();
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, " AudioClient reset failed due to {}", WASAPIErrToStr(hr));
+ return 0;
+ }
+ hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf);
+ if (FAILED(hr))
+ {
+ #ifdef _DEBUG
+ CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr));
+#endif
+ m_isDirty = true; //flag new device or re-init needed
+ return INT_MAX;
+ }
+
+ memset(buf, 0, NumFramesRequested * m_format.m_frameSize); //fill buffer with silence
+
+ hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
+ if (FAILED(hr))
+ {
+ #ifdef _DEBUG
+ CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr));
+#endif
+ m_isDirty = true; //flag new device or re-init needed
+ return INT_MAX;
+ }
+ m_sinkFrames += NumFramesRequested;
+
+ hr = m_pAudioClient->Start(); //start the audio driver running
+ if (FAILED(hr))
+ CLog::LogF(LOGERROR, "AudioClient Start Failed");
+ m_running = true; //signal that we're processing frames
+ return 0U;
+ }
+
+#ifndef _DEBUG
+ /* Get clock time for latency checks */
+ QueryPerformanceFrequency(&timerFreq);
+ QueryPerformanceCounter(&timerStart);
+#endif
+
+ /* Wait for Audio Driver to tell us it's got a buffer available */
+ DWORD eventAudioCallback;
+ eventAudioCallback = WaitForSingleObject(m_needDataEvent, 1100);
+
+ if(eventAudioCallback != WAIT_OBJECT_0 || !&buf)
+ {
+ CLog::LogF(LOGERROR, "Endpoint Buffer timed out");
+ return INT_MAX;
+ }
+
+ if (!m_running)
+ return 0;
+
+#ifndef _DEBUG
+ QueryPerformanceCounter(&timerStop);
+ LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
+ double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart;
+ m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
+
+ if (m_avgTimeWaiting < 3.0)
+ {
+ CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
+ (int)m_avgTimeWaiting);
+ }
+#endif
+
+ hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf);
+ if (FAILED(hr))
+ {
+#ifdef _DEBUG
+ CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr));
+#endif
+ return INT_MAX;
+ }
+
+ // fill buffer
+ memcpy(buf, m_bufferPtr == 0 ? buffer : m_buffer.data(),
+ NumFramesRequested * m_format.m_frameSize);
+ m_bufferPtr = 0;
+
+ hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
+ if (FAILED(hr))
+ {
+#ifdef _DEBUG
+ CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr));
+#endif
+ return INT_MAX;
+ }
+ m_sinkFrames += NumFramesRequested;
+
+ if (FramesToCopy != frames)
+ {
+ m_bufferPtr = frames-FramesToCopy;
+ memcpy(m_buffer.data(), buffer + FramesToCopy * m_format.m_frameSize,
+ m_bufferPtr * m_format.m_frameSize);
+ }
+
+ return frames;
+}
+
+void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
+{
+ CAEDeviceInfo deviceInfo;
+ CAEChannelInfo deviceChannels;
+ bool add192 = false;
+ bool add48 = false;
+
+ WAVEFORMATEXTENSIBLE wfxex = {};
+ HRESULT hr;
+
+ const bool onlyPT = (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Xbox);
+
+ for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails())
+ {
+ deviceInfo.m_channels.Reset();
+ deviceInfo.m_dataFormats.clear();
+ deviceInfo.m_sampleRates.clear();
+ deviceChannels.Reset();
+
+ for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
+ {
+ if (details.uiChannelMask & WASAPIChannelOrder[c])
+ deviceChannels += AEChannelNames[c];
+ }
+
+ IAEWASAPIDevice* pDevice;
+ hr = CAESinkFactoryWin::ActivateWASAPIDevice(details.strDeviceId, &pDevice);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint failed.");
+ goto failed;
+ }
+
+ ComPtr<IAudioClient> pClient = nullptr;
+ hr = pDevice->Activate(pClient.GetAddressOf());
+ if (SUCCEEDED(hr))
+ {
+ /* Test format DTS-HD-HR */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
+ wfxex.Format.nSamplesPerSec = 192000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
+ {
+ CLog::LogF(LOGINFO,
+ "Exclusive mode is not allowed on device \"{}\", check device settings.",
+ details.strDescription);
+ SafeRelease(&pDevice);
+ continue;
+ }
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD),
+ details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ add192 = true;
+ }
+
+ /* Test format DTS-HD */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
+ wfxex.Format.nSamplesPerSec = 192000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 8;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
+ {
+ CLog::LogF(LOGINFO,
+ "Exclusive mode is not allowed on device \"{}\", check device settings.",
+ details.strDescription);
+ SafeRelease(&pDevice);
+ continue;
+ }
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA),
+ details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ add192 = true;
+ }
+
+ /* Test format Dolby TrueHD */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD),
+ details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+ add192 = true;
+ }
+
+ /* Test format Dolby EAC3 */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3),
+ details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ add192 = true;
+ }
+
+ /* Test format DTS */
+ wfxex.Format.nSamplesPerSec = 48000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ "STREAM_TYPE_DTS", details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ add48 = true;
+ }
+
+ /* Test format Dolby AC3 */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
+ {
+ if(FAILED(hr))
+ {
+ CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3),
+ details.strDescription);
+ }
+
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ add48 = true;
+ }
+
+ /* Test format for PCM format iteration */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+
+ for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
+ {
+ if (p < AE_FMT_FLOAT)
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat) p);
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ if (p == AE_FMT_S24NE4MSB)
+ {
+ wfxex.Samples.wValidBitsPerSample = 24;
+ }
+ else if (p <= AE_FMT_S24NE4 && p >= AE_FMT_S24BE4)
+ {
+ // not supported
+ continue;
+ }
+ else
+ {
+ wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
+ }
+
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr))
+ deviceInfo.m_dataFormats.push_back((AEDataFormat) p);
+ }
+
+ /* Test format for sample rate iteration */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ for (int j = 0; j < WASAPISampleRateCount; j++)
+ {
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+ if (SUCCEEDED(hr))
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
+ else if (wfxex.Format.nSamplesPerSec == 192000 && add192)
+ {
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
+ CLog::LogF(LOGINFO, "sample rate 192khz on device \"{}\" seems to be not supported.",
+ details.strDescription);
+ }
+ else if (wfxex.Format.nSamplesPerSec == 48000 && add48)
+ {
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
+ CLog::LogF(LOGINFO, "sample rate 48khz on device \"{}\" seems to be not supported.",
+ details.strDescription);
+ }
+ }
+ pClient = nullptr;
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "Failed to activate device for passthrough capability testing.");
+ }
+
+ deviceInfo.m_deviceName = details.strDeviceId;
+ deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
+ deviceInfo.m_displayNameExtra = std::string("WASAPI: ").append(details.strDescription);
+ deviceInfo.m_deviceType = details.eDeviceType;
+ deviceInfo.m_channels = deviceChannels;
+
+ /* Store the device info */
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfo.m_onlyPassthrough = onlyPT;
+
+ if (!deviceInfo.m_streamTypes.empty())
+ deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
+
+ deviceInfoList.push_back(deviceInfo);
+
+ if (details.bDefault)
+ {
+ deviceInfo.m_deviceName = std::string("default");
+ deviceInfo.m_displayName = std::string("default");
+ deviceInfo.m_displayNameExtra = std::string("");
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfo.m_onlyPassthrough = onlyPT;
+ deviceInfoList.push_back(deviceInfo);
+ }
+
+ SafeRelease(&pDevice);
+ }
+ return;
+
+failed:
+
+ if (FAILED(hr))
+ CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr));
+}
+
+//Private utility functions////////////////////////////////////////////////////
+
+void CAESinkWASAPI::BuildWaveFormatExtensibleIEC61397(AEAudioFormat &format, WAVEFORMATEXTENSIBLE_IEC61937 &wfxex)
+{
+ /* Fill the common structure */
+ CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex.FormatExt);
+
+ /* Code below kept for future use - preferred for later Windows versions */
+ /* but can cause problems on older Windows versions and drivers */
+ /*
+ wfxex.FormatExt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE_IEC61937)-sizeof(WAVEFORMATEX);
+ wfxex.dwEncodedChannelCount = format.m_channelLayout.Count();
+ wfxex.dwEncodedSamplesPerSec = bool(format.m_dataFormat == AE_FMT_TRUEHD ||
+ format.m_dataFormat == AE_FMT_DTSHD ||
+ format.m_dataFormat == AE_FMT_EAC3) ? 96000L : 48000L;
+ wfxex.dwAverageBytesPerSec = 0; //Ignored */
+}
+
+bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format)
+{
+ WAVEFORMATEXTENSIBLE_IEC61937 wfxex_iec61937;
+ WAVEFORMATEXTENSIBLE &wfxex = wfxex_iec61937.FormatExt;
+
+ if (format.m_dataFormat <= AE_FMT_FLOAT)
+ CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
+ else if (format.m_dataFormat == AE_FMT_RAW)
+ BuildWaveFormatExtensibleIEC61397(format, wfxex_iec61937);
+ else
+ {
+ // planar formats are currently not supported by this sink
+ format.m_dataFormat = AE_FMT_FLOAT;
+ CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
+ }
+
+ // Prevents NULL speaker mask. To do: debug exact cause.
+ // When this happens requested AE format is AE_FMT_FLOAT + channel layout
+ // RAW, RAW, RAW... (6 channels). Only happens at end of playback PT
+ // stream, force to defaults does not affect functionality or user
+ // experience. Only avoids crash.
+ if (!wfxex.dwChannelMask && format.m_dataFormat <= AE_FMT_FLOAT)
+ {
+ CLog::LogF(LOGWARNING, "NULL Channel Mask detected. Default values are enforced.");
+ format.m_sampleRate = 0; // force defaults in following code
+ }
+
+ /* Test for incomplete format and provide defaults */
+ if (format.m_sampleRate == 0 ||
+ format.m_channelLayout == CAEChannelInfo(nullptr) ||
+ format.m_dataFormat <= AE_FMT_INVALID ||
+ format.m_dataFormat >= AE_FMT_MAX ||
+ format.m_channelLayout.Count() == 0)
+ {
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nSamplesPerSec = 44100L;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Format.nBlockAlign = 4;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
+ wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ }
+
+ HRESULT hr = m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+
+ int closestMatch = 0;
+ unsigned int requestedChannels = 0;
+ unsigned int noOfCh = 0;
+ uint64_t desired_map = 0;
+ bool matchNoChannelsOnly = false;
+
+ if (SUCCEEDED(hr))
+ {
+ CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize");
+ goto initialize;
+ }
+ else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT) //It failed for a reason unrelated to an unsupported format.
+ {
+ CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr));
+ return false;
+ }
+ else if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
+ return false;
+
+ CLog::LogFC(LOGDEBUG, LOGAUDIO,
+ "IsFormatSupported failed ({}) - trying to find a compatible format",
+ WASAPIErrToStr(hr));
+
+ requestedChannels = wfxex.Format.nChannels;
+ desired_map = CAESinkFactoryWin::SpeakerMaskFromAEChannels(format.m_channelLayout);
+
+ /* The requested format is not supported by the device. Find something that works */
+ CLog::Log(LOGDEBUG,
+ "AESinkWASAPI: Input channels are [{}] - Trying to find a matching output layout",
+ std::string(format.m_channelLayout));
+ for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
+ {
+ // if requested layout is not supported, try standard layouts which contain
+ // at least the same channels as the input source
+ // as the last resort try stereo
+ if (layout == ARRAYSIZE(layoutsList))
+ {
+ if (matchNoChannelsOnly)
+ {
+ wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfxex.Format.nChannels = 2;
+ }
+ else
+ {
+ matchNoChannelsOnly = true;
+ layout = -1;
+ CLog::Log(LOGWARNING, "AESinkWASAPI: Match only number of audio channels as fallback");
+ continue;
+ }
+ }
+ else if (layout >= 0)
+ {
+ wfxex.dwChannelMask = CAESinkFactoryWin::ChLayoutToChMask(layoutsList[layout], &noOfCh);
+ wfxex.Format.nChannels = noOfCh;
+ int res = desired_map & wfxex.dwChannelMask;
+ if (matchNoChannelsOnly)
+ {
+ if (noOfCh < requestedChannels)
+ continue; // number of channels doesn't match requested channels
+ }
+ else
+ {
+ if (res != desired_map)
+ continue; // output channel layout doesn't match input channels
+ }
+ }
+ CAEChannelInfo foundChannels;
+ CAESinkFactoryWin::AEChannelsFromSpeakerMask(foundChannels, wfxex.dwChannelMask);
+ CLog::Log(LOGDEBUG, "AESinkWASAPI: Trying matching channel layout [{}]",
+ std::string(foundChannels));
+
+ for (int j = 0; j < sizeof(testFormats)/sizeof(sampleFormat); j++)
+ {
+ closestMatch = -1;
+
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = testFormats[j].subFormat;
+ wfxex.Format.wBitsPerSample = testFormats[j].bitsPerSample;
+ wfxex.Samples.wValidBitsPerSample = testFormats[j].validBitsPerSample;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+
+ for (int i = 0 ; i < WASAPISampleRateCount; i++)
+ {
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ /* Trace format match iteration loop via log */
+#if 0
+ CLog::Log(LOGDEBUG, "WASAPI: Trying Format: {}, {}, {}, {}", CAEUtil::DataFormatToStr(testFormats[j].subFormatType),
+ wfxex.Format.nSamplesPerSec,
+ wfxex.Format.wBitsPerSample,
+ wfxex.Samples.wValidBitsPerSample);
+#endif
+
+ hr = m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ /* If the current sample rate matches the source then stop looking and use it */
+ if ((WASAPISampleRates[i] == format.m_sampleRate) && (testFormats[j].subFormatType <= format.m_dataFormat))
+ goto initialize;
+ /* If this rate is closer to the source then the previous one, save it */
+ else if (closestMatch < 0 || abs((int)WASAPISampleRates[i] - (int)format.m_sampleRate) < abs((int)WASAPISampleRates[closestMatch] - (int)format.m_sampleRate))
+ closestMatch = i;
+ }
+ else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
+ CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr));
+ }
+
+ if (closestMatch >= 0)
+ {
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ goto initialize;
+ }
+ }
+ CLog::Log(LOGDEBUG, "AESinkWASAPI: Format [{}] not supported by driver",
+ std::string(foundChannels));
+ }
+
+ CLog::LogF(LOGERROR, "Unable to locate a supported output format for the device. "
+ "Check the speaker settings in the control panel.");
+
+ /* We couldn't find anything supported. This should never happen */
+ /* unless the user set the wrong speaker setting in the control panel */
+ return false;
+
+initialize:
+
+ CAESinkFactoryWin::AEChannelsFromSpeakerMask(m_channelLayout, wfxex.dwChannelMask);
+ format.m_channelLayout = m_channelLayout;
+
+ /* When the stream is raw, the values in the format structure are set to the link */
+ /* parameters, so store the encoded stream values here for the IsCompatible function */
+ m_encodedChannels = wfxex.Format.nChannels;
+ m_encodedSampleRate = (format.m_dataFormat == AE_FMT_RAW) ? format.m_streamInfo.m_sampleRate : format.m_sampleRate;
+ wfxex_iec61937.dwEncodedChannelCount = wfxex.Format.nChannels;
+ wfxex_iec61937.dwEncodedSamplesPerSec = m_encodedSampleRate;
+
+ /* Set up returned sink format for engine */
+ if (format.m_dataFormat != AE_FMT_RAW)
+ {
+ if (wfxex.Format.wBitsPerSample == 32)
+ {
+ if (wfxex.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
+ format.m_dataFormat = AE_FMT_FLOAT;
+ else if (wfxex.Samples.wValidBitsPerSample == 32)
+ format.m_dataFormat = AE_FMT_S32NE;
+ else
+ format.m_dataFormat = AE_FMT_S24NE4MSB;
+ }
+ else if (wfxex.Format.wBitsPerSample == 24)
+ format.m_dataFormat = AE_FMT_S24NE3;
+ else
+ format.m_dataFormat = AE_FMT_S16NE;
+ }
+
+ format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
+ format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
+
+ REFERENCE_TIME audioSinkBufferDurationMsec, hnsLatency;
+
+ audioSinkBufferDurationMsec = (REFERENCE_TIME)500000;
+ if (IsUSBDevice())
+ {
+ CLog::LogF(LOGDEBUG, "detected USB device, increasing buffer size");
+ audioSinkBufferDurationMsec = (REFERENCE_TIME)1000000;
+ }
+ audioSinkBufferDurationMsec = (REFERENCE_TIME)((audioSinkBufferDurationMsec / format.m_frameSize) * format.m_frameSize); //even number of frames
+
+ if (format.m_dataFormat == AE_FMT_RAW)
+ format.m_dataFormat = AE_FMT_S16NE;
+
+ hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
+ audioSinkBufferDurationMsec, audioSinkBufferDurationMsec, &wfxex.Format, NULL);
+
+ if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
+ {
+ /* WASAPI requires aligned buffer */
+ /* Get the next aligned frame */
+ hr = m_pAudioClient->GetBufferSize(&m_uiBufferLen);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "GetBufferSize Failed : {}", WASAPIErrToStr(hr));
+ return false;
+ }
+
+ audioSinkBufferDurationMsec = (REFERENCE_TIME) ((10000.0 * 1000 / wfxex.Format.nSamplesPerSec * m_uiBufferLen) + 0.5);
+
+ /* Release the previous allocations */
+ /* Create a new audio client */
+ hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Device Activation Failed : {}", WASAPIErrToStr(hr));
+ return false;
+ }
+
+ /* Open the stream and associate it with an audio session */
+ hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
+ audioSinkBufferDurationMsec, audioSinkBufferDurationMsec, &wfxex.Format, NULL);
+ }
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Failed to initialize WASAPI in exclusive mode {} - ({}).", HRESULT(hr),
+ WASAPIErrToStr(hr));
+ CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
+ CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
+ CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
+ CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
+ CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
+ CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
+ CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
+ CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
+ CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
+ CLog::Log(LOGDEBUG, " Enc. Channels : {}", wfxex_iec61937.dwEncodedChannelCount);
+ CLog::Log(LOGDEBUG, " Enc. Samples/Sec: {}", wfxex_iec61937.dwEncodedSamplesPerSec);
+ CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
+ CLog::Log(LOGDEBUG, " Periodicty : {}", audioSinkBufferDurationMsec);
+ return false;
+ }
+
+ /* Latency of WASAPI buffers in event-driven mode is equal to the returned value */
+ /* of GetStreamLatency converted from 100ns intervals to seconds then multiplied */
+ /* by two as there are two equally-sized buffers and playback starts when the */
+ /* second buffer is filled. Multiplying the returned 100ns intervals by 0.0000002 */
+ /* is handles both the unit conversion and twin buffers. */
+ hr = m_pAudioClient->GetStreamLatency(&hnsLatency);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "GetStreamLatency Failed : {}", WASAPIErrToStr(hr));
+ return false;
+ }
+
+ m_sinkLatency = hnsLatency * 0.0000002;
+
+ CLog::LogF(LOGINFO, "WASAPI Exclusive Mode Sink Initialized using: {}, {}, {}",
+ CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
+ wfxex.Format.nChannels);
+ return true;
+}
+
+void CAESinkWASAPI::Drain()
+{
+ if(!m_pAudioClient)
+ return;
+
+ AEDelayStatus status;
+ GetDelay(status);
+
+ KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 500)));
+
+ if (m_running)
+ {
+ try
+ {
+ m_pAudioClient->Stop(); //stop the audio output
+ m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
+ m_sinkFrames = 0;
+ }
+ catch (...)
+ {
+ CLog::LogF(LOGDEBUG, "Invalidated AudioClient - Releasing");
+ }
+ }
+ m_running = false;
+}
+
+bool CAESinkWASAPI::IsUSBDevice()
+{
+ return m_pDevice && m_pDevice->IsUSBDevice();
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h
new file mode 100644
index 0000000..30826de
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+#include <stdint.h>
+#include <vector>
+
+#include <Audioclient.h>
+#include <mmdeviceapi.h>
+#include <wrl/client.h>
+
+class CAESinkWASAPI : public IAESink
+{
+public:
+ CAESinkWASAPI();
+ virtual ~CAESinkWASAPI();
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force = false);
+
+ // IAESink overrides
+ const char *GetName() override { return "WASAPI"; }
+ bool Initialize(AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+private:
+ bool InitializeExclusive(AEAudioFormat &format);
+ static void BuildWaveFormatExtensibleIEC61397(AEAudioFormat &format, WAVEFORMATEXTENSIBLE_IEC61937 &wfxex);
+ bool IsUSBDevice();
+
+ HANDLE m_needDataEvent{0};
+ IAEWASAPIDevice* m_pDevice{nullptr};
+ Microsoft::WRL::ComPtr<IAudioClient> m_pAudioClient;
+ Microsoft::WRL::ComPtr<IAudioRenderClient> m_pRenderClient;
+ Microsoft::WRL::ComPtr<IAudioClock> m_pAudioClock;
+
+ AEAudioFormat m_format{};
+ unsigned int m_encodedChannels{0};
+ unsigned int m_encodedSampleRate{0};
+ CAEChannelInfo m_channelLayout;
+ std::string m_device;
+
+ enum AEDataFormat sinkReqFormat = AE_FMT_INVALID;
+ enum AEDataFormat sinkRetFormat = AE_FMT_INVALID;
+
+ bool m_running{false};
+ bool m_initialized{false};
+ bool m_isSuspended{false}; // sink is in a suspended state - release audio device
+ bool m_isDirty{false}; // sink output failed - needs re-init or new device
+
+ // time between next buffer of data from SoftAE and driver call for data
+ double m_avgTimeWaiting{50.0};
+ double m_sinkLatency{0.0}; // time in seconds of total duration of the two WASAPI buffers
+
+ unsigned int m_uiBufferLen{0}; // wasapi endpoint buffer size, in frames
+ uint64_t m_sinkFrames{0};
+ uint64_t m_clockFreq{0};
+
+ std::vector<uint8_t> m_buffer;
+ int m_bufferPtr{0};
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp
new file mode 100644
index 0000000..c7c40ab
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkXAudio.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include "platform/win10/AsyncHelpers.h"
+#include "platform/win32/CharsetConverter.h"
+
+#include <algorithm>
+#include <stdint.h>
+
+#include <ksmedia.h>
+#include <mfapi.h>
+#include <mmdeviceapi.h>
+#include <mmreg.h>
+#include <wrl/implements.h>
+
+using namespace Microsoft::WRL;
+
+extern const char *WASAPIErrToStr(HRESULT err);
+
+#define EXIT_ON_FAILURE(hr, reason, ...) \
+ if (FAILED(hr)) \
+ { \
+ CLog::Log(LOGERROR, reason " - {}", __VA_ARGS__, WASAPIErrToStr(hr)); \
+ goto failed; \
+ }
+#define XAUDIO_BUFFERS_IN_QUEUE 2
+
+template <class TVoice>
+inline void SafeDestroyVoice(TVoice **ppVoice)
+{
+ if (*ppVoice)
+ {
+ (*ppVoice)->DestroyVoice();
+ *ppVoice = nullptr;
+ }
+}
+
+/// ----------------- CAESinkXAudio ------------------------
+
+CAESinkXAudio::CAESinkXAudio() :
+ m_masterVoice(nullptr),
+ m_sourceVoice(nullptr),
+ m_encodedChannels(0),
+ m_encodedSampleRate(0),
+ sinkReqFormat(AE_FMT_INVALID),
+ sinkRetFormat(AE_FMT_INVALID),
+ m_AvgBytesPerSec(0),
+ m_dwChunkSize(0),
+ m_dwFrameSize(0),
+ m_dwBufferLen(0),
+ m_sinkFrames(0),
+ m_framesInBuffers(0),
+ m_running(false),
+ m_initialized(false),
+ m_isSuspended(false),
+ m_isDirty(false),
+ m_uiBufferLen(0),
+ m_avgTimeWaiting(50)
+{
+ m_channelLayout.Reset();
+
+ HRESULT hr = XAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "XAudio initialization failed.");
+ }
+#ifdef _DEBUG
+ else
+ {
+ XAUDIO2_DEBUG_CONFIGURATION config = {};
+ config.BreakMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_API_CALLS | XAUDIO2_LOG_STREAMING;
+ config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_API_CALLS | XAUDIO2_LOG_STREAMING;
+ config.LogTiming = true;
+ config.LogFunctionName = true;
+ m_xAudio2->SetDebugConfiguration(&config, 0);
+ }
+#endif // _DEBUG
+}
+
+CAESinkXAudio::~CAESinkXAudio()
+{
+ if (m_xAudio2)
+ m_xAudio2.Reset();
+}
+
+void CAESinkXAudio::Register()
+{
+ AE::AESinkRegEntry reg;
+ reg.sinkName = "XAUDIO";
+ reg.createFunc = CAESinkXAudio::Create;
+ reg.enumerateFunc = CAESinkXAudio::EnumerateDevicesEx;
+ AE::CAESinkFactory::RegisterSink(reg);
+}
+
+IAESink* CAESinkXAudio::Create(std::string &device, AEAudioFormat &desiredFormat)
+{
+ IAESink *sink = new CAESinkXAudio();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device)
+{
+ if (m_initialized)
+ return false;
+
+ m_device = device;
+ bool bdefault = false;
+ HRESULT hr = S_OK;
+
+ /* Save requested format */
+ /* Clear returned format */
+ sinkReqFormat = format.m_dataFormat;
+ sinkRetFormat = AE_FMT_INVALID;
+
+ if (!InitializeInternal(device, format))
+ {
+ CLog::Log(LOGINFO, __FUNCTION__": Could not Initialize voices with that format");
+ goto failed;
+ }
+
+ format.m_frames = m_uiBufferLen;
+ m_format = format;
+ sinkRetFormat = format.m_dataFormat;
+
+ m_initialized = true;
+ m_isDirty = false;
+
+ return true;
+
+failed:
+ CLog::Log(LOGERROR, __FUNCTION__": XAudio initialization failed.");
+ return true;
+}
+
+void CAESinkXAudio::Deinitialize()
+{
+ if (!m_initialized && !m_isDirty)
+ return;
+
+ if (m_running)
+ {
+ try
+ {
+ m_sourceVoice->Stop();
+ m_sourceVoice->FlushSourceBuffers();
+ m_sinkFrames = 0;
+ m_framesInBuffers = 0;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__);
+ }
+ }
+ m_running = false;
+
+ SafeDestroyVoice(&m_sourceVoice);
+ SafeDestroyVoice(&m_masterVoice);
+
+ m_initialized = false;
+}
+
+/**
+ * @brief rescale uint64_t without overflowing on large values
+ */
+static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den)
+{
+ return ((val / den) * num) + (((val % den) * num) / den);
+}
+
+void CAESinkXAudio::GetDelay(AEDelayStatus& status)
+{
+ HRESULT hr = S_OK;
+ uint64_t pos = 0, tick = 0;
+ int retries = 0;
+
+ if (!m_initialized)
+ {
+ status.SetDelay(0.0);
+ return;
+ }
+
+ XAUDIO2_VOICE_STATE state;
+ m_sourceVoice->GetState(&state, 0);
+
+ double delay = (double)(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate;
+ status.SetDelay(delay);
+ return;
+}
+
+double CAESinkXAudio::GetCacheTotal()
+{
+ if (!m_initialized)
+ return 0.0;
+
+ return XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames / (double)m_format.m_sampleRate;
+}
+
+double CAESinkXAudio::GetLatency()
+{
+ if (!m_initialized || !m_xAudio2)
+ return 0.0;
+
+ XAUDIO2_PERFORMANCE_DATA perfData;
+ m_xAudio2->GetPerformanceData(&perfData);
+
+ return perfData.CurrentLatencyInSamples / (double) m_format.m_sampleRate;
+}
+
+unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
+{
+ if (!m_initialized)
+ return 0;
+
+ HRESULT hr = S_OK;
+ DWORD flags = 0;
+
+#ifndef _DEBUG
+ LARGE_INTEGER timerStart;
+ LARGE_INTEGER timerStop;
+ LARGE_INTEGER timerFreq;
+#endif
+ size_t dataLenght = frames * m_format.m_frameSize;
+
+ struct buffer_ctx *ctx = new buffer_ctx;
+ ctx->data = new uint8_t[dataLenght];
+ ctx->frames = frames;
+ ctx->sink = this;
+ memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLenght);
+
+ XAUDIO2_BUFFER xbuffer = {};
+ xbuffer.AudioBytes = dataLenght;
+ xbuffer.pAudioData = ctx->data;
+ xbuffer.pContext = ctx;
+
+ if (!m_running) //first time called, pre-fill buffer then start voice
+ {
+ m_sourceVoice->Stop();
+ hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", WASAPIErrToStr(hr));
+ delete ctx;
+ return 0;
+ }
+ hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "voice start failed due to {}", WASAPIErrToStr(hr));
+ m_isDirty = true; //flag new device or re-init needed
+ delete ctx;
+ return INT_MAX;
+ }
+ m_sinkFrames += frames;
+ m_framesInBuffers += frames;
+ m_running = true; //signal that we're processing frames
+ return frames;
+ }
+
+#ifndef _DEBUG
+ /* Get clock time for latency checks */
+ QueryPerformanceFrequency(&timerFreq);
+ QueryPerformanceCounter(&timerStart);
+#endif
+
+ /* Wait for Audio Driver to tell us it's got a buffer available */
+ //XAUDIO2_VOICE_STATE state;
+ //while (m_sourceVoice->GetState(&state), state.BuffersQueued >= XAUDIO_BUFFERS_IN_QUEUE)
+ while (m_format.m_frames * XAUDIO_BUFFERS_IN_QUEUE <= m_framesInBuffers.load())
+ {
+ DWORD eventAudioCallback;
+ eventAudioCallback = WaitForSingleObjectEx(m_voiceCallback.mBufferEnd.get(), 1100, TRUE);
+ if (eventAudioCallback != WAIT_OBJECT_0)
+ {
+ CLog::LogF(LOGERROR, "voice buffer timed out");
+ delete ctx;
+ return INT_MAX;
+ }
+ }
+
+ if (!m_running)
+ return 0;
+
+#ifndef _DEBUG
+ QueryPerformanceCounter(&timerStop);
+ LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
+ double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart;
+ m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
+
+ if (m_avgTimeWaiting < 3.0)
+ {
+ CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
+ (int)m_avgTimeWaiting);
+ }
+#endif
+
+ hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
+ if (FAILED(hr))
+ {
+ #ifdef _DEBUG
+ CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr));
+#endif
+ delete ctx;
+ return INT_MAX;
+ }
+
+ m_sinkFrames += frames;
+ m_framesInBuffers += frames;
+
+ return frames;
+}
+
+void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
+{
+ HRESULT hr = S_OK, hr2 = S_OK;
+ CAEDeviceInfo deviceInfo;
+ CAEChannelInfo deviceChannels;
+ WAVEFORMATEXTENSIBLE wfxex = {};
+ bool add192 = false;
+
+ UINT32 eflags = 0;// XAUDIO2_DEBUG_ENGINE;
+
+ IXAudio2MasteringVoice* mMasterVoice = nullptr;
+ IXAudio2SourceVoice* mSourceVoice = nullptr;
+ Microsoft::WRL::ComPtr<IXAudio2> xaudio2;
+ hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGDEBUG, __FUNCTION__": Failed to activate XAudio for capability testing.");
+ goto failed;
+ }
+
+ for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails())
+ {
+ deviceInfo.m_channels.Reset();
+ deviceInfo.m_dataFormats.clear();
+ deviceInfo.m_sampleRates.clear();
+
+ std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId);
+
+ /* Test format DTS-HD-MA */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.Format.nSamplesPerSec = 192000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 8;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, deviceId.c_str(), nullptr, AudioCategory_Media);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+
+ if (FAILED(hr))
+ {
+ CLog::Log(
+ LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA), details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
+ add192 = true;
+ }
+ SafeDestroyVoice(&mSourceVoice);
+
+ /* Test format DTS-HD-HR */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.Format.nSamplesPerSec = 192000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, deviceId.c_str(), nullptr, AudioCategory_Media);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD), details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
+ add192 = true;
+ }
+ SafeDestroyVoice(&mSourceVoice);
+
+ /* Test format Dolby TrueHD */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
+ wfxex.Format.nChannels = 8;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+ if (FAILED(hr))
+ {
+ CLog::Log(
+ LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD), details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
+ add192 = true;
+ }
+
+ /* Test format Dolby EAC3 */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ SafeDestroyVoice(&mSourceVoice);
+ SafeDestroyVoice(&mMasterVoice);
+ hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, deviceId.c_str(), nullptr, AudioCategory_Media);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3), details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ add192 = true;
+ }
+
+ /* Test format DTS */
+ wfxex.Format.nSamplesPerSec = 48000;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ SafeDestroyVoice(&mSourceVoice);
+ SafeDestroyVoice(&mMasterVoice);
+ hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, deviceId.c_str(), nullptr, AudioCategory_Media);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ "STREAM_TYPE_DTS", details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ }
+ SafeDestroyVoice(&mSourceVoice);
+
+ /* Test format Dolby AC3 */
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
+
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
+ CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3), details.strDescription);
+ }
+ else
+ {
+ deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ }
+
+ /* Test format for PCM format iteration */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+
+ for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
+ {
+ if (p < AE_FMT_FLOAT)
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat)p);
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ if (p == AE_FMT_S24NE4MSB)
+ {
+ wfxex.Samples.wValidBitsPerSample = 24;
+ }
+ else if (p <= AE_FMT_S24NE4 && p >= AE_FMT_S24BE4)
+ {
+ // not supported
+ continue;
+ }
+ else
+ {
+ wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
+ }
+
+ SafeDestroyVoice(&mSourceVoice);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+
+ if (SUCCEEDED(hr))
+ deviceInfo.m_dataFormats.push_back((AEDataFormat)p);
+ }
+
+ /* Test format for sample rate iteration */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ for (int j = 0; j < WASAPISampleRateCount; j++)
+ {
+ SafeDestroyVoice(&mSourceVoice);
+ SafeDestroyVoice(&mMasterVoice);
+
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, deviceId.c_str(), nullptr, AudioCategory_Media);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+ if (SUCCEEDED(hr))
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
+ else if (wfxex.Format.nSamplesPerSec == 192000 && add192)
+ {
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": sample rate 192khz on device \"{}\" seems to be not supported.",
+ details.strDescription);
+ }
+ }
+ SafeDestroyVoice(&mSourceVoice);
+ SafeDestroyVoice(&mMasterVoice);
+
+ deviceInfo.m_deviceName = details.strDeviceId;
+ deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
+ deviceInfo.m_displayNameExtra = std::string("XAudio: ").append(details.strDescription);
+ deviceInfo.m_deviceType = details.eDeviceType;
+ deviceInfo.m_channels = layoutsByChCount[details.nChannels];
+
+ /* Store the device info */
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfo.m_onlyPCM = true;
+
+ if (!deviceInfo.m_streamTypes.empty())
+ deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
+
+ deviceInfoList.push_back(deviceInfo);
+
+ if (details.bDefault)
+ {
+ deviceInfo.m_deviceName = std::string("default");
+ deviceInfo.m_displayName = std::string("default");
+ deviceInfo.m_displayNameExtra = std::string("");
+ deviceInfo.m_wantsIECPassthrough = true;
+ deviceInfo.m_onlyPCM = true;
+ deviceInfoList.push_back(deviceInfo);
+ }
+ }
+
+failed:
+
+ if (FAILED(hr))
+ CLog::Log(LOGERROR, __FUNCTION__ ": Failed to enumerate XAudio endpoint devices ({}).",
+ WASAPIErrToStr(hr));
+}
+
+/// ------------------- Private utility functions -----------------------------------
+
+bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &format)
+{
+ std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId);
+ WAVEFORMATEXTENSIBLE wfxex = {};
+
+ if ( format.m_dataFormat <= AE_FMT_FLOAT
+ || format.m_dataFormat == AE_FMT_RAW)
+ CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
+ else
+ {
+ // planar formats are currently not supported by this sink
+ format.m_dataFormat = AE_FMT_FLOAT;
+ CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
+ }
+
+ /* Test for incomplete format and provide defaults */
+ if (format.m_sampleRate == 0 ||
+ format.m_channelLayout == CAEChannelInfo(nullptr) ||
+ format.m_dataFormat <= AE_FMT_INVALID ||
+ format.m_dataFormat >= AE_FMT_MAX ||
+ format.m_channelLayout.Count() == 0)
+ {
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.Format.nChannels = 2;
+ wfxex.Format.nSamplesPerSec = 44100L;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Format.nBlockAlign = 4;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
+ wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ }
+
+ bool bdefault = StringUtils::EndsWithNoCase(deviceId, std::string("default"));
+
+ HRESULT hr;
+ IXAudio2MasteringVoice* pMasterVoice = nullptr;
+
+ if (!bdefault)
+ {
+ hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, device.c_str(), nullptr, AudioCategory_Media);
+ }
+
+ if (!pMasterVoice)
+ {
+ if (!bdefault)
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": Could not locate the device named \"{}\" in the list of Xaudio "
+ "endpoint devices. Trying the default device...",
+ KODI::PLATFORM::WINDOWS::FromW(device));
+ }
+
+ // smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result
+ // workaround: device = nullptr will initialize default audio endpoint
+ hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, 0, nullptr, AudioCategory_Media);
+ if (FAILED(hr) || !pMasterVoice)
+ {
+ CLog::Log(LOGINFO,
+ __FUNCTION__ ": Could not retrieve the default XAudio audio endpoint ({}).",
+ WASAPIErrToStr(hr));
+ return false;
+ }
+ }
+
+ m_masterVoice = pMasterVoice;
+
+ int closestMatch = 0;
+ unsigned int requestedChannels = 0;
+ unsigned int noOfCh = 0;
+
+ hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
+ if (SUCCEEDED(hr))
+ {
+ CLog::Log(LOGINFO, __FUNCTION__": Format is Supported - will attempt to Initialize");
+ goto initialize;
+ }
+
+ if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
+ return false;
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
+ CLog::Log(LOGDEBUG,
+ __FUNCTION__ ": CreateSourceVoice failed ({}) - trying to find a compatible format",
+ WASAPIErrToStr(hr));
+
+ requestedChannels = wfxex.Format.nChannels;
+
+ /* The requested format is not supported by the device. Find something that works */
+ for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
+ {
+ // if requested layout is not supported, try standard layouts with at least
+ // the number of channels as requested
+ // as the last resort try stereo
+ if (layout == ARRAYSIZE(layoutsList))
+ {
+ wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfxex.Format.nChannels = 2;
+ }
+ else if (layout >= 0)
+ {
+ wfxex.dwChannelMask = CAESinkFactoryWin::ChLayoutToChMask(layoutsList[layout], &noOfCh);
+ wfxex.Format.nChannels = noOfCh;
+ if (noOfCh < requestedChannels)
+ continue;
+ }
+
+ for (int j = 0; j < sizeof(testFormats)/sizeof(sampleFormat); j++)
+ {
+ closestMatch = -1;
+
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = testFormats[j].subFormat;
+ wfxex.Format.wBitsPerSample = testFormats[j].bitsPerSample;
+ wfxex.Samples.wValidBitsPerSample = testFormats[j].validBitsPerSample;
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+
+ for (int i = 0 ; i < WASAPISampleRateCount; i++)
+ {
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+
+ hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
+ 0, device.c_str(), nullptr, AudioCategory_Media);
+ if (SUCCEEDED(hr))
+ {
+ hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
+ if (SUCCEEDED(hr))
+ {
+ /* If the current sample rate matches the source then stop looking and use it */
+ if ((WASAPISampleRates[i] == format.m_sampleRate) && (testFormats[j].subFormatType <= format.m_dataFormat))
+ goto initialize;
+ /* If this rate is closer to the source then the previous one, save it */
+ else if (closestMatch < 0 || abs((int)WASAPISampleRates[i] - (int)format.m_sampleRate) < abs((int)WASAPISampleRates[closestMatch] - (int)format.m_sampleRate))
+ closestMatch = i;
+ }
+ }
+
+ if (FAILED(hr))
+ CLog::Log(LOGERROR, __FUNCTION__ ": creating voices failed ({})", WASAPIErrToStr(hr));
+ }
+
+ if (closestMatch >= 0)
+ {
+ wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ goto initialize;
+ }
+ }
+ }
+
+ CLog::Log(LOGERROR, __FUNCTION__": Unable to locate a supported output format for the device. Check the speaker settings in the control panel.");
+
+ /* We couldn't find anything supported. This should never happen */
+ /* unless the user set the wrong speaker setting in the control panel */
+ return false;
+
+initialize:
+
+ CAESinkFactoryWin::AEChannelsFromSpeakerMask(m_channelLayout, wfxex.dwChannelMask);
+ format.m_channelLayout = m_channelLayout;
+
+ /* When the stream is raw, the values in the format structure are set to the link */
+ /* parameters, so store the encoded stream values here for the IsCompatible function */
+ m_encodedChannels = wfxex.Format.nChannels;
+ m_encodedSampleRate = (format.m_dataFormat == AE_FMT_RAW) ? format.m_streamInfo.m_sampleRate : format.m_sampleRate;
+
+ /* Set up returned sink format for engine */
+ if (format.m_dataFormat != AE_FMT_RAW)
+ {
+ if (wfxex.Format.wBitsPerSample == 32)
+ {
+ if (wfxex.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
+ format.m_dataFormat = AE_FMT_FLOAT;
+ else if (wfxex.Samples.wValidBitsPerSample == 32)
+ format.m_dataFormat = AE_FMT_S32NE;
+ else
+ format.m_dataFormat = AE_FMT_S24NE4MSB;
+ }
+ else if (wfxex.Format.wBitsPerSample == 24)
+ format.m_dataFormat = AE_FMT_S24NE3;
+ else
+ format.m_dataFormat = AE_FMT_S16NE;
+ }
+
+ format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
+ format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
+
+ if (format.m_dataFormat == AE_FMT_RAW)
+ format.m_dataFormat = AE_FMT_S16NE;
+
+ hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, __FUNCTION__ ": Voice start failed : {}", WASAPIErrToStr(hr));
+ CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
+ CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
+ CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
+ CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
+ CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
+ CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
+ CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
+ CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
+ CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
+ CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
+ return false;
+ }
+
+ XAUDIO2_PERFORMANCE_DATA perfData;
+ m_xAudio2->GetPerformanceData(&perfData);
+ if (!perfData.TotalSourceVoiceCount)
+ {
+ CLog::Log(LOGERROR, __FUNCTION__ ": GetPerformanceData Failed : {}", WASAPIErrToStr(hr));
+ return false;
+ }
+
+ m_uiBufferLen = (int)(format.m_sampleRate * 0.02);
+ m_dwFrameSize = wfxex.Format.nBlockAlign;
+ m_dwChunkSize = m_dwFrameSize * m_uiBufferLen;
+ m_dwBufferLen = m_dwChunkSize * 4;
+ m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
+
+ CLog::Log(LOGINFO, __FUNCTION__ ": XAudio Sink Initialized using: {}, {}, {}",
+ CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
+ wfxex.Format.nChannels);
+
+ m_sourceVoice->Stop();
+
+ return true;
+}
+
+void CAESinkXAudio::Drain()
+{
+ if(!m_sourceVoice)
+ return;
+
+ AEDelayStatus status;
+ GetDelay(status);
+
+ KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 500)));
+
+ if (m_running)
+ {
+ try
+ {
+ m_sourceVoice->Stop();
+ m_sourceVoice->FlushSourceBuffers();
+ m_sinkFrames = 0;
+ m_framesInBuffers = 0;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__);
+ }
+ }
+ m_running = false;
+}
+
+bool CAESinkXAudio::IsUSBDevice()
+{
+#if 0 // TODO
+ IPropertyStore *pProperty = nullptr;
+ PROPVARIANT varName;
+ PropVariantInit(&varName);
+ bool ret = false;
+
+ HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, &pProperty);
+ if (!SUCCEEDED(hr))
+ return ret;
+ hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
+
+ std::string str = localWideToUtf(varName.pwszVal);
+ StringUtils::ToUpper(str);
+ ret = (str == "USB");
+ PropVariantClear(&varName);
+ if (pProperty)
+ pProperty->Release();
+#endif
+ return false;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h
new file mode 100644
index 0000000..7c34e83
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+#include <stdint.h>
+
+#include <mmdeviceapi.h>
+#include <ppltasks.h>
+#include <wrl/implements.h>
+#include <x3daudio.h>
+#include <xapofx.h>
+#include <xaudio2.h>
+#include <xaudio2fx.h>
+#pragma comment(lib,"xaudio2.lib")
+
+class CAESinkXAudio : public IAESink
+{
+public:
+ virtual const char *GetName() { return "XAudio"; }
+
+ CAESinkXAudio();
+ virtual ~CAESinkXAudio();
+
+ static void Register();
+ static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
+
+ bool Initialize (AEAudioFormat &format, std::string &device) override;
+ void Deinitialize() override;
+
+ void GetDelay(AEDelayStatus& status) override;
+ double GetCacheTotal() override;
+ double GetLatency() override;
+ unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+ static void EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force = false);
+
+private:
+ struct buffer_ctx
+ {
+ uint8_t *data;
+ uint32_t frames;
+ CAESinkXAudio* sink;
+
+ ~buffer_ctx()
+ {
+ delete[] data;
+ sink->m_framesInBuffers -= frames;
+ sink = nullptr;
+ }
+ };
+
+ struct VoiceCallback : public IXAudio2VoiceCallback
+ {
+ VoiceCallback()
+ {
+ mBufferEnd.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ if (!mBufferEnd)
+ {
+ throw std::exception("CreateEvent");
+ }
+ }
+ virtual ~VoiceCallback() { }
+
+ STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) override {}
+ STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {}
+ STDMETHOD_(void, OnStreamEnd)() override {}
+ STDMETHOD_(void, OnBufferStart)(void*) override {}
+ STDMETHOD_(void, OnBufferEnd)(void* context) override
+ {
+ SetEvent(mBufferEnd.get());
+ struct buffer_ctx *ctx = static_cast<struct buffer_ctx*>(context);
+ delete ctx;
+ }
+
+ STDMETHOD_(void, OnLoopEnd)(void*) override {}
+ STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override {}
+
+ struct handle_closer
+ {
+ void operator()(HANDLE h) const
+ {
+ assert(h != INVALID_HANDLE_VALUE);
+ if (h)
+ CloseHandle(h);
+ }
+ };
+ std::unique_ptr<void, handle_closer> mBufferEnd;
+ };
+
+ bool InitializeInternal(std::string deviceId, AEAudioFormat &format);
+ bool IsUSBDevice();
+
+ Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2;
+ IXAudio2MasteringVoice* m_masterVoice;
+ IXAudio2SourceVoice* m_sourceVoice;
+ VoiceCallback m_voiceCallback;
+
+ AEAudioFormat m_format;
+ unsigned int m_encodedChannels;
+ unsigned int m_encodedSampleRate;
+ CAEChannelInfo m_channelLayout;
+ std::string m_device;
+
+ enum AEDataFormat sinkReqFormat;
+ enum AEDataFormat sinkRetFormat;
+
+ unsigned int m_uiBufferLen; /* xaudio endpoint buffer size, in frames */
+ unsigned int m_AvgBytesPerSec;
+ unsigned int m_dwChunkSize;
+ unsigned int m_dwFrameSize;
+ unsigned int m_dwBufferLen;
+ uint64_t m_sinkFrames;
+ std::atomic<uint16_t> m_framesInBuffers;
+
+ double m_avgTimeWaiting; /* time between next buffer of data from SoftAE and driver call for data */
+
+ bool m_running;
+ bool m_initialized;
+ bool m_isSuspended; /* sink is in a suspended state - release audio device */
+ bool m_isDirty; /* sink output failed - needs re-init or new device */
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.cpp b/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.cpp
new file mode 100644
index 0000000..2d2bd78
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ALSADeviceMonitor.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "utils/log.h"
+
+#include "platform/linux/FDEventMonitor.h"
+
+#include <libudev.h>
+
+CALSADeviceMonitor::CALSADeviceMonitor() :
+ m_udev(NULL),
+ m_udevMonitor(NULL)
+{
+}
+
+CALSADeviceMonitor::~CALSADeviceMonitor()
+{
+ Stop();
+}
+
+void CALSADeviceMonitor::Start()
+{
+ int err;
+
+ if (!m_udev)
+ {
+ m_udev = udev_new();
+ if (!m_udev)
+ {
+ CLog::Log(LOGWARNING, "CALSADeviceMonitor::Start - Unable to open udev handle");
+ return;
+ }
+
+ m_udevMonitor = udev_monitor_new_from_netlink(m_udev, "udev");
+ if (!m_udevMonitor)
+ {
+ CLog::Log(LOGERROR, "CALSADeviceMonitor::Start - udev_monitor_new_from_netlink() failed");
+ goto err_unref_udev;
+ }
+
+ err = udev_monitor_filter_add_match_subsystem_devtype(m_udevMonitor, "sound", NULL);
+ if (err)
+ {
+ CLog::Log(LOGERROR, "CALSADeviceMonitor::Start - udev_monitor_filter_add_match_subsystem_devtype() failed");
+ goto err_unref_monitor;
+ }
+
+ err = udev_monitor_enable_receiving(m_udevMonitor);
+ if (err)
+ {
+ CLog::Log(LOGERROR, "CALSADeviceMonitor::Start - udev_monitor_enable_receiving() failed");
+ goto err_unref_monitor;
+ }
+
+ const auto eventMonitor = CServiceBroker::GetPlatform().GetService<CFDEventMonitor>();
+ eventMonitor->AddFD(CFDEventMonitor::MonitoredFD(udev_monitor_get_fd(m_udevMonitor), POLLIN,
+ FDEventCallback, m_udevMonitor),
+ m_fdMonitorId);
+ }
+
+ return;
+
+err_unref_monitor:
+ udev_monitor_unref(m_udevMonitor);
+ m_udevMonitor = NULL;
+err_unref_udev:
+ udev_unref(m_udev);
+ m_udev = NULL;
+}
+
+void CALSADeviceMonitor::Stop()
+{
+ if (m_udev)
+ {
+ const auto eventMonitor = CServiceBroker::GetPlatform().GetService<CFDEventMonitor>();
+ eventMonitor->RemoveFD(m_fdMonitorId);
+
+ udev_monitor_unref(m_udevMonitor);
+ m_udevMonitor = NULL;
+ udev_unref(m_udev);
+ m_udev = NULL;
+ }
+}
+
+void CALSADeviceMonitor::FDEventCallback(int id, int fd, short revents, void *data)
+{
+ struct udev_monitor *udevMonitor = (struct udev_monitor *)data;
+ bool audioDevicesChanged = false;
+ struct udev_device *device;
+
+ while ((device = udev_monitor_receive_device(udevMonitor)) != NULL)
+ {
+ const char* action = udev_device_get_action(device);
+ const char* soundInitialized = udev_device_get_property_value(device, "SOUND_INITIALIZED");
+
+ if (!action || !soundInitialized)
+ continue;
+
+ /* cardX devices emit a "change" event when ready (i.e. all subdevices added) */
+ if (strcmp(action, "change") == 0)
+ {
+ CLog::Log(LOGDEBUG, "CALSADeviceMonitor - ALSA card added (\"{}\", \"{}\")",
+ udev_device_get_syspath(device), udev_device_get_devpath(device));
+ audioDevicesChanged = true;
+ }
+ else if (strcmp(action, "remove") == 0)
+ {
+ CLog::Log(LOGDEBUG, "CALSADeviceMonitor - ALSA card removed");
+ audioDevicesChanged = true;
+ }
+
+ udev_device_unref(device);
+ }
+
+ if (audioDevicesChanged)
+ {
+ CServiceBroker::GetActiveAE()->DeviceChange();
+ }
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h b/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h
new file mode 100644
index 0000000..3bb8615
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "platform/Platform.h"
+
+#include <string>
+#include <vector>
+
+#include <alsa/asoundlib.h>
+
+class CALSADeviceMonitor : public IPlatformService
+{
+public:
+ CALSADeviceMonitor();
+ ~CALSADeviceMonitor();
+
+ void Start();
+ void Stop();
+
+private:
+ static void FDEventCallback(int id, int fd, short revents, void *data);
+
+ int m_fdMonitorId = 0;
+
+ struct udev *m_udev;
+ struct udev_monitor* m_udevMonitor;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.cpp b/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.cpp
new file mode 100644
index 0000000..5572920
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ALSAHControlMonitor.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "utils/log.h"
+
+#include "platform/linux/FDEventMonitor.h"
+
+CALSAHControlMonitor::CALSAHControlMonitor() = default;
+
+CALSAHControlMonitor::~CALSAHControlMonitor()
+{
+ Clear();
+}
+
+bool CALSAHControlMonitor::Add(const std::string& ctlHandleName,
+ snd_ctl_elem_iface_t interface,
+ unsigned int device,
+ const std::string& name)
+{
+ snd_hctl_t *hctl = GetHandle(ctlHandleName);
+
+ if (!hctl)
+ {
+ return false;
+ }
+
+ snd_ctl_elem_id_t *id;
+
+ snd_ctl_elem_id_alloca(&id);
+
+ snd_ctl_elem_id_set_interface(id, interface);
+ snd_ctl_elem_id_set_name (id, name.c_str());
+ snd_ctl_elem_id_set_device (id, device);
+
+ snd_hctl_elem_t *elem = snd_hctl_find_elem(hctl, id);
+
+ if (!elem)
+ {
+ PutHandle(ctlHandleName);
+ return false;
+ }
+
+ snd_hctl_elem_set_callback(elem, HCTLCallback);
+
+ return true;
+}
+
+void CALSAHControlMonitor::Clear()
+{
+ Stop();
+
+ for (std::map<std::string, CTLHandle>::iterator it = m_ctlHandles.begin();
+ it != m_ctlHandles.end(); ++it)
+ {
+ snd_hctl_close(it->second.handle);
+ }
+ m_ctlHandles.clear();
+}
+
+void CALSAHControlMonitor::Start()
+{
+ assert(m_fdMonitorIds.empty());
+
+ std::vector<struct pollfd> pollfds;
+ std::vector<CFDEventMonitor::MonitoredFD> monitoredFDs;
+
+ for (std::map<std::string, CTLHandle>::iterator it = m_ctlHandles.begin();
+ it != m_ctlHandles.end(); ++it)
+ {
+ pollfds.resize(snd_hctl_poll_descriptors_count(it->second.handle));
+ int fdcount = snd_hctl_poll_descriptors(it->second.handle, &pollfds[0], pollfds.size());
+
+ for (int j = 0; j < fdcount; ++j)
+ {
+ monitoredFDs.emplace_back(pollfds[j].fd, pollfds[j].events, FDEventCallback, it->second.handle);
+ }
+ }
+
+ const auto eventMonitor = CServiceBroker::GetPlatform().GetService<CFDEventMonitor>();
+ eventMonitor->AddFDs(monitoredFDs, m_fdMonitorIds);
+}
+
+
+void CALSAHControlMonitor::Stop()
+{
+ const auto eventMonitor = CServiceBroker::GetPlatform().GetService<CFDEventMonitor>();
+ eventMonitor->RemoveFDs(m_fdMonitorIds);
+
+ m_fdMonitorIds.clear();
+}
+
+int CALSAHControlMonitor::HCTLCallback(snd_hctl_elem_t *elem, unsigned int mask)
+{
+ /* _REMOVE is a special value instead of a bit and must be checked first */
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ {
+ /* Either the device was removed (which is handled in ALSADeviceMonitor instead)
+ * or snd_hctl_close() got called. */
+ return 0;
+ }
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE)
+ {
+ CLog::Log(LOGDEBUG, "CALSAHControlMonitor - Monitored ALSA hctl value changed");
+
+ /*
+ * Currently we just re-enumerate on any change.
+ * Custom callbacks for handling other control monitoring may be implemented when needed.
+ */
+ CServiceBroker::GetActiveAE()->DeviceChange();
+ }
+
+ return 0;
+}
+
+void CALSAHControlMonitor::FDEventCallback(int id, int fd, short revents, void *data)
+{
+ /* Run ALSA event handling when the FD has events */
+ snd_hctl_t *hctl = (snd_hctl_t *)data;
+ snd_hctl_handle_events(hctl);
+}
+
+snd_hctl_t* CALSAHControlMonitor::GetHandle(const std::string& ctlHandleName)
+{
+ if (!m_ctlHandles.count(ctlHandleName))
+ {
+ snd_hctl_t *hctl;
+
+ if (snd_hctl_open(&hctl, ctlHandleName.c_str(), 0) != 0)
+ {
+ CLog::Log(LOGWARNING, "CALSAHControlMonitor::GetHandle - snd_hctl_open() failed for \"{}\"",
+ ctlHandleName);
+ return NULL;
+ }
+ if (snd_hctl_load(hctl) != 0)
+ {
+ CLog::Log(LOGERROR, "CALSAHControlMonitor::GetHandle - snd_hctl_load() failed for \"{}\"",
+ ctlHandleName);
+ snd_hctl_close(hctl);
+ return NULL;
+ }
+
+ snd_hctl_nonblock(hctl, 1);
+
+ m_ctlHandles[ctlHandleName] = CTLHandle(hctl);
+ }
+
+ m_ctlHandles[ctlHandleName].useCount++;
+ return m_ctlHandles[ctlHandleName].handle;
+}
+
+void CALSAHControlMonitor::PutHandle(const std::string& ctlHandleName)
+{
+ if (--m_ctlHandles[ctlHandleName].useCount == 0)
+ {
+ snd_hctl_close(m_ctlHandles[ctlHandleName].handle);
+ m_ctlHandles.erase(ctlHandleName);
+ }
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h b/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h
new file mode 100644
index 0000000..165f375
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "platform/Platform.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <alsa/asoundlib.h>
+
+class CALSAHControlMonitor : public IPlatformService
+{
+public:
+ CALSAHControlMonitor();
+ ~CALSAHControlMonitor();
+
+ bool Add(const std::string& ctlHandleName,
+ snd_ctl_elem_iface_t interface,
+ unsigned int device,
+ const std::string& name);
+
+ void Clear();
+
+ void Start();
+ void Stop();
+
+private:
+ static int HCTLCallback(snd_hctl_elem_t *elem, unsigned int mask);
+ static void FDEventCallback(int id, int fd, short revents, void *data);
+
+ snd_hctl_t* GetHandle(const std::string& ctlHandleName);
+ void PutHandle(const std::string& ctlHandleName);
+
+ struct CTLHandle
+ {
+ snd_hctl_t *handle;
+ int useCount = 0;
+
+ explicit CTLHandle(snd_hctl_t *handle_) : handle(handle_) {}
+ CTLHandle() : handle(NULL) {}
+ };
+
+ std::map<std::string, CTLHandle> m_ctlHandles;
+
+ std::vector<int> m_fdMonitorIds;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.cpp b/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.cpp
new file mode 100644
index 0000000..2ccab00
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CoreAudioHelpers.h"
+
+#include <sstream>
+
+// Helper Functions
+std::string GetError(OSStatus error)
+{
+ char buffer[128] = {};
+
+ *(UInt32 *)(buffer + 1) = CFSwapInt32HostToBig(error);
+ if (isprint(buffer[1]) && isprint(buffer[2]) &&
+ isprint(buffer[3]) && isprint(buffer[4]))
+ {
+ buffer[0] = buffer[5] = '\'';
+ buffer[6] = '\0';
+ }
+ else
+ {
+ // no, format it as an integer
+ sprintf(buffer, "%d", (int)error);
+ }
+
+ return std::string(buffer);
+}
+
+const char* StreamDescriptionToString(AudioStreamBasicDescription desc, std::string &str)
+{
+ char fourCC[5] = {
+ (char)((desc.mFormatID >> 24) & 0xFF),
+ (char)((desc.mFormatID >> 16) & 0xFF),
+ (char)((desc.mFormatID >> 8) & 0xFF),
+ (char) (desc.mFormatID & 0xFF),
+ 0
+ };
+
+ std::stringstream sstr;
+ switch (desc.mFormatID)
+ {
+ case kAudioFormatLinearPCM:
+ sstr << "["
+ << fourCC
+ << "] "
+ << ((desc.mFormatFlags & kAudioFormatFlagIsNonMixable) ? "" : "Mixable " )
+ << ((desc.mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? "Non-" : "" )
+ << "Interleaved "
+ << desc.mChannelsPerFrame
+ << " Channel "
+ << desc.mBitsPerChannel
+ << "-bit "
+ << ((desc.mFormatFlags & kAudioFormatFlagIsFloat) ? "Floating Point " : "Signed Integer ")
+ << ((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
+ << " ("
+ << (UInt32)desc.mSampleRate
+ << "Hz)";
+ str = sstr.str();
+ break;
+ case kAudioFormatAC3:
+ sstr << "["
+ << fourCC
+ << "] "
+ << ((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
+ << " AC-3/DTS ("
+ << (UInt32)desc.mSampleRate
+ << "Hz)";
+ str = sstr.str();
+ break;
+ case kAudioFormat60958AC3:
+ sstr << "["
+ << fourCC
+ << "] AC-3/DTS for S/PDIF "
+ << ((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
+ << " ("
+ << (UInt32)desc.mSampleRate
+ << "Hz)";
+ str = sstr.str();
+ break;
+ default:
+ sstr << "["
+ << fourCC
+ << "]";
+ break;
+ }
+ return str.c_str();
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h b/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h
new file mode 100644
index 0000000..082c8e1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <AudioToolbox/AudioToolbox.h>
+
+// Helper Functions
+std::string GetError(OSStatus error);
+const char* StreamDescriptionToString(AudioStreamBasicDescription desc, std::string &str);
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.cpp b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.cpp
new file mode 100644
index 0000000..4074ee7
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.cpp
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEDeviceEnumerationOSX.h"
+
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <sstream>
+
+#define CA_MAX_CHANNELS 72
+// default channel map - in case it can't be fetched from the device
+static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
+ AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
+ AE_CH_UNKNOWN1 , AE_CH_UNKNOWN2 , AE_CH_UNKNOWN3 , AE_CH_UNKNOWN4 ,
+ AE_CH_UNKNOWN5 , AE_CH_UNKNOWN6 , AE_CH_UNKNOWN7 , AE_CH_UNKNOWN8 ,
+ AE_CH_UNKNOWN9 , AE_CH_UNKNOWN10 , AE_CH_UNKNOWN11 , AE_CH_UNKNOWN12 ,
+ AE_CH_UNKNOWN13 , AE_CH_UNKNOWN14 , AE_CH_UNKNOWN15 , AE_CH_UNKNOWN16 ,
+ AE_CH_UNKNOWN17 , AE_CH_UNKNOWN18 , AE_CH_UNKNOWN19 , AE_CH_UNKNOWN20 ,
+ AE_CH_UNKNOWN21 , AE_CH_UNKNOWN22 , AE_CH_UNKNOWN23 , AE_CH_UNKNOWN24 ,
+ AE_CH_UNKNOWN25 , AE_CH_UNKNOWN26 , AE_CH_UNKNOWN27 , AE_CH_UNKNOWN28 ,
+ AE_CH_UNKNOWN29 , AE_CH_UNKNOWN30 , AE_CH_UNKNOWN31 , AE_CH_UNKNOWN32 ,
+ AE_CH_UNKNOWN33 , AE_CH_UNKNOWN34 , AE_CH_UNKNOWN35 , AE_CH_UNKNOWN36 ,
+ AE_CH_UNKNOWN37 , AE_CH_UNKNOWN38 , AE_CH_UNKNOWN39 , AE_CH_UNKNOWN40 ,
+ AE_CH_UNKNOWN41 , AE_CH_UNKNOWN42 , AE_CH_UNKNOWN43 , AE_CH_UNKNOWN44 ,
+ AE_CH_UNKNOWN45 , AE_CH_UNKNOWN46 , AE_CH_UNKNOWN47 , AE_CH_UNKNOWN48 ,
+ AE_CH_UNKNOWN49 , AE_CH_UNKNOWN50 , AE_CH_UNKNOWN51 , AE_CH_UNKNOWN52 ,
+ AE_CH_UNKNOWN53 , AE_CH_UNKNOWN54 , AE_CH_UNKNOWN55 , AE_CH_UNKNOWN56 ,
+ AE_CH_UNKNOWN57 , AE_CH_UNKNOWN58 , AE_CH_UNKNOWN59 , AE_CH_UNKNOWN60 ,
+ AE_CH_UNKNOWN61 , AE_CH_UNKNOWN62 , AE_CH_UNKNOWN63 , AE_CH_UNKNOWN64 ,
+
+ AE_CH_NULL
+};
+
+AEDeviceEnumerationOSX::AEDeviceEnumerationOSX(AudioDeviceID deviceID)
+: m_deviceID(deviceID)
+, m_isPlanar(false)
+, m_caDevice(deviceID)
+{
+ Enumerate();
+}
+
+bool AEDeviceEnumerationOSX::Enumerate()
+{
+ AudioStreamIdList streamList;
+ bool isDigital = isDigitalDevice();
+ bool ret = false;
+ UInt32 transportType = m_caDevice.GetTransportType();
+ m_caStreamInfos.clear();
+ m_isPlanar = true;
+ m_deviceName = m_caDevice.GetName();
+
+ if (m_caDevice.GetStreams(&streamList))
+ {
+ for (UInt32 streamIdx = 0; streamIdx < streamList.size(); streamIdx++)
+ {
+ caStreamInfo info;
+ info.streamID = streamList[streamIdx];
+ info.numChannels = m_caDevice.GetNumChannelsOfStream(streamIdx);
+ // one stream with num channels other then 1 is enough to make this device non-planar
+ if (info.numChannels != 1)
+ m_isPlanar = false;
+
+ CCoreAudioStream::GetAvailablePhysicalFormats(streamList[streamIdx], &info.formatList);
+
+ CCoreAudioStream::GetAvailableVirtualFormats(streamList[streamIdx], &info.formatListVirt);
+
+ hasPassthroughOrDigitalFormats(info.formatList, info.hasPassthroughFormats, info.isDigital);
+
+ info.isDigital |= isDigital;
+ info.deviceType = getDeviceType(info.hasPassthroughFormats, info.isDigital, info.numChannels, transportType);
+ m_caStreamInfos.push_back(info);
+ }
+ ret = true;
+ }
+
+ return ret;
+}
+
+// device/stream is digital if the transportType is digital
+// or the devicename suggests that it is digital
+bool AEDeviceEnumerationOSX::isDigitalDevice() const
+{
+ bool isDigital = m_caDevice.IsDigital();
+
+ // if it is no digital stream per definition
+ // check if the device name suggests that it is digital
+ // (some hackintonshs are not so smart in announcing correct
+ // ca devices ...
+ if (!isDigital)
+ {
+ std::string devNameLower = m_caDevice.GetName();
+ StringUtils::ToLower(devNameLower);
+ isDigital = devNameLower.find("digital") != std::string::npos;
+ }
+ return isDigital;
+}
+
+void AEDeviceEnumerationOSX::hasPassthroughOrDigitalFormats(const StreamFormatList &formatList, bool &hasPassthroughFormats, bool &hasDigitalFormat) const
+{
+ hasDigitalFormat = false;
+ hasPassthroughFormats = false;
+
+ for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
+ {
+ const AudioStreamBasicDescription &desc = formatList[formatIdx].mFormat;
+ if (desc.mFormatID == kAudioFormatAC3 || desc.mFormatID == kAudioFormat60958AC3)
+ {
+ hasDigitalFormat = true;
+ hasPassthroughFormats = true;
+ break;
+ }
+ else
+ {
+ // PassthroughFormat 2ch 16bit LE 48000Hz / 192000Hz */
+ if (desc.mBitsPerChannel == 16 &&
+ !(desc.mFormatFlags & kAudioFormatFlagIsBigEndian) &&
+ desc.mChannelsPerFrame == 2 &&
+ (desc.mSampleRate == 48000 || desc.mSampleRate == 192000))
+ {
+ hasPassthroughFormats = true;
+ }
+ }
+ }
+}
+
+enum AEDeviceType AEDeviceEnumerationOSX::getDeviceType(bool hasPassthroughFormats,
+ bool isDigital,
+ UInt32 numChannels,
+ UInt32 transportType) const
+{
+ // flag indicating that the device name "sounds" like HDMI
+ bool hasHdmiName = m_deviceName.find("HDMI") != std::string::npos;
+ // flag indicating that the device name "sounds" like DisplayPort
+ bool hasDisplayPortName = m_deviceName.find("DisplayPort") != std::string::npos;
+ enum AEDeviceType deviceType = AE_DEVTYPE_PCM;//default
+
+ // decide the type of the device based on the discovered information
+ // in the streams
+ // device defaults to PCM (see start of the while loop)
+ // it can be HDMI, DisplayPort or Optical
+ // for all of those types it needs to support
+ // passthroughformats and needs to be a digital port
+ if (hasPassthroughFormats && isDigital)
+ {
+ // if the max number of channels was more then 2
+ // this can be HDMI or DisplayPort or Thunderbolt
+ if (numChannels > 2)
+ {
+ // either the devicename suggests its HDMI
+ // or CA reported the transportType as HDMI
+ if (hasHdmiName || transportType == kIOAudioDeviceTransportTypeHdmi)
+ deviceType = AE_DEVTYPE_HDMI;
+
+ // either the devicename suggests its DisplayPort
+ // or CA reported the transportType as DisplayPort or Thunderbolt
+ if (hasDisplayPortName ||
+ transportType == kIOAudioDeviceTransportTypeDisplayPort ||
+ transportType == kIOAudioDeviceTransportTypeThunderbolt)
+ deviceType = AE_DEVTYPE_DP;
+ }
+ else// treat all other digital passthrough devices as optical
+ deviceType = AE_DEVTYPE_IEC958;
+
+ //treat all other digital devices as HDMI to let options open to the user
+ if (deviceType == AE_DEVTYPE_PCM)
+ deviceType = AE_DEVTYPE_HDMI;
+ }
+
+ // devicename based overwrites from former code - maybe FIXME at some point when we
+ // are sure that the upper detection does its job in all[tm] use cases
+ if (hasHdmiName)
+ deviceType = AE_DEVTYPE_HDMI;
+ if (hasDisplayPortName)
+ deviceType = AE_DEVTYPE_DP;
+
+ return deviceType;
+}
+
+CADeviceList AEDeviceEnumerationOSX::GetDeviceInfoList() const
+{
+ CADeviceList list;
+ UInt32 numDevices = m_caStreamInfos.size();
+ if (m_isPlanar)
+ numDevices = 1;
+
+ for (UInt32 streamIdx = 0; streamIdx < numDevices; streamIdx++)
+ {
+ CAEDeviceInfo deviceInfo;
+ struct CADeviceInstance devInstance;
+ devInstance.audioDeviceId = m_deviceID;
+ devInstance.streamIndex = streamIdx;
+ devInstance.sourceId = INT_MAX;//don't set audio source by default
+
+ deviceInfo.m_deviceName = getDeviceNameForStream(streamIdx);
+ deviceInfo.m_displayName = m_deviceName;
+ deviceInfo.m_displayNameExtra = getExtraDisplayNameForStream(streamIdx);
+ deviceInfo.m_channels = getChannelInfoForStream(streamIdx);
+ deviceInfo.m_sampleRates = getSampleRateListForStream(streamIdx);
+ deviceInfo.m_dataFormats = getFormatListForStream(streamIdx);
+ deviceInfo.m_streamTypes = getTypeListForStream(streamIdx);
+ deviceInfo.m_deviceType = m_caStreamInfos[streamIdx].deviceType;
+ deviceInfo.m_wantsIECPassthrough = true;
+
+ CoreAudioDataSourceList sourceList;
+ // if this enumerator contains multiple devices with more then 1 source we add :source suffixes to the
+ // device names and overwrite the extraname with the source name
+ if (numDevices == 1 && m_caDevice.GetDataSources(&sourceList) && sourceList.size() > 1)
+ {
+ for (unsigned sourceIdx = 0; sourceIdx < sourceList.size(); sourceIdx++)
+ {
+ std::stringstream sourceIdxStr;
+ sourceIdxStr << sourceIdx;
+ deviceInfo.m_deviceName = getDeviceNameForStream(streamIdx) + ":source" + sourceIdxStr.str();
+ deviceInfo.m_displayNameExtra = m_caDevice.GetDataSourceName(sourceList[sourceIdx]);
+ devInstance.sourceId = sourceList[sourceIdx];
+ list.push_back(std::make_pair(devInstance, deviceInfo));
+ }
+ }
+ else
+ list.push_back(std::make_pair(devInstance, deviceInfo));
+ }
+ return list;
+}
+
+unsigned int AEDeviceEnumerationOSX::GetNumPlanes() const
+{
+ if (m_isPlanar)
+ return m_caStreamInfos.size();
+ else
+ return 1;//interleaved - one plane
+}
+
+bool AEDeviceEnumerationOSX::hasSampleRate(const AESampleRateList &list, const unsigned int samplerate) const
+{
+ for (size_t i = 0; i < list.size(); ++i)
+ {
+ if (list[i] == samplerate)
+ return true;
+ }
+ return false;
+}
+
+bool AEDeviceEnumerationOSX::hasDataFormat(const AEDataFormatList &list, const enum AEDataFormat format) const
+{
+ for (size_t i = 0; i < list.size(); ++i)
+ {
+ if (list[i] == format)
+ return true;
+ }
+ return false;
+}
+
+bool AEDeviceEnumerationOSX::hasDataType(const AEDataTypeList &list, CAEStreamInfo::DataType type) const
+{
+ for (size_t i = 0; i < list.size(); ++i)
+ {
+ if (list[i] == type)
+ return true;
+ }
+ return false;
+}
+
+AEDataFormatList AEDeviceEnumerationOSX::getFormatListForStream(UInt32 streamIdx) const
+{
+ AEDataFormatList returnDataFormatList;
+ if (streamIdx >= m_caStreamInfos.size())
+ return returnDataFormatList;
+
+ // check the streams
+ const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
+ for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
+ {
+ AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
+ AEDataFormatList aeFormatList = caFormatToAE(formatDesc, m_caStreamInfos[streamIdx].isDigital);
+ for (UInt32 formatListIdx = 0; formatListIdx < aeFormatList.size(); formatListIdx++)
+ {
+ if (!hasDataFormat(returnDataFormatList, aeFormatList[formatListIdx]))
+ returnDataFormatList.push_back(aeFormatList[formatListIdx]);
+ }
+ }
+ return returnDataFormatList;
+}
+
+AEDataTypeList AEDeviceEnumerationOSX::getTypeListForStream(UInt32 streamIdx) const
+{
+ AEDataTypeList returnDataTypeList;
+ if (streamIdx >= m_caStreamInfos.size())
+ return returnDataTypeList;
+
+ // check the streams
+ const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
+ for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
+ {
+ AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
+ AEDataTypeList aeTypeList = caFormatToAEType(formatDesc, m_caStreamInfos[streamIdx].isDigital);
+ for (UInt32 typeListIdx = 0; typeListIdx < aeTypeList.size(); typeListIdx++)
+ {
+ if (!hasDataType(returnDataTypeList, aeTypeList[typeListIdx]))
+ returnDataTypeList.push_back(aeTypeList[typeListIdx]);
+ }
+ }
+ return returnDataTypeList;
+}
+
+CAEChannelInfo AEDeviceEnumerationOSX::getChannelInfoForStream(UInt32 streamIdx) const
+{
+ CAEChannelInfo channelInfo;
+ if (streamIdx >= m_caStreamInfos.size())
+ return channelInfo;
+
+ if (m_isPlanar)
+ {
+ //get channel map to match the devices channel layout as set in audio-midi-setup
+ GetAEChannelMap(channelInfo, GetNumPlanes());
+ }
+ else
+ {
+ //get channel map to match the devices channel layout as set in audio-midi-setup
+ GetAEChannelMap(channelInfo, m_caDevice.GetNumChannelsOfStream(streamIdx));
+ }
+ return channelInfo;
+}
+
+AEDataFormatList AEDeviceEnumerationOSX::caFormatToAE(const AudioStreamBasicDescription &formatDesc, bool isDigital) const
+{
+ AEDataFormatList formatList;
+ // add stream format info
+ switch (formatDesc.mFormatID)
+ {
+ case kAudioFormatAC3:
+ case kAudioFormat60958AC3:
+ break;
+ default:
+ switch(formatDesc.mBitsPerChannel)
+ {
+ case 16:
+ if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
+ {
+ formatList.push_back(AE_FMT_S16BE);
+ }
+ else
+ {
+ formatList.push_back(AE_FMT_S16LE);
+ }
+ formatList.push_back(AE_FMT_S16NE); // if we support either LE or BE - we always support NE too...
+ break;
+ case 24:
+ if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
+ {
+ formatList.push_back(AE_FMT_S24BE3);
+ }
+ else
+ {
+ formatList.push_back(AE_FMT_S24LE3);
+ }
+ formatList.push_back(AE_FMT_S24NE3); // if we support either LE or BE - we always support NE too...
+ break;
+ case 32:
+ if (formatDesc.mFormatFlags & kAudioFormatFlagIsFloat)
+ {
+ formatList.push_back(AE_FMT_FLOAT);
+ }
+ else
+ {
+ if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
+ {
+ formatList.push_back(AE_FMT_S32BE);
+ }
+ else
+ {
+ formatList.push_back(AE_FMT_S32LE);
+ }
+ formatList.push_back(AE_FMT_S32NE); // if we support either LE or BE - we always support NE too...
+ }
+ break;
+ }
+ break;
+ }
+ return formatList;
+}
+
+AEDataTypeList AEDeviceEnumerationOSX::caFormatToAEType(const AudioStreamBasicDescription &formatDesc, bool isDigital) const
+{
+ AEDataTypeList typeList;
+ // add stream format info
+ switch (formatDesc.mFormatID)
+ {
+ case kAudioFormatAC3:
+ case kAudioFormat60958AC3:
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ break;
+ default:
+ switch(formatDesc.mBitsPerChannel)
+ {
+ case 16:
+ if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
+ ;
+ else
+ {
+ /* Passthrough is possible with a 2ch digital output */
+ if (formatDesc.mChannelsPerFrame == 2 && isDigital)
+ {
+ if (formatDesc.mSampleRate == 48000)
+ {
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
+ }
+ else if (formatDesc.mSampleRate == 192000)
+ {
+ typeList.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return typeList;
+}
+
+AESampleRateList AEDeviceEnumerationOSX::getSampleRateListForStream(UInt32 streamIdx) const
+{
+ AESampleRateList returnSampleRateList;
+ if (streamIdx >= m_caStreamInfos.size())
+ return returnSampleRateList;
+
+ // check the streams
+ const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
+ for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
+ {
+ AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
+ // add sample rate info
+ // for devices which return kAudioStreamAnyRatee
+ // we add 44.1khz and 48khz - user can use
+ // the "fixed" audio config to force one of them
+ if (formatDesc.mSampleRate == kAudioStreamAnyRate)
+ {
+ CLog::Log(LOGINFO, "{} reported samplerate is kAudioStreamAnyRate adding 44.1khz and 48khz",
+ __FUNCTION__);
+ formatDesc.mSampleRate = 44100;
+ if (!hasSampleRate(returnSampleRateList, formatDesc.mSampleRate))
+ returnSampleRateList.push_back(formatDesc.mSampleRate);
+ formatDesc.mSampleRate = 48000;
+ }
+
+ if (!hasSampleRate(returnSampleRateList, formatDesc.mSampleRate))
+ returnSampleRateList.push_back(formatDesc.mSampleRate);
+ }
+
+ return returnSampleRateList;
+}
+
+std::string AEDeviceEnumerationOSX::getDeviceNameForStream(UInt32 streamIdx) const
+{
+ std::stringstream deviceIdStr;
+ deviceIdStr << m_deviceID;
+
+ // device name is the devicename from coreaudio + the device id to make it unique
+ // for example devicename could be DisplayPort and couldn't be distinguished if multiple
+ // DisplayPort devices are available
+ std::string deviceName = m_deviceName + "-" + deviceIdStr.str();
+ if (m_isPlanar)// planar devices are saved as :stream0
+ {
+ deviceName += ":stream0";
+ }
+ else
+ {
+ std::stringstream streamIdxStr;
+ streamIdxStr << streamIdx;
+ deviceName += ":stream" + streamIdxStr.str();
+ }
+ return deviceName;
+}
+
+std::string AEDeviceEnumerationOSX::getExtraDisplayNameForStream(UInt32 streamIdx) const
+{
+ // for distinguishing the streams inside one device we add
+ // the corresponding channels to the extraDisplayName
+ // planar devices are ignored here as their streams are
+ // the channels not different subdevices
+ if (m_caStreamInfos.size() > 1 && !m_isPlanar)
+ {
+ // build a string with the channels for this stream
+ UInt32 startChannel = 0;
+ CCoreAudioStream::GetStartingChannelInDevice(m_caStreamInfos[streamIdx].streamID, startChannel);
+ UInt32 numChannels = m_caDevice.GetNumChannelsOfStream(streamIdx);
+ std::stringstream extraName;
+ extraName << "Channels ";
+ extraName << startChannel;
+ extraName << " - ";
+ extraName << startChannel + numChannels - 1;
+ CLog::Log(LOGINFO,
+ "{} adding stream {} as pseudo device with start channel {} and {} channels total",
+ __FUNCTION__, (unsigned int)streamIdx, (unsigned int)startChannel,
+ (unsigned int)numChannels);
+ return extraName.str();
+ }
+
+ //for all other devices use the datasource as extraname
+ return m_caDevice.GetCurrentDataSourceName();
+}
+
+float AEDeviceEnumerationOSX::scoreSampleRate(Float64 destinationRate, unsigned int sourceRate) const
+{
+ float score = 0;
+ double intPortion;
+ double fracPortion = modf(destinationRate / sourceRate, &intPortion);
+
+ score += (1 - fracPortion) * 1000; // prefer sample rates that are multiples of the source sample rate
+ score += (intPortion == 1.0) ? 500 : 0; // prefer exact matches over other multiples
+ score += (intPortion > 1 && intPortion < 100) ? (100 - intPortion) / 100 * 100 : 0; // prefer smaller multiples otherwise
+
+ return score;
+}
+
+float AEDeviceEnumerationOSX::ScoreFormat(const AudioStreamBasicDescription &formatDesc, const AEAudioFormat &format) const
+{
+ float score = 0;
+
+ if (format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_NULL)
+ {
+ if (formatDesc.mBitsPerChannel != 16)
+ return score;
+ if (formatDesc.mSampleRate != format.m_sampleRate)
+ return score;
+ if (formatDesc.mChannelsPerFrame != format.m_channelLayout.Count())
+ return score;
+ score += 5;
+
+ if (formatDesc.mFormatID == kAudioFormat60958AC3 ||
+ formatDesc.mFormatID == 'IAC3' ||
+ formatDesc.mFormatID == kAudioFormatAC3)
+ {
+ score += 1;
+ }
+ }
+ // non-passthrough, whatever works is fine
+ else if (formatDesc.mFormatID == kAudioFormatLinearPCM)
+ {
+ score += scoreSampleRate(formatDesc.mSampleRate, format.m_sampleRate);
+
+ if (formatDesc.mChannelsPerFrame == format.m_channelLayout.Count())
+ score += 5;
+ else if (formatDesc.mChannelsPerFrame > format.m_channelLayout.Count())
+ score += 1;
+ if (format.m_dataFormat == AE_FMT_FLOAT || format.m_dataFormat == AE_FMT_FLOATP)
+ { // for float, prefer the highest bitdepth we have
+ if (formatDesc.mBitsPerChannel >= 16)
+ score += (formatDesc.mBitsPerChannel / 8);
+ }
+ else
+ {
+ if (formatDesc.mBitsPerChannel == CAEUtil::DataFormatToBits(format.m_dataFormat))
+ score += 5;
+ else if (formatDesc.mBitsPerChannel > CAEUtil::DataFormatToBits(format.m_dataFormat))
+ score += 1;
+ }
+ }
+
+ return score;
+}
+
+bool AEDeviceEnumerationOSX::FindSuitableFormatForStream(UInt32 &streamIdx, const AEAudioFormat &format, bool virt, AudioStreamBasicDescription &outputFormat, AudioStreamID &outputStream) const
+{
+ CLog::Log(LOGDEBUG, "{}: Finding stream for format {}", __FUNCTION__,
+ CAEUtil::DataFormatToStr(format.m_dataFormat));
+
+ bool formatFound = false;
+ float outputScore = 0;
+ UInt32 streamIdxStart = streamIdx;
+ UInt32 streamIdxEnd = streamIdx + 1;
+ UInt32 streamIdxCurrent = streamIdx;
+
+ if (streamIdx == INT_MAX)
+ {
+ streamIdxStart = 0;
+ streamIdxEnd = m_caStreamInfos.size();
+ streamIdxCurrent = 0;
+ }
+
+ if (streamIdxCurrent >= m_caStreamInfos.size())
+ return false;
+
+ // loop over all streams or over given streams (depends on initial value of param streamIdx
+ for(streamIdxCurrent = streamIdxStart; streamIdxCurrent < streamIdxEnd; streamIdxCurrent++)
+ {
+
+ // Probe physical or virtual formats
+ const StreamFormatList *formats = &m_caStreamInfos[streamIdxCurrent].formatList;
+ if (virt)
+ formats = &m_caStreamInfos[streamIdxCurrent].formatListVirt;
+
+ for (StreamFormatList::const_iterator j = formats->begin(); j != formats->end(); ++j)
+ {
+ AudioStreamBasicDescription formatDesc = j->mFormat;
+
+ // for devices with kAudioStreamAnyRate
+ // assume that the user uses a fixed config
+ // and knows what he is doing - so we use
+ // the requested samplerate here
+ if (formatDesc.mSampleRate == kAudioStreamAnyRate)
+ formatDesc.mSampleRate = format.m_sampleRate;
+
+ float score = ScoreFormat(formatDesc, format);
+
+ std::string formatString;
+ if (!virt)
+ CLog::Log(LOGDEBUG, "{}: Physical Format: {} rated {:f}", __FUNCTION__,
+ StreamDescriptionToString(formatDesc, formatString), score);
+ else
+ CLog::Log(LOGDEBUG, "{}: Virtual Format: {} rated {:f}", __FUNCTION__,
+ StreamDescriptionToString(formatDesc, formatString), score);
+
+ if (score > outputScore)
+ {
+ outputScore = score;
+ outputFormat = formatDesc;
+ outputStream = m_caStreamInfos[streamIdxCurrent].streamID;
+ streamIdx = streamIdxCurrent;// return the streamIdx for the caller
+ formatFound = true;
+ }
+ }
+ }
+
+ if (m_isPlanar)
+ outputFormat.mChannelsPerFrame = std::min((size_t)format.m_channelLayout.Count(), m_caStreamInfos.size());
+
+ return formatFound;
+}
+
+// map coraudio channel labels to activeae channel labels
+enum AEChannel AEDeviceEnumerationOSX::caChannelToAEChannel(const AudioChannelLabel &CAChannelLabel) const
+{
+ enum AEChannel ret = AE_CH_NULL;
+ static unsigned int unknownChannel = AE_CH_UNKNOWN1;
+ switch(CAChannelLabel)
+ {
+ case kAudioChannelLabel_Left:
+ ret = AE_CH_FL;
+ break;
+ case kAudioChannelLabel_Right:
+ ret = AE_CH_FR;
+ break;
+ case kAudioChannelLabel_Center:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_LFEScreen:
+ ret = AE_CH_LFE;
+ break;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ ret = AE_CH_SL;
+ break;
+ case kAudioChannelLabel_RightSurroundDirect:
+ ret = AE_CH_SR;
+ break;
+ case kAudioChannelLabel_LeftCenter:
+ ret = AE_CH_FLOC;
+ break;
+ case kAudioChannelLabel_RightCenter:
+ ret = AE_CH_FROC;
+ break;
+ case kAudioChannelLabel_CenterSurround:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_LeftSurround:
+ ret = AE_CH_SL;
+ break;
+ case kAudioChannelLabel_RightSurround:
+ ret = AE_CH_SR;
+ break;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ ret = AE_CH_TFL;
+ break;
+ case kAudioChannelLabel_VerticalHeightRight:
+ ret = AE_CH_TFR;
+ break;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ ret = AE_CH_TFC;
+ break;
+ case kAudioChannelLabel_TopCenterSurround:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_TopBackLeft:
+ ret = AE_CH_TBL;
+ break;
+ case kAudioChannelLabel_TopBackRight:
+ ret = AE_CH_TBR;
+ break;
+ case kAudioChannelLabel_TopBackCenter:
+ ret = AE_CH_TBC;
+ break;
+ case kAudioChannelLabel_RearSurroundLeft:
+ ret = AE_CH_BL;
+ break;
+ case kAudioChannelLabel_RearSurroundRight:
+ ret = AE_CH_BR;
+ break;
+ case kAudioChannelLabel_LeftWide:
+ ret = AE_CH_BLOC;
+ break;
+ case kAudioChannelLabel_RightWide:
+ ret = AE_CH_BROC;
+ break;
+ case kAudioChannelLabel_LFE2:
+ ret = AE_CH_LFE;
+ break;
+ case kAudioChannelLabel_LeftTotal:
+ ret = AE_CH_FL;
+ break;
+ case kAudioChannelLabel_RightTotal:
+ ret = AE_CH_FR;
+ break;
+ case kAudioChannelLabel_HearingImpaired:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_Narration:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_Mono:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_DialogCentricMix:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_CenterSurroundDirect:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_Haptic:
+ ret = AE_CH_FC;
+ break;
+ default:
+ ret = (enum AEChannel)unknownChannel++;
+ }
+ if (unknownChannel == AE_CH_MAX)
+ unknownChannel = AE_CH_UNKNOWN1;
+
+ return ret;
+}
+
+//Note: in multichannel mode CA will either pull 2 channels of data (stereo) or 6/8 channels of data
+//(every speaker setup with more then 2 speakers). The difference between the number of real speakers
+//and 6/8 channels needs to be padded with unknown channels so that the sample size fits 6/8 channels
+//
+//device [in] - the device whose channel layout should be used
+//channelMap [in/out] - if filled it will it indicates that we are called from initialize and we log the requested map, out returns the channelMap for device
+//channelsPerFrame [in] - the number of channels this device is configured to (e.x. 2 or 6/8)
+void AEDeviceEnumerationOSX::GetAEChannelMap(CAEChannelInfo &channelMap, unsigned int channelsPerFrame) const
+{
+ CCoreAudioChannelLayout calayout;
+ bool logMapping = channelMap.Count() > 0; // only log if the engine requests a layout during init
+ bool mapAvailable = false;
+ unsigned int numberChannelsInDeviceLayout = CA_MAX_CHANNELS; // default number of channels from CAChannelMap
+ AudioChannelLayout *layout = NULL;
+
+ // try to fetch either the multichannel or the stereo channel layout from the device
+ if (channelsPerFrame == 2 || channelMap.Count() == 2)
+ mapAvailable = m_caDevice.GetPreferredChannelLayoutForStereo(calayout);
+ else
+ mapAvailable = m_caDevice.GetPreferredChannelLayout(calayout);
+
+ // if a map was fetched - check if it is usable
+ if (mapAvailable)
+ {
+ layout = calayout;
+ if (layout == NULL || layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
+ mapAvailable = false;// wrong map format
+ else
+ numberChannelsInDeviceLayout = layout->mNumberChannelDescriptions;
+ }
+
+ // start the mapping action
+ // the number of channels to be added to the outgoing channelmap
+ // this is CA_MAX_CHANNELS at max and might be lower for some output devices (channelsPerFrame)
+ unsigned int numChannelsToMap = std::min((unsigned int)CA_MAX_CHANNELS, (unsigned int)channelsPerFrame);
+
+ // if there was a map fetched we force the number of
+ // channels to map to channelsPerFrame (this allows mapping
+ // of more then CA_MAX_CHANNELS if needed)
+ if (mapAvailable)
+ numChannelsToMap = channelsPerFrame;
+
+ std::string layoutStr;
+
+ if (logMapping)
+ {
+ CLog::Log(LOGDEBUG, "{} Engine requests layout {}", __FUNCTION__, ((std::string)channelMap));
+
+ if (mapAvailable)
+ CLog::Log(LOGDEBUG, "{} trying to map to {} layout: {}", __FUNCTION__,
+ channelsPerFrame == 2 ? "stereo" : "multichannel",
+ calayout.ChannelLayoutToString(*layout, layoutStr));
+ else
+ CLog::Log(LOGDEBUG, "{} no map available - using static multichannel map layout",
+ __FUNCTION__);
+ }
+
+ channelMap.Reset();// start with an empty map
+
+ for (unsigned int channel = 0; channel < numChannelsToMap; channel++)
+ {
+ // we only try to map channels which are defined in the device layout
+ enum AEChannel currentChannel;
+ if (channel < numberChannelsInDeviceLayout)
+ {
+ // get the channel from the fetched map
+ if (mapAvailable)
+ currentChannel = caChannelToAEChannel(layout->mChannelDescriptions[channel].mChannelLabel);
+ else// get the channel from the default map
+ currentChannel = CAChannelMap[channel];
+
+ }
+ else// fill with unknown channels
+ currentChannel = caChannelToAEChannel(kAudioChannelLabel_Unknown);
+
+ if(!channelMap.HasChannel(currentChannel))// only add if not already added
+ channelMap += currentChannel;
+ }
+
+ if (logMapping)
+ CLog::Log(LOGDEBUG, "{} mapped channels to layout {}", __FUNCTION__, ((std::string)channelMap));
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h
new file mode 100644
index 0000000..7baae8f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Sinks/osx/CoreAudioDevice.h"
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+struct CADeviceInstance
+{
+ AudioDeviceID audioDeviceId;
+ unsigned int streamIndex;
+ unsigned int sourceId;
+};
+typedef std::vector< std::pair<struct CADeviceInstance, CAEDeviceInfo> > CADeviceList;
+
+//Hierarchy:
+// Device
+// - 1..n streams
+// 1..n formats
+// - 0..n sources
+//on non planar devices we have numstreams * numsources devices for our list
+//on planar devices we have 1 * numsources devices for our list
+class AEDeviceEnumerationOSX
+{
+public:
+ /*!
+ * @brief C'tor - initialises the Enumerator and calls Enumerate
+ * @param deviceID - the CoreAudio Device ID which will be the base of the enumerated device list
+ */
+ AEDeviceEnumerationOSX(AudioDeviceID deviceID);
+ // d'tor
+ ~AEDeviceEnumerationOSX() = default;
+
+ /*!
+ * @brief Gets the device list which was enumerated by the last call to Enumerate
+ * (which is also called in c'tor).
+ *
+ * @return Returns the device list.
+ */
+ CADeviceList GetDeviceInfoList() const;
+
+ /*!
+ * @brief Fetches all metadata from the CoreAudio device which is needed to generate a proper DeviceList for AE
+ * This method is always called from C'tor but can be called multiple times if the streams of a device
+ * changed. This fills m_caStreamInfos.
+ * After this call - GetDeviceInfoList will reflect the Enumerated metadata.
+ * @return false when streamlist couldn't be fetched from device - else true
+ */
+ bool Enumerate();
+
+ /*!
+ * @brief Returns the number of Planes for a device. This will be 1 for non-planar devices and > 1 for planar devices.
+ * @return Number of planes for this device.
+ */
+ unsigned int GetNumPlanes() const;
+
+ /*!
+ * @brief Checks if the m_deviceID belongs to a planar or non-planar device.
+ * @return true if m_deviceID belongs to a planar device - else false.
+ */
+ bool IsPlanar() const { return m_isPlanar; }
+
+ /*!
+ * @brief Tries to find a suitable CoreAudio format which matches the given AEAudioFormat as close as possible.
+ * @param streamIdx [in/out] - if streamIdx != INT_MAX only formats of the given streamIdx are checked
+ * if streamIdx == INT_MAX - formats of all streams in the device are considered
+ * On success this parameter returns the selected streamIdx.
+ *
+ * @param format [in] - the requested AE format which should be matched to the stream formats of CA
+ * @param outputFormat [out] - the found CA format which matches best to the requested AE format
+ * @param outputStream [out] - the coreaudio streamid which contains the coreaudio format returned in outputFormat
+ * @return true if a matching corea audio format was found - else false
+ */
+ bool FindSuitableFormatForStream(UInt32 &streamIdx, const AEAudioFormat &format, bool virt,
+ AudioStreamBasicDescription &outputFormat,
+ AudioStreamID &outputStream) const;
+
+ /*!
+ * @brief Returns the device name which belongs to m_deviceID without any stream/source suffixes
+ * @return the CA device name
+ */
+ std::string GetMasterDeviceName() const { return m_deviceName; }
+
+ /*!
+ * @brief Tries to return a proper channelmap from CA in a format AE understands
+ * @param channelMap [in/out] - returns the found channelmap in AE format
+ * if initialised with a map the number of channels is used to determine
+ * if stereo or multichannel map should be fetched
+ * @param channelsPerFrame [int] - the number of channels which should be mapped
+ * (also decides if stereo or multichannel map is fetched similar to channelMap param)
+ */
+ void GetAEChannelMap(CAEChannelInfo &channelMap, unsigned int channelsPerFrame) const;
+
+ /*!
+ * @brief Scores a format based on:
+ * 1. Matching passthrough characteristics (i.e. passthrough flag)
+ * 2. Matching sample rate.
+ * 3. Matching bits per channel (or higher).
+ * 4. Matching number of channels (or higher).
+ *
+ * @param formatDesc [in] - The CA FormatDescription which should be scored
+ * @param format [in] - the AE format which should be matched as good as possible
+ * @return - the score of formatDesc - higher scores indicate better matching to "format"
+ * (scores > 10000 indicate passthrough formats)
+ */
+ // public because its used in unit tests ...
+ float ScoreFormat(const AudioStreamBasicDescription &formatDesc, const AEAudioFormat &format) const;
+
+private:
+
+ /*!
+ * @brief Checks if this is a digital device based on CA transportType or name
+ * @return - true if this is a digital device - else false.
+ */
+ bool isDigitalDevice() const;
+
+ /*!
+ * @brief Checks if there are passthrough formats or digital formats
+ * (the latter are passthrough formats with dedicated format config like AC3/DTS)
+ * @param formatList [in] - the format list to be evaluated
+ * @param hasPassthroughFormats [out] - true if there were passthrough formats in the list
+ * @param hasDigitalFormat [out] - true if there were dedicated passthrough formats in the list
+ */
+ void hasPassthroughOrDigitalFormats(const StreamFormatList &formatList,
+ bool &hasPassthroughFormats,
+ bool &hasDigitalFormat) const;
+
+ /*!
+ * @brief Gets the AE devicetype for this device based the given criteria
+ * @param hasPassthroughFormats [in] - flag indicating that the device has passthrough formats
+ * @param isDigital [in] - flag indicating that the device is digital
+ * @param numChannels [in] - the number of channels of the device
+ * @param transportType [in] - the transportType of the device
+ * @return the AE devicetype
+ */
+ enum AEDeviceType getDeviceType(bool hasPassthroughFormats, bool isDigital,
+ UInt32 numChannels, UInt32 transportType) const;
+
+ /*!
+ * @brief Fetches all ca streams from the ca device and fills m_channelsPerStream and m_streams
+ */
+ void fillStreamList();
+
+ /*!
+ * @brief Scores a samplerate based on:
+ * 1. Prefer exact match
+ * 2. Prefer exact multiple of source samplerate and prefer the lowest
+ *
+ * @param destinationRate [in] - the destination samplerate to score
+ * @param sourceRate [in] - the sourceRate of the audio format - this is the samplerate the score is based on
+ * @return the score
+ */
+ float scoreSampleRate(Float64 destinationRate, unsigned int sourceRate) const;
+
+ bool hasSampleRate(const AESampleRateList &list, const unsigned int samplerate) const;
+ bool hasDataFormat(const AEDataFormatList &list, const enum AEDataFormat format) const;
+ bool hasDataType(const AEDataTypeList &list, CAEStreamInfo::DataType type) const;
+
+ /*!
+ * @brief Converts a CA format description to a list of AEFormat descriptions (as one format can result
+ * in more then 1 AE format - e.x. AC3 ca format results in AC3 and DTS AE format
+ *
+ * @param formatDesc [in] - The CA format description to be converted
+ * @param isDigital [in] - Flag indicating if the parent stream of formatDesc is digital
+ * (for allowing bitstreaming without dedicated AC3 format in CA)
+ * @return The list of converted AE formats.
+ */
+ AEDataFormatList caFormatToAE(const AudioStreamBasicDescription &formatDesc, bool isDigital) const;
+ AEDataTypeList caFormatToAEType(const AudioStreamBasicDescription &formatDesc, bool isDigital) const;
+
+
+ /*!
+ * @brief Convert a CA channel label to an AE channel.
+ * @param CAChannelLabel - the CA channel label to be converted
+ * @return the corresponding AEChannel
+ */
+ enum AEChannel caChannelToAEChannel(const AudioChannelLabel &CAChannelLabel) const;
+
+ // for filling out the AEDeviceInfo object based on
+ // the data gathered on the last call to Enumerate()
+ /*!
+ * @brief Returns all AE formats for the CA stream at the given index
+ * @param streamIdx [in] - index into m_caStreamInfos
+ * @return - the list of AE formats in that stream.
+ */
+ AEDataFormatList getFormatListForStream(UInt32 streamIdx) const;
+
+ AEDataTypeList getTypeListForStream(UInt32 streamIdx) const;
+
+ /*!
+ * @brief Returns the AE channelinfo/channel map for the CA stream at the given index
+ * @param streamIdx [in] - index into m_caStreamInfos
+ * @return - the of AE channel info for that stream.
+ */
+ CAEChannelInfo getChannelInfoForStream(UInt32 streamIdx) const;
+
+ /*!
+ * @brief Returns the AE samplerates for the CA stream at the given index
+ * @param streamIdx [in] - index into m_caStreamInfos
+ * @return - the list of AE samplerates for that stream.
+ */
+ AESampleRateList getSampleRateListForStream(UInt32 streamIdx) const;
+
+ /*!
+ * @brief Returns the AE device name for the CA stream at the given index
+ * @param streamIdx [in] - index into m_caStreamInfos
+ * @return - The devicename for that stream
+ */
+ std::string getDeviceNameForStream(UInt32 streamIdx) const;
+
+ /*!
+ * @brief - Returns the extra displayname shown to the User in addition to getDisplayNameForStream
+ * for the CA stream at the given index
+ * @param streamIdx [in] - index into m_caStreamInfos
+ * @return - The extra displayname for that stream (might be empty)
+ */
+ std::string getExtraDisplayNameForStream(UInt32 streamIdx) const;
+
+ AudioDeviceID m_deviceID;
+ bool m_isPlanar;
+ std::string m_deviceName;
+
+ typedef struct
+ {
+ AudioStreamID streamID;
+ StreamFormatList formatList;
+ StreamFormatList formatListVirt;
+ UInt32 numChannels;
+ bool isDigital;
+ bool hasPassthroughFormats;
+ enum AEDeviceType deviceType;
+ } caStreamInfo;
+ std::vector<caStreamInfo> m_caStreamInfos;
+ CCoreAudioDevice m_caDevice;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.cpp b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.cpp
new file mode 100644
index 0000000..8c2446f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CoreAudioChannelLayout.h"
+
+#include <AudioToolbox/AudioToolbox.h>
+
+#define MAX_CHANNEL_LABEL 45
+
+const char* g_ChannelLabels[] =
+{
+ "Unused", // kAudioChannelLabel_Unused
+ "Left", // kAudioChannelLabel_Left
+ "Right", // kAudioChannelLabel_Right
+ "Center", // kAudioChannelLabel_Center
+ "LFE", // kAudioChannelLabel_LFEScreen
+ "Side Left", // kAudioChannelLabel_LeftSurround
+ "Side Right", // kAudioChannelLabel_RightSurround
+ "Left Center", // kAudioChannelLabel_LeftCenter
+ "Right Center", // kAudioChannelLabel_RightCenter
+ "Back Center", // kAudioChannelLabel_CenterSurround
+ "Back Left", // kAudioChannelLabel_LeftSurroundDirect
+ "Back Right", // kAudioChannelLabel_RightSurroundDirect
+ "Top Center", // kAudioChannelLabel_TopCenterSurround
+ "Vertical Back Left", // kAudioChannelLabel_VerticalHeightLeft
+ "Vertical Back Center", // kAudioChannelLabel_VerticalHeightCenter
+ "Vertical Back Right", // kAudioChannelLabel_VerticalHeightRight
+ "Top Back Left", // kAudioChannelLabel_VerticalHeightLeft
+ "Top Back Center", // kAudioChannelLabel_VerticalHeightCenter
+ "Top Back Right", // kAudioChannelLabel_VerticalHeightRight
+
+ // gap
+
+ "unused19", "unused20", "unused21", "unused22", "unused23", "unused24", "unused25",
+ "unused26", "unused27", "unused28", "unused29", "unused30", "unused31", "unused32",
+
+ "Rear Left", // kAudioChannelLabel_RearSurroundLeft
+ "Rear Right", // kAudioChannelLabel_RearSurroundRight
+ "Left Wide", // kAudioChannelLabel_LeftWide
+ "Right Wide", // kAudioChannelLabel_RightWide
+ "LFE2", // kAudioChannelLabel_LFE2
+ "Left Total", // kAudioChannelLabel_LeftTotal
+ "Right Total", // kAudioChannelLabel_RightTotal
+ "HearingImpaired", // kAudioChannelLabel_HearingImpaired
+ "Narration", // kAudioChannelLabel_Narration
+ "Mono", // kAudioChannelLabel_Mono
+ "DialogCentricMix", // kAudioChannelLabel_DialogCentricMix
+ "CenterSurroundDirect", // kAudioChannelLabel_CenterSurroundDirect
+ "Haptic", // kAudioChannelLabel_Haptic
+
+};
+
+CCoreAudioChannelLayout::CCoreAudioChannelLayout() :
+ m_pLayout(NULL)
+{
+}
+
+CCoreAudioChannelLayout::CCoreAudioChannelLayout(AudioChannelLayout& layout) :
+m_pLayout(NULL)
+{
+ CopyLayout(layout);
+}
+
+CCoreAudioChannelLayout::~CCoreAudioChannelLayout()
+{
+ free(m_pLayout);
+}
+
+bool CCoreAudioChannelLayout::CopyLayout(AudioChannelLayout& layout)
+{
+ enum {
+ kVariableLengthArray_deprecated = 1
+ };
+
+ free(m_pLayout);
+ m_pLayout = NULL;
+
+ // This method always produces a layout with a ChannelDescriptions structure
+
+ OSStatus ret = 0;
+ UInt32 channels = GetChannelCountForLayout(layout);
+ UInt32 size = sizeof(AudioChannelLayout) + (channels - kVariableLengthArray_deprecated) * sizeof(AudioChannelDescription);
+
+ if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions)
+ {
+ // We can copy the whole layout
+ m_pLayout = (AudioChannelLayout*)malloc(size);
+ memcpy(m_pLayout, &layout, size);
+ }
+ else if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
+ {
+ // Deconstruct the bitmap to get the layout
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(layout.mChannelBitmap), &layout.mChannelBitmap, &propSize);
+ m_pLayout = (AudioChannelLayout*)malloc(propSize);
+ ret = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(layout.mChannelBitmap), &layout.mChannelBitmap, &propSize, m_pLayout);
+ m_pLayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ }
+ else
+ {
+ // Convert the known layout to a custom layout
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(layout.mChannelLayoutTag), &layout.mChannelLayoutTag, &propSize);
+ m_pLayout = (AudioChannelLayout*)malloc(propSize);
+ ret = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(layout.mChannelLayoutTag), &layout.mChannelLayoutTag, &propSize, m_pLayout);
+ m_pLayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ }
+
+ return (ret == noErr);
+}
+
+bool CCoreAudioChannelLayout::CopyLayoutForStereo(UInt32 layout[2])
+{
+ enum {
+ kVariableLengthArray_deprecated = 1
+ };
+
+ free(m_pLayout);
+ m_pLayout = NULL;
+
+ UInt32 channels = 2;
+ UInt32 size = sizeof(AudioChannelLayout) + (channels - kVariableLengthArray_deprecated) * sizeof(AudioChannelDescription);
+
+ m_pLayout = (AudioChannelLayout*)malloc(size);
+ m_pLayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ m_pLayout->mNumberChannelDescriptions = 2;//stereo
+
+ AudioChannelDescription desc;
+ desc.mChannelFlags = kAudioChannelFlags_AllOff;
+ memset(desc.mCoordinates, 0, sizeof(desc.mCoordinates));
+
+ desc.mChannelLabel = layout[0];// label for channel 1
+ m_pLayout->mChannelDescriptions[0] = desc;
+
+ desc.mChannelLabel = layout[1];// label for channel 2
+ m_pLayout->mChannelDescriptions[1] = desc;
+ return true;
+}
+
+UInt32 CCoreAudioChannelLayout::GetChannelCountForLayout(AudioChannelLayout& layout)
+{
+ UInt32 channels = 0;
+ if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
+ {
+ // Channels are in fixed-order('USB Order'), any combination
+ UInt32 bitmap = layout.mChannelBitmap;
+ for (UInt32 c = 0; c < (sizeof(layout.mChannelBitmap) << 3); c++)
+ {
+ if (bitmap & 0x1)
+ channels++;
+ bitmap >>= 1;
+ }
+ }
+ else if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions)
+ {
+ // Channels are in any order, any combination
+ channels = layout.mNumberChannelDescriptions;
+ }
+ else
+ {
+ // Channels are in a predefined order and combination
+ channels = AudioChannelLayoutTag_GetNumberOfChannels(layout.mChannelLayoutTag);
+ }
+
+ return channels;
+}
+
+const char* CCoreAudioChannelLayout::ChannelLabelToString(UInt32 label)
+{
+ if (label > MAX_CHANNEL_LABEL)
+ return "Unknown";
+ return g_ChannelLabels[label];
+}
+
+const char* CCoreAudioChannelLayout::ChannelLayoutToString(AudioChannelLayout& layout, std::string& str)
+{
+ AudioChannelLayout* pLayout = NULL;
+
+ if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions)
+ {
+ pLayout = &layout;
+ }
+ else if (layout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
+ {
+ // Deconstruct the bitmap to get the layout
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(layout.mChannelBitmap), &layout.mChannelBitmap, &propSize);
+ pLayout = (AudioChannelLayout*)calloc(propSize, 1);
+ AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(layout.mChannelBitmap), &layout.mChannelBitmap, &propSize, pLayout);
+ }
+ else
+ {
+ // Predefined layout 'tag'
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(layout.mChannelLayoutTag), &layout.mChannelLayoutTag, &propSize);
+ pLayout = (AudioChannelLayout*)calloc(propSize, 1);
+ AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(layout.mChannelLayoutTag), &layout.mChannelLayoutTag, &propSize, pLayout);
+ }
+
+ for (UInt32 c = 0; c < pLayout->mNumberChannelDescriptions; c++)
+ {
+ str += "[";
+ str += ChannelLabelToString(pLayout->mChannelDescriptions[c].mChannelLabel);
+ if (pLayout->mChannelDescriptions[c].mChannelLabel > MAX_CHANNEL_LABEL)
+ {
+ str += "(" + std::to_string(pLayout->mChannelDescriptions[c].mChannelLabel) + ")";
+ }
+ str += "] ";
+ }
+
+ if (layout.mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
+ free(pLayout);
+
+ return str.c_str();
+}
+
+bool CCoreAudioChannelLayout::AllChannelUnknown()
+{
+ AudioChannelLayout* pLayout = NULL;
+
+ if (!m_pLayout)
+ return false;
+
+ if (m_pLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions)
+ {
+ pLayout = m_pLayout;
+ }
+ else if (m_pLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
+ {
+ // Deconstruct the bitmap to get the layout
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(m_pLayout->mChannelBitmap), &m_pLayout->mChannelBitmap, &propSize);
+ pLayout = (AudioChannelLayout*)calloc(propSize, 1);
+ AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(m_pLayout->mChannelBitmap), &m_pLayout->mChannelBitmap, &propSize, pLayout);
+ }
+ else
+ {
+ // Predefined layout 'tag'
+ UInt32 propSize = 0;
+ AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(m_pLayout->mChannelLayoutTag), &m_pLayout->mChannelLayoutTag, &propSize);
+ pLayout = (AudioChannelLayout*)calloc(propSize, 1);
+ AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(m_pLayout->mChannelLayoutTag), &m_pLayout->mChannelLayoutTag, &propSize, pLayout);
+ }
+
+ for (UInt32 c = 0; c < pLayout->mNumberChannelDescriptions; c++)
+ {
+ if (pLayout->mChannelDescriptions[c].mChannelLabel != kAudioChannelLabel_Unknown)
+ {
+ return false;
+ }
+ }
+
+ if (m_pLayout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
+ free(pLayout);
+
+ return true;
+}
+
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h
new file mode 100644
index 0000000..a055b25
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <CoreAudio/CoreAudio.h>
+
+typedef std::vector<SInt32> CoreAudioChannelList;
+typedef std::list<AudioChannelLayoutTag> AudioChannelLayoutList;
+
+const AudioChannelLayoutTag g_LayoutMap[] =
+{
+ kAudioChannelLayoutTag_Stereo, // PCM_LAYOUT_2_0 = 0,
+ kAudioChannelLayoutTag_Stereo, // PCM_LAYOUT_2_0 = 0,
+ kAudioChannelLayoutTag_DVD_4, // PCM_LAYOUT_2_1,
+ kAudioChannelLayoutTag_MPEG_3_0_A, // PCM_LAYOUT_3_0,
+ kAudioChannelLayoutTag_DVD_10, // PCM_LAYOUT_3_1,
+ kAudioChannelLayoutTag_DVD_3, // PCM_LAYOUT_4_0,
+ kAudioChannelLayoutTag_DVD_6, // PCM_LAYOUT_4_1,
+ kAudioChannelLayoutTag_MPEG_5_0_A, // PCM_LAYOUT_5_0,
+ kAudioChannelLayoutTag_MPEG_5_1_A, // PCM_LAYOUT_5_1,
+ kAudioChannelLayoutTag_AudioUnit_7_0, // PCM_LAYOUT_7_0, ** This layout may be incorrect...no content to testß˚ **
+ kAudioChannelLayoutTag_MPEG_7_1_A, // PCM_LAYOUT_7_1
+};
+
+const AudioChannelLabel g_LabelMap[] =
+{
+ kAudioChannelLabel_Unused, // PCM_FRONT_LEFT,
+ kAudioChannelLabel_Left, // PCM_FRONT_LEFT,
+ kAudioChannelLabel_Right, // PCM_FRONT_RIGHT,
+ kAudioChannelLabel_Center, // PCM_FRONT_CENTER,
+ kAudioChannelLabel_LFEScreen, // PCM_LOW_FREQUENCY,
+ kAudioChannelLabel_LeftSurroundDirect, // PCM_BACK_LEFT, *** This is incorrect, but has been changed to match VideoPlayer
+ kAudioChannelLabel_RightSurroundDirect, // PCM_BACK_RIGHT, *** This is incorrect, but has been changed to match VideoPlayer
+ kAudioChannelLabel_LeftCenter, // PCM_FRONT_LEFT_OF_CENTER,
+ kAudioChannelLabel_RightCenter, // PCM_FRONT_RIGHT_OF_CENTER,
+ kAudioChannelLabel_CenterSurround, // PCM_BACK_CENTER,
+ kAudioChannelLabel_LeftSurround, // PCM_SIDE_LEFT, *** This is incorrect, but has been changed to match VideoPlayer
+ kAudioChannelLabel_RightSurround, // PCM_SIDE_RIGHT, *** This is incorrect, but has been changed to match VideoPlayer
+ kAudioChannelLabel_VerticalHeightLeft, // PCM_TOP_FRONT_LEFT,
+ kAudioChannelLabel_VerticalHeightRight, // PCM_TOP_FRONT_RIGHT,
+ kAudioChannelLabel_VerticalHeightCenter, // PCM_TOP_FRONT_CENTER,
+ kAudioChannelLabel_TopCenterSurround, // PCM_TOP_CENTER,
+ kAudioChannelLabel_TopBackLeft, // PCM_TOP_BACK_LEFT,
+ kAudioChannelLabel_TopBackRight, // PCM_TOP_BACK_RIGHT,
+ kAudioChannelLabel_TopBackCenter // PCM_TOP_BACK_CENTER
+};
+
+class CCoreAudioChannelLayout
+{
+public:
+ CCoreAudioChannelLayout();
+ CCoreAudioChannelLayout(AudioChannelLayout &layout);
+ virtual ~CCoreAudioChannelLayout();
+
+ operator AudioChannelLayout*() {return m_pLayout;}
+
+ bool CopyLayout(AudioChannelLayout &layout);
+ bool CopyLayoutForStereo(UInt32 layout[2]);
+ static UInt32 GetChannelCountForLayout(AudioChannelLayout &layout);
+ static const char* ChannelLabelToString(UInt32 label);
+ static const char* ChannelLayoutToString(AudioChannelLayout &layout, std::string &str);
+ bool AllChannelUnknown();
+protected:
+ AudioChannelLayout* m_pLayout;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.cpp b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.cpp
new file mode 100644
index 0000000..69f7580
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.cpp
@@ -0,0 +1,938 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CoreAudioDevice.h"
+
+#include "CoreAudioChannelLayout.h"
+#include "CoreAudioHardware.h"
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "utils/log.h"
+
+#include "platform/darwin/DarwinUtils.h"
+
+#include <unistd.h>
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// CCoreAudioDevice
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+CCoreAudioDevice::CCoreAudioDevice(AudioDeviceID deviceId) : m_DeviceId(deviceId)
+{
+}
+
+CCoreAudioDevice::~CCoreAudioDevice()
+{
+ Close();
+}
+
+bool CCoreAudioDevice::Open(AudioDeviceID deviceId)
+{
+ m_DeviceId = deviceId;
+ m_BufferSizeRestore = GetBufferSize();
+ return true;
+}
+
+void CCoreAudioDevice::Close()
+{
+ if (!m_DeviceId)
+ return;
+
+ // Stop the device if it was started
+ Stop();
+
+ // Unregister the IOProc if we have one
+ RemoveIOProc();
+
+ SetHogStatus(false);
+ CCoreAudioHardware::SetAutoHogMode(false);
+
+ if (m_MixerRestore > -1) // We changed the mixer status
+ SetMixingSupport((m_MixerRestore ? true : false));
+ m_MixerRestore = -1;
+
+ if (m_SampleRateRestore != 0.0)
+ SetNominalSampleRate(m_SampleRateRestore);
+
+ if (m_BufferSizeRestore && m_BufferSizeRestore != GetBufferSize())
+ {
+ SetBufferSize(m_BufferSizeRestore);
+ m_BufferSizeRestore = 0;
+ }
+
+ m_IoProc = NULL;
+ m_DeviceId = 0;
+ m_ObjectListenerProc = NULL;
+}
+
+void CCoreAudioDevice::Start()
+{
+ if (!m_DeviceId || m_Started)
+ return;
+
+ OSStatus ret = AudioDeviceStart(m_DeviceId, m_IoProc);
+ if (ret)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::Start: "
+ "Unable to start device. Error = {}",
+ GetError(ret));
+ else
+ m_Started = true;
+}
+
+void CCoreAudioDevice::Stop()
+{
+ if (!m_DeviceId || !m_Started)
+ return;
+
+ OSStatus ret = AudioDeviceStop(m_DeviceId, m_IoProc);
+ if (ret)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::Stop: "
+ "Unable to stop device. Error = {}",
+ GetError(ret));
+ m_Started = false;
+}
+
+void CCoreAudioDevice::RemoveObjectListenerProc(AudioObjectPropertyListenerProc callback, void* pClientData)
+{
+ if (!m_DeviceId)
+ return;
+
+ AudioObjectPropertyAddress audioProperty;
+ audioProperty.mSelector = kAudioObjectPropertySelectorWildcard;
+ audioProperty.mScope = kAudioObjectPropertyScopeWildcard;
+ audioProperty.mElement = kAudioObjectPropertyElementWildcard;
+
+ OSStatus ret = AudioObjectRemovePropertyListener(m_DeviceId, &audioProperty, callback, pClientData);
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::RemoveObjectListenerProc: "
+ "Unable to set ObjectListener callback. Error = {}",
+ GetError(ret));
+ }
+ m_ObjectListenerProc = NULL;
+}
+
+bool CCoreAudioDevice::SetObjectListenerProc(AudioObjectPropertyListenerProc callback, void* pClientData)
+{
+ // Allow only one ObjectListener at a time
+ if (!m_DeviceId || m_ObjectListenerProc)
+ return false;
+
+ AudioObjectPropertyAddress audioProperty;
+ audioProperty.mSelector = kAudioObjectPropertySelectorWildcard;
+ audioProperty.mScope = kAudioObjectPropertyScopeWildcard;
+ audioProperty.mElement = kAudioObjectPropertyElementWildcard;
+
+ OSStatus ret = AudioObjectAddPropertyListener(m_DeviceId, &audioProperty, callback, pClientData);
+
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetObjectListenerProc: "
+ "Unable to remove ObjectListener callback. Error = {}",
+ GetError(ret));
+ return false;
+ }
+
+ m_ObjectListenerProc = callback;
+ return true;
+}
+bool CCoreAudioDevice::AddIOProc(AudioDeviceIOProc ioProc, void* pCallbackData)
+{
+ // Allow only one IOProc at a time
+ if (!m_DeviceId || m_IoProc)
+ return false;
+
+ OSStatus ret = AudioDeviceCreateIOProcID(m_DeviceId, ioProc, pCallbackData, &m_IoProc);
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::AddIOProc: "
+ "Unable to add IOProc. Error = {}",
+ GetError(ret));
+ m_IoProc = NULL;
+ return false;
+ }
+
+ Start();
+
+ return true;
+}
+
+bool CCoreAudioDevice::RemoveIOProc()
+{
+ if (!m_DeviceId || !m_IoProc)
+ return false;
+
+ Stop();
+
+ OSStatus ret = AudioDeviceDestroyIOProcID(m_DeviceId, m_IoProc);
+ if (ret)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::RemoveIOProc: "
+ "Unable to remove IOProc. Error = {}",
+ GetError(ret));
+
+ m_IoProc = NULL; // Clear the reference no matter what
+
+ usleep(100000);
+
+ return true;
+}
+
+std::string CCoreAudioDevice::GetName() const
+{
+ if (!m_DeviceId)
+ return "";
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
+
+ std::string name;
+ CFStringRef deviceName = NULL;
+ UInt32 propertySize = sizeof(deviceName);
+
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &deviceName);
+
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetName: "
+ "Unable to get device name - id: {:#04x}. Error = {}",
+ (uint)m_DeviceId, GetError(ret));
+ }
+ else
+ {
+ CDarwinUtils::CFStringRefToUTF8String(deviceName, name);
+ CFRelease(deviceName);
+ }
+
+ return name;
+}
+
+bool CCoreAudioDevice::IsDigital() const
+{
+ bool isDigital = false;
+ UInt32 transportType = 0;
+ if (!m_DeviceId)
+ return false;
+
+ transportType = GetTransportType();
+ if (transportType == INT_MAX)
+ return false;
+
+ if (transportType == kIOAudioDeviceTransportTypeFireWire)
+ isDigital = true;
+ if (transportType == kIOAudioDeviceTransportTypeUSB)
+ isDigital = true;
+ if (transportType == kIOAudioDeviceTransportTypeHdmi)
+ isDigital = true;
+ if (transportType == kIOAudioDeviceTransportTypeDisplayPort)
+ isDigital = true;
+ if (transportType == kIOAudioDeviceTransportTypeThunderbolt)
+ isDigital = true;
+ if (transportType == kAudioStreamTerminalTypeDigitalAudioInterface)
+ isDigital = true;
+
+ return isDigital;
+}
+
+UInt32 CCoreAudioDevice::GetTransportType() const
+{
+ UInt32 transportType = 0;
+ if (!m_DeviceId)
+ return INT_MAX;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyTransportType;
+
+ UInt32 propertySize = sizeof(transportType);
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &transportType);
+ if (ret != noErr)
+ return INT_MAX;
+ return transportType;
+}
+
+UInt32 CCoreAudioDevice::GetTotalOutputChannels() const
+{
+ UInt32 channels = 0;
+
+ if (!m_DeviceId)
+ return channels;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
+
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_DeviceId, &propertyAddress, 0, NULL, &size);
+ if (ret != noErr)
+ return channels;
+
+ AudioBufferList* pList = (AudioBufferList*)malloc(size);
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &size, pList);
+ if (ret == noErr)
+ {
+ for(UInt32 buffer = 0; buffer < pList->mNumberBuffers; ++buffer)
+ channels += pList->mBuffers[buffer].mNumberChannels;
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetTotalOutputChannels: "
+ "Unable to get total device output channels - id: {:#04x}. Error = {}",
+ (uint)m_DeviceId, GetError(ret));
+ }
+
+ free(pList);
+
+ return channels;
+}
+
+UInt32 CCoreAudioDevice::GetNumChannelsOfStream(UInt32 streamIdx) const
+{
+ UInt32 channels = 0;
+
+ if (!m_DeviceId)
+ return channels;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
+
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_DeviceId, &propertyAddress, 0, NULL, &size);
+ if (ret != noErr)
+ return channels;
+
+ AudioBufferList* pList = (AudioBufferList*)malloc(size);
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &size, pList);
+ if (ret == noErr)
+ {
+ if (streamIdx < pList->mNumberBuffers)
+ channels = pList->mBuffers[streamIdx].mNumberChannels;
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetNumChannelsOfStream: "
+ "Unable to get number of stream output channels - id: {:#04x}. Error = {}",
+ (uint)m_DeviceId, GetError(ret));
+ }
+
+ free(pList);
+
+ return channels;
+}
+
+bool CCoreAudioDevice::GetStreams(AudioStreamIdList* pList)
+{
+ if (!pList || !m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyStreams;
+
+ UInt32 propertySize = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_DeviceId, &propertyAddress, 0, NULL, &propertySize);
+ if (ret != noErr)
+ return false;
+
+ UInt32 streamCount = propertySize / sizeof(AudioStreamID);
+ AudioStreamID* pStreamList = new AudioStreamID[streamCount];
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, pStreamList);
+ if (ret == noErr)
+ {
+ for (UInt32 stream = 0; stream < streamCount; stream++)
+ pList->push_back(pStreamList[stream]);
+ }
+ delete[] pStreamList;
+
+ return ret == noErr;
+}
+
+
+bool CCoreAudioDevice::IsRunning()
+{
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDeviceIsRunning;
+
+ UInt32 isRunning = 0;
+ UInt32 propertySize = sizeof(isRunning);
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &isRunning);
+ if (ret != noErr)
+ return false;
+
+ return isRunning != 0;
+}
+
+bool CCoreAudioDevice::SetHogStatus(bool hog)
+{
+ // According to Jeff Moore (Core Audio, Apple), Setting kAudioDevicePropertyHogMode
+ // is a toggle and the only way to tell if you do get hog mode is to compare
+ // the returned pid against getpid, if the match, you have hog mode, if not you don't.
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyHogMode;
+
+ if (hog)
+ {
+ // Not already set
+ if (m_HogPid == -1)
+ {
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, sizeof(m_HogPid), &m_HogPid);
+
+ // even if setting hogmode was successful our PID might not get written
+ // into m_HogPid (so it stays -1). Readback hogstatus for judging if we
+ // had success on getting hog status
+ // We do this only when AudioObjectSetPropertyData didn't set m_HogPid because
+ // it seems that in the other cases the GetHogStatus could return -1
+ // which would overwrite our valid m_HogPid again
+ // Man we should never touch this shit again ;)
+ if (m_HogPid == -1)
+ m_HogPid = GetHogStatus();
+
+ if (ret || m_HogPid != getpid())
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetHogStatus: "
+ "Unable to set 'hog' status. Error = {}",
+ GetError(ret));
+ return false;
+ }
+ }
+ }
+ else
+ {
+ // Currently Set
+ if (m_HogPid > -1)
+ {
+ pid_t hogPid = -1;
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, sizeof(hogPid), &hogPid);
+ if (ret || hogPid == getpid())
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetHogStatus: "
+ "Unable to release 'hog' status. Error = {}",
+ GetError(ret));
+ return false;
+ }
+ // Reset internal state
+ m_HogPid = hogPid;
+ }
+ }
+ return true;
+}
+
+pid_t CCoreAudioDevice::GetHogStatus()
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyHogMode;
+
+ pid_t hogPid = -1;
+ UInt32 size = sizeof(hogPid);
+ AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &size, &hogPid);
+
+ return hogPid;
+}
+
+bool CCoreAudioDevice::SetMixingSupport(UInt32 mix)
+{
+ if (!m_DeviceId)
+ return false;
+
+ if (!GetMixingSupport())
+ return false;
+
+ int restore = -1;
+ if (m_MixerRestore == -1)
+ {
+ // This is our first change to this setting. Store the original setting for restore
+ restore = (GetMixingSupport() ? 1 : 0);
+ }
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertySupportsMixing;
+
+ UInt32 mixEnable = mix ? 1 : 0;
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, sizeof(mixEnable), &mixEnable);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetMixingSupport: "
+ "Unable to set MixingSupport to {}. Error = {}",
+ mix ? "'On'" : "'Off'", GetError(ret));
+ return false;
+ }
+ if (m_MixerRestore == -1)
+ m_MixerRestore = restore;
+ return true;
+}
+
+bool CCoreAudioDevice::GetMixingSupport()
+{
+ if (!m_DeviceId)
+ return false;
+
+ UInt32 size;
+ UInt32 mix = 0;
+ Boolean writable = false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertySupportsMixing;
+
+ if( AudioObjectHasProperty( m_DeviceId, &propertyAddress ) )
+ {
+ OSStatus ret = AudioObjectIsPropertySettable(m_DeviceId, &propertyAddress, &writable);
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SupportsMixing: "
+ "Unable to get propertyinfo mixing support. Error = {}",
+ GetError(ret));
+ writable = false;
+ }
+
+ if (writable)
+ {
+ size = sizeof(mix);
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &size, &mix);
+ if (ret != noErr)
+ mix = 0;
+ }
+ }
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioDevice::SupportsMixing: "
+ "Device mixing support : {}.",
+ mix ? "'Yes'" : "'No'");
+
+ return (mix > 0);
+}
+
+bool CCoreAudioDevice::SetCurrentVolume(Float32 vol)
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kHALOutputParam_Volume;
+
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, sizeof(Float32), &vol);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetCurrentVolume: "
+ "Unable to set AudioUnit volume. Error = {}",
+ GetError(ret));
+ return false;
+ }
+ return true;
+}
+
+bool CCoreAudioDevice::GetPreferredChannelLayout(CCoreAudioChannelLayout& layout) const
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout;
+
+ UInt32 propertySize = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_DeviceId, &propertyAddress, 0, NULL, &propertySize);
+ if (ret)
+ return false;
+
+ void* pBuf = malloc(propertySize);
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, pBuf);
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetPreferredChannelLayout: "
+ "Unable to retrieve preferred channel layout. Error = {}",
+ GetError(ret));
+ else
+ {
+ // Copy the result into the caller's instance
+ layout.CopyLayout(*((AudioChannelLayout*)pBuf));
+ }
+ free(pBuf);
+ return (ret == noErr);
+}
+
+bool CCoreAudioDevice::GetPreferredChannelLayoutForStereo(CCoreAudioChannelLayout &layout) const
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyPreferredChannelsForStereo;
+
+ UInt32 channels[2];// this will receive the channel labels
+ UInt32 propertySize = sizeof(channels);
+
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &channels);
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetPreferredChannelLayoutForStereo: "
+ "Unable to retrieve preferred channel layout. Error = {}",
+ GetError(ret));
+ else
+ {
+ // Copy/generate a layout into the result into the caller's instance
+ layout.CopyLayoutForStereo(channels);
+ }
+ return (ret == noErr);
+}
+
+std::string CCoreAudioDevice::GetCurrentDataSourceName() const
+{
+ UInt32 dataSourceId = 0;
+ std::string dataSourceName = "";
+ if(GetDataSource(dataSourceId))
+ {
+ dataSourceName = GetDataSourceName(dataSourceId);
+ }
+ return dataSourceName;
+}
+
+std::string CCoreAudioDevice::GetDataSourceName(UInt32 dataSourceId) const
+{
+ UInt32 propertySize = 0;
+ CFStringRef dataSourceNameCF;
+ std::string dataSourceName;
+ std::string ret = "";
+
+ if (!m_DeviceId)
+ return ret;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
+
+ AudioValueTranslation translation;
+ translation.mInputData = &dataSourceId;
+ translation.mInputDataSize = sizeof(UInt32);
+ translation.mOutputData = &dataSourceNameCF;
+ translation.mOutputDataSize = sizeof ( CFStringRef );
+ propertySize = sizeof(AudioValueTranslation);
+ OSStatus status = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &translation);
+
+ if (( status == noErr ) && dataSourceNameCF )
+ {
+ if (CDarwinUtils::CFStringRefToUTF8String(dataSourceNameCF, dataSourceName))
+ {
+ ret = dataSourceName;
+ }
+ CFRelease ( dataSourceNameCF );
+ }
+
+ return ret;
+}
+
+bool CCoreAudioDevice::GetDataSource(UInt32 &dataSourceId) const
+{
+ bool ret = false;
+
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDataSource;
+
+ UInt32 size = sizeof(dataSourceId);
+ OSStatus status = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &size, &dataSourceId);
+ if(status == noErr)
+ ret = true;
+
+ return ret;
+}
+
+bool CCoreAudioDevice::SetDataSource(UInt32 &dataSourceId)
+{
+ bool ret = false;
+
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDataSource;
+
+ UInt32 size = sizeof(dataSourceId);
+ OSStatus status = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, size, &dataSourceId);
+ if(status == noErr)
+ ret = true;
+
+ return ret;
+}
+
+bool CCoreAudioDevice::GetDataSources(CoreAudioDataSourceList* pList) const
+{
+ if (!pList || !m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyDataSources;
+
+ UInt32 propertySize = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_DeviceId, &propertyAddress, 0, NULL, &propertySize);
+ if (ret != noErr)
+ return false;
+
+ UInt32 sources = propertySize / sizeof(UInt32);
+ UInt32* pSources = new UInt32[sources];
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, pSources);
+ if (ret == noErr)
+ {
+ for (UInt32 i = 0; i < sources; i++)
+ pList->push_back(pSources[i]);
+ }
+ delete[] pSources;
+ return (!ret);
+}
+
+Float64 CCoreAudioDevice::GetNominalSampleRate()
+{
+ if (!m_DeviceId)
+ return 0.0;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
+
+ Float64 sampleRate = 0.0;
+ UInt32 propertySize = sizeof(Float64);
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &sampleRate);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetNominalSampleRate: "
+ "Unable to retrieve current device sample rate. Error = {}",
+ GetError(ret));
+
+ return 0.0;
+ }
+ return sampleRate;
+}
+
+bool CCoreAudioDevice::SetNominalSampleRate(Float64 sampleRate)
+{
+ if (!m_DeviceId || sampleRate == 0.0)
+ return false;
+
+ Float64 currentRate = GetNominalSampleRate();
+ if (currentRate == sampleRate)
+ return true; //No need to change
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
+
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, sizeof(Float64), &sampleRate);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetNominalSampleRate: "
+ "Unable to set current device sample rate to {:0.0f}. Error = {}",
+ (float)sampleRate, GetError(ret));
+ return false;
+ }
+ if (m_SampleRateRestore == 0.0)
+ m_SampleRateRestore = currentRate;
+
+ return true;
+}
+
+UInt32 CCoreAudioDevice::GetNumLatencyFrames()
+{
+ UInt32 num_latency_frames = 0;
+ if (!m_DeviceId)
+ return 0;
+
+ // number of frames of latency in the AudioDevice
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyLatency;
+
+ UInt32 i_param = 0;
+ UInt32 i_param_size = sizeof(uint32_t);
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &i_param_size, &i_param);
+ if (ret == noErr)
+ num_latency_frames += i_param;
+
+ // number of frames in the IO buffers
+ propertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &i_param_size, &i_param);
+ if (ret == noErr)
+ num_latency_frames += i_param;
+
+ // number for frames in ahead the current hardware position that is safe to do IO
+ propertyAddress.mSelector = kAudioDevicePropertySafetyOffset;
+ ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &i_param_size, &i_param);
+ if (ret == noErr)
+ num_latency_frames += i_param;
+
+ return (num_latency_frames);
+}
+
+UInt32 CCoreAudioDevice::GetBufferSize()
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
+
+ UInt32 size = 0;
+ UInt32 propertySize = sizeof(size);
+ OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &size);
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::GetBufferSize: "
+ "Unable to retrieve buffer size. Error = {}",
+ GetError(ret));
+ return size;
+}
+
+bool CCoreAudioDevice::SetBufferSize(UInt32 size)
+{
+ if (!m_DeviceId)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
+ propertyAddress.mElement = 0;
+ propertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
+
+ UInt32 propertySize = sizeof(size);
+ OSStatus ret = AudioObjectSetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, propertySize, &size);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioDevice::SetBufferSize: "
+ "Unable to set buffer size. Error = {}",
+ GetError(ret));
+ }
+
+ if (GetBufferSize() != size)
+ CLog::Log(LOGERROR, "CCoreAudioDevice::SetBufferSize: Buffer size change not applied.");
+
+ return (ret == noErr);
+}
+
+XbmcThreads::EndTime<> CCoreAudioDevice::m_callbackSuppressTimer;
+AudioObjectPropertyListenerProc CCoreAudioDevice::m_defaultOutputDeviceChangedCB = NULL;
+
+
+OSStatus CCoreAudioDevice::defaultOutputDeviceChanged(AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void* inClientData)
+{
+ if (m_callbackSuppressTimer.IsTimePast() && m_defaultOutputDeviceChangedCB != NULL)
+ return m_defaultOutputDeviceChangedCB(inObjectID, inNumberAddresses, inAddresses, inClientData);
+ return 0;
+}
+
+void CCoreAudioDevice::RegisterDeviceChangedCB(bool bRegister, AudioObjectPropertyListenerProc callback, void *ref)
+{
+ OSStatus ret = noErr;
+ AudioObjectPropertyAddress inAdr =
+ {
+ kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ if (bRegister)
+ ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &inAdr, callback, ref);
+ else
+ ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &inAdr, callback, ref);
+
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioAE::Deinitialize - error {} a listener callback for device changes!",
+ bRegister ? "attaching" : "removing");
+}
+
+void CCoreAudioDevice::RegisterDefaultOutputDeviceChangedCB(bool bRegister, AudioObjectPropertyListenerProc callback, void *ref)
+{
+ OSStatus ret = noErr;
+ static int registered = -1;
+
+ //only allow registration once
+ if (bRegister == (registered == 1))
+ return;
+
+ AudioObjectPropertyAddress inAdr =
+ {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ if (bRegister)
+ {
+ ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &inAdr, defaultOutputDeviceChanged, ref);
+ m_defaultOutputDeviceChangedCB = callback;
+ }
+ else
+ {
+ ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &inAdr, defaultOutputDeviceChanged, ref);
+ m_defaultOutputDeviceChangedCB = NULL;
+ }
+
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioAE::Deinitialize - error {} a listener callback for default output "
+ "device changes!",
+ bRegister ? "attaching" : "removing");
+ else
+ registered = bRegister ? 1 : 0;
+}
+
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.h b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.h
new file mode 100644
index 0000000..99d84f6
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Sinks/osx/CoreAudioStream.h"
+#include "threads/SystemClock.h"
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <CoreAudio/CoreAudio.h>
+
+typedef std::vector<UInt32> CoreAudioDataSourceList;
+typedef std::list<AudioDeviceID> CoreAudioDeviceList;
+
+class CCoreAudioChannelLayout;
+
+class CCoreAudioDevice
+{
+public:
+ CCoreAudioDevice() = default;
+ explicit CCoreAudioDevice(AudioDeviceID deviceId);
+ virtual ~CCoreAudioDevice();
+
+ bool Open(AudioDeviceID deviceId);
+ void Close();
+
+ void Start();
+ void Stop();
+ void RemoveObjectListenerProc(AudioObjectPropertyListenerProc callback, void *pClientData);
+ bool SetObjectListenerProc(AudioObjectPropertyListenerProc callback, void *pClientData);
+
+ AudioDeviceID GetId() const {return m_DeviceId;}
+ std::string GetName() const;
+ bool IsDigital() const;
+ UInt32 GetTransportType() const;
+ UInt32 GetTotalOutputChannels() const;
+ UInt32 GetNumChannelsOfStream(UInt32 streamIdx) const;
+ bool GetStreams(AudioStreamIdList *pList);
+ bool IsRunning();
+ bool SetHogStatus(bool hog);
+ pid_t GetHogStatus();
+ bool SetMixingSupport(UInt32 mix);
+ bool GetMixingSupport();
+ bool SetCurrentVolume(Float32 vol);
+ bool GetPreferredChannelLayout(CCoreAudioChannelLayout &layout) const;
+ bool GetPreferredChannelLayoutForStereo(CCoreAudioChannelLayout &layout) const;
+ bool GetDataSources(CoreAudioDataSourceList *pList) const;
+ bool GetDataSource(UInt32 &dataSourceId) const;
+ bool SetDataSource(UInt32 &dataSourceId);
+ std::string GetDataSourceName(UInt32 dataSourceId) const;
+ std::string GetCurrentDataSourceName() const;
+ Float64 GetNominalSampleRate();
+ bool SetNominalSampleRate(Float64 sampleRate);
+ UInt32 GetNumLatencyFrames();
+ UInt32 GetBufferSize();
+ bool SetBufferSize(UInt32 size);
+
+ static void RegisterDeviceChangedCB(bool bRegister, AudioObjectPropertyListenerProc callback, void *ref);
+ static void RegisterDefaultOutputDeviceChangedCB(bool bRegister, AudioObjectPropertyListenerProc callback, void *ref);
+ // suppresses the default output device changed callback for given time in ms
+ static void SuppressDefaultOutputDeviceCB(unsigned int suppressTimeMs)
+ {
+ m_callbackSuppressTimer.Set(std::chrono::milliseconds(suppressTimeMs));
+ }
+
+ bool AddIOProc(AudioDeviceIOProc ioProc, void* pCallbackData);
+ bool RemoveIOProc();
+protected:
+ bool m_Started = false;
+ AudioDeviceID m_DeviceId = 0;
+ int m_MixerRestore = -1;
+ AudioDeviceIOProc m_IoProc = nullptr;
+ AudioObjectPropertyListenerProc m_ObjectListenerProc = nullptr;
+
+ Float64 m_SampleRateRestore = 0.0;
+ pid_t m_HogPid = -1;
+ unsigned int m_frameSize = 0;
+ unsigned int m_OutputBufferIndex = 0;
+ unsigned int m_BufferSizeRestore = 0;
+
+ static XbmcThreads::EndTime<> m_callbackSuppressTimer;
+ static AudioObjectPropertyListenerProc m_defaultOutputDeviceChangedCB;
+ static OSStatus defaultOutputDeviceChanged(AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void* inClientData);
+
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.cpp b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.cpp
new file mode 100644
index 0000000..a59c8f3
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CoreAudioHardware.h"
+
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "utils/log.h"
+
+#include "platform/darwin/DarwinUtils.h"
+
+bool CCoreAudioHardware::GetAutoHogMode()
+{
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioHardwarePropertyHogModeIsAllowed;
+
+ UInt32 val = 0;
+ UInt32 size = sizeof(val);
+ OSStatus ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, &val);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::GetAutoHogMode: "
+ "Unable to get auto 'hog' mode. Error = {}",
+ GetError(ret));
+ return false;
+ }
+ return (val == 1);
+}
+
+void CCoreAudioHardware::SetAutoHogMode(bool enable)
+{
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioHardwarePropertyHogModeIsAllowed;
+
+ UInt32 val = enable ? 1 : 0;
+ UInt32 size = sizeof(val);
+ OSStatus ret = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, size, &val);
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::SetAutoHogMode: "
+ "Unable to set auto 'hog' mode. Error = {}",
+ GetError(ret));
+}
+
+void CCoreAudioHardware::ResetAudioDevices()
+{
+ CLog::Log(LOGDEBUG, "CCoreAudioHardware::ResetAudioDevices resetting our devices to LPCM");
+ CoreAudioDeviceList list;
+ if (GetOutputDevices(&list))
+ {
+ for (CoreAudioDeviceList::iterator it = list.begin(); it != list.end(); ++it)
+ {
+ CCoreAudioDevice device(*it);
+
+ AudioStreamIdList streams;
+ if (device.GetStreams(&streams))
+ {
+ CLog::Log(LOGDEBUG, "CCoreAudioHardware::ResetAudioDevices {} streams for device {}",
+ streams.size(), device.GetName());
+ for (AudioStreamIdList::iterator ait = streams.begin(); ait != streams.end(); ++ait)
+ ResetStream(*ait);
+ }
+ }
+ }
+}
+
+void CCoreAudioHardware::ResetStream(AudioStreamID streamId)
+{
+ CCoreAudioStream stream;
+ stream.Open(streamId);
+
+ AudioStreamBasicDescription desc;
+ if (stream.GetPhysicalFormat(&desc))
+ {
+ if (desc.mFormatID == 'IAC3' || desc.mFormatID == kAudioFormat60958AC3)
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CCoreAudioHardware::ResetStream stream 0x{:x} is in encoded format.. setting to LPCM",
+ (unsigned int)streamId);
+
+ StreamFormatList availableFormats;
+ if (stream.GetAvailablePhysicalFormats(&availableFormats))
+ {
+ for (StreamFormatList::iterator fmtIt = availableFormats.begin(); fmtIt != availableFormats.end() ; ++fmtIt)
+ {
+ AudioStreamRangedDescription fmtDesc = *fmtIt;
+ if (fmtDesc.mFormat.mFormatID == kAudioFormatLinearPCM)
+ {
+ AudioStreamBasicDescription newFmt = fmtDesc.mFormat;
+
+ if (stream.SetPhysicalFormat(&newFmt))
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ stream.Close(false);
+}
+
+AudioDeviceID CCoreAudioHardware::FindAudioDevice(const std::string &searchName)
+{
+ AudioDeviceID deviceId = 0;
+
+ if (!searchName.length())
+ return deviceId;
+
+ std::string searchNameLowerCase = searchName;
+ std::transform(searchNameLowerCase.begin(), searchNameLowerCase.end(), searchNameLowerCase.begin(), ::tolower );
+ if (searchNameLowerCase.compare("default") == 0)
+ {
+ AudioDeviceID defaultDevice = GetDefaultOutputDevice();
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioHardware::FindAudioDevice: "
+ "Returning default device [{:#04x}].",
+ (uint)defaultDevice);
+ return defaultDevice;
+ }
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioHardware::FindAudioDevice: "
+ "Searching for device - {}.",
+ searchName);
+
+ // Obtain a list of all available audio devices
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioHardwarePropertyDevices;
+
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::FindAudioDevice: "
+ "Unable to retrieve the size of the list of available devices. Error = {}",
+ GetError(ret));
+ return 0;
+ }
+
+ size_t deviceCount = size / sizeof(AudioDeviceID);
+ AudioDeviceID* pDevices = new AudioDeviceID[deviceCount];
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, pDevices);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::FindAudioDevice: "
+ "Unable to retrieve the list of available devices. Error = {}",
+ GetError(ret));
+ delete[] pDevices;
+ return 0;
+ }
+
+ // Attempt to locate the requested device
+ std::string deviceName;
+ for (size_t dev = 0; dev < deviceCount; dev++)
+ {
+ CCoreAudioDevice device;
+ device.Open((pDevices[dev]));
+ deviceName = device.GetName();
+ std::transform( deviceName.begin(), deviceName.end(), deviceName.begin(), ::tolower );
+ if (searchNameLowerCase.compare(deviceName) == 0)
+ deviceId = pDevices[dev];
+ if (deviceId)
+ break;
+ }
+ delete[] pDevices;
+
+ return deviceId;
+}
+
+AudioDeviceID CCoreAudioHardware::GetDefaultOutputDevice()
+{
+ AudioDeviceID deviceId = 0;
+ static AudioDeviceID lastDeviceId = 0;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+
+ UInt32 size = sizeof(AudioDeviceID);
+ OSStatus ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, &deviceId);
+
+ // outputDevice is set to 0 if there is no audio device available
+ // or if the default device is set to an encoded format
+ if (ret != noErr || !deviceId)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::GetDefaultOutputDevice:"
+ " Unable to identify default output device. Error = {}",
+ GetError(ret));
+ // if there was no error and no deviceId was returned
+ // return the last known default device
+ if (ret == noErr && !deviceId)
+ return lastDeviceId;
+ else
+ return 0;
+ }
+
+ lastDeviceId = deviceId;
+
+ return deviceId;
+}
+
+void CCoreAudioHardware::GetOutputDeviceName(std::string& name)
+{
+ name = "Default";
+ AudioDeviceID deviceId = GetDefaultOutputDevice();
+
+ if (deviceId)
+ {
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioObjectPropertyName;
+
+ CFStringRef theDeviceName = NULL;
+ UInt32 propertySize = sizeof(CFStringRef);
+ OSStatus ret = AudioObjectGetPropertyData(deviceId, &propertyAddress, 0, NULL, &propertySize, &theDeviceName);
+ if (ret != noErr)
+ return;
+
+ CDarwinUtils::CFStringRefToUTF8String(theDeviceName, name);
+
+ CFRelease(theDeviceName);
+ }
+}
+
+UInt32 CCoreAudioHardware::GetOutputDevices(CoreAudioDeviceList *pList)
+{
+ UInt32 found = 0;
+ if (!pList)
+ return found;
+
+ // Obtain a list of all available audio devices
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioHardwarePropertyDevices;
+
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size);
+ if (ret != noErr)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::GetOutputDevices:"
+ " Unable to retrieve the size of the list of available devices. Error = {}",
+ GetError(ret));
+ return found;
+ }
+
+ size_t deviceCount = size / sizeof(AudioDeviceID);
+ AudioDeviceID* pDevices = new AudioDeviceID[deviceCount];
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, pDevices);
+ if (ret != noErr)
+ CLog::Log(LOGERROR,
+ "CCoreAudioHardware::GetOutputDevices:"
+ " Unable to retrieve the list of available devices. Error = {}",
+ GetError(ret));
+ else
+ {
+ for (size_t dev = 0; dev < deviceCount; dev++)
+ {
+ CCoreAudioDevice device(pDevices[dev]);
+ if (device.GetTotalOutputChannels() == 0)
+ continue;
+ found++;
+ pList->push_back(pDevices[dev]);
+ }
+ }
+ delete[] pDevices;
+
+ return found;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.h b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.h
new file mode 100644
index 0000000..9c6cd45
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioHardware.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Sinks/osx/CoreAudioDevice.h"
+
+// There is only one AudioSystemObject instance system-side.
+// Therefore, all CCoreAudioHardware methods are static
+class CCoreAudioHardware
+{
+public:
+ static bool GetAutoHogMode();
+ static void SetAutoHogMode(bool enable);
+ static AudioStreamBasicDescription* FormatsList(AudioStreamID stream);
+ static AudioStreamID* StreamsList(AudioDeviceID device);
+ static void ResetAudioDevices();
+ static void ResetStream(AudioStreamID streamId);
+ static AudioDeviceID FindAudioDevice(const std::string &deviceName);
+ static AudioDeviceID GetDefaultOutputDevice();
+ static void GetOutputDeviceName(std::string &name);
+ static UInt32 GetOutputDevices(CoreAudioDeviceList *pList);
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.cpp b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.cpp
new file mode 100644
index 0000000..7e94e3c
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CoreAudioStream.h"
+
+#include "CoreAudioDevice.h"
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "utils/log.h"
+
+using namespace std::chrono_literals;
+
+CCoreAudioStream::CCoreAudioStream()
+{
+ m_OriginalVirtualFormat.mFormatID = 0;
+ m_OriginalPhysicalFormat.mFormatID = 0;
+}
+
+CCoreAudioStream::~CCoreAudioStream()
+{
+ Close();
+}
+
+bool CCoreAudioStream::Open(AudioStreamID streamId)
+{
+ m_StreamId = streamId;
+ CLog::Log(LOGDEBUG, "CCoreAudioStream::Open: Opened stream {:#04x}.", (uint)m_StreamId);
+
+ // watch for physical property changes.
+ AudioObjectPropertyAddress propertyAOPA;
+ propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
+ propertyAOPA.mSelector = kAudioStreamPropertyPhysicalFormat;
+ if (AudioObjectAddPropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
+ CLog::Log(LOGERROR, "CCoreAudioStream::Open: couldn't set up a physical property listener.");
+
+ // watch for virtual property changes.
+ propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
+ propertyAOPA.mSelector = kAudioStreamPropertyVirtualFormat;
+ if (AudioObjectAddPropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
+ CLog::Log(LOGERROR, "CCoreAudioStream::Open: couldn't set up a virtual property listener.");
+
+ return true;
+}
+
+//! @todo Should it even be possible to change both the
+//! physical and virtual formats, since the devices do it themselves?
+void CCoreAudioStream::Close(bool restore)
+{
+ if (!m_StreamId)
+ return;
+
+ std::string formatString;
+
+ // remove the physical/virtual property listeners before we make changes
+ // that will trigger callbacks that we do not care about.
+ AudioObjectPropertyAddress propertyAOPA;
+ propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
+ propertyAOPA.mSelector = kAudioStreamPropertyPhysicalFormat;
+ if (AudioObjectRemovePropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
+ CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Couldn't remove property listener.");
+
+ propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
+ propertyAOPA.mSelector = kAudioStreamPropertyVirtualFormat;
+ if (AudioObjectRemovePropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
+ CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Couldn't remove property listener.");
+
+ // Revert any format changes we made
+ if (restore && m_OriginalVirtualFormat.mFormatID && m_StreamId)
+ {
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioStream::Close: "
+ "Restoring original virtual format for stream {:#04x}. ({})",
+ (uint)m_StreamId, StreamDescriptionToString(m_OriginalVirtualFormat, formatString));
+ AudioStreamBasicDescription setFormat = m_OriginalVirtualFormat;
+ SetVirtualFormat(&setFormat);
+ }
+ if (restore && m_OriginalPhysicalFormat.mFormatID && m_StreamId)
+ {
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioStream::Close: "
+ "Restoring original physical format for stream {:#04x}. ({})",
+ (uint)m_StreamId, StreamDescriptionToString(m_OriginalPhysicalFormat, formatString));
+ AudioStreamBasicDescription setFormat = m_OriginalPhysicalFormat;
+ SetPhysicalFormat(&setFormat);
+ }
+
+ m_OriginalVirtualFormat.mFormatID = 0;
+ m_OriginalPhysicalFormat.mFormatID = 0;
+ CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Closed stream {:#04x}.", (uint)m_StreamId);
+ m_StreamId = 0;
+}
+
+UInt32 CCoreAudioStream::GetDirection()
+{
+ if (!m_StreamId)
+ return 0;
+
+ UInt32 val = 0;
+ UInt32 size = sizeof(UInt32);
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyDirection;
+
+ OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, &val);
+ if (ret)
+ return 0;
+
+ return val;
+}
+
+// WARNING - don't rely on this method - the return value of
+// GetTerminalType is driver specific - the checked return
+// values are only recommendations from apple
+bool CCoreAudioStream::IsDigitalOutput(AudioStreamID id)
+{
+ UInt32 type = GetTerminalType(id);
+ // yes apple is mixing types here...
+ return (type == kAudioStreamTerminalTypeDigitalAudioInterface ||
+ type == kIOAudioDeviceTransportTypeDisplayPort ||
+ type == kIOAudioDeviceTransportTypeHdmi ||
+ type == kIOAudioDeviceTransportTypeFireWire ||
+ type == kIOAudioDeviceTransportTypeThunderbolt ||
+ type == kIOAudioDeviceTransportTypeUSB);
+}
+
+bool CCoreAudioStream::GetStartingChannelInDevice(AudioStreamID id, UInt32 &startingChannel)
+{
+ if (!id)
+ return false;
+
+ UInt32 i_param_size = sizeof(UInt32);
+ UInt32 i_param;
+ startingChannel = 0;
+ bool ret = false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyStartingChannel;
+
+ // number of frames of latency in the AudioStream
+ OSStatus status = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &i_param_size, &i_param);
+ if (status == noErr)
+ {
+ startingChannel = i_param;
+ ret = true;
+ }
+
+ return ret;
+}
+
+UInt32 CCoreAudioStream::GetTerminalType(AudioStreamID id)
+{
+ if (!id)
+ return 0;
+
+ UInt32 val = 0;
+ UInt32 size = sizeof(UInt32);
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyTerminalType;
+
+ OSStatus ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &size, &val);
+ if (ret)
+ return 0;
+ return val;
+}
+
+UInt32 CCoreAudioStream::GetNumLatencyFrames()
+{
+ if (!m_StreamId)
+ return 0;
+
+ UInt32 i_param_size = sizeof(uint32_t);
+ UInt32 i_param, num_latency_frames = 0;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyLatency;
+
+ // number of frames of latency in the AudioStream
+ OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &i_param_size, &i_param);
+ if (ret == noErr)
+ {
+ num_latency_frames += i_param;
+ }
+
+ return num_latency_frames;
+}
+
+bool CCoreAudioStream::GetVirtualFormat(AudioStreamBasicDescription* pDesc)
+{
+ if (!pDesc || !m_StreamId)
+ return false;
+
+ UInt32 size = sizeof(AudioStreamBasicDescription);
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyVirtualFormat;
+ OSStatus ret = AudioObjectGetPropertyDataSize(m_StreamId, &propertyAddress, 0, NULL, &size);
+ if (ret)
+ return false;
+
+ ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, pDesc);
+ if (ret)
+ return false;
+ return true;
+}
+
+bool CCoreAudioStream::SetVirtualFormat(AudioStreamBasicDescription* pDesc)
+{
+ if (!pDesc || !m_StreamId)
+ return false;
+
+ std::string formatString;
+
+ // suppress callbacks for the default output device change
+ // for the next 2 seconds because setting format
+ // might trigger a change (when setting/unsetting an encoded
+ // passthrough format)
+ CCoreAudioDevice::SuppressDefaultOutputDeviceCB(2000);
+
+
+ if (!m_OriginalVirtualFormat.mFormatID)
+ {
+ // Store the original format (as we found it) so that it can be restored later
+ if (!GetVirtualFormat(&m_OriginalVirtualFormat))
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetVirtualFormat: "
+ "Unable to retrieve current virtual format for stream {:#04x}.",
+ (uint)m_StreamId);
+ return false;
+ }
+ }
+ m_virtual_format_event.Reset();
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyVirtualFormat;
+
+ UInt32 propertySize = sizeof(AudioStreamBasicDescription);
+ OSStatus ret = AudioObjectSetPropertyData(m_StreamId, &propertyAddress, 0, NULL, propertySize, pDesc);
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetVirtualFormat: "
+ "Unable to set virtual format for stream {:#04x}. Error = {}",
+ (uint)m_StreamId, GetError(ret));
+ return false;
+ }
+
+ // The AudioStreamSetProperty is not only asynchronous,
+ // it is also not Atomic, in its behaviour.
+ // Therefore we check 5 times before we really give up.
+ // FIXME: failing isn't actually implemented yet.
+ for (int i = 0; i < 10; ++i)
+ {
+ AudioStreamBasicDescription checkVirtualFormat;
+ if (!GetVirtualFormat(&checkVirtualFormat))
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetVirtualFormat: "
+ "Unable to retrieve current physical format for stream {:#04x}.",
+ (uint)m_StreamId);
+ return false;
+ }
+ if (checkVirtualFormat.mSampleRate == pDesc->mSampleRate &&
+ checkVirtualFormat.mFormatID == pDesc->mFormatID &&
+ checkVirtualFormat.mFramesPerPacket == pDesc->mFramesPerPacket)
+ {
+ // The right format is now active.
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioStream::SetVirtualFormat: "
+ "Virtual format for stream {:#04x}. now active ({})",
+ (uint)m_StreamId, StreamDescriptionToString(checkVirtualFormat, formatString));
+ break;
+ }
+ m_virtual_format_event.Wait(100ms);
+ }
+ return true;
+}
+
+bool CCoreAudioStream::GetPhysicalFormat(AudioStreamBasicDescription* pDesc)
+{
+ if (!pDesc || !m_StreamId)
+ return false;
+
+ UInt32 size = sizeof(AudioStreamBasicDescription);
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyPhysicalFormat;
+
+ OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, pDesc);
+ if (ret)
+ return false;
+ return true;
+}
+
+bool CCoreAudioStream::SetPhysicalFormat(AudioStreamBasicDescription* pDesc)
+{
+ if (!pDesc || !m_StreamId)
+ return false;
+
+ std::string formatString;
+
+ // suppress callbacks for the default output device change
+ // for the next 2 seconds because setting format
+ // might trigger a change (when setting/unsetting an encoded
+ // passthrough format)
+ CCoreAudioDevice::SuppressDefaultOutputDeviceCB(2000);
+
+ if (!m_OriginalPhysicalFormat.mFormatID)
+ {
+ // Store the original format (as we found it) so that it can be restored later
+ if (!GetPhysicalFormat(&m_OriginalPhysicalFormat))
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetPhysicalFormat: "
+ "Unable to retrieve current physical format for stream {:#04x}.",
+ (uint)m_StreamId);
+ return false;
+ }
+ }
+ m_physical_format_event.Reset();
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyPhysicalFormat;
+
+ UInt32 propertySize = sizeof(AudioStreamBasicDescription);
+ OSStatus ret = AudioObjectSetPropertyData(m_StreamId, &propertyAddress, 0, NULL, propertySize, pDesc);
+ if (ret)
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetPhysicalFormat: "
+ "Unable to set physical format for stream {:#04x}. Error = {}",
+ (uint)m_StreamId, GetError(ret));
+ return false;
+ }
+
+ // The AudioStreamSetProperty is not only asynchronous,
+ // it is also not Atomic, in its behaviour.
+ // Therefore we check 5 times before we really give up.
+ // FIXME: failing isn't actually implemented yet.
+ for(int i = 0; i < 10; ++i)
+ {
+ AudioStreamBasicDescription checkPhysicalFormat;
+ if (!GetPhysicalFormat(&checkPhysicalFormat))
+ {
+ CLog::Log(LOGERROR,
+ "CCoreAudioStream::SetPhysicalFormat: "
+ "Unable to retrieve current physical format for stream {:#04x}.",
+ (uint)m_StreamId);
+ return false;
+ }
+ if (checkPhysicalFormat.mSampleRate == pDesc->mSampleRate &&
+ checkPhysicalFormat.mFormatID == pDesc->mFormatID &&
+ checkPhysicalFormat.mFramesPerPacket == pDesc->mFramesPerPacket &&
+ checkPhysicalFormat.mChannelsPerFrame == pDesc->mChannelsPerFrame)
+ {
+ // The right format is now active.
+ CLog::Log(LOGDEBUG,
+ "CCoreAudioStream::SetPhysicalFormat: "
+ "Physical format for stream {:#04x}. now active ({})",
+ (uint)m_StreamId, StreamDescriptionToString(checkPhysicalFormat, formatString));
+ break;
+ }
+ m_physical_format_event.Wait(100ms);
+ }
+
+ return true;
+}
+
+bool CCoreAudioStream::GetAvailableVirtualFormats(StreamFormatList* pList)
+{
+ return GetAvailableVirtualFormats(m_StreamId, pList);
+}
+
+bool CCoreAudioStream::GetAvailableVirtualFormats(AudioStreamID id, StreamFormatList* pList)
+{
+ if (!pList || !id)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats;
+
+ UInt32 propertySize = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(id, &propertyAddress, 0, NULL, &propertySize);
+ if (ret)
+ return false;
+
+ UInt32 formatCount = propertySize / sizeof(AudioStreamRangedDescription);
+ AudioStreamRangedDescription *pFormatList = new AudioStreamRangedDescription[formatCount];
+ ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &propertySize, pFormatList);
+ if (!ret)
+ {
+ for (UInt32 format = 0; format < formatCount; format++)
+ pList->push_back(pFormatList[format]);
+ }
+ delete[] pFormatList;
+ return (ret == noErr);
+}
+
+bool CCoreAudioStream::GetAvailablePhysicalFormats(StreamFormatList* pList)
+{
+ return GetAvailablePhysicalFormats(m_StreamId, pList);
+}
+
+bool CCoreAudioStream::GetAvailablePhysicalFormats(AudioStreamID id, StreamFormatList* pList)
+{
+ if (!pList || !id)
+ return false;
+
+ AudioObjectPropertyAddress propertyAddress;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mSelector = kAudioStreamPropertyAvailablePhysicalFormats;
+
+ UInt32 propertySize = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(id, &propertyAddress, 0, NULL, &propertySize);
+ if (ret)
+ return false;
+
+ UInt32 formatCount = propertySize / sizeof(AudioStreamRangedDescription);
+ AudioStreamRangedDescription *pFormatList = new AudioStreamRangedDescription[formatCount];
+ ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &propertySize, pFormatList);
+ if (!ret)
+ {
+ for (UInt32 format = 0; format < formatCount; format++)
+ pList->push_back(pFormatList[format]);
+ }
+ delete[] pFormatList;
+ return (ret == noErr);
+}
+
+OSStatus CCoreAudioStream::HardwareStreamListener(AudioObjectID inObjectID,
+ UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
+{
+ CCoreAudioStream *ca_stream = (CCoreAudioStream*)inClientData;
+
+ for (UInt32 i = 0; i < inNumberAddresses; i++)
+ {
+ if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat)
+ {
+ AudioStreamBasicDescription actualFormat;
+ UInt32 propertySize = sizeof(AudioStreamBasicDescription);
+ // hardware physical format has changed.
+ if (AudioObjectGetPropertyData(ca_stream->m_StreamId, &inAddresses[i], 0, NULL, &propertySize, &actualFormat) == noErr)
+ {
+ std::string formatString;
+ CLog::Log(LOGINFO,
+ "CCoreAudioStream::HardwareStreamListener: "
+ "Hardware physical format changed to {}",
+ StreamDescriptionToString(actualFormat, formatString));
+ ca_stream->m_physical_format_event.Set();
+ }
+ }
+ else if (inAddresses[i].mSelector == kAudioStreamPropertyVirtualFormat)
+ {
+ // hardware virtual format has changed.
+ AudioStreamBasicDescription actualFormat;
+ UInt32 propertySize = sizeof(AudioStreamBasicDescription);
+ if (AudioObjectGetPropertyData(ca_stream->m_StreamId, &inAddresses[i], 0, NULL, &propertySize, &actualFormat) == noErr)
+ {
+ std::string formatString;
+ CLog::Log(LOGINFO,
+ "CCoreAudioStream::HardwareStreamListener: "
+ "Hardware virtual format changed to {}",
+ StreamDescriptionToString(actualFormat, formatString));
+ ca_stream->m_virtual_format_event.Set();
+ }
+ }
+ }
+
+ return noErr;
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.h b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.h
new file mode 100644
index 0000000..a0c6778
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/osx/CoreAudioStream.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Event.h"
+
+#include <list>
+#include <vector>
+
+#include <CoreAudio/CoreAudio.h>
+#include <IOKit/audio/IOAudioTypes.h>
+
+
+typedef std::vector<AudioStreamID> AudioStreamIdList;
+typedef std::vector<AudioStreamRangedDescription> StreamFormatList;
+
+class CCoreAudioStream
+{
+public:
+ CCoreAudioStream();
+ virtual ~CCoreAudioStream();
+
+ bool Open(AudioStreamID streamId);
+ void Close(bool restore = true);
+
+ AudioStreamID GetId() const {return m_StreamId;}
+ UInt32 GetDirection();
+ static UInt32 GetTerminalType(AudioStreamID id);
+ UInt32 GetNumLatencyFrames();
+ bool GetVirtualFormat(AudioStreamBasicDescription *pDesc);
+ bool GetPhysicalFormat(AudioStreamBasicDescription *pDesc);
+ bool SetVirtualFormat(AudioStreamBasicDescription *pDesc);
+ bool SetPhysicalFormat(AudioStreamBasicDescription *pDesc);
+ bool GetAvailableVirtualFormats(StreamFormatList *pList);
+ bool GetAvailablePhysicalFormats(StreamFormatList *pList);
+ static bool GetAvailableVirtualFormats(AudioStreamID id, StreamFormatList *pList);
+ static bool GetAvailablePhysicalFormats(AudioStreamID id, StreamFormatList *pList);
+ static bool IsDigitalOutput(AudioStreamID id);
+ static bool GetStartingChannelInDevice(AudioStreamID id, UInt32 &startingChannel);
+
+protected:
+ static OSStatus HardwareStreamListener(AudioObjectID inObjectID,
+ UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void* inClientData);
+
+ CEvent m_virtual_format_event;
+ CEvent m_physical_format_event;
+
+ AudioStreamID m_StreamId = 0;
+ AudioStreamBasicDescription m_OriginalVirtualFormat;
+ AudioStreamBasicDescription m_OriginalPhysicalFormat;
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.cpp
new file mode 100644
index 0000000..487b9ca
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.cpp
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkPipewire.h"
+
+#include "CompileInfo.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/pipewire/Pipewire.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireCore.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireNode.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireStream.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h"
+#include "utils/Map.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <pipewire/keys.h>
+#include <spa/param/audio/raw.h>
+
+using namespace std::chrono_literals;
+
+namespace
+{
+
+// clang-format off
+constexpr std::array<uint32_t, 14> defaultSampleRates = {
+ 5512,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000};
+// clang-format on
+
+constexpr auto formatMap = make_map<spa_audio_format, AEDataFormat>(
+ {{SPA_AUDIO_FORMAT_U8, AEDataFormat::AE_FMT_U8},
+ {SPA_AUDIO_FORMAT_S16, AEDataFormat::AE_FMT_S16NE},
+ {SPA_AUDIO_FORMAT_S24_32, AEDataFormat::AE_FMT_S24NE4},
+ {SPA_AUDIO_FORMAT_S32, AEDataFormat::AE_FMT_S32NE},
+ {SPA_AUDIO_FORMAT_S24, AEDataFormat::AE_FMT_S24NE3},
+ {SPA_AUDIO_FORMAT_F32, AEDataFormat::AE_FMT_FLOAT}});
+
+constexpr uint8_t PWFormatToSampleSize(spa_audio_format format)
+{
+ switch (format)
+ {
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_U8:
+ return 1;
+ case SPA_AUDIO_FORMAT_S16:
+ return 2;
+ case SPA_AUDIO_FORMAT_S24:
+ return 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_F32:
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+constexpr std::string_view PWFormatToString(spa_audio_format format)
+{
+ switch (format)
+ {
+ case SPA_AUDIO_FORMAT_U8:
+ return "u8";
+ case SPA_AUDIO_FORMAT_S8:
+ return "s8";
+ case SPA_AUDIO_FORMAT_S16:
+ return "s16";
+ case SPA_AUDIO_FORMAT_S24:
+ return "s24";
+ case SPA_AUDIO_FORMAT_S24_32:
+ return "s24_32";
+ case SPA_AUDIO_FORMAT_S32:
+ return "s32";
+ case SPA_AUDIO_FORMAT_F32:
+ return "f32";
+ default:
+ return "(invalid)";
+ }
+}
+
+spa_audio_format AEFormatToPWFormat(AEDataFormat& format)
+{
+ const auto it = std::find_if(formatMap.cbegin(), formatMap.cend(),
+ [&format](auto p) { return p.second == format; });
+ if (it != formatMap.cend())
+ return it->first;
+
+ // default if format not found in map
+ return SPA_AUDIO_FORMAT_F32;
+}
+
+constexpr AEDataFormat PWFormatToAEFormat(spa_audio_format& format)
+{
+ return formatMap.at(format);
+}
+
+// clang-format off
+constexpr auto channelMap = make_map<spa_audio_channel, AEChannel>({
+ {SPA_AUDIO_CHANNEL_FL, AEChannel::AE_CH_FL},
+ {SPA_AUDIO_CHANNEL_FR, AEChannel::AE_CH_FR},
+ {SPA_AUDIO_CHANNEL_FC, AEChannel::AE_CH_FC},
+ {SPA_AUDIO_CHANNEL_LFE, AEChannel::AE_CH_LFE},
+ {SPA_AUDIO_CHANNEL_SL, AEChannel::AE_CH_SL},
+ {SPA_AUDIO_CHANNEL_SR, AEChannel::AE_CH_SR},
+ {SPA_AUDIO_CHANNEL_FLC, AEChannel::AE_CH_FLOC},
+ {SPA_AUDIO_CHANNEL_FRC, AEChannel::AE_CH_FROC},
+ {SPA_AUDIO_CHANNEL_RC, AEChannel::AE_CH_BC},
+ {SPA_AUDIO_CHANNEL_RL, AEChannel::AE_CH_BL},
+ {SPA_AUDIO_CHANNEL_RR, AEChannel::AE_CH_BR},
+ {SPA_AUDIO_CHANNEL_TC, AEChannel::AE_CH_TC},
+ {SPA_AUDIO_CHANNEL_TFL, AEChannel::AE_CH_TFL},
+ {SPA_AUDIO_CHANNEL_TFC, AEChannel::AE_CH_TFC},
+ {SPA_AUDIO_CHANNEL_TFR, AEChannel::AE_CH_TFR},
+ {SPA_AUDIO_CHANNEL_TRL, AEChannel::AE_CH_TBL},
+ {SPA_AUDIO_CHANNEL_TRC, AEChannel::AE_CH_TBC},
+ {SPA_AUDIO_CHANNEL_TRR, AEChannel::AE_CH_TBR},
+ {SPA_AUDIO_CHANNEL_BC, AEChannel::AE_CH_BC},
+ {SPA_AUDIO_CHANNEL_BLC, AEChannel::AE_CH_BLOC},
+ {SPA_AUDIO_CHANNEL_BRC, AEChannel::AE_CH_BROC}});
+// clang-format on
+
+std::vector<spa_audio_channel> AEChannelMapToPWChannelMap(CAEChannelInfo& channelInfo)
+{
+ std::vector<spa_audio_channel> channels;
+ for (uint32_t count = 0; count < channelInfo.Count(); count++)
+ {
+ const auto it =
+ std::find_if(channelMap.cbegin(), channelMap.cend(),
+ [&channelInfo, &count](auto p) { return p.second == channelInfo[count]; });
+
+ if (it != channelMap.cend())
+ channels.emplace_back(it->first);
+ }
+
+ return channels;
+}
+
+CAEChannelInfo PWChannelMapToAEChannelMap(std::vector<spa_audio_channel>& channelInfo)
+{
+ CAEChannelInfo channels;
+ for (const auto& channel : channelInfo)
+ channels += channelMap.at(channel);
+
+ return channels;
+}
+
+} // namespace
+
+namespace AE
+{
+namespace SINK
+{
+
+std::unique_ptr<PIPEWIRE::CPipewire> pipewire;
+
+bool CAESinkPipewire::Register()
+{
+ pipewire = std::make_unique<PIPEWIRE::CPipewire>();
+
+ bool success{false};
+
+ try
+ {
+ success = pipewire->Start();
+ }
+ catch (std::exception& e)
+ {
+ success = false;
+ }
+
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "CAESinkPipewire::{} - failed to connect to server", __FUNCTION__);
+ pipewire.reset();
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CAESinkPipewire::{} - connected to server", __FUNCTION__);
+
+ AE::AESinkRegEntry entry;
+ entry.sinkName = "PIPEWIRE";
+ entry.createFunc = CAESinkPipewire::Create;
+ entry.enumerateFunc = CAESinkPipewire::EnumerateDevicesEx;
+ entry.cleanupFunc = CAESinkPipewire::Destroy;
+ AE::CAESinkFactory::RegisterSink(entry);
+
+ return true;
+}
+
+IAESink* CAESinkPipewire::Create(std::string& device, AEAudioFormat& desiredFormat)
+{
+ IAESink* sink = new CAESinkPipewire();
+ if (sink->Initialize(desiredFormat, device))
+ return sink;
+
+ delete sink;
+ return nullptr;
+}
+
+void CAESinkPipewire::EnumerateDevicesEx(AEDeviceInfoList& list, bool force)
+{
+ auto loop = pipewire->GetThreadLoop();
+ loop->Lock();
+
+ CAEDeviceInfo defaultDevice;
+ defaultDevice.m_deviceType = AE_DEVTYPE_PCM;
+ defaultDevice.m_deviceName = "Default";
+ defaultDevice.m_displayName = "Default";
+ defaultDevice.m_displayNameExtra = "Default Output Device (PIPEWIRE)";
+ defaultDevice.m_wantsIECPassthrough = true;
+
+ std::for_each(formatMap.cbegin(), formatMap.cend(),
+ [&defaultDevice](const auto& pair)
+ { defaultDevice.m_dataFormats.emplace_back(pair.second); });
+
+ std::for_each(defaultSampleRates.cbegin(), defaultSampleRates.cend(),
+ [&defaultDevice](const auto& rate)
+ { defaultDevice.m_sampleRates.emplace_back(rate); });
+
+ defaultDevice.m_channels = CAEChannelInfo(AE_CH_LAYOUT_2_0);
+
+ list.emplace_back(defaultDevice);
+
+ for (const auto& global : pipewire->GetGlobals())
+ {
+ CAEDeviceInfo device;
+ device.m_deviceType = AE_DEVTYPE_PCM;
+ device.m_deviceName = global.second->name;
+ device.m_displayName = global.second->description;
+ device.m_displayNameExtra = global.second->description + " (PIPEWIRE)";
+ device.m_wantsIECPassthrough = true;
+
+ std::for_each(formatMap.cbegin(), formatMap.cend(),
+ [&device](const auto& pair) { device.m_dataFormats.emplace_back(pair.second); });
+
+ std::for_each(defaultSampleRates.cbegin(), defaultSampleRates.cend(),
+ [&device](const auto& rate) { device.m_sampleRates.emplace_back(rate); });
+
+ auto proxy = global.second->proxy.get();
+ auto node = static_cast<PIPEWIRE::CPipewireNode*>(proxy);
+
+ node->EnumerateFormats();
+
+ int ret = loop->Wait(5s);
+ if (ret == -ETIMEDOUT)
+ {
+ CLog::Log(LOGDEBUG,
+ "CAESinkPipewire::{} - timed out out waiting for formats to be enumerated",
+ __FUNCTION__);
+ continue;
+ }
+
+ auto& channels = node->GetChannels();
+ if (channels.size() < 1)
+ continue;
+
+ for (const auto& channel : channels)
+ {
+ const auto ch = channelMap.find(channel);
+ if (ch != channelMap.cend())
+ device.m_channels += ch->second;
+ }
+
+ list.emplace_back(device);
+ }
+
+ loop->Unlock();
+}
+
+void CAESinkPipewire::Destroy()
+{
+ pipewire.reset();
+}
+
+bool CAESinkPipewire::Initialize(AEAudioFormat& format, std::string& device)
+{
+ auto core = pipewire->GetCore();
+ auto loop = pipewire->GetThreadLoop();
+ auto& stream = pipewire->GetStream();
+
+ loop->Lock();
+
+ auto& globals = pipewire->GetGlobals();
+
+ uint32_t id;
+ if (device == "Default")
+ {
+ id = PW_ID_ANY;
+ }
+ else
+ {
+ auto target = std::find_if(globals.begin(), globals.end(),
+ [&device](const auto& p) { return device == p.second->name; });
+ if (target == globals.end())
+ {
+ loop->Unlock();
+ return false;
+ }
+
+ id = target->first;
+ }
+
+ stream = std::make_shared<PIPEWIRE::CPipewireStream>(core->Get());
+
+ stream->AddListener(pipewire.get());
+
+ m_latency = 20; // ms
+ uint32_t frames = std::nearbyint((m_latency * format.m_sampleRate) / 1000.0);
+ std::string fraction = StringUtils::Format("{}/{}", frames, format.m_sampleRate);
+
+ std::array<spa_dict_item, 5> items = {
+ SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Audio"),
+ SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY, "Playback"),
+ SPA_DICT_ITEM_INIT(PW_KEY_APP_NAME, CCompileInfo::GetAppName()),
+ SPA_DICT_ITEM_INIT(PW_KEY_NODE_NAME, CCompileInfo::GetAppName()),
+ SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, fraction.c_str())};
+
+ auto properties = SPA_DICT_INIT(items.data(), items.size());
+ stream->UpdateProperties(&properties);
+
+ auto pwFormat = AEFormatToPWFormat(format.m_dataFormat);
+ format.m_dataFormat = PWFormatToAEFormat(pwFormat);
+
+ auto pwChannels = AEChannelMapToPWChannelMap(format.m_channelLayout);
+ format.m_channelLayout = PWChannelMapToAEChannelMap(pwChannels);
+
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - rate: {}", __FUNCTION__, format.m_sampleRate);
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - channels: {}", __FUNCTION__, pwChannels.size());
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - format: {}", __FUNCTION__, PWFormatToString(pwFormat));
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - samplesize: {}", __FUNCTION__,
+ PWFormatToSampleSize(pwFormat));
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - framesize: {}", __FUNCTION__,
+ pwChannels.size() * PWFormatToSampleSize(pwFormat));
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - latency: {}/{} ({:.3f}s)", __FUNCTION__, frames,
+ format.m_sampleRate, static_cast<double>(frames) / format.m_sampleRate);
+
+ spa_audio_info_raw info{};
+ info.format = pwFormat;
+ info.flags = SPA_AUDIO_FLAG_NONE;
+ info.rate = format.m_sampleRate;
+ info.channels = static_cast<uint32_t>(pwChannels.size());
+
+ for (size_t index = 0; index < pwChannels.size(); index++)
+ info.position[index] = pwChannels[index];
+
+ if (!stream->Connect(id, info))
+ {
+ loop->Unlock();
+ return false;
+ }
+
+ pw_stream_state state;
+ do
+ {
+ state = stream->GetState();
+ if (state == PW_STREAM_STATE_PAUSED)
+ break;
+
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - waiting", __FUNCTION__);
+
+ int ret = loop->Wait(5s);
+ if (ret == -ETIMEDOUT)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - timed out waiting for stream to be paused",
+ __FUNCTION__);
+ loop->Unlock();
+ return false;
+ }
+ } while (state != PW_STREAM_STATE_PAUSED);
+
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - initialized", __FUNCTION__);
+
+ format.m_frameSize = pwChannels.size() * PWFormatToSampleSize(pwFormat);
+ format.m_frames = frames;
+
+ m_format = format;
+
+ loop->Unlock();
+
+ return true;
+}
+
+void CAESinkPipewire::Deinitialize()
+{
+ auto loop = pipewire->GetThreadLoop();
+ auto& stream = pipewire->GetStream();
+
+ loop->Lock();
+
+ stream->Flush(false);
+
+ loop->Unlock();
+
+ stream.reset();
+}
+
+double CAESinkPipewire::GetCacheTotal()
+{
+ return m_latency / 1000.0;
+}
+
+unsigned int CAESinkPipewire::AddPackets(uint8_t** data, unsigned int frames, unsigned int offset)
+{
+ auto loop = pipewire->GetThreadLoop();
+ auto& stream = pipewire->GetStream();
+
+ loop->Lock();
+
+ if (stream->GetState() == PW_STREAM_STATE_PAUSED)
+ stream->SetActive(true);
+
+ pw_buffer* pwBuffer = nullptr;
+ while (!pwBuffer)
+ {
+ pwBuffer = stream->DequeueBuffer();
+ if (pwBuffer)
+ break;
+
+ int ret = loop->Wait(1s);
+ if (ret == -ETIMEDOUT)
+ {
+ loop->Unlock();
+ return 0;
+ }
+ }
+
+ spa_buffer* spaBuffer = pwBuffer->buffer;
+ spa_data* spaData = &spaBuffer->datas[0];
+
+ size_t length = frames * m_format.m_frameSize;
+
+ if (spaData->maxsize < length)
+ length = spaData->maxsize;
+
+ void* buffer = data[0] + offset * m_format.m_frameSize;
+
+ std::memcpy(spaData->data, buffer, length);
+
+ spaData->chunk->offset = 0;
+ spaData->chunk->stride = m_format.m_frameSize;
+ spaData->chunk->size = length;
+
+ stream->QueueBuffer(pwBuffer);
+
+ loop->Unlock();
+
+ return length / m_format.m_frameSize;
+}
+
+void CAESinkPipewire::GetDelay(AEDelayStatus& status)
+{
+ status.SetDelay(m_latency / 1000.0);
+}
+
+void CAESinkPipewire::Drain()
+{
+ auto loop = pipewire->GetThreadLoop();
+ auto& stream = pipewire->GetStream();
+
+ loop->Lock();
+
+ stream->Flush(true);
+
+ int ret = loop->Wait(1s);
+ if (ret == -ETIMEDOUT)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - wait timed out, already drained?", __FUNCTION__);
+ }
+
+ loop->Unlock();
+}
+
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h b/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h
new file mode 100644
index 0000000..b346e82
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AESink.h"
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+
+namespace AE
+{
+namespace SINK
+{
+
+class CAESinkPipewire : public IAESink
+{
+public:
+ CAESinkPipewire() = default;
+ ~CAESinkPipewire() override = default;
+
+ static bool Register();
+ static IAESink* Create(std::string& device, AEAudioFormat& desiredFormat);
+ static void EnumerateDevicesEx(AEDeviceInfoList& list, bool force = false);
+ static void Destroy();
+
+ // overrides via IAESink
+ const char* GetName() override { return "PIPEWIRE"; }
+
+ bool Initialize(AEAudioFormat& format, std::string& device) override;
+ void Deinitialize() override;
+
+ double GetCacheTotal() override;
+ void GetDelay(AEDelayStatus& status) override;
+
+ unsigned int AddPackets(uint8_t** data, unsigned int frames, unsigned int offset) override;
+ void Drain() override;
+
+private:
+ AEAudioFormat m_format;
+ double m_latency;
+};
+
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.cpp
new file mode 100644
index 0000000..5943aed
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Pipewire.h"
+
+#include "cores/AudioEngine/Sinks/pipewire/PipewireContext.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireCore.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireRegistry.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h"
+#include "utils/log.h"
+
+#include <pipewire/pipewire.h>
+
+using namespace std::chrono_literals;
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewire::CPipewire()
+{
+ pw_init(nullptr, nullptr);
+}
+
+CPipewire::~CPipewire()
+{
+ if (m_loop)
+ {
+ m_loop->Unlock();
+ m_loop->Stop();
+ }
+
+ m_stream.reset();
+ m_registry.reset();
+ m_core.reset();
+ m_context.reset();
+ m_loop.reset();
+
+ pw_deinit();
+}
+
+bool CPipewire::Start()
+{
+ m_loop = std::make_unique<CPipewireThreadLoop>();
+
+ m_context = std::make_unique<CPipewireContext>(m_loop->Get());
+
+ m_loop->Lock();
+
+ if (!m_loop->Start())
+ {
+ CLog::Log(LOGERROR, "Pipewire: failed to start threaded mainloop: {}", strerror(errno));
+ return false;
+ }
+
+ m_core = std::make_unique<CPipewireCore>(m_context->Get());
+ m_core->AddListener(this);
+
+ m_registry = std::make_unique<CPipewireRegistry>(m_core->Get());
+ m_registry->AddListener(this);
+
+ m_core->Sync();
+
+ int ret = m_loop->Wait(5s);
+
+ m_loop->Unlock();
+
+ if (ret == -ETIMEDOUT)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkPipewire::{} - timed out out waiting for synchronization",
+ __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.h b/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.h
new file mode 100644
index 0000000..c69655e
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/Pipewire.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Sinks/pipewire/PipewireProxy.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <pipewire/properties.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireThreadLoop;
+class CPipewireContext;
+class CPipewireCore;
+class CPipewireRegistry;
+class CPipewireStream;
+
+class CPipewire
+{
+public:
+ CPipewire();
+ ~CPipewire();
+
+ bool Start();
+
+ CPipewireThreadLoop* GetThreadLoop() { return m_loop.get(); }
+ CPipewireContext* GetContext() { return m_context.get(); }
+ CPipewireCore* GetCore() { return m_core.get(); }
+ CPipewireRegistry* GetRegistry() { return m_registry.get(); }
+ std::shared_ptr<CPipewireStream>& GetStream() { return m_stream; }
+
+ struct PipewirePropertiesDeleter
+ {
+ void operator()(pw_properties* p) { pw_properties_free(p); }
+ };
+
+ struct global
+ {
+ std::string name;
+ std::string description;
+ uint32_t id;
+ uint32_t permissions;
+ std::string type;
+ uint32_t version;
+ std::unique_ptr<pw_properties, PipewirePropertiesDeleter> properties;
+ std::unique_ptr<CPipewireProxy> proxy;
+ };
+
+ std::map<uint32_t, std::unique_ptr<global>>& GetGlobals() { return m_globals; }
+
+private:
+ std::map<uint32_t, std::unique_ptr<global>> m_globals;
+
+ std::unique_ptr<CPipewireThreadLoop> m_loop;
+ std::unique_ptr<CPipewireContext> m_context;
+ std::unique_ptr<CPipewireCore> m_core;
+ std::unique_ptr<CPipewireRegistry> m_registry;
+ std::shared_ptr<CPipewireStream> m_stream;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.cpp
new file mode 100644
index 0000000..7683ede
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireContext.h"
+
+#include "utils/log.h"
+
+#include <stdexcept>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireContext::CPipewireContext(pw_loop* loop)
+{
+ m_context.reset(pw_context_new(loop, nullptr, 0));
+ if (!m_context)
+ {
+ CLog::Log(LOGERROR, "CPipewireContext: failed to create context: {}", strerror(errno));
+ throw std::runtime_error("CPipewireContext: failed to create context");
+ }
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.h
new file mode 100644
index 0000000..eeb880c
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireContext.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <pipewire/context.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireContext
+{
+public:
+ explicit CPipewireContext(pw_loop* loop);
+ CPipewireContext() = delete;
+ ~CPipewireContext() = default;
+
+ pw_context* Get() const { return m_context.get(); }
+
+private:
+ struct PipewireContextDeleter
+ {
+ void operator()(pw_context* p) { pw_context_destroy(p); }
+ };
+
+ std::unique_ptr<pw_context, PipewireContextDeleter> m_context;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.cpp
new file mode 100644
index 0000000..f5230b0
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireCore.h"
+
+#include "cores/AudioEngine/Sinks/pipewire/Pipewire.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h"
+#include "utils/log.h"
+
+#include <stdexcept>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireCore::CPipewireCore(pw_context* context) : m_coreEvents(CreateCoreEvents())
+{
+ m_core.reset(pw_context_connect(context, nullptr, 0));
+ if (!m_core)
+ {
+ CLog::Log(LOGERROR, "CPipewireCore: failed to create core: {}", strerror(errno));
+ throw std::runtime_error("CPipewireCore: failed to create core");
+ }
+}
+
+CPipewireCore::~CPipewireCore()
+{
+ spa_hook_remove(&m_coreListener);
+}
+
+void CPipewireCore::AddListener(void* userdata)
+{
+ pw_core_add_listener(m_core.get(), &m_coreListener, &m_coreEvents, userdata);
+}
+
+void CPipewireCore::Sync()
+{
+ m_sync = pw_core_sync(m_core.get(), 0, m_sync);
+}
+
+void CPipewireCore::OnCoreDone(void* userdata, uint32_t id, int seq)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+ auto core = pipewire->GetCore();
+ auto loop = pipewire->GetThreadLoop();
+
+ if (core->GetSync() == seq)
+ loop->Signal(false);
+}
+
+pw_core_events CPipewireCore::CreateCoreEvents()
+{
+ pw_core_events coreEvents = {};
+ coreEvents.version = PW_VERSION_CORE_EVENTS;
+ coreEvents.done = OnCoreDone;
+
+ return coreEvents;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.h
new file mode 100644
index 0000000..5f78718
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireCore.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <pipewire/core.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireCore
+{
+public:
+ explicit CPipewireCore(pw_context* context);
+ CPipewireCore() = delete;
+ ~CPipewireCore();
+
+ pw_core* Get() const { return m_core.get(); }
+
+ void AddListener(void* userdata);
+ void Sync();
+ int GetSync() const { return m_sync; }
+
+private:
+ static void OnCoreDone(void* userdata, uint32_t id, int seq);
+
+ static pw_core_events CreateCoreEvents();
+
+ const pw_core_events m_coreEvents;
+
+ spa_hook m_coreListener;
+
+ struct PipewireCoreDeleter
+ {
+ void operator()(pw_core* p) { pw_core_disconnect(p); }
+ };
+
+ std::unique_ptr<pw_core, PipewireCoreDeleter> m_core;
+
+ int m_sync;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.cpp
new file mode 100644
index 0000000..66e3576
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireNode.h"
+
+#include "cores/AudioEngine/Sinks/pipewire/Pipewire.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <spa/param/format.h>
+#include <spa/pod/iter.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireNode::CPipewireNode(pw_registry* registry, uint32_t id, const char* type)
+ : CPipewireProxy(registry, id, type, PW_VERSION_NODE), m_nodeEvents(CreateNodeEvents())
+{
+}
+
+CPipewireNode::~CPipewireNode()
+{
+ spa_hook_remove(&m_objectListener);
+}
+
+void CPipewireNode::AddListener(void* userdata)
+{
+ m_pipewire = reinterpret_cast<CPipewire*>(userdata);
+
+ pw_proxy_add_object_listener(m_proxy.get(), &m_objectListener, &m_nodeEvents, this);
+
+ CPipewireProxy::AddListener(userdata);
+}
+
+void CPipewireNode::EnumerateFormats()
+{
+ if (!m_info)
+ return;
+
+ for (uint32_t param = 0; param < m_info->n_params; param++)
+ {
+ if (m_info->params[param].id == SPA_PARAM_EnumFormat)
+ pw_node_enum_params(m_proxy.get(), 0, m_info->params[param].id, 0, 0, NULL);
+ }
+}
+
+void CPipewireNode::Info(void* userdata, const struct pw_node_info* info)
+{
+ auto node = reinterpret_cast<CPipewireNode*>(userdata);
+
+ if (node->m_info)
+ {
+ CLog::Log(LOGDEBUG, "CPipewireNode::{} - node {} changed", __FUNCTION__, info->id);
+ pw_node_info* m_info = node->m_info.get();
+ m_info = pw_node_info_update(m_info, info);
+ }
+ else
+ {
+ node->m_info.reset(pw_node_info_update(node->m_info.get(), info));
+ }
+}
+
+template<typename T>
+static T Parse(uint32_t type, void* body, uint32_t size)
+{
+ switch (type)
+ {
+ case SPA_TYPE_Id:
+ case SPA_TYPE_Int:
+ return *reinterpret_cast<T*>(body);
+
+ default:
+ throw std::runtime_error(StringUtils::Format("unhandled type: {}", type));
+ }
+}
+
+template<typename T>
+static std::set<T> ParseArray(uint32_t type, void* body, uint32_t size)
+{
+ switch (type)
+ {
+ case SPA_TYPE_Id:
+ case SPA_TYPE_Int:
+ {
+ std::set<T> values;
+ values.emplace(Parse<T>(type, body, size));
+
+ return values;
+ }
+ case SPA_TYPE_Array:
+ {
+ auto array = reinterpret_cast<spa_pod_array_body*>(body);
+ void* p;
+ std::set<T> values;
+ SPA_POD_ARRAY_BODY_FOREACH(array, size, p)
+ values.emplace(Parse<T>(array->child.type, p, array->child.size));
+
+ return values;
+ }
+ case SPA_TYPE_Choice:
+ {
+ auto choice = reinterpret_cast<spa_pod_choice_body*>(body);
+ void* p;
+ std::set<T> values;
+ SPA_POD_CHOICE_BODY_FOREACH(choice, size, p)
+ values.emplace(Parse<T>(choice->child.type, p, choice->child.size));
+
+ return values;
+ }
+ default:
+ throw std::runtime_error(StringUtils::Format("unhandled array: {}", type));
+ }
+}
+
+void CPipewireNode::Parse(uint32_t type, void* body, uint32_t size)
+{
+ switch (type)
+ {
+ case SPA_TYPE_Object:
+ {
+ auto object = reinterpret_cast<spa_pod_object_body*>(body);
+
+ switch (object->type)
+ {
+ case SPA_TYPE_OBJECT_Format:
+ {
+ spa_pod_prop* prop;
+ SPA_POD_OBJECT_BODY_FOREACH(object, size, prop)
+ {
+ spa_format format = static_cast<spa_format>(prop->key);
+
+ switch (format)
+ {
+ case SPA_FORMAT_AUDIO_format:
+ {
+ m_formats = ParseArray<spa_audio_format>(
+ prop->value.type, SPA_POD_CONTENTS(spa_pod_prop, prop), prop->value.size);
+ break;
+ }
+ case SPA_FORMAT_AUDIO_rate:
+ {
+ m_rates = ParseArray<uint32_t>(
+ prop->value.type, SPA_POD_CONTENTS(spa_pod_prop, prop), prop->value.size);
+ break;
+ }
+ case SPA_FORMAT_AUDIO_position:
+ {
+ m_channels = ParseArray<spa_audio_channel>(
+ prop->value.type, SPA_POD_CONTENTS(spa_pod_prop, prop), prop->value.size);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ break;
+ }
+ default:
+ return;
+ }
+
+ break;
+ }
+ default:
+ return;
+ }
+}
+
+void CPipewireNode::Param(void* userdata,
+ int seq,
+ uint32_t id,
+ uint32_t index,
+ uint32_t next,
+ const struct spa_pod* param)
+{
+ auto node = reinterpret_cast<CPipewireNode*>(userdata);
+ auto pipewire = node->GetPipewire();
+ auto loop = pipewire->GetThreadLoop();
+
+ node->Parse(SPA_POD_TYPE(param), SPA_POD_BODY(param), SPA_POD_BODY_SIZE(param));
+
+ loop->Signal(false);
+}
+
+pw_node_events CPipewireNode::CreateNodeEvents()
+{
+ pw_node_events nodeEvents = {};
+ nodeEvents.version = PW_VERSION_NODE_EVENTS;
+ nodeEvents.info = Info;
+ nodeEvents.param = Param;
+
+ return nodeEvents;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.h
new file mode 100644
index 0000000..9da85fb
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireNode.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Sinks/pipewire/PipewireProxy.h"
+
+#include <memory>
+#include <set>
+
+#include <pipewire/node.h>
+#include <spa/param/audio/raw.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewire;
+
+class CPipewireNode : public CPipewireProxy
+{
+public:
+ explicit CPipewireNode(pw_registry* registry, uint32_t id, const char* type);
+ CPipewireNode() = delete;
+ ~CPipewireNode() override;
+
+ void AddListener(void* userdata) override;
+
+ void EnumerateFormats();
+
+ pw_node_info* GetInfo() { return m_info.get(); }
+
+ std::set<spa_audio_format>& GetFormats() { return m_formats; }
+ std::set<spa_audio_channel>& GetChannels() { return m_channels; }
+ std::set<uint32_t>& GetRates() { return m_rates; }
+
+ CPipewire* GetPipewire() { return m_pipewire; }
+
+private:
+ void Parse(uint32_t type, void* body, uint32_t size);
+
+ static void Info(void* userdata, const struct pw_node_info* info);
+ static void Param(void* userdata,
+ int seq,
+ uint32_t id,
+ uint32_t index,
+ uint32_t next,
+ const struct spa_pod* param);
+
+ static pw_node_events CreateNodeEvents();
+
+ const pw_node_events m_nodeEvents;
+
+ spa_hook m_objectListener;
+
+ struct PipewireNodeInfoDeleter
+ {
+ void operator()(pw_node_info* p) { pw_node_info_free(p); }
+ };
+
+ std::unique_ptr<pw_node_info, PipewireNodeInfoDeleter> m_info;
+
+ std::set<spa_audio_format> m_formats;
+ std::set<spa_audio_channel> m_channels;
+ std::set<uint32_t> m_rates;
+
+ CPipewire* m_pipewire;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.cpp
new file mode 100644
index 0000000..4d21281
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireProxy.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "utils/log.h"
+
+#include <stdexcept>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireProxy::CPipewireProxy(pw_registry* registry,
+ uint32_t id,
+ const char* type,
+ uint32_t version)
+ : m_proxyEvents(CreateProxyEvents())
+{
+ m_proxy.reset(reinterpret_cast<pw_proxy*>(pw_registry_bind(registry, id, type, version, 0)));
+ if (!m_proxy)
+ {
+ CLog::Log(LOGERROR, "CPipewireProxy: failed to create proxy: {}", strerror(errno));
+ throw std::runtime_error("CPipewireProxy: failed to create proxy");
+ }
+}
+
+CPipewireProxy::~CPipewireProxy()
+{
+ spa_hook_remove(&m_proxyListener);
+}
+
+void CPipewireProxy::AddListener(void* userdata)
+{
+ pw_proxy_add_listener(m_proxy.get(), &m_proxyListener, &m_proxyEvents, nullptr);
+}
+
+void CPipewireProxy::Bound(void* userdata, uint32_t id)
+{
+ CLog::Log(LOGDEBUG, "CPipewireProxy::{} - id={}", __FUNCTION__, id);
+
+ auto AE = CServiceBroker::GetActiveAE();
+ if (AE)
+ AE->DeviceCountChange("PIPEWIRE");
+}
+
+void CPipewireProxy::Removed(void* userdata)
+{
+ CLog::Log(LOGDEBUG, "CPipewireProxy::{}", __FUNCTION__);
+
+ auto AE = CServiceBroker::GetActiveAE();
+ if (AE)
+ AE->DeviceCountChange("PIPEWIRE");
+}
+
+pw_proxy_events CPipewireProxy::CreateProxyEvents()
+{
+ pw_proxy_events proxyEvents = {};
+ proxyEvents.version = PW_VERSION_PROXY_EVENTS;
+ proxyEvents.bound = Bound;
+ proxyEvents.removed = Removed;
+
+ return proxyEvents;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.h
new file mode 100644
index 0000000..e864c10
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireProxy.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <pipewire/core.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireProxy
+{
+public:
+ CPipewireProxy() = delete;
+ virtual ~CPipewireProxy();
+
+ virtual void AddListener(void* userdata);
+
+protected:
+ explicit CPipewireProxy(pw_registry* registry, uint32_t id, const char* type, uint32_t version);
+
+ struct PipewireProxyDeleter
+ {
+ void operator()(pw_proxy* p) { pw_proxy_destroy(p); }
+ };
+
+ std::unique_ptr<pw_proxy, PipewireProxyDeleter> m_proxy;
+
+private:
+ static void Bound(void* userdata, uint32_t id);
+ static void Removed(void* userdata);
+
+ static pw_proxy_events CreateProxyEvents();
+
+ const pw_proxy_events m_proxyEvents;
+
+ spa_hook m_proxyListener;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.cpp
new file mode 100644
index 0000000..04e912d
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireRegistry.h"
+
+#include "cores/AudioEngine/Sinks/pipewire/Pipewire.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireNode.h"
+#include "utils/log.h"
+
+#include <stdexcept>
+
+#include <pipewire/keys.h>
+#include <pipewire/node.h>
+#include <pipewire/type.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireRegistry::CPipewireRegistry(pw_core* core) : m_registryEvents(CreateRegistryEvents())
+{
+ m_registry.reset(pw_core_get_registry(core, PW_VERSION_REGISTRY, 0));
+ if (!m_registry)
+ {
+ CLog::Log(LOGERROR, "CPipewireRegistry: failed to create registry: {}", strerror(errno));
+ throw std::runtime_error("CPipewireRegistry: failed to create registry");
+ }
+}
+
+void CPipewireRegistry::AddListener(void* userdata)
+{
+ pw_registry_add_listener(m_registry.get(), &m_registryListener, &m_registryEvents, userdata);
+}
+
+void CPipewireRegistry::OnGlobalAdded(void* userdata,
+ uint32_t id,
+ uint32_t permissions,
+ const char* type,
+ uint32_t version,
+ const struct spa_dict* props)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+
+ if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
+ {
+ const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (!mediaClass)
+ return;
+
+ if (strcmp(mediaClass, "Audio/Sink") != 0)
+ return;
+
+ const char* name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ if (!name)
+ return;
+
+ const char* desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
+ if (!desc)
+ return;
+
+ auto& globals = pipewire->GetGlobals();
+
+ globals[id] = std::make_unique<CPipewire::global>();
+ globals[id]->name = std::string(name);
+ globals[id]->description = std::string(desc);
+ globals[id]->id = id;
+ globals[id]->permissions = permissions;
+ globals[id]->type = std::string(type);
+ globals[id]->version = version;
+ globals[id]->properties.reset(pw_properties_new_dict(props));
+ globals[id]->proxy = std::make_unique<CPipewireNode>(pipewire->GetRegistry()->Get(), id, type);
+ globals[id]->proxy->AddListener(userdata);
+ }
+}
+
+void CPipewireRegistry::OnGlobalRemoved(void* userdata, uint32_t id)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+ auto& globals = pipewire->GetGlobals();
+
+ auto global = globals.find(id);
+ if (global != globals.end())
+ {
+ CLog::Log(LOGDEBUG, "CPipewireRegistry::{} - id={} type={}", __FUNCTION__, id,
+ global->second->type);
+
+ globals.erase(global);
+ }
+}
+
+pw_registry_events CPipewireRegistry::CreateRegistryEvents()
+{
+ pw_registry_events registryEvents = {};
+ registryEvents.version = PW_VERSION_REGISTRY_EVENTS;
+ registryEvents.global = OnGlobalAdded;
+ registryEvents.global_remove = OnGlobalRemoved;
+
+ return registryEvents;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.h
new file mode 100644
index 0000000..6cbc7dd
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireRegistry.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <pipewire/core.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireRegistry
+{
+public:
+ explicit CPipewireRegistry(pw_core* core);
+ CPipewireRegistry() = delete;
+ ~CPipewireRegistry() = default;
+
+ pw_registry* Get() const { return m_registry.get(); }
+
+ void AddListener(void* userdata);
+
+private:
+ static void OnGlobalAdded(void* userdata,
+ uint32_t id,
+ uint32_t permissions,
+ const char* type,
+ uint32_t version,
+ const struct spa_dict* props);
+ static void OnGlobalRemoved(void* userdata, uint32_t id);
+
+ static pw_registry_events CreateRegistryEvents();
+
+ const pw_registry_events m_registryEvents;
+
+ spa_hook m_registryListener;
+ struct PipewireRegistryDeleter
+ {
+ void operator()(pw_registry* p) { pw_proxy_destroy(reinterpret_cast<pw_proxy*>(p)); }
+ };
+
+ std::unique_ptr<pw_registry, PipewireRegistryDeleter> m_registry;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.cpp
new file mode 100644
index 0000000..10d298f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireStream.h"
+
+#include "cores/AudioEngine/Sinks/pipewire/Pipewire.h"
+#include "cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h"
+#include "utils/log.h"
+
+#include <stdexcept>
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/pod/builder.h>
+#include <spa/utils/result.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireStream::CPipewireStream(pw_core* core) : m_streamEvents(CreateStreamEvents())
+{
+ m_stream.reset(pw_stream_new(core, nullptr, pw_properties_new(nullptr, nullptr)));
+ if (!m_stream)
+ {
+ CLog::Log(LOGERROR, "CPipewireStream: failed to create stream: {}", strerror(errno));
+ throw std::runtime_error("CPipewireStream: failed to create stream");
+ }
+}
+
+CPipewireStream::~CPipewireStream()
+{
+ spa_hook_remove(&m_streamListener);
+}
+
+void CPipewireStream::AddListener(void* userdata)
+{
+ pw_stream_add_listener(m_stream.get(), &m_streamListener, &m_streamEvents, userdata);
+}
+
+bool CPipewireStream::Connect(uint32_t id, spa_audio_info_raw& info)
+{
+ std::array<uint8_t, 1024> buffer;
+ auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
+ auto params = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &info);
+
+ int ret = pw_stream_connect(m_stream.get(), PW_DIRECTION_OUTPUT, id,
+ static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_INACTIVE |
+ PW_STREAM_FLAG_MAP_BUFFERS),
+ const_cast<const spa_pod**>(&params), 1);
+
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CPipewireStream: failed to connect stream: {}", spa_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+pw_stream_state CPipewireStream::GetState()
+{
+ return pw_stream_get_state(m_stream.get(), nullptr);
+}
+
+void CPipewireStream::SetActive(bool active)
+{
+ pw_stream_set_active(m_stream.get(), active);
+}
+
+pw_buffer* CPipewireStream::DequeueBuffer()
+{
+ return pw_stream_dequeue_buffer(m_stream.get());
+}
+
+void CPipewireStream::QueueBuffer(pw_buffer* buffer)
+{
+ pw_stream_queue_buffer(m_stream.get(), buffer);
+}
+
+void CPipewireStream::Flush(bool drain)
+{
+ pw_stream_flush(m_stream.get(), drain);
+}
+
+uint32_t CPipewireStream::GetNodeId()
+{
+ return pw_stream_get_node_id(m_stream.get());
+}
+
+void CPipewireStream::UpdateProperties(spa_dict* dict)
+{
+ pw_stream_update_properties(m_stream.get(), dict);
+}
+
+
+void CPipewireStream::StateChanged(void* userdata,
+ enum pw_stream_state old,
+ enum pw_stream_state state,
+ const char* error)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+ auto loop = pipewire->GetThreadLoop();
+ auto stream = pipewire->GetStream();
+
+ CLog::Log(LOGDEBUG, "CPipewireStream::{} - stream state changed {} -> {}", __FUNCTION__,
+ pw_stream_state_as_string(old), pw_stream_state_as_string(state));
+
+ if (state == PW_STREAM_STATE_STREAMING)
+ CLog::Log(LOGDEBUG, "CPipewireStream::{} - stream node {}", __FUNCTION__, stream->GetNodeId());
+
+ if (state == PW_STREAM_STATE_ERROR)
+ CLog::Log(LOGDEBUG, "CPipewireStream::{} - stream node {} error: {}", __FUNCTION__,
+ stream->GetNodeId(), error);
+
+ loop->Signal(false);
+}
+
+void CPipewireStream::Process(void* userdata)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+ auto loop = pipewire->GetThreadLoop();
+
+ loop->Signal(false);
+}
+
+void CPipewireStream::Drained(void* userdata)
+{
+ auto pipewire = reinterpret_cast<CPipewire*>(userdata);
+ auto loop = pipewire->GetThreadLoop();
+ auto stream = pipewire->GetStream();
+
+ stream->SetActive(false);
+
+ CLog::Log(LOGDEBUG, "CPipewireStream::{}", __FUNCTION__);
+
+ loop->Signal(false);
+}
+
+pw_stream_events CPipewireStream::CreateStreamEvents()
+{
+ pw_stream_events streamEvents = {};
+ streamEvents.version = PW_VERSION_STREAM_EVENTS;
+ streamEvents.state_changed = StateChanged;
+ streamEvents.process = Process;
+ streamEvents.drained = Drained;
+
+ return streamEvents;
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.h
new file mode 100644
index 0000000..695c576
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireStream.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <pipewire/core.h>
+#include <pipewire/stream.h>
+#include <spa/param/audio/raw.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireStream
+{
+public:
+ explicit CPipewireStream(pw_core* core);
+ CPipewireStream() = delete;
+ ~CPipewireStream();
+
+ pw_stream* Get() const { return m_stream.get(); }
+
+ void AddListener(void* userdata);
+ bool Connect(uint32_t id, spa_audio_info_raw& info);
+
+ pw_stream_state GetState();
+ void SetActive(bool active);
+
+ pw_buffer* DequeueBuffer();
+ void QueueBuffer(pw_buffer* buffer);
+
+ void Flush(bool drain);
+
+ uint32_t GetNodeId();
+
+ void UpdateProperties(spa_dict* dict);
+
+private:
+ static void StateChanged(void* userdata,
+ enum pw_stream_state old,
+ enum pw_stream_state state,
+ const char* error);
+ static void Process(void* userdata);
+ static void Drained(void* userdata);
+
+ static pw_stream_events CreateStreamEvents();
+
+ const pw_stream_events m_streamEvents;
+
+ spa_hook m_streamListener;
+
+ struct PipewireStreamDeleter
+ {
+ void operator()(pw_stream* p) { pw_stream_destroy(p); }
+ };
+
+ std::unique_ptr<pw_stream, PipewireStreamDeleter> m_stream;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.cpp b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.cpp
new file mode 100644
index 0000000..8334097
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipewireThreadLoop.h"
+
+#include "utils/log.h"
+
+#include <stdexcept>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+CPipewireThreadLoop::CPipewireThreadLoop()
+{
+ m_mainloop.reset(pw_thread_loop_new("pipewire", nullptr));
+ if (!m_mainloop)
+ {
+ CLog::Log(LOGERROR, "CPipewireThreadLoop: failed to create main loop: {}", strerror(errno));
+ throw std::runtime_error("CPipewireThreadLoop: failed to create main loop");
+ }
+}
+
+bool CPipewireThreadLoop::Start()
+{
+ return pw_thread_loop_start(m_mainloop.get()) == 0;
+}
+
+void CPipewireThreadLoop::Stop()
+{
+ pw_thread_loop_stop(m_mainloop.get());
+}
+
+void CPipewireThreadLoop::Lock()
+{
+ pw_thread_loop_lock(m_mainloop.get());
+}
+
+void CPipewireThreadLoop::Unlock()
+{
+ pw_thread_loop_unlock(m_mainloop.get());
+}
+
+int CPipewireThreadLoop::Wait(std::chrono::nanoseconds timeout)
+{
+ timespec abstime;
+ pw_thread_loop_get_time(m_mainloop.get(), &abstime, timeout.count());
+
+ return pw_thread_loop_timed_wait_full(m_mainloop.get(), &abstime);
+}
+
+void CPipewireThreadLoop::Signal(bool accept)
+{
+ pw_thread_loop_signal(m_mainloop.get(), accept);
+}
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h
new file mode 100644
index 0000000..32c219b
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireThreadLoop.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include <pipewire/thread-loop.h>
+
+namespace AE
+{
+namespace SINK
+{
+namespace PIPEWIRE
+{
+
+class CPipewireThreadLoop
+{
+public:
+ CPipewireThreadLoop();
+ ~CPipewireThreadLoop() = default;
+
+ pw_loop* Get() const { return pw_thread_loop_get_loop(m_mainloop.get()); }
+
+ bool Start();
+ void Stop();
+
+ void Lock();
+ void Unlock();
+
+ int Wait(std::chrono::nanoseconds timeout);
+ void Signal(bool accept);
+
+private:
+ struct PipewireThreadLoopDeleter
+ {
+ void operator()(pw_thread_loop* p) { pw_thread_loop_destroy(p); }
+ };
+
+ std::unique_ptr<pw_thread_loop, PipewireThreadLoopDeleter> m_mainloop;
+};
+
+} // namespace PIPEWIRE
+} // namespace SINK
+} // namespace AE
diff --git a/xbmc/cores/AudioEngine/Sinks/test/CMakeLists.txt b/xbmc/cores/AudioEngine/Sinks/test/CMakeLists.txt
new file mode 100644
index 0000000..48788a2
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/test/CMakeLists.txt
@@ -0,0 +1,7 @@
+if(MACOSX)
+ list(APPEND SOURCES TestAESinkDARWINOSX.cpp)
+endif()
+
+if(SOURCES)
+ core_add_test_library(audioengine_sink_test)
+endif()
diff --git a/xbmc/cores/AudioEngine/Sinks/test/TestAESinkDARWINOSX.cpp b/xbmc/cores/AudioEngine/Sinks/test/TestAESinkDARWINOSX.cpp
new file mode 100644
index 0000000..d4b7adc
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/test/TestAESinkDARWINOSX.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
+#include "cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h"
+#include "cores/AudioEngine/Sinks/osx/CoreAudioHardware.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+std::vector<AudioStreamBasicDescription> stereoFormatsWithPassthrough;
+std::vector<AudioStreamBasicDescription> stereoFormatsWithoutPassthrough;
+std::vector<AudioStreamBasicDescription> allFormatsWithPassthrough;
+std::vector<AudioStreamBasicDescription> allFormatsWithoutPassthrough;
+
+void addLPCMFormats(std::vector<AudioStreamBasicDescription> &streamFormats)
+{
+ AudioStreamBasicDescription streamFormat;
+
+ FillOutASBDForLPCM(streamFormat, 96000, 8, 16, 16, false, false, false);
+ streamFormats.push_back(streamFormat); // + 0
+
+ FillOutASBDForLPCM(streamFormat, 48000, 8, 16, 16, false, false, false);
+ streamFormats.push_back(streamFormat); // + 1
+
+ FillOutASBDForLPCM(streamFormat, 44100, 8, 16, 16, false, false, false);
+ streamFormats.push_back(streamFormat); // + 2
+
+ FillOutASBDForLPCM(streamFormat, 96000, 8, 20, 20, false, false, false);
+ streamFormats.push_back(streamFormat); // + 3
+
+ FillOutASBDForLPCM(streamFormat, 48000, 8, 20, 20, false, false, false);
+ streamFormats.push_back(streamFormat); // + 4
+
+ FillOutASBDForLPCM(streamFormat, 44100, 8, 20, 20, false, false, false);
+ streamFormats.push_back(streamFormat); // + 5
+
+ FillOutASBDForLPCM(streamFormat, 96000, 8, 24, 24, false, false, false);
+ streamFormats.push_back(streamFormat); // + 6
+
+ FillOutASBDForLPCM(streamFormat, 48000, 8, 24, 24, false, false, false);
+ streamFormats.push_back(streamFormat); // + 7
+
+ FillOutASBDForLPCM(streamFormat, 44100, 8, 24, 24, false, false, false);
+ streamFormats.push_back(streamFormat); // + 8
+}
+
+void addPassthroughFormats(std::vector<AudioStreamBasicDescription> &streamFormats)
+{
+ AudioStreamBasicDescription streamFormat;
+
+ FillOutASBDForLPCM(streamFormat, 96000, 2, 16, 16, false, false, false);
+ streamFormat.mFormatID = kAudioFormat60958AC3;
+ streamFormats.push_back(streamFormat); // stereoFormatsWithoutPassthrough.size() + 0
+
+ FillOutASBDForLPCM(streamFormat, 48000, 2, 16, 16, false, false, false);
+ streamFormat.mFormatID = kAudioFormat60958AC3;
+ streamFormats.push_back(streamFormat); // stereoFormatsWithoutPassthrough.size() + 1
+
+ FillOutASBDForLPCM(streamFormat, 44100, 2, 16, 16, false, false, false);
+ streamFormat.mFormatID = kAudioFormat60958AC3;
+ streamFormats.push_back(streamFormat); // stereoFormatsWithoutPassthrough.size() + 2
+}
+
+void initStereoFormatsWithoutPassthrough()
+{
+ AudioStreamBasicDescription streamFormat;
+ FillOutASBDForLPCM(streamFormat, 96000, 2, 16, 16, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 0
+
+ FillOutASBDForLPCM(streamFormat, 48000, 2, 16, 16, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 1
+
+ FillOutASBDForLPCM(streamFormat, 44100, 2, 16, 16, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 2
+
+ FillOutASBDForLPCM(streamFormat, 96000, 2, 20, 20, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 3
+
+ FillOutASBDForLPCM(streamFormat, 48000, 2, 20, 20, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 4
+
+ FillOutASBDForLPCM(streamFormat, 44100, 2, 20, 20, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 5
+
+ FillOutASBDForLPCM(streamFormat, 96000, 2, 24, 24, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 6
+
+ FillOutASBDForLPCM(streamFormat, 48000, 2, 24, 24, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 7
+
+ FillOutASBDForLPCM(streamFormat, 44100, 2, 24, 24, false, false, false);
+ stereoFormatsWithoutPassthrough.push_back(streamFormat); // 8
+}
+
+void initStreamFormats()
+{
+ stereoFormatsWithPassthrough.clear();
+ stereoFormatsWithoutPassthrough.clear();
+ allFormatsWithPassthrough.clear();
+ allFormatsWithoutPassthrough.clear();
+
+ initStereoFormatsWithoutPassthrough();
+
+ stereoFormatsWithPassthrough = stereoFormatsWithoutPassthrough;
+ allFormatsWithoutPassthrough = stereoFormatsWithoutPassthrough;
+
+ addLPCMFormats( allFormatsWithoutPassthrough);
+
+ allFormatsWithPassthrough = allFormatsWithoutPassthrough;
+
+ addPassthroughFormats(stereoFormatsWithPassthrough);
+ addPassthroughFormats(allFormatsWithPassthrough);
+}
+
+
+AEAudioFormat getAC3AEFormat()
+{
+ AEAudioFormat srcFormat;
+ srcFormat.m_dataFormat = AE_FMT_RAW;
+ srcFormat.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ srcFormat.m_sampleRate = 48000;
+ srcFormat.m_channelLayout = AE_CH_LAYOUT_2_0;
+ srcFormat.m_frames = 0;
+ srcFormat.m_frameSize = 0;
+ return srcFormat;
+}
+
+AEAudioFormat getStereo22050AEFormat()
+{
+ AEAudioFormat srcFormat;
+ srcFormat.m_dataFormat = AE_FMT_FLOAT;
+ srcFormat.m_sampleRate = 22050;
+ srcFormat.m_channelLayout = AE_CH_LAYOUT_2_0;
+ srcFormat.m_frames = 0;
+ srcFormat.m_frameSize = 0;
+ return srcFormat;
+}
+
+AEAudioFormat getStereo48000AEFormat()
+{
+ AEAudioFormat srcFormat;
+ srcFormat.m_dataFormat = AE_FMT_FLOAT;
+ srcFormat.m_sampleRate = 48000;
+ srcFormat.m_channelLayout = AE_CH_LAYOUT_2_0;
+ srcFormat.m_frames = 0;
+ srcFormat.m_frameSize = 0;
+ return srcFormat;
+}
+
+AEAudioFormat getLPCM96000AEFormat()
+{
+ AEAudioFormat srcFormat;
+ srcFormat.m_dataFormat = AE_FMT_FLOAT;
+ srcFormat.m_sampleRate = 96000;
+ srcFormat.m_channelLayout = AE_CH_LAYOUT_5_1;
+ srcFormat.m_frames = 0;
+ srcFormat.m_frameSize = 0;
+ return srcFormat;
+}
+
+unsigned int findMatchingFormat(const std::vector<AudioStreamBasicDescription> &formatList, const AEAudioFormat &srcFormat)
+{
+ unsigned int formatIdx = 0;
+ float highestScore = 0;
+ float currentScore = 0;
+ AEDeviceEnumerationOSX devEnum((AudioDeviceID)0);
+
+// fprintf(stderr, "%s: Matching streamFormat for source: %s with samplerate: %d\n", __FUNCTION__, CAEUtil::DataFormatToStr(srcFormat.m_dataFormat), srcFormat.m_sampleRate);
+ for (unsigned int i = 0; i < formatList.size(); i++)
+ {
+ AudioStreamBasicDescription desc = formatList[i];
+ std::string formatString;
+ currentScore = devEnum.ScoreFormat(desc, srcFormat);
+// fprintf(stderr, "%s: Physical Format: %s idx: %d rated %f\n", __FUNCTION__, StreamDescriptionToString(desc, formatString), i, currentScore);
+
+ if (currentScore > highestScore)
+ {
+ formatIdx = i;
+ highestScore = currentScore;
+ }
+ }
+
+ return formatIdx;
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchAc3InStereoWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ //try to match the stream formats for ac3
+ AEAudioFormat srcFormat = getAC3AEFormat();
+ // mach ac3 in streamformats with dedicated passthrough format
+ formatIdx = findMatchingFormat(stereoFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, stereoFormatsWithoutPassthrough.size() + 1);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchAc3InStereoWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ //try to match the stream formats for ac3
+ AEAudioFormat srcFormat = getAC3AEFormat();
+ //match ac3 in streamformats without dedicated passthrough format (bitstream)
+ formatIdx = findMatchingFormat(stereoFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)1);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchAc3InAllWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ //try to match the stream formats for ac3
+ AEAudioFormat srcFormat = getAC3AEFormat();
+ // mach ac3 in streamformats with dedicated passthrough format
+ formatIdx = findMatchingFormat(allFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx,allFormatsWithoutPassthrough.size() + 1);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchAc3InAllWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ //try to match the stream formats for ac3
+ AEAudioFormat srcFormat = getAC3AEFormat();
+ //match ac3 in streamformats without dedicated passthrough format (bitstream)
+ formatIdx = findMatchingFormat(allFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)1);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo22050InStereoWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 22050hz
+ AEAudioFormat srcFormat = getStereo22050AEFormat();
+ formatIdx = findMatchingFormat(stereoFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)8);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo22050InStereoWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 22050hz
+ AEAudioFormat srcFormat = getStereo22050AEFormat();
+
+ formatIdx = findMatchingFormat(stereoFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)8);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo22050InAllWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 22050hz
+ AEAudioFormat srcFormat = getStereo22050AEFormat();
+ formatIdx = findMatchingFormat(allFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)8);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo22050InAllWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 22050hz
+ AEAudioFormat srcFormat = getStereo22050AEFormat();
+
+ formatIdx = findMatchingFormat(allFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)8);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo48000InStereoWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 48000hz
+ AEAudioFormat srcFormat = getStereo48000AEFormat();
+ formatIdx = findMatchingFormat(stereoFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)7);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo48000InStereoWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 48000hz
+ AEAudioFormat srcFormat = getStereo48000AEFormat();
+
+ formatIdx = findMatchingFormat(stereoFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)7);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo48000InAllWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 48000hz
+ AEAudioFormat srcFormat = getStereo48000AEFormat();
+ formatIdx = findMatchingFormat(allFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)7);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloatStereo48000InAllWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match stereo float 48000hz
+ AEAudioFormat srcFormat = getStereo48000AEFormat();
+
+ formatIdx = findMatchingFormat(allFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)7);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloat5_1_96000InStereoWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match lpcm float 96000hz
+ AEAudioFormat srcFormat = getLPCM96000AEFormat();
+ formatIdx = findMatchingFormat(stereoFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)6);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloat5_1_96000InStereoWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match lpcm float 96000hz
+ AEAudioFormat srcFormat = getLPCM96000AEFormat();
+
+ formatIdx = findMatchingFormat(stereoFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)6);
+}
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloat5_1_96000InAllWithPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match lpcm float 96000hz
+ AEAudioFormat srcFormat = getLPCM96000AEFormat();
+ formatIdx = findMatchingFormat(allFormatsWithPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)15);
+}
+
+
+TEST(TestAESinkDARWINOSXScoreStream, MatchFloat5_1_96000InAllWithoutPassthroughFormats)
+{
+ unsigned int formatIdx = 0;
+ initStreamFormats();
+
+ // match lpcm float 96000hz
+ AEAudioFormat srcFormat = getLPCM96000AEFormat();
+
+ formatIdx = findMatchingFormat(allFormatsWithoutPassthrough, srcFormat);
+ EXPECT_EQ(formatIdx, (unsigned int)15);
+}
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp
new file mode 100644
index 0000000..7f6e4f9
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "AESinkFactoryWin.h"
+
+#include "utils/log.h"
+
+#define ERRTOSTR(err) case err: return #err
+
+const char *WASAPIErrToStr(HRESULT err)
+{
+ switch (err)
+ {
+ ERRTOSTR(AUDCLNT_E_NOT_INITIALIZED);
+ ERRTOSTR(AUDCLNT_E_ALREADY_INITIALIZED);
+ ERRTOSTR(AUDCLNT_E_WRONG_ENDPOINT_TYPE);
+ ERRTOSTR(AUDCLNT_E_DEVICE_INVALIDATED);
+ ERRTOSTR(AUDCLNT_E_NOT_STOPPED);
+ ERRTOSTR(AUDCLNT_E_BUFFER_TOO_LARGE);
+ ERRTOSTR(AUDCLNT_E_OUT_OF_ORDER);
+ ERRTOSTR(AUDCLNT_E_UNSUPPORTED_FORMAT);
+ ERRTOSTR(AUDCLNT_E_INVALID_SIZE);
+ ERRTOSTR(AUDCLNT_E_DEVICE_IN_USE);
+ ERRTOSTR(AUDCLNT_E_BUFFER_OPERATION_PENDING);
+ ERRTOSTR(AUDCLNT_E_THREAD_NOT_REGISTERED);
+ ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED);
+ ERRTOSTR(AUDCLNT_E_ENDPOINT_CREATE_FAILED);
+ ERRTOSTR(AUDCLNT_E_SERVICE_NOT_RUNNING);
+ ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED);
+ ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_ONLY);
+ ERRTOSTR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL);
+ ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_SET);
+ ERRTOSTR(AUDCLNT_E_INCORRECT_BUFFER_SIZE);
+ ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_ERROR);
+ ERRTOSTR(AUDCLNT_E_CPUUSAGE_EXCEEDED);
+ ERRTOSTR(AUDCLNT_E_BUFFER_ERROR);
+ ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED);
+ ERRTOSTR(AUDCLNT_E_INVALID_DEVICE_PERIOD);
+ ERRTOSTR(E_POINTER);
+ ERRTOSTR(E_INVALIDARG);
+ ERRTOSTR(E_OUTOFMEMORY);
+ default: break;
+ }
+ return "Undefined";
+}
+
+ void CAESinkFactoryWin::AEChannelsFromSpeakerMask(CAEChannelInfo& channelLayout, DWORD speakers)
+ {
+ channelLayout.Reset();
+
+ for (int i = 0; i < WASAPI_SPEAKER_COUNT; i++)
+ {
+ if (speakers & WASAPIChannelOrder[i])
+ channelLayout += AEChannelNames[i];
+ }
+
+ };
+
+ DWORD CAESinkFactoryWin::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
+ {
+ DWORD mask = 0;
+
+ for (unsigned int i = 0; i < channels.Count(); i++)
+ {
+ for (unsigned int j = 0; j < WASAPI_SPEAKER_COUNT; j++)
+ if (channels[i] == AEChannelNames[j])
+ mask |= WASAPIChannelOrder[j];
+ }
+ return mask;
+ };
+
+ DWORD CAESinkFactoryWin::ChLayoutToChMask(const enum AEChannel * layout, unsigned int * numberOfChannels /*= NULL*/)
+ {
+ if (numberOfChannels)
+ *numberOfChannels = 0;
+ if (!layout)
+ return 0;
+
+ DWORD mask = 0;
+ unsigned int i;
+ for (i = 0; layout[i] != AE_CH_NULL; i++)
+ mask |= WASAPIChannelOrder[layout[i]];
+
+ if (numberOfChannels)
+ *numberOfChannels = i;
+
+ return mask;
+ };
+
+ void CAESinkFactoryWin::BuildWaveFormatExtensible(AEAudioFormat &format, WAVEFORMATEXTENSIBLE &wfxex)
+ {
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+
+ if (format.m_dataFormat != AE_FMT_RAW) // PCM data
+ {
+ wfxex.dwChannelMask = CAESinkFactoryWin::SpeakerMaskFromAEChannels(format.m_channelLayout);
+ wfxex.Format.nChannels = (WORD)format.m_channelLayout.Count();
+ wfxex.Format.nSamplesPerSec = format.m_sampleRate;
+ wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat)format.m_dataFormat);
+ wfxex.SubFormat = format.m_dataFormat < AE_FMT_FLOAT ? KSDATAFORMAT_SUBTYPE_PCM : KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ }
+ else //Raw bitstream
+ {
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ if (format.m_dataFormat == AE_FMT_RAW &&
+ ((format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_AC3) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_CORE) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_2048) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_1024) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTS_512)))
+ {
+ if (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3)
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
+ else
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
+ wfxex.dwChannelMask = bool(format.m_channelLayout.Count() == 2) ? KSAUDIO_SPEAKER_STEREO : KSAUDIO_SPEAKER_5POINT1;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.Format.nChannels = (WORD)format.m_channelLayout.Count();
+ wfxex.Format.nSamplesPerSec = format.m_sampleRate;
+ if (format.m_streamInfo.m_sampleRate == 0)
+ CLog::Log(LOGERROR, "Invalid sample rate supplied for RAW format");
+ }
+ else if (format.m_dataFormat == AE_FMT_RAW &&
+ ((format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD) ||
+ (format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD)))
+ {
+ // IEC 61937 transmissions over HDMI
+ wfxex.Format.nSamplesPerSec = 192000L;
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+
+ switch (format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
+ wfxex.Format.nChannels = 8; // Four IEC 60958 Lines.
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.nChannels = 8; // Four IEC 60958 Lines.
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
+ wfxex.Format.nChannels = 2; // One IEC 60958 Lines.
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ break;
+ }
+
+ if (format.m_channelLayout.Count() == 8)
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ else
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
+ }
+ }
+
+ if (format.m_dataFormat == AE_FMT_S24NE4MSB)
+ wfxex.Samples.wValidBitsPerSample = 24;
+ else
+ wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
+
+ wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
+ wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
+ };
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h
new file mode 100644
index 0000000..dbe53f3
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "utils/log.h"
+
+#include <audioclient.h>
+
+#define WASAPI_SPEAKER_COUNT 21
+
+#ifdef TARGET_WINDOWS_STORE
+#ifndef _WAVEFORMATEXTENSIBLE_IEC61937_
+#define _WAVEFORMATEXTENSIBLE_IEC61937_
+typedef struct _WAVEFORMATEXTENSIBLE_IEC61937
+{
+ WAVEFORMATEXTENSIBLE FormatExt;
+ uint32_t dwEncodedSamplesPerSec;
+ uint32_t dwEncodedChannelCount;
+ uint32_t dwAverageBytesPerSec;
+} WAVEFORMATEXTENSIBLE_IEC61937, *PWAVEFORMATEXTENSIBLE_IEC61937;
+#endif /* _WAVEFORMATEXTENSIBLE_IEC61937_ */
+
+#define STATIC_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS \
+ 0x0000000aL, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+DEFINE_GUIDSTRUCT("0000000a-0cea-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS);
+#define KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS)
+
+#define STATIC_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL\
+ DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_DOLBY_AC3_SPDIF)
+DEFINE_GUIDSTRUCT("00000092-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL);
+#define KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL)
+
+#define STATIC_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP \
+ 0x0000000cL, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+DEFINE_GUIDSTRUCT("0000000c-0cea-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP);
+#define KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP)
+
+#define STATIC_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD \
+ 0x0000000bL, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+DEFINE_GUIDSTRUCT("0000000b-0cea-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD);
+#define KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD)
+
+#define STATIC_KSDATAFORMAT_SUBTYPE_IEC61937_DTS\
+ DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_DTS)
+DEFINE_GUIDSTRUCT("00000008-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEC61937_DTS);
+#define KSDATAFORMAT_SUBTYPE_IEC61937_DTS DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_IEC61937_DTS)
+
+
+#define KSAUDIO_SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
+#define KSAUDIO_SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
+ SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
+ SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \
+ SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define KSAUDIO_SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
+ SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
+ SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
+#endif
+
+static const unsigned int WASAPISampleRateCount = 10;
+static const unsigned int WASAPISampleRates[] = { 384000, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 22050, 11025 };
+
+static const enum AEChannel layoutsList[][16] =
+{
+ /* Most common configurations */
+ {AE_CH_FC, AE_CH_NULL}, // Mono
+ {AE_CH_FL, AE_CH_FR, AE_CH_NULL}, // Stereo
+ {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_NULL}, // 2.1
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_NULL}, // 3 front speakers
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_NULL}, // 3 front speakers + LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_NULL}, // Quad
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL}, // Quad + LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BC, AE_CH_NULL}, // Surround
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BC, AE_CH_LFE, AE_CH_NULL}, // Surround + LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_NULL}, // Standard 5.1
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_NULL}, // 5.1 wide (obsolete)
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_NULL}, // Standard 7.1
+ /* Less common configurations */
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_FLOC,AE_CH_FROC,AE_CH_NULL}, // 7.1 wide (obsolete)
+ /* Exotic configurations */
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_SL, AE_CH_SR, AE_CH_NULL}, // Standard 5.1 w/o LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL}, // 5.1 wide w/o LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_SL, AE_CH_SR, AE_CH_BC, AE_CH_NULL}, // Standard 5.1 w/o LFE + Back Center
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BC, AE_CH_BR, AE_CH_NULL}, // 5.1 wide w/o LFE + Back Center
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_TC, AE_CH_NULL}, // DVD speakers
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_LFE, AE_CH_NULL}, // 5.1 wide + Back Center
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_NULL}, // Standard 7.1 w/o LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_FLOC,AE_CH_FROC,AE_CH_NULL}, // 7.1 wide w/o LFE
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BC, AE_CH_BR, AE_CH_NULL}, // Standard 7.1 + Back Center
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_FLOC,AE_CH_FROC,AE_CH_NULL}, // Standard 7.1 + front wide
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_NULL}, // Standard 7.1 + 2 front top
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_TFC, AE_CH_NULL}, // Standard 7.1 + 3 front top
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_TBL, AE_CH_TBR, AE_CH_NULL}, // Standard 7.1 + 2 front top + 2 back top
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_TFC, AE_CH_TBL, AE_CH_TBR, AE_CH_NULL}, // Standard 7.1 + 3 front top + 2 back top
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_TFC, AE_CH_TBL, AE_CH_TBR, AE_CH_TBC, AE_CH_NULL}, // Standard 7.1 + 3 front top + 3 back top
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_TFL, AE_CH_TFR, AE_CH_TFC, AE_CH_TBL, AE_CH_TBR, AE_CH_TBC, AE_CH_TC, AE_CH_NULL} // Standard 7.1 + 3 front top + 3 back top + Top Center
+};
+
+static enum AEChannel layoutsByChCount[9][9] = {
+ {AE_CH_NULL},
+ {AE_CH_FC, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_LFE, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR, AE_CH_LFE, AE_CH_NULL}};
+
+static const unsigned int WASAPIChannelOrder[] = {AE_CH_RAW,
+ SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, SPEAKER_FRONT_CENTER,
+ SPEAKER_LOW_FREQUENCY, SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT,
+ SPEAKER_FRONT_LEFT_OF_CENTER, SPEAKER_FRONT_RIGHT_OF_CENTER,
+ SPEAKER_BACK_CENTER, SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
+ SPEAKER_TOP_FRONT_LEFT, SPEAKER_TOP_FRONT_RIGHT, SPEAKER_TOP_FRONT_CENTER,
+ SPEAKER_TOP_CENTER, SPEAKER_TOP_BACK_LEFT, SPEAKER_TOP_BACK_RIGHT,
+ SPEAKER_TOP_BACK_CENTER, SPEAKER_RESERVED, SPEAKER_RESERVED};
+
+static const enum AEChannel AEChannelNames[] = {AE_CH_RAW,
+ AE_CH_FL, AE_CH_FR, AE_CH_FC,
+ AE_CH_LFE, AE_CH_BL, AE_CH_BR,
+ AE_CH_FLOC, AE_CH_FROC,
+ AE_CH_BC, AE_CH_SL, AE_CH_SR,
+ AE_CH_TFL, AE_CH_TFR, AE_CH_TFC ,
+ AE_CH_TC , AE_CH_TBL, AE_CH_TBR,
+ AE_CH_TBC, AE_CH_BLOC, AE_CH_BROC};
+
+struct winEndpointsToAEDeviceType
+{
+ std::string winEndpointType;
+ AEDeviceType aeDeviceType;
+};
+
+const winEndpointsToAEDeviceType winEndpoints[] =
+{
+ { "Network Device - ", AE_DEVTYPE_PCM },
+ { "Speakers - ", AE_DEVTYPE_PCM },
+ { "LineLevel - ", AE_DEVTYPE_PCM },
+ { "Headphones - ", AE_DEVTYPE_PCM },
+ { "Microphone - ", AE_DEVTYPE_PCM },
+ { "Headset - ", AE_DEVTYPE_PCM },
+ { "Handset - ", AE_DEVTYPE_PCM },
+ { "Digital Passthrough - ", AE_DEVTYPE_IEC958 },
+ { "SPDIF - ", AE_DEVTYPE_IEC958 },
+ { "HDMI - ", AE_DEVTYPE_HDMI },
+ { "Unknown - ", AE_DEVTYPE_PCM },
+};
+
+struct sampleFormat
+{
+ GUID subFormat;
+ unsigned int bitsPerSample;
+ unsigned int validBitsPerSample;
+ AEDataFormat subFormatType;
+};
+
+//! @todo
+//! Sample formats go from float -> 32 bit int -> 24 bit int (packed in 32) -> -> 24 bit int -> 16 bit int */
+//! versions of Kodi before 14.0 had a bug which made S24NE4MSB the first format selected
+//! this bug worked around some driver bug of some IEC958 devices which report S32 but can't handle it
+//! correctly. So far I have never seen and WASAPI device using S32 and don't think probing S24 before
+//! S32 has any negative impact.
+static const sampleFormat testFormats[] = { {KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 32, 32, AE_FMT_FLOAT},
+ {KSDATAFORMAT_SUBTYPE_PCM, 32, 24, AE_FMT_S24NE4MSB},
+ {KSDATAFORMAT_SUBTYPE_PCM, 32, 32, AE_FMT_S32NE},
+ {KSDATAFORMAT_SUBTYPE_PCM, 24, 24, AE_FMT_S24NE3},
+ {KSDATAFORMAT_SUBTYPE_PCM, 16, 16, AE_FMT_S16NE} };
+
+struct RendererDetail
+{
+ std::string strDeviceId;
+ std::string strDescription;
+ std::string strWinDevType;
+ AEDeviceType eDeviceType;
+ unsigned int nChannels;
+ unsigned int uiChannelMask;
+ bool bDefault;
+};
+
+struct IAEWASAPIDevice
+{
+ std::string deviceId;
+ virtual int Release() = 0;
+ virtual HRESULT Activate(IAudioClient** ppAudioClient) = 0;
+ virtual bool IsUSBDevice() = 0;
+};
+
+class CAESinkFactoryWin
+{
+public:
+ /*
+ Gets list of audio renderers available on platform
+ */
+ static std::vector<RendererDetail> GetRendererDetails();
+ /*
+ Gets default device id
+ */
+ static std::string GetDefaultDeviceId();
+ /*
+ Initialize IAEWASAPIDevice
+ */
+ static HRESULT ActivateWASAPIDevice(std::string &device, IAEWASAPIDevice** ppDevice);
+
+ static void AEChannelsFromSpeakerMask(CAEChannelInfo& channelLayout, DWORD speakers);
+
+ static DWORD SpeakerMaskFromAEChannels(const CAEChannelInfo &channels);
+
+ static DWORD ChLayoutToChMask(const enum AEChannel * layout, unsigned int * numberOfChannels = NULL);
+
+ static void BuildWaveFormatExtensible(AEAudioFormat &format, WAVEFORMATEXTENSIBLE &wfxex);
+};
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp
new file mode 100644
index 0000000..3ef08b1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "AESinkFactoryWin.h"
+#include "utils/log.h"
+
+#include "platform/win10/AsyncHelpers.h"
+#include "platform/win32/CharsetConverter.h"
+
+#include <mmdeviceapi.h>
+#include <mmreg.h>
+#include <winrt/Windows.Devices.Enumeration.h>
+#include <winrt/Windows.Media.Devices.Core.h>
+#include <winrt/Windows.Media.Devices.h>
+
+using namespace winrt::Windows::Devices::Enumeration;
+using namespace winrt::Windows::Media::Devices;
+using namespace winrt::Windows::Media::Devices::Core;
+using namespace Microsoft::WRL;
+
+static winrt::hstring PKEY_Device_FriendlyName = L"System.ItemNameDisplay";
+static winrt::hstring PKEY_AudioEndpoint_FormFactor = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 0";
+static winrt::hstring PKEY_AudioEndpoint_ControlPanelPageProvider = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 1";
+static winrt::hstring PKEY_AudioEndpoint_Association = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 2";
+static winrt::hstring PKEY_AudioEndpoint_PhysicalSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 3";
+static winrt::hstring PKEY_AudioEndpoint_GUID = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 4";
+static winrt::hstring PKEY_AudioEndpoint_Disable_SysFx = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 5";
+static winrt::hstring PKEY_AudioEndpoint_FullRangeSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 6";
+static winrt::hstring PKEY_AudioEndpoint_Supports_EventDriven_Mode = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 7";
+static winrt::hstring PKEY_AudioEndpoint_JackSubType = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 8";
+static winrt::hstring PKEY_AudioEndpoint_Default_VolumeInDb = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 9";
+static winrt::hstring PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
+static winrt::hstring PKEY_Device_EnumeratorName = L"{a45c254e-df1c-4efd-8020-67d146a850e0} 24";
+
+std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
+{
+ std::vector<RendererDetail> list;
+ try
+ {
+ // Get the string identifier of the audio renderer
+ auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
+ auto audioSelector = MediaDevice::GetAudioRenderSelector();
+
+ // Add custom properties to the query
+ DeviceInformationCollection devInfocollection = Wait(DeviceInformation::FindAllAsync(audioSelector,
+ {
+ PKEY_AudioEndpoint_FormFactor,
+ PKEY_AudioEndpoint_GUID,
+ PKEY_AudioEndpoint_PhysicalSpeakers,
+ PKEY_AudioEngine_DeviceFormat,
+ PKEY_Device_EnumeratorName
+ }));
+ if (devInfocollection == nullptr || devInfocollection.Size() == 0)
+ goto failed;
+
+ for (unsigned int i = 0; i < devInfocollection.Size(); i++)
+ {
+ RendererDetail details;
+
+ DeviceInformation devInfo = devInfocollection.GetAt(i);
+ if (devInfo.Properties().Size() == 0)
+ goto failed;
+
+ winrt::IInspectable propObj = nullptr;
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_FormFactor);
+ if (!propObj)
+ goto failed;
+
+ details.strWinDevType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].winEndpointType;
+ details.eDeviceType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].aeDeviceType;
+
+ unsigned long ulChannelMask = 0;
+ unsigned int nChannels = 0;
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEngine_DeviceFormat);
+ if (propObj)
+ {
+ winrt::com_array<uint8_t> com_arr;
+ propObj.as<winrt::IPropertyValue>().GetUInt8Array(com_arr);
+
+ WAVEFORMATEXTENSIBLE* smpwfxex = (WAVEFORMATEXTENSIBLE*)com_arr.data();
+ nChannels = std::max(std::min(smpwfxex->Format.nChannels, (WORD)8), (WORD)2);
+ ulChannelMask = smpwfxex->dwChannelMask;
+ }
+ else
+ {
+ // suppose stereo
+ nChannels = 2;
+ ulChannelMask = 3;
+ }
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_PhysicalSpeakers);
+ details.uiChannelMask = propObj ? propObj.as<winrt::IPropertyValue>().GetUInt32() : ulChannelMask;
+ details.nChannels = nChannels;
+
+ details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str());
+ details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str());
+
+ details.bDefault = (devInfo.Id() == defaultId);
+
+ list.push_back(details);
+ }
+ return list;
+ }
+ catch (...)
+ {
+ }
+
+failed:
+ CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate audio renderer devices.");
+ return list;
+}
+
+class CAudioInterfaceActivator : public winrt::implements<CAudioInterfaceActivator, IActivateAudioInterfaceCompletionHandler>
+{
+ Concurrency::task_completion_event<IAudioClient*> m_ActivateCompleted;
+
+ STDMETHODIMP ActivateCompleted(IActivateAudioInterfaceAsyncOperation *pAsyncOp)
+ {
+ HRESULT hr = S_OK, hr2 = S_OK;
+ ComPtr<IUnknown> clientUnk;
+ IAudioClient* audioClient2;
+
+ // Get the audio activation result as IUnknown pointer
+ hr2 = pAsyncOp->GetActivateResult(&hr, &clientUnk);
+
+ // Report activation failure
+ if (FAILED(hr))
+ {
+ m_ActivateCompleted.set_exception(winrt::hresult_error(hr));
+ goto exit;
+ }
+
+ // Report failure to get activate result
+ if (FAILED(hr2))
+ {
+ m_ActivateCompleted.set_exception(winrt::hresult_error(hr2));
+ goto exit;
+ }
+
+ // Query for the activated IAudioClient2 interface
+ hr = clientUnk->QueryInterface(IID_PPV_ARGS(&audioClient2));
+
+ if (FAILED(hr))
+ {
+ m_ActivateCompleted.set_exception(winrt::hresult_error(hr));
+ goto exit;
+ }
+
+ // Set the completed event and return success
+ m_ActivateCompleted.set(audioClient2);
+
+ exit:
+ return hr;
+
+ }
+public:
+ static Concurrency::task<IAudioClient*> ActivateAsync(LPCWCHAR pszDeviceId)
+ {
+ winrt::com_ptr<CAudioInterfaceActivator> activator = winrt::make_self<CAudioInterfaceActivator>();
+ ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
+
+ HRESULT hr = ActivateAudioInterfaceAsync(
+ pszDeviceId,
+ __uuidof(IAudioClient2),
+ nullptr,
+ activator.get(),
+ &asyncOp);
+
+ if (FAILED(hr))
+ throw winrt::hresult_error(hr);
+
+ // Wait for the activate completed event and return result
+ return Concurrency::create_task(activator->m_ActivateCompleted);
+
+ }
+};
+
+struct AEWASAPIDeviceWin10 : public IAEWASAPIDevice
+{
+ HRESULT AEWASAPIDeviceWin10::Activate(IAudioClient** ppAudioClient)
+ {
+ if (!ppAudioClient)
+ return E_POINTER;
+
+ HRESULT hr;
+ std::wstring deviceIdW = KODI::PLATFORM::WINDOWS::ToW(deviceId);
+ IAudioClient* pClient = CAudioInterfaceActivator::ActivateAsync(deviceIdW.c_str())
+ .then([&hr](Concurrency::task<IAudioClient*> task) -> IAudioClient*
+ {
+ try
+ {
+ return task.get();
+ }
+ catch (const winrt::hresult_error& ex)
+ {
+ hr = ex.code();
+ return nullptr;
+ }
+ }).get();
+
+ if (pClient)
+ {
+ *ppAudioClient = pClient;
+ return S_OK;
+ }
+ return E_UNEXPECTED;
+ };
+
+ int AEWASAPIDeviceWin10::Release() override
+ {
+ delete this;
+ return 0;
+ };
+
+ bool AEWASAPIDeviceWin10::IsUSBDevice() override
+ {
+ // TODO
+ return false;
+ }
+};
+
+std::string CAESinkFactoryWin::GetDefaultDeviceId()
+{
+ // Get the string identifier of the audio renderer
+ auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
+ return KODI::PLATFORM::WINDOWS::FromW(defaultId.c_str());
+}
+
+HRESULT CAESinkFactoryWin::ActivateWASAPIDevice(std::string &device, IAEWASAPIDevice** ppDevice)
+{
+ if (!ppDevice)
+ return E_POINTER;
+
+ AEWASAPIDeviceWin10* pDevice = new AEWASAPIDeviceWin10;
+ pDevice->deviceId = device;
+
+ *ppDevice = pDevice;
+ return S_OK;
+} \ No newline at end of file
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp
new file mode 100644
index 0000000..aef296e
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "AESinkFactoryWin.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/win32/CharsetConverter.h"
+
+#include <algorithm>
+
+#include <mmdeviceapi.h>
+#include <wrl/client.h>
+
+const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+const IID IID_IAudioClient = __uuidof(IAudioClient);
+
+DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
+DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
+
+extern const char *WASAPIErrToStr(HRESULT err);
+#define EXIT_ON_FAILURE(hr, reason) \
+ if (FAILED(hr)) \
+ { \
+ CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
+ goto failed; \
+ }
+
+using namespace Microsoft::WRL;
+
+std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
+{
+ std::vector<RendererDetail> list;
+ ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
+ ComPtr<IMMDevice> pDefaultDevice = nullptr;
+ LPWSTR pwszID = nullptr;
+ std::wstring wstrDDID;
+ HRESULT hr;
+ UINT uiCount = 0;
+
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
+
+
+ // get the default audio endpoint
+ if (pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDefaultDevice.GetAddressOf()) == S_OK)
+ {
+ if (pDefaultDevice->GetId(&pwszID) == S_OK)
+ {
+ wstrDDID = pwszID;
+ CoTaskMemFree(pwszID);
+ }
+ pDefaultDevice.Reset();
+ }
+
+ // enumerate over all audio endpoints
+ hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
+
+ hr = pEnumDevices->GetCount(&uiCount);
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
+
+ for (UINT i = 0; i < uiCount; i++)
+ {
+ RendererDetail details{};
+ ComPtr<IMMDevice> pDevice = nullptr;
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+ PropVariantInit(&varName);
+
+ hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint failed.");
+ goto failed;
+ }
+
+ hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.ReleaseAndGetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint properties failed.");
+ goto failed;
+ }
+
+ hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint device name failed.");
+ goto failed;
+ }
+
+ details.strDescription = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
+ goto failed;
+ }
+
+ details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint form factor failed.");
+ goto failed;
+ }
+ details.strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
+ details.eDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
+
+ PropVariantClear(&varName);
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, &varName);
+ if (FAILED(hr))
+ {
+ CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint speaker layout failed.");
+ goto failed;
+ }
+
+ details.uiChannelMask = std::max(varName.uintVal, (unsigned int)KSAUDIO_SPEAKER_STEREO);
+ PropVariantClear(&varName);
+
+ if (pDevice->GetId(&pwszID) == S_OK)
+ {
+ if (wstrDDID.compare(pwszID) == 0)
+ details.bDefault = true;
+
+ CoTaskMemFree(pwszID);
+ }
+
+ list.push_back(details);
+ }
+
+ return list;
+
+failed:
+
+ CLog::Log(LOGERROR, "Failed to enumerate audio renderer devices.");
+ return list;
+}
+
+struct AEWASAPIDeviceWin32 : public IAEWASAPIDevice
+{
+ friend CAESinkFactoryWin;
+
+ HRESULT AEWASAPIDeviceWin32::Activate(IAudioClient** ppAudioClient)
+ {
+ HRESULT hr = S_FALSE;
+
+ if (!ppAudioClient)
+ return E_POINTER;
+
+ try
+ {
+ ComPtr<IAudioClient> pClient = nullptr;
+ hr = m_pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, reinterpret_cast<void**>(pClient.GetAddressOf()));
+ if (SUCCEEDED(hr) && pClient)
+ {
+ *ppAudioClient = pClient.Detach();
+ return hr;
+ }
+ }
+ catch (...) {}
+ return hr;
+ };
+
+ int AEWASAPIDeviceWin32::Release() override
+ {
+ delete this;
+ return 0;
+ };
+
+ bool AEWASAPIDeviceWin32::IsUSBDevice() override
+ {
+ bool ret = false;
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+ PropVariantInit(&varName);
+
+ HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
+ if (!SUCCEEDED(hr))
+ return ret;
+ hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
+
+ std::string str = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ StringUtils::ToUpper(str);
+ ret = (str == "USB");
+ PropVariantClear(&varName);
+ return ret;
+ }
+
+protected:
+ AEWASAPIDeviceWin32(IMMDevice* pDevice)
+ : m_pDevice(pDevice)
+ {
+ }
+
+private:
+ ComPtr<IMMDevice> m_pDevice{ nullptr };
+};
+
+std::string CAESinkFactoryWin::GetDefaultDeviceId()
+{
+ std::string strDeviceId = "";
+ ComPtr<IMMDevice> pDevice = nullptr;
+ ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ LPWSTR pwszID = NULL;
+ std::wstring wstrDDID;
+
+ HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
+
+ // get the default audio endpoint
+ if (pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDevice.GetAddressOf()) == S_OK)
+ {
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+ PropVariantInit(&varName);
+
+ hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint properties failed.");
+ goto failed;
+ }
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
+ goto failed;
+ }
+ strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ PropVariantClear(&varName);
+
+ }
+
+failed:
+ return strDeviceId;
+}
+
+HRESULT CAESinkFactoryWin::ActivateWASAPIDevice(std::string &device, IAEWASAPIDevice **ppDevice)
+{
+ ComPtr<IMMDevice> pDevice = nullptr;
+ ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
+ ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
+ UINT uiCount = 0;
+
+ if (!ppDevice)
+ return E_POINTER;
+
+ HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
+ EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
+
+ /* Get our device. First try to find the named device. */
+
+ hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
+
+ hr = pEnumDevices->GetCount(&uiCount);
+ EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
+
+ for (UINT i = 0; i < uiCount; i++)
+ {
+ ComPtr<IPropertyStore> pProperty = nullptr;
+ PROPVARIANT varName;
+
+ hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.")
+
+ hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
+ EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint properties failed.")
+
+ hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
+ goto failed;
+ }
+
+ std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+
+ if (device == strDevName)
+ i = uiCount;
+ else
+ pDevice.Reset();
+
+ PropVariantClear(&varName);
+ }
+
+ if (pDevice)
+ {
+ AEWASAPIDeviceWin32* pAEDevice = new AEWASAPIDeviceWin32(pDevice.Get());
+ pAEDevice->deviceId = device;
+ *ppDevice = pAEDevice;
+ return S_OK;
+ }
+
+ return E_FAIL;
+
+failed:
+ CLog::LogF(LOGERROR, "WASAPI initialization failed.");
+ return hr;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEAudioFormat.h b/xbmc/cores/AudioEngine/Utils/AEAudioFormat.h
new file mode 100644
index 0000000..c0638c1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEAudioFormat.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEChannelInfo.h"
+#include "AEStreamInfo.h"
+
+#define AE_IS_PLANAR(x) ((x) >= AE_FMT_U8P && (x) <= AE_FMT_FLOATP)
+
+/**
+ * The audio format structure that fully defines a stream's audio information
+ */
+struct AEAudioFormat
+{
+ /**
+ * The stream's data format (eg, AE_FMT_S16LE)
+ */
+ enum AEDataFormat m_dataFormat;
+
+ /**
+ * The stream's sample rate (eg, 48000)
+ */
+ unsigned int m_sampleRate;
+
+ /**
+ * The stream's channel layout
+ */
+ CAEChannelInfo m_channelLayout;
+
+ /**
+ * The number of frames per period
+ */
+ unsigned int m_frames;
+
+ /**
+ * The size of one frame in bytes
+ */
+ unsigned int m_frameSize;
+
+ /**
+ * Stream info of raw passthrough
+ */
+ CAEStreamInfo m_streamInfo;
+
+ AEAudioFormat()
+ {
+ m_dataFormat = AE_FMT_INVALID;
+ m_sampleRate = 0;
+ m_frames = 0;
+ m_frameSize = 0;
+ }
+
+ bool operator==(const AEAudioFormat& fmt) const
+ {
+ return m_dataFormat == fmt.m_dataFormat &&
+ m_sampleRate == fmt.m_sampleRate &&
+ m_channelLayout == fmt.m_channelLayout &&
+ m_frames == fmt.m_frames &&
+ m_frameSize == fmt.m_frameSize &&
+ m_streamInfo == fmt.m_streamInfo;
+ }
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
new file mode 100644
index 0000000..cf1c515
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEBitstreamPacker.h"
+
+#include "AEPackIEC61937.h"
+#include "AEStreamInfo.h"
+#include "utils/log.h"
+
+#include <array>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+extern "C"
+{
+#include <libavutil/intreadwrite.h>
+}
+
+namespace
+{
+constexpr auto BURST_HEADER_SIZE = 8;
+constexpr auto EAC3_MAX_BURST_PAYLOAD_SIZE = 24576 - BURST_HEADER_SIZE;
+
+constexpr auto MAT_PKT_OFFSET = 61440;
+constexpr auto MAT_FRAME_SIZE = 61424;
+
+/* magic MAT format values, meaning is unknown at this point */
+constexpr std::array<uint8_t, 20> mat_start_code = {
+ 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00,
+ 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0,
+};
+
+constexpr std::array<uint8_t, 12> mat_middle_code = {
+ 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0,
+};
+
+constexpr std::array<uint8_t, 16> mat_end_code = {
+ 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11,
+};
+
+struct MatCode
+{
+ int pos;
+ const uint8_t* code;
+ unsigned int len;
+};
+
+std::array<MatCode, 3> MatCodes = {{
+ {0, mat_start_code.data(), mat_start_code.size()},
+ {30708, mat_middle_code.data(), mat_middle_code.size()},
+ {MAT_FRAME_SIZE - mat_end_code.size(), mat_end_code.data(), mat_end_code.size()},
+}};
+
+} // unnamed namespace
+
+CAEBitstreamPacker::CAEBitstreamPacker()
+{
+ Reset();
+}
+
+CAEBitstreamPacker::~CAEBitstreamPacker()
+{
+}
+
+void CAEBitstreamPacker::Pack(CAEStreamInfo &info, uint8_t* data, int size)
+{
+ m_pauseDuration = 0;
+ switch (info.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ PackTrueHD(info, data, size);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ PackDTSHD (info, data, size);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ m_dataSize = CAEPackIEC61937::PackAC3(data, size, m_packedBuffer);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ PackEAC3 (info, data, size);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ m_dataSize = CAEPackIEC61937::PackDTS_512(data, size, m_packedBuffer, info.m_dataIsLE);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ m_dataSize = CAEPackIEC61937::PackDTS_1024(data, size, m_packedBuffer, info.m_dataIsLE);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ m_dataSize = CAEPackIEC61937::PackDTS_2048(data, size, m_packedBuffer, info.m_dataIsLE);
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "CAEBitstreamPacker::Pack - no pack function");
+ }
+}
+
+bool CAEBitstreamPacker::PackPause(CAEStreamInfo &info, unsigned int millis, bool iecBursts)
+{
+ // re-use last buffer
+ if (m_pauseDuration == millis)
+ return false;
+
+ switch (info.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ m_dataSize = CAEPackIEC61937::PackPause(m_packedBuffer, millis, GetOutputChannelMap(info).Count() * 2, GetOutputRate(info), 4, info.m_sampleRate);
+ m_pauseDuration = millis;
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ m_dataSize = CAEPackIEC61937::PackPause(m_packedBuffer, millis, GetOutputChannelMap(info).Count() * 2, GetOutputRate(info), 3, info.m_sampleRate);
+ m_pauseDuration = millis;
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "CAEBitstreamPacker::Pack - no pack function");
+ }
+
+ if (!iecBursts)
+ {
+ memset(m_packedBuffer, 0, m_dataSize);
+ }
+
+ return true;
+}
+
+unsigned int CAEBitstreamPacker::GetSize() const
+{
+ return m_dataSize;
+}
+
+uint8_t* CAEBitstreamPacker::GetBuffer()
+{
+ return m_packedBuffer;
+}
+
+void CAEBitstreamPacker::Reset()
+{
+ m_dataSize = 0;
+ m_pauseDuration = 0;
+ m_packedBuffer[0] = 0;
+}
+
+/* we need to pack 24 TrueHD audio units into the unknown MAT format before packing into IEC61937 */
+void CAEBitstreamPacker::PackTrueHD(CAEStreamInfo &info, uint8_t* data, int size)
+{
+ /* create the buffer if it doesn't already exist */
+ if (m_trueHD[0].empty())
+ {
+ m_trueHD[0].resize(MAT_FRAME_SIZE);
+ m_trueHD[1].resize(MAT_FRAME_SIZE);
+ m_thd = {};
+ }
+
+ if (size < 10)
+ return;
+
+ uint8_t* pBuf = m_trueHD[m_thd.bufferIndex].data();
+ const uint8_t* pData = data;
+
+ int totalFrameSize = size;
+ int dataRem = size;
+ int paddingRem = 0;
+ int ratebits = 0;
+ int nextCodeIdx = 0;
+ uint16_t inputTiming = 0;
+ bool havePacket = false;
+
+ if (AV_RB24(data + 4) == 0xf8726f)
+ {
+ /* major sync unit, fetch sample rate */
+ if (data[7] == 0xba)
+ ratebits = data[8] >> 4;
+ else if (data[7] == 0xbb)
+ ratebits = data[9] >> 4;
+ else
+ return;
+
+ m_thd.samplesPerFrame = 40 << (ratebits & 3);
+ }
+
+ if (!m_thd.samplesPerFrame)
+ return;
+
+ inputTiming = AV_RB16(data + 2);
+
+ if (m_thd.prevFrameSize)
+ {
+ uint16_t deltaSamples = inputTiming - m_thd.prevFrameTime;
+ /*
+ * One multiple-of-48kHz frame is 1/1200 sec and the IEC 61937 rate
+ * is 768kHz = 768000*4 bytes/sec.
+ * The nominal space per frame is therefore
+ * (768000*4 bytes/sec) * (1/1200 sec) = 2560 bytes.
+ * For multiple-of-44.1kHz frames: 1/1102.5 sec, 705.6kHz, 2560 bytes.
+ *
+ * 2560 is divisible by samplesPerFrame.
+ */
+ int deltaBytes = deltaSamples * 2560 / m_thd.samplesPerFrame;
+
+ /* padding needed before this frame */
+ paddingRem = deltaBytes - m_thd.prevFrameSize;
+
+ // detects stream discontinuities
+ if (paddingRem < 0 || paddingRem >= MAT_FRAME_SIZE * 2)
+ {
+ m_thd = {}; // recovering after seek
+ return;
+ }
+ }
+
+ for (nextCodeIdx = 0; nextCodeIdx < static_cast<int>(MatCodes.size()); nextCodeIdx++)
+ if (m_thd.bufferFilled <= MatCodes[nextCodeIdx].pos)
+ break;
+
+ if (nextCodeIdx >= static_cast<int>(MatCodes.size()))
+ return;
+
+ while (paddingRem || dataRem || MatCodes[nextCodeIdx].pos == m_thd.bufferFilled)
+ {
+ if (MatCodes[nextCodeIdx].pos == m_thd.bufferFilled)
+ {
+ /* time to insert MAT code */
+ int codeLen = MatCodes[nextCodeIdx].len;
+ int codeLenRemaining = codeLen;
+ memcpy(pBuf + MatCodes[nextCodeIdx].pos, MatCodes[nextCodeIdx].code, codeLen);
+ m_thd.bufferFilled += codeLen;
+
+ nextCodeIdx++;
+ if (nextCodeIdx == static_cast<int>(MatCodes.size()))
+ {
+ nextCodeIdx = 0;
+
+ /* this was the last code, move to the next MAT frame */
+ havePacket = true;
+ m_thd.outputBuffer = pBuf;
+ m_thd.bufferIndex ^= 1;
+ pBuf = m_trueHD[m_thd.bufferIndex].data();
+ m_thd.bufferFilled = 0;
+
+ /* inter-frame gap has to be counted as well, add it */
+ codeLenRemaining += MAT_PKT_OFFSET - MAT_FRAME_SIZE;
+ }
+
+ if (paddingRem)
+ {
+ /* consider the MAT code as padding */
+ const int countedAsPadding = std::min(paddingRem, codeLenRemaining);
+ paddingRem -= countedAsPadding;
+ codeLenRemaining -= countedAsPadding;
+ }
+ /* count the remainder of the code as part of frame size */
+ if (codeLenRemaining)
+ totalFrameSize += codeLenRemaining;
+ }
+
+ if (paddingRem)
+ {
+ const int paddingLen = std::min(MatCodes[nextCodeIdx].pos - m_thd.bufferFilled, paddingRem);
+
+ memset(pBuf + m_thd.bufferFilled, 0, paddingLen);
+ m_thd.bufferFilled += paddingLen;
+ paddingRem -= paddingLen;
+
+ if (paddingRem)
+ continue; /* time to insert MAT code */
+ }
+
+ if (dataRem)
+ {
+ const int dataLen = std::min(MatCodes[nextCodeIdx].pos - m_thd.bufferFilled, dataRem);
+
+ memcpy(pBuf + m_thd.bufferFilled, pData, dataLen);
+ m_thd.bufferFilled += dataLen;
+ pData += dataLen;
+ dataRem -= dataLen;
+ }
+ }
+
+ m_thd.prevFrameSize = totalFrameSize;
+ m_thd.prevFrameTime = inputTiming;
+
+ if (!havePacket)
+ return;
+
+ m_dataSize = CAEPackIEC61937::PackTrueHD(m_thd.outputBuffer, MAT_FRAME_SIZE, m_packedBuffer);
+}
+
+void CAEBitstreamPacker::PackDTSHD(CAEStreamInfo &info, uint8_t* data, int size)
+{
+ static const uint8_t dtshd_start_code[10] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe };
+ unsigned int dataSize = sizeof(dtshd_start_code) + 2 + size;
+
+ if (dataSize > m_dtsHDSize)
+ {
+ m_dtsHDSize = dataSize;
+ m_dtsHD.resize(dataSize);
+ memcpy(m_dtsHD.data(), dtshd_start_code, sizeof(dtshd_start_code));
+ }
+
+ m_dtsHD[sizeof(dtshd_start_code) + 0] = ((uint16_t)size & 0xFF00) >> 8;
+ m_dtsHD[sizeof(dtshd_start_code) + 1] = ((uint16_t)size & 0x00FF);
+ memcpy(m_dtsHD.data() + sizeof(dtshd_start_code) + 2, data, size);
+
+ m_dataSize =
+ CAEPackIEC61937::PackDTSHD(m_dtsHD.data(), dataSize, m_packedBuffer, info.m_dtsPeriod);
+}
+
+void CAEBitstreamPacker::PackEAC3(CAEStreamInfo &info, uint8_t* data, int size)
+{
+ unsigned int framesPerBurst = info.m_repeat;
+
+ if (m_eac3FramesPerBurst != framesPerBurst)
+ {
+ /* switched streams, discard partial burst */
+ m_eac3Size = 0;
+ m_eac3FramesPerBurst = framesPerBurst;
+ }
+
+ if (m_eac3FramesPerBurst == 1)
+ {
+ /* simple case, just pass through */
+ m_dataSize = CAEPackIEC61937::PackEAC3(data, size, m_packedBuffer);
+ }
+ else
+ {
+ /* multiple frames needed to achieve 6 blocks as required by IEC 61937-3:2007 */
+
+ if (m_eac3.size() == 0)
+ m_eac3.resize(EAC3_MAX_BURST_PAYLOAD_SIZE);
+
+ unsigned int newsize = m_eac3Size + size;
+ bool overrun = newsize > EAC3_MAX_BURST_PAYLOAD_SIZE;
+
+ if (!overrun)
+ {
+ memcpy(m_eac3.data() + m_eac3Size, data, size);
+ m_eac3Size = newsize;
+ m_eac3FramesCount++;
+ }
+
+ if (m_eac3FramesCount >= m_eac3FramesPerBurst || overrun)
+ {
+ m_dataSize = CAEPackIEC61937::PackEAC3(m_eac3.data(), m_eac3Size, m_packedBuffer);
+ m_eac3Size = 0;
+ m_eac3FramesCount = 0;
+ }
+ }
+}
+
+unsigned int CAEBitstreamPacker::GetOutputRate(const CAEStreamInfo& info)
+{
+ unsigned int rate;
+ switch (info.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ rate = info.m_sampleRate;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ rate = info.m_sampleRate * 4;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ if (info.m_sampleRate == 48000 ||
+ info.m_sampleRate == 96000 ||
+ info.m_sampleRate == 192000)
+ rate = 192000;
+ else
+ rate = 176400;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ rate = info.m_sampleRate;
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ rate = 192000;
+ break;
+ default:
+ rate = 48000;
+ break;
+ }
+ return rate;
+}
+
+CAEChannelInfo CAEBitstreamPacker::GetOutputChannelMap(const CAEStreamInfo& info)
+{
+ int channels = 2;
+ switch (info.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ channels = 2;
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ channels = 8;
+ break;
+
+ default:
+ break;
+ }
+
+ CAEChannelInfo channelMap;
+ for (int i=0; i<channels; i++)
+ {
+ channelMap += AE_CH_RAW;
+ }
+
+ return channelMap;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h
new file mode 100644
index 0000000..1497c03
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEChannelInfo.h"
+#include "AEPackIEC61937.h"
+
+#include <list>
+#include <stdint.h>
+#include <vector>
+
+class CAEStreamInfo;
+
+class CAEBitstreamPacker
+{
+public:
+ CAEBitstreamPacker();
+ ~CAEBitstreamPacker();
+
+ void Pack(CAEStreamInfo &info, uint8_t* data, int size);
+ bool PackPause(CAEStreamInfo &info, unsigned int millis, bool iecBursts);
+ void Reset();
+ uint8_t* GetBuffer();
+ unsigned int GetSize() const;
+ static unsigned int GetOutputRate(const CAEStreamInfo& info);
+ static CAEChannelInfo GetOutputChannelMap(const CAEStreamInfo& info);
+
+private:
+ void PackTrueHD(CAEStreamInfo &info, uint8_t* data, int size);
+ void PackDTSHD(CAEStreamInfo &info, uint8_t* data, int size);
+ void PackEAC3(CAEStreamInfo &info, uint8_t* data, int size);
+
+ /* we keep the trueHD and dtsHD buffers separate so that we can handle a fast stream switch */
+ std::vector<uint8_t> m_trueHD[2];
+
+ struct TrueHD
+ {
+ int prevFrameSize;
+ int samplesPerFrame;
+ int bufferFilled;
+ int bufferIndex;
+ uint16_t prevFrameTime;
+ uint8_t* outputBuffer;
+ };
+
+ TrueHD m_thd{};
+
+ std::vector<uint8_t> m_dtsHD;
+ unsigned int m_dtsHDSize = 0;
+
+ std::vector<uint8_t> m_eac3;
+ unsigned int m_eac3Size = 0;
+ unsigned int m_eac3FramesCount = 0;
+ unsigned int m_eac3FramesPerBurst = 0;
+
+ unsigned int m_dataSize = 0;
+ uint8_t m_packedBuffer[MAX_IEC61937_PACKET];
+ unsigned int m_pauseDuration = 0;
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AEChannelData.h b/xbmc/cores/AudioEngine/Utils/AEChannelData.h
new file mode 100644
index 0000000..71010bb
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEChannelData.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/**
+ * The possible channels
+ */
+enum AEChannel
+{
+ AE_CH_NULL = -1,
+ AE_CH_RAW ,
+
+ AE_CH_FL , AE_CH_FR , AE_CH_FC , AE_CH_LFE, AE_CH_BL , AE_CH_BR , AE_CH_FLOC,
+ AE_CH_FROC, AE_CH_BC , AE_CH_SL , AE_CH_SR , AE_CH_TFL , AE_CH_TFR , AE_CH_TFC ,
+ AE_CH_TC , AE_CH_TBL, AE_CH_TBR, AE_CH_TBC, AE_CH_BLOC, AE_CH_BROC,
+
+ /* p16v devices */
+ AE_CH_UNKNOWN1 , AE_CH_UNKNOWN2 , AE_CH_UNKNOWN3 , AE_CH_UNKNOWN4 ,
+ AE_CH_UNKNOWN5 , AE_CH_UNKNOWN6 , AE_CH_UNKNOWN7 , AE_CH_UNKNOWN8 ,
+ AE_CH_UNKNOWN9 , AE_CH_UNKNOWN10, AE_CH_UNKNOWN11, AE_CH_UNKNOWN12,
+ AE_CH_UNKNOWN13, AE_CH_UNKNOWN14, AE_CH_UNKNOWN15, AE_CH_UNKNOWN16,
+ AE_CH_UNKNOWN17, AE_CH_UNKNOWN18, AE_CH_UNKNOWN19, AE_CH_UNKNOWN20,
+ AE_CH_UNKNOWN21, AE_CH_UNKNOWN22, AE_CH_UNKNOWN23, AE_CH_UNKNOWN24,
+ AE_CH_UNKNOWN25, AE_CH_UNKNOWN26, AE_CH_UNKNOWN27, AE_CH_UNKNOWN28,
+ AE_CH_UNKNOWN29, AE_CH_UNKNOWN30, AE_CH_UNKNOWN31, AE_CH_UNKNOWN32,
+ AE_CH_UNKNOWN33, AE_CH_UNKNOWN34, AE_CH_UNKNOWN35, AE_CH_UNKNOWN36,
+ AE_CH_UNKNOWN37, AE_CH_UNKNOWN38, AE_CH_UNKNOWN39, AE_CH_UNKNOWN40,
+ AE_CH_UNKNOWN41, AE_CH_UNKNOWN42, AE_CH_UNKNOWN43, AE_CH_UNKNOWN44,
+ AE_CH_UNKNOWN45, AE_CH_UNKNOWN46, AE_CH_UNKNOWN47, AE_CH_UNKNOWN48,
+ AE_CH_UNKNOWN49, AE_CH_UNKNOWN50, AE_CH_UNKNOWN51, AE_CH_UNKNOWN52,
+ AE_CH_UNKNOWN53, AE_CH_UNKNOWN54, AE_CH_UNKNOWN55, AE_CH_UNKNOWN56,
+ AE_CH_UNKNOWN57, AE_CH_UNKNOWN58, AE_CH_UNKNOWN59, AE_CH_UNKNOWN60,
+ AE_CH_UNKNOWN61, AE_CH_UNKNOWN62, AE_CH_UNKNOWN63, AE_CH_UNKNOWN64,
+
+ AE_CH_MAX
+};
+
+/**
+ * Standard channel layouts
+ */
+enum AEStdChLayout
+{
+ AE_CH_LAYOUT_INVALID = -1,
+
+ AE_CH_LAYOUT_1_0 = 0,
+ AE_CH_LAYOUT_2_0,
+ AE_CH_LAYOUT_2_1,
+ AE_CH_LAYOUT_3_0,
+ AE_CH_LAYOUT_3_1,
+ AE_CH_LAYOUT_4_0,
+ AE_CH_LAYOUT_4_1,
+ AE_CH_LAYOUT_5_0,
+ AE_CH_LAYOUT_5_1,
+ AE_CH_LAYOUT_7_0,
+ AE_CH_LAYOUT_7_1,
+
+ AE_CH_LAYOUT_MAX
+};
+
+/**
+ * The various data formats
+ * LE = Little Endian, BE = Big Endian, NE = Native Endian
+ * @note This is ordered from the worst to best preferred formats
+ */
+enum AEDataFormat
+{
+ AE_FMT_INVALID = -1,
+
+ AE_FMT_U8,
+
+ AE_FMT_S16BE,
+ AE_FMT_S16LE,
+ AE_FMT_S16NE,
+
+ AE_FMT_S32BE,
+ AE_FMT_S32LE,
+ AE_FMT_S32NE,
+
+ AE_FMT_S24BE4,
+ AE_FMT_S24LE4,
+ AE_FMT_S24NE4, // 24 bits in lower 3 bytes
+ AE_FMT_S24NE4MSB, // S32 with bits_per_sample < 32
+
+ AE_FMT_S24BE3,
+ AE_FMT_S24LE3,
+ AE_FMT_S24NE3, /* S24 in 3 bytes */
+
+ AE_FMT_DOUBLE,
+ AE_FMT_FLOAT,
+
+ // Bitstream
+ AE_FMT_RAW,
+
+ /* planar formats */
+ AE_FMT_U8P,
+ AE_FMT_S16NEP,
+ AE_FMT_S32NEP,
+ AE_FMT_S24NE4P,
+ AE_FMT_S24NE4MSBP,
+ AE_FMT_S24NE3P,
+ AE_FMT_DOUBLEP,
+ AE_FMT_FLOATP,
+
+ AE_FMT_MAX
+};
diff --git a/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp
new file mode 100644
index 0000000..e3d74ca
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEChannelInfo.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <limits>
+#include <string.h>
+
+CAEChannelInfo::CAEChannelInfo()
+{
+ Reset();
+}
+
+CAEChannelInfo::CAEChannelInfo(const enum AEChannel* rhs)
+{
+ *this = rhs;
+}
+
+CAEChannelInfo::CAEChannelInfo(const AEStdChLayout rhs)
+{
+ *this = rhs;
+}
+
+void CAEChannelInfo::ResolveChannels(const CAEChannelInfo& rhs)
+{
+ /* mono gets upmixed to dual mono */
+ if (m_channelCount == 1 && m_channels[0] == AE_CH_FC)
+ {
+ Reset();
+ *this += AE_CH_FL;
+ *this += AE_CH_FR;
+ return;
+ }
+
+ bool srcHasSL = false;
+ bool srcHasSR = false;
+ bool srcHasRL = false;
+ bool srcHasRR = false;
+ bool srcHasBC = false;
+
+ bool dstHasSL = false;
+ bool dstHasSR = false;
+ bool dstHasRL = false;
+ bool dstHasRR = false;
+ bool dstHasBC = false;
+
+ for (unsigned int c = 0; c < rhs.m_channelCount; ++c)
+ switch(rhs.m_channels[c])
+ {
+ case AE_CH_SL: dstHasSL = true; break;
+ case AE_CH_SR: dstHasSR = true; break;
+ case AE_CH_BL: dstHasRL = true; break;
+ case AE_CH_BR: dstHasRR = true; break;
+ case AE_CH_BC: dstHasBC = true; break;
+ default:
+ break;
+ }
+
+ CAEChannelInfo newInfo;
+ for (unsigned int i = 0; i < m_channelCount; ++i)
+ {
+ switch (m_channels[i])
+ {
+ case AE_CH_SL: srcHasSL = true; break;
+ case AE_CH_SR: srcHasSR = true; break;
+ case AE_CH_BL: srcHasRL = true; break;
+ case AE_CH_BR: srcHasRR = true; break;
+ case AE_CH_BC: srcHasBC = true; break;
+ default:
+ break;
+ }
+
+ bool found = false;
+ for (unsigned int c = 0; c < rhs.m_channelCount; ++c)
+ if (m_channels[i] == rhs.m_channels[c])
+ {
+ found = true;
+ break;
+ }
+
+ if (found)
+ newInfo += m_channels[i];
+ }
+
+ /* we need to ensure we end up with rear or side channels for downmix to work */
+ if (srcHasSL && !dstHasSL && dstHasRL && !newInfo.HasChannel(AE_CH_BL))
+ newInfo += AE_CH_BL;
+ if (srcHasSR && !dstHasSR && dstHasRR && !newInfo.HasChannel(AE_CH_BR))
+ newInfo += AE_CH_BR;
+ if (srcHasRL && !dstHasRL && dstHasSL && !newInfo.HasChannel(AE_CH_SL))
+ newInfo += AE_CH_SL;
+ if (srcHasRR && !dstHasRR && dstHasSR && !newInfo.HasChannel(AE_CH_SR))
+ newInfo += AE_CH_SR;
+
+ // mix back center if not available in destination layout
+ // prefer mixing into backs if available
+ if (srcHasBC && !dstHasBC)
+ {
+ if (dstHasRL && !newInfo.HasChannel(AE_CH_BL))
+ newInfo += AE_CH_BL;
+ else if (dstHasSL && !newInfo.HasChannel(AE_CH_SL))
+ newInfo += AE_CH_SL;
+
+ if (dstHasRR && !newInfo.HasChannel(AE_CH_BR))
+ newInfo += AE_CH_BR;
+ else if (dstHasSR && !newInfo.HasChannel(AE_CH_SR))
+ newInfo += AE_CH_SR;
+ }
+
+ *this = newInfo;
+}
+
+void CAEChannelInfo::Reset()
+{
+ m_channelCount = 0;
+ for (AEChannel& channel : m_channels)
+ channel = AE_CH_NULL;
+}
+
+CAEChannelInfo& CAEChannelInfo::operator=(const CAEChannelInfo& rhs)
+{
+ if (this == &rhs)
+ return *this;
+
+ /* clone the information */
+ m_channelCount = rhs.m_channelCount;
+ memcpy(m_channels, rhs.m_channels, sizeof(enum AEChannel) * rhs.m_channelCount);
+
+ return *this;
+}
+
+CAEChannelInfo& CAEChannelInfo::operator=(const enum AEChannel* rhs)
+{
+ Reset();
+ if (rhs == NULL)
+ return *this;
+
+ while (m_channelCount < AE_CH_MAX && rhs[m_channelCount] != AE_CH_NULL)
+ {
+ m_channels[m_channelCount] = rhs[m_channelCount];
+ ++m_channelCount;
+ }
+
+ /* the last entry should be NULL, if not we were passed a non null terminated list */
+ assert(rhs[m_channelCount] == AE_CH_NULL);
+
+ return *this;
+}
+
+CAEChannelInfo& CAEChannelInfo::operator=(const enum AEStdChLayout rhs)
+{
+ assert(rhs > AE_CH_LAYOUT_INVALID && rhs < AE_CH_LAYOUT_MAX);
+
+ static constexpr enum AEChannel layouts[AE_CH_LAYOUT_MAX][9] = {
+ {AE_CH_FC, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR, AE_CH_NULL},
+ {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR,
+ AE_CH_NULL}};
+
+ *this = layouts[rhs];
+ return *this;
+}
+
+bool CAEChannelInfo::operator==(const CAEChannelInfo& rhs) const
+{
+ /* if the channel count doesn't match, no need to check further */
+ if (m_channelCount != rhs.m_channelCount)
+ return false;
+
+ /* make sure the channel order is the same */
+ for (unsigned int i = 0; i < m_channelCount; ++i)
+ if (m_channels[i] != rhs.m_channels[i])
+ return false;
+
+ return true;
+}
+
+bool CAEChannelInfo::operator!=(const CAEChannelInfo& rhs) const
+{
+ return !(*this == rhs);
+}
+
+CAEChannelInfo& CAEChannelInfo::operator+=(const enum AEChannel& rhs)
+{
+ assert(m_channelCount < AE_CH_MAX);
+ assert(rhs > AE_CH_NULL && rhs < AE_CH_MAX);
+
+ m_channels[m_channelCount++] = rhs;
+ return *this;
+}
+
+CAEChannelInfo& CAEChannelInfo::operator-=(const enum AEChannel& rhs)
+{
+ assert(rhs > AE_CH_NULL && rhs < AE_CH_MAX);
+
+ unsigned int i = 0;
+ while(i < m_channelCount && m_channels[i] != rhs)
+ i++;
+ if (i >= m_channelCount)
+ return *this; // Channel not found
+
+ for(; i < m_channelCount-1; i++)
+ m_channels[i] = m_channels[i+1];
+
+ m_channels[i] = AE_CH_NULL;
+ m_channelCount--;
+ return *this;
+}
+
+enum AEChannel CAEChannelInfo::operator[](unsigned int i) const
+{
+ assert(i < m_channelCount);
+ return m_channels[i];
+}
+
+CAEChannelInfo::operator std::string() const
+{
+ if (m_channelCount == 0)
+ return "NULL";
+
+ std::string s;
+ for (unsigned int i = 0; i < m_channelCount - 1; ++i)
+ {
+ s.append(GetChName(m_channels[i]));
+ s.append(", ");
+ }
+ s.append(GetChName(m_channels[m_channelCount-1]));
+
+ return s;
+}
+
+bool CAEChannelInfo::IsChannelValid(const unsigned int pos)
+{
+ assert(pos < m_channelCount);
+ bool isValid = false;
+ if (m_channels[pos] > AE_CH_NULL && m_channels[pos] < AE_CH_UNKNOWN1)
+ isValid = true;
+
+ return isValid;
+}
+
+bool CAEChannelInfo::IsLayoutValid()
+{
+ if (m_channelCount == 0)
+ return false;
+
+ for (unsigned int i = 0; i < m_channelCount; ++i)
+ {
+ // we need at least one valid channel
+ if (IsChannelValid(i))
+ return true;
+ }
+ return false;
+}
+
+const char* CAEChannelInfo::GetChName(const enum AEChannel ch)
+{
+ assert(ch >= 0 && ch < AE_CH_MAX);
+
+ static const char* channels[AE_CH_MAX] =
+ {
+ "RAW" ,
+ "FL" , "FR" , "FC" , "LFE", "BL" , "BR" , "FLOC",
+ "FROC", "BC" , "SL" , "SR" , "TFL" , "TFR" , "TFC" ,
+ "TC" , "TBL", "TBR", "TBC", "BLOC", "BROC",
+
+ /* p16v devices */
+ "UNKNOWN1" , "UNKNOWN2" , "UNKNOWN3" , "UNKNOWN4" ,
+ "UNKNOWN5" , "UNKNOWN6" , "UNKNOWN7" , "UNKNOWN8" ,
+ "UNKNOWN9" , "UNKNOWN10", "UNKNOWN11", "UNKNOWN12",
+ "UNKNOWN13", "UNKNOWN14", "UNKNOWN15", "UNKNOWN16",
+ "UNKNOWN17", "UNKNOWN18", "UNKNOWN19", "UNKNOWN20",
+ "UNKNOWN21", "UNKNOWN22", "UNKNOWN23", "UNKNOWN24",
+ "UNKNOWN25", "UNKNOWN26", "UNKNOWN27", "UNKNOWN28",
+ "UNKNOWN29", "UNKNOWN30", "UNKNOWN31", "UNKNOWN32",
+ "UNKNOWN33", "UNKNOWN34", "UNKNOWN35", "UNKNOWN36",
+ "UNKNOWN37", "UNKNOWN38", "UNKNOWN39", "UNKNOWN40",
+ "UNKNOWN41", "UNKNOWN42", "UNKNOWN43", "UNKNOWN44",
+ "UNKNOWN45", "UNKNOWN46", "UNKNOWN47", "UNKNOWN48",
+ "UNKNOWN49", "UNKNOWN50", "UNKNOWN51", "UNKNOWN52",
+ "UNKNOWN53", "UNKNOWN54", "UNKNOWN55", "UNKNOWN56",
+ "UNKNOWN57", "UNKNOWN58", "UNKNOWN59", "UNKNOWN60",
+ "UNKNOWN61", "UNKNOWN62", "UNKNOWN63", "UNKNOWN64"
+ };
+
+ return channels[ch];
+}
+
+bool CAEChannelInfo::HasChannel(const enum AEChannel ch) const
+{
+ for (unsigned int i = 0; i < m_channelCount; ++i)
+ if (m_channels[i] == ch)
+ return true;
+ return false;
+}
+
+bool CAEChannelInfo::ContainsChannels(const CAEChannelInfo& rhs) const
+{
+ for (unsigned int i = 0; i < rhs.m_channelCount; ++i)
+ {
+ if (!HasChannel(rhs.m_channels[i]))
+ return false;
+ }
+ return true;
+}
+
+void CAEChannelInfo::ReplaceChannel(const enum AEChannel from, const enum AEChannel to)
+{
+ for (unsigned int i = 0; i < m_channelCount; ++i)
+ {
+ if (m_channels[i] == from)
+ {
+ m_channels[i] = to;
+ break;
+ }
+ }
+}
+
+int CAEChannelInfo::BestMatch(const std::vector<CAEChannelInfo>& dsts, int* score) const
+{
+ CAEChannelInfo availableDstChannels;
+ for (unsigned int i = 0; i < dsts.size(); i++)
+ availableDstChannels.AddMissingChannels(dsts[i]);
+
+ /* if we have channels not existing in any destination layout but that
+ * are remappable (e.g. RC => RL+RR), do those remaps */
+ CAEChannelInfo src(*this);
+ src.ResolveChannels(availableDstChannels);
+
+ bool remapped = (src != *this);
+ /* good enough approximation (does not account for added channels) */
+ int dropped = std::max((int)src.Count() - (int)Count(), 0);
+
+ int bestScore = std::numeric_limits<int>::min();
+ int bestMatch = -1;
+
+ for (unsigned int i = 0; i < dsts.size(); i++)
+ {
+ const CAEChannelInfo& dst = dsts[i];
+ int okChannels = 0;
+
+ for (unsigned int j = 0; j < src.Count(); j++)
+ okChannels += dst.HasChannel(src[j]);
+
+ int missingChannels = src.Count() - okChannels;
+ int extraChannels = dst.Count() - okChannels;
+
+ int curScore = 0 - (missingChannels + dropped) * 1000 - extraChannels * 10 - (remapped ? 1 : 0);
+
+ if (curScore > bestScore)
+ {
+ bestScore = curScore;
+ bestMatch = i;
+ if (curScore == 0)
+ break;
+ }
+ }
+
+ if (score)
+ *score = bestScore;
+
+ return bestMatch;
+}
+
+void CAEChannelInfo::AddMissingChannels(const CAEChannelInfo& rhs)
+{
+ for (unsigned int i = 0; i < rhs.Count(); i++)
+ if (!HasChannel(rhs[i]))
+ *this += rhs[i];
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEChannelInfo.h b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.h
new file mode 100644
index 0000000..1b59e5f
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEChannelData.h"
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+class CHelper_libKODI_audioengine;
+
+class CAEChannelInfo {
+ friend class CHelper_libKODI_audioengine;
+
+public:
+ CAEChannelInfo();
+ explicit CAEChannelInfo(const enum AEChannel* rhs);
+ CAEChannelInfo(const enum AEStdChLayout rhs);
+ ~CAEChannelInfo() = default;
+ CAEChannelInfo(const CAEChannelInfo&) = default;
+ CAEChannelInfo& operator=(const CAEChannelInfo& rhs);
+ CAEChannelInfo& operator=(const enum AEChannel* rhs);
+ CAEChannelInfo& operator=(const enum AEStdChLayout rhs);
+ bool operator==(const CAEChannelInfo& rhs) const;
+ bool operator!=(const CAEChannelInfo& rhs) const;
+ CAEChannelInfo& operator+=(const enum AEChannel& rhs);
+ CAEChannelInfo& operator-=(const enum AEChannel& rhs);
+ enum AEChannel operator[](unsigned int i) const;
+ operator std::string() const;
+
+ /* remove any channels that dont exist in the provided info */
+ void ResolveChannels(const CAEChannelInfo& rhs);
+ void Reset();
+ inline unsigned int Count() const { return m_channelCount; }
+ static const char* GetChName(const enum AEChannel ch);
+ bool HasChannel(const enum AEChannel ch) const;
+ bool IsChannelValid(const unsigned int pos);
+ bool IsLayoutValid();
+ bool ContainsChannels(const CAEChannelInfo& rhs) const;
+ void ReplaceChannel(const enum AEChannel from, const enum AEChannel to);
+ int BestMatch(const std::vector<CAEChannelInfo>& dsts, int* score = NULL) const;
+ void AddMissingChannels(const CAEChannelInfo& rhs);
+
+private:
+ unsigned int m_channelCount;
+ enum AEChannel m_channels[AE_CH_MAX];
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp
new file mode 100644
index 0000000..473a849
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEDeviceInfo.h"
+
+#include "AEUtil.h"
+
+#include <sstream>
+
+CAEDeviceInfo::operator std::string()
+{
+ std::stringstream ss;
+ ss << "m_deviceName : " << m_deviceName << '\n';
+ ss << "m_displayName : " << m_displayName << '\n';
+ ss << "m_displayNameExtra: " << m_displayNameExtra << '\n';
+ ss << "m_deviceType : " << DeviceTypeToString(m_deviceType) + '\n';
+ ss << "m_channels : " << (std::string)m_channels << '\n';
+
+ ss << "m_sampleRates : ";
+ for (AESampleRateList::iterator itt = m_sampleRates.begin(); itt != m_sampleRates.end(); ++itt)
+ {
+ if (itt != m_sampleRates.begin())
+ ss << ',';
+ ss << *itt;
+ }
+ ss << '\n';
+
+ ss << "m_dataFormats : ";
+ for (AEDataFormatList::iterator itt = m_dataFormats.begin(); itt != m_dataFormats.end(); ++itt)
+ {
+ if (itt != m_dataFormats.begin())
+ ss << ',';
+ ss << CAEUtil::DataFormatToStr(*itt);
+ }
+ ss << '\n';
+
+ ss << "m_streamTypes : ";
+ for (AEDataTypeList::iterator itt = m_streamTypes.begin(); itt != m_streamTypes.end(); ++itt)
+ {
+ if (itt != m_streamTypes.begin())
+ ss << ',';
+ ss << CAEUtil::StreamTypeToStr(*itt);
+ }
+ if (m_streamTypes.empty())
+ ss << "No passthrough capabilities";
+ ss << '\n';
+
+ return ss.str();
+}
+
+std::string CAEDeviceInfo::DeviceTypeToString(enum AEDeviceType deviceType)
+{
+ switch (deviceType)
+ {
+ case AE_DEVTYPE_PCM : return "AE_DEVTYPE_PCM" ; break;
+ case AE_DEVTYPE_IEC958: return "AE_DEVTYPE_IEC958"; break;
+ case AE_DEVTYPE_HDMI : return "AE_DEVTYPE_HDMI" ; break;
+ case AE_DEVTYPE_DP : return "AE_DEVTYPE_DP" ; break;
+ }
+ return "INVALID";
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h
new file mode 100644
index 0000000..387d617
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEChannelInfo.h"
+#include "cores/AudioEngine/Utils/AEStreamInfo.h"
+
+#include <string>
+#include <vector>
+
+typedef std::vector<unsigned int> AESampleRateList;
+typedef std::vector<enum AEDataFormat> AEDataFormatList;
+typedef std::vector<CAEStreamInfo::DataType> AEDataTypeList;
+
+enum AEDeviceType {
+ AE_DEVTYPE_PCM,
+ AE_DEVTYPE_IEC958,
+ AE_DEVTYPE_HDMI,
+ AE_DEVTYPE_DP
+};
+
+/**
+ * This classt provides the details of what the audio output hardware is capable of
+ */
+class CAEDeviceInfo
+{
+public:
+ std::string m_deviceName; /* the driver device name */
+ std::string m_displayName; /* the friendly display name */
+ std::string m_displayNameExtra; /* additional display name info, ie, monitor name from ELD */
+ enum AEDeviceType m_deviceType; /* the device type, PCM, IEC958 or HDMI */
+ CAEChannelInfo m_channels; /* the channels the device is capable of rendering */
+ AESampleRateList m_sampleRates; /* the samplerates the device is capable of rendering */
+ AEDataFormatList m_dataFormats; /* the dataformats the device is capable of rendering */
+ AEDataTypeList m_streamTypes;
+
+ bool m_wantsIECPassthrough; /* if sink supports passthrough encapsulation is done when set to true */
+
+ bool m_onlyPassthrough{false}; // sink only only should be used for passthrough (audio PT device)
+ bool m_onlyPCM{false}; // sink only should be used for PCM (audio device)
+
+ operator std::string();
+ static std::string DeviceTypeToString(enum AEDeviceType deviceType);
+};
+
+typedef std::vector<CAEDeviceInfo> AEDeviceInfoList;
diff --git a/xbmc/cores/AudioEngine/Utils/AEELDParser.cpp b/xbmc/cores/AudioEngine/Utils/AEELDParser.cpp
new file mode 100644
index 0000000..a9a4cf1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEELDParser.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEELDParser.h"
+
+#include "AEDeviceInfo.h"
+#include "utils/EndianSwap.h"
+
+#include <algorithm>
+#include <functional>
+#include <stdio.h>
+#include <string.h>
+
+#define GRAB_BITS(buf, byte, lowbit, bits) ((buf[byte] >> (lowbit)) & ((1 << (bits)) - 1))
+
+typedef struct
+{
+ uint8_t eld_ver;
+ uint8_t baseline_eid_len;
+ uint8_t cea_edid_ver;
+ uint8_t monitor_name_length;
+ uint8_t sad_count;
+ uint8_t conn_type;
+ bool s_ai;
+ bool hdcp;
+ uint8_t audio_sync_delay;
+ bool rlrc; /* rear left and right of center */
+ bool flrc; /* front left and right of center */
+ bool rc; /* rear center */
+ bool rlr; /* rear left and right */
+ bool fc; /* front center */
+ bool lfe; /* LFE */
+ bool flr; /* front left and right */
+ uint64_t port_id;
+ char mfg_name[4];
+ uint16_t product_code;
+ std::string monitor_name;
+} ELDHeader;
+
+#define ELD_VER_CEA_816D 2
+#define ELD_VER_PARTIAL 31
+
+#define ELD_EDID_VER_NONE 0
+#define ELD_EDID_VER_CEA_861 1
+#define ELD_EDID_VER_CEA_861_A 2
+#define ELD_EDID_VER_CEA_861_BCD 3
+
+#define ELD_CONN_TYPE_HDMI 0
+#define ELD_CONN_TYPE_DP 1
+#define ELD_CONN_TYPE_RESERVED1 2
+#define ELD_CONN_TYPE_RESERVED2 3
+
+#define CEA_861_FORMAT_RESERVED1 0
+#define CEA_861_FORMAT_LPCM 1
+#define CEA_861_FORMAT_AC3 2
+#define CEA_861_FORMAT_MPEG1 3
+#define CEA_861_FORMAT_MP3 4
+#define CEA_861_FORMAT_MPEG2 5
+#define CEA_861_FORMAT_AAC 6
+#define CEA_861_FORMAT_DTS 7
+#define CEA_861_FORMAT_ATRAC 8
+#define CEA_861_FORMAT_SACD 9
+#define CEA_861_FORMAT_EAC3 10
+#define CEA_861_FORMAT_DTSHD 11
+#define CEA_861_FORMAT_MLP 12
+#define CEA_861_FORMAT_DST 13
+#define CEA_861_FORMAT_WMAPRO 14
+#define CEA_861_FORMAT_RESERVED2 15
+
+#define rtrim(s) s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end())
+
+void CAEELDParser::Parse(const uint8_t *data, size_t length, CAEDeviceInfo& info)
+{
+ ELDHeader header;
+ header.eld_ver = (data[0 ] & 0xF8) >> 3;
+ if (header.eld_ver != ELD_VER_CEA_816D && header.eld_ver != ELD_VER_PARTIAL)
+ return;
+
+ header.baseline_eid_len = data[2 ];
+ header.cea_edid_ver = (data[4 ] & 0xE0) >> 5;
+ header.monitor_name_length = data[4 ] & 0x1F;
+ header.sad_count = (data[5 ] & 0xF0) >> 4;
+ header.conn_type = (data[5 ] & 0x0C) >> 2;
+ header.s_ai = (data[5 ] & 0x02) == 0x02;
+ header.hdcp = (data[5 ] & 0x01) == 0x01;
+ header.audio_sync_delay = data[6 ];
+ header.rlrc = (data[7 ] & 0x40) == 0x40;
+ header.flrc = (data[7 ] & 0x20) == 0x20;
+ header.rc = (data[7 ] & 0x10) == 0x10;
+ header.rlr = (data[7 ] & 0x08) == 0x08;
+ header.fc = (data[7 ] & 0x04) == 0x04;
+ header.lfe = (data[7 ] & 0x02) == 0x02;
+ header.flr = (data[7 ] & 0x01) == 0x01;
+ header.port_id = Endian_SwapLE64(*((const uint64_t*)(data + 8)));
+ header.mfg_name[0] = 'A' + ((data[16] >> 2) & 0x1F) - 1;
+ header.mfg_name[1] = 'A' + (((data[16] << 3) | (data[17] >> 5)) & 0x1F) - 1;
+ header.mfg_name[2] = 'A' + (data[17] & 0x1F) - 1;
+ header.mfg_name[3] = '\0';
+ header.product_code = Endian_SwapLE16(*((const uint16_t*)(data + 18)));
+
+ switch (header.conn_type)
+ {
+ case ELD_CONN_TYPE_HDMI: info.m_deviceType = AE_DEVTYPE_HDMI; break;
+ case ELD_CONN_TYPE_DP : info.m_deviceType = AE_DEVTYPE_DP ; break;
+ }
+
+ info.m_displayNameExtra = header.mfg_name;
+ if (header.monitor_name_length <= 16)
+ {
+ header.monitor_name.assign((const char *)(data + 20), header.monitor_name_length);
+ rtrim(header.monitor_name);
+ if (header.monitor_name.length() > 0)
+ {
+ info.m_displayNameExtra.append(" ");
+ info.m_displayNameExtra.append(header.monitor_name);
+ if (header.conn_type == ELD_CONN_TYPE_HDMI)
+ info.m_displayNameExtra.append(" on HDMI" );
+ else
+ info.m_displayNameExtra.append(" on DisplayPort");
+ }
+ }
+
+ if (header.flr)
+ {
+ if (!info.m_channels.HasChannel(AE_CH_FL))
+ info.m_channels += AE_CH_FL;
+ if (!info.m_channels.HasChannel(AE_CH_FR))
+ info.m_channels += AE_CH_FR;
+ }
+
+ if (header.lfe)
+ if (!info.m_channels.HasChannel(AE_CH_LFE))
+ info.m_channels += AE_CH_LFE;
+
+ if (header.fc)
+ if (!info.m_channels.HasChannel(AE_CH_FC))
+ info.m_channels += AE_CH_FC;
+
+ if (header.rlr)
+ {
+ if (!info.m_channels.HasChannel(AE_CH_BL))
+ info.m_channels += AE_CH_BL;
+ if (!info.m_channels.HasChannel(AE_CH_BR))
+ info.m_channels += AE_CH_BR;
+ }
+
+ if (header.rc)
+ if (!info.m_channels.HasChannel(AE_CH_BC))
+ info.m_channels += AE_CH_BC;
+
+ if (header.flrc)
+ {
+ if (!info.m_channels.HasChannel(AE_CH_FLOC))
+ info.m_channels += AE_CH_FLOC;
+ if (!info.m_channels.HasChannel(AE_CH_FROC))
+ info.m_channels += AE_CH_FROC;
+ }
+
+ if (header.rlrc)
+ {
+ if (!info.m_channels.HasChannel(AE_CH_BLOC))
+ info.m_channels += AE_CH_BLOC;
+ if (!info.m_channels.HasChannel(AE_CH_BROC))
+ info.m_channels += AE_CH_BROC;
+ }
+
+ const uint8_t *sad = data + 20 + header.monitor_name_length;
+ for(uint8_t i = 0; i < header.sad_count; ++i)
+ {
+ uint8_t offset = i * 3;
+ uint8_t formatCode = (sad[offset + 0] >> 3) & 0xF;
+ //uint8_t channelCount = (sad[offset + 0] & 0x7) + 1;
+ //uint8_t sampleRates = sad[offset + 1];
+
+ AEDataFormat fmt = AE_FMT_INVALID;
+ switch (formatCode)
+ {
+ //! @todo implement
+ case CEA_861_FORMAT_AC3 : fmt = AE_FMT_RAW ; break;
+ case CEA_861_FORMAT_DTS : fmt = AE_FMT_RAW ; break;
+ case CEA_861_FORMAT_DTSHD: fmt = AE_FMT_RAW ; break;
+ case CEA_861_FORMAT_EAC3 : fmt = AE_FMT_RAW ; break;
+ case CEA_861_FORMAT_LPCM : fmt = AE_FMT_RAW ; break;
+ case CEA_861_FORMAT_MLP : fmt = AE_FMT_RAW; break;
+ }
+
+ if (fmt == AE_FMT_INVALID)
+ continue;
+
+ if (std::find(info.m_dataFormats.begin(), info.m_dataFormats.end(), fmt) == info.m_dataFormats.end())
+ info.m_dataFormats.push_back(fmt);
+ }
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEELDParser.h b/xbmc/cores/AudioEngine/Utils/AEELDParser.h
new file mode 100644
index 0000000..1e8bfb1
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEELDParser.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstring>
+#include <stdint.h>
+
+class CAEDeviceInfo;
+
+class CAEELDParser {
+public:
+ static void Parse(const uint8_t *data, size_t length, CAEDeviceInfo& info);
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AELimiter.cpp b/xbmc/cores/AudioEngine/Utils/AELimiter.cpp
new file mode 100644
index 0000000..393d1d5
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AELimiter.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AELimiter.h"
+
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MathUtils.h"
+
+#include <algorithm>
+#include <math.h>
+
+CAELimiter::CAELimiter()
+{
+ m_amplify = 1.0f;
+ m_attenuation = 1.0f;
+ m_samplerate = 48000.0f;
+ m_holdcounter = 0;
+ m_increase = 0.0f;
+}
+
+float CAELimiter::Run(float* frame[AE_CH_MAX], int channels, int offset /*= 0*/, bool planar /*= false*/)
+{
+ float highest = 0.0f;
+ if (!planar)
+ {
+ for(int i=0; i<channels; i++)
+ {
+ highest = std::max(highest, fabsf(*(frame[0]+offset+i)));
+ }
+ }
+ else
+ {
+ for(int i=0; i<channels; i++)
+ {
+ highest = std::max(highest, fabsf(*(frame[i]+offset)));
+ }
+ }
+
+ float sample = highest * m_amplify;
+ if (sample * m_attenuation > 1.0f)
+ {
+ m_attenuation = 1.0f / sample;
+ m_holdcounter = MathUtils::round_int(static_cast<double>(
+ m_samplerate *
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_limiterHold));
+ m_increase = powf(std::min(sample, 10000.0f), 1.0f / (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_limiterRelease * m_samplerate));
+ }
+
+ float attenuation = m_attenuation;
+
+ if (m_holdcounter > 0)
+ {
+ m_holdcounter--;
+ }
+ else
+ {
+ if (m_increase > 0.0f)
+ {
+ m_attenuation *= m_increase;
+ if (m_attenuation > 1.0f)
+ {
+ m_increase = 0.0f;
+ m_attenuation = 1.0f;
+ }
+ }
+ }
+
+ return attenuation * m_amplify;
+}
+
diff --git a/xbmc/cores/AudioEngine/Utils/AELimiter.h b/xbmc/cores/AudioEngine/Utils/AELimiter.h
new file mode 100644
index 0000000..a79fa66
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AELimiter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEAudioFormat.h"
+
+#include <algorithm>
+
+class CAELimiter
+{
+ private:
+ float m_amplify;
+ float m_attenuation;
+ float m_samplerate;
+ int m_holdcounter;
+ float m_increase;
+
+ public:
+ CAELimiter();
+
+ void SetAmplification(float amplify)
+ {
+ m_amplify = std::max(std::min(amplify, 1000.0f), 0.0f);
+ }
+
+ float GetAmplification() const
+ {
+ return m_amplify;
+ }
+
+ void SetSamplerate(int samplerate)
+ {
+ m_samplerate = (float)samplerate;
+ }
+
+ float Run(float* frame[AE_CH_MAX], int channels, int offset = 0, bool planar = false);
+};
diff --git a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp
new file mode 100644
index 0000000..411d481
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEPackIEC61937.h"
+
+#include <cassert>
+#include <string.h>
+
+#define IEC61937_PREAMBLE1 0xF872
+#define IEC61937_PREAMBLE2 0x4E1F
+
+inline void SwapEndian(uint16_t *dst, uint16_t *src, unsigned int size)
+{
+ for (unsigned int i = 0; i < size; ++i, ++dst, ++src)
+ *dst = ((*src & 0xFF00) >> 8) | ((*src & 0x00FF) << 8);
+}
+
+int CAEPackIEC61937::PackAC3(uint8_t *data, unsigned int size, uint8_t *dest)
+{
+ assert(size <= OUT_FRAMESTOBYTES(AC3_FRAME_SIZE));
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_length = size << 3;
+
+ if (data == NULL)
+ data = packet->m_data;
+#ifdef __BIG_ENDIAN__
+ else
+ memcpy(packet->m_data, data, size);
+#else
+
+ int bitstream_mode = data[5] & 0x7;
+ packet->m_type = IEC61937_TYPE_AC3 | (bitstream_mode << 8);
+
+ size += size & 0x1;
+ SwapEndian((uint16_t*)packet->m_data, (uint16_t*)data, size >> 1);
+#endif
+
+ memset(packet->m_data + size, 0, OUT_FRAMESTOBYTES(AC3_FRAME_SIZE) - IEC61937_DATA_OFFSET - size);
+ return OUT_FRAMESTOBYTES(AC3_FRAME_SIZE);
+}
+
+int CAEPackIEC61937::PackEAC3(uint8_t *data, unsigned int size, uint8_t *dest)
+{
+ assert(size <= OUT_FRAMESTOBYTES(EAC3_FRAME_SIZE));
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_type = IEC61937_TYPE_EAC3;
+ packet->m_length = size;
+
+ if (data == NULL)
+ data = packet->m_data;
+#ifdef __BIG_ENDIAN__
+ else
+ memcpy(packet->m_data, data, size);
+#else
+ size += size & 0x1;
+ SwapEndian((uint16_t*)packet->m_data, (uint16_t*)data, size >> 1);
+#endif
+
+ memset(packet->m_data + size, 0, OUT_FRAMESTOBYTES(EAC3_FRAME_SIZE) - IEC61937_DATA_OFFSET - size);
+ return OUT_FRAMESTOBYTES(EAC3_FRAME_SIZE);
+}
+
+int CAEPackIEC61937::PackDTS_512(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian)
+{
+ return PackDTS(data, size, dest, littleEndian, OUT_FRAMESTOBYTES(DTS1_FRAME_SIZE), IEC61937_TYPE_DTS1);
+}
+
+int CAEPackIEC61937::PackDTS_1024(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian)
+{
+ return PackDTS(data, size, dest, littleEndian, OUT_FRAMESTOBYTES(DTS2_FRAME_SIZE), IEC61937_TYPE_DTS2);
+}
+
+int CAEPackIEC61937::PackDTS_2048(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian)
+{
+ return PackDTS(data, size, dest, littleEndian, OUT_FRAMESTOBYTES(DTS3_FRAME_SIZE), IEC61937_TYPE_DTS3);
+}
+
+int CAEPackIEC61937::PackTrueHD(uint8_t *data, unsigned int size, uint8_t *dest)
+{
+ if (size == 0)
+ return OUT_FRAMESTOBYTES(TRUEHD_FRAME_SIZE);
+
+ assert(size <= OUT_FRAMESTOBYTES(TRUEHD_FRAME_SIZE));
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_type = IEC61937_TYPE_TRUEHD;
+ packet->m_length = size;
+
+ if (data == NULL)
+ data = packet->m_data;
+#ifdef __BIG_ENDIAN__
+ else
+ memcpy(packet->m_data, data, size);
+#else
+ size += size & 0x1;
+ SwapEndian((uint16_t*)packet->m_data, (uint16_t*)data, size >> 1);
+#endif
+
+ memset(packet->m_data + size, 0, OUT_FRAMESTOBYTES(TRUEHD_FRAME_SIZE) - IEC61937_DATA_OFFSET - size);
+ return OUT_FRAMESTOBYTES(TRUEHD_FRAME_SIZE);
+}
+
+int CAEPackIEC61937::PackDTSHD(uint8_t *data, unsigned int size, uint8_t *dest, unsigned int period)
+{
+ unsigned int subtype;
+ switch (period)
+ {
+ case 512: subtype = 0; break;
+ case 1024: subtype = 1; break;
+ case 2048: subtype = 2; break;
+ case 4096: subtype = 3; break;
+ case 8192: subtype = 4; break;
+ case 16384: subtype = 5; break;
+
+ default:
+ return 0;
+ }
+
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_type = IEC61937_TYPE_DTSHD | (subtype << 8);
+
+ /* Align so that (length_code & 0xf) == 0x8. This is reportedly needed
+ * with some receivers, but the exact requirement is unconfirmed. */
+ packet->m_length = ((size + 0x17) &~ 0x0f) - 0x08;
+
+ if (data == NULL)
+ data = packet->m_data;
+#ifdef __BIG_ENDIAN__
+ else
+ memcpy(packet->m_data, data, size);
+#else
+ size += size & 0x1;
+ SwapEndian((uint16_t*)packet->m_data, (uint16_t*)data, size >> 1);
+#endif
+
+ unsigned int burstsize = period << 2;
+ memset(packet->m_data + size, 0, burstsize - IEC61937_DATA_OFFSET - size);
+ return burstsize;
+}
+
+int CAEPackIEC61937::PackDTS(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian,
+ unsigned int frameSize, uint16_t type)
+{
+ assert(size <= frameSize);
+
+ /* BE is the standard endianness, byteswap needed if LE */
+ bool byteSwapNeeded = littleEndian;
+
+#ifndef __BIG_ENDIAN__
+ /* on LE systems we want LE output, byteswap needed */
+ byteSwapNeeded ^= true;
+#endif
+
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+ uint8_t *dataTo;
+
+ if (size == frameSize)
+ {
+ /* No packing possible or needed, DTS stream is suitable for direct output */
+ dataTo = dest;
+ }
+ else if (size <= frameSize - IEC61937_DATA_OFFSET)
+ {
+ /* Fits to IEC61937, perform packing */
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_type = type;
+ packet->m_length = size << 3;
+
+ dataTo = packet->m_data;
+ }
+ else
+ {
+ /* Stream is unsuitable for both packing and direct output */
+ return 0;
+ }
+
+ if (data == NULL)
+ data = dataTo;
+ else if (!byteSwapNeeded)
+ memcpy(dataTo, data, size);
+
+ if (byteSwapNeeded)
+ {
+ size += size & 0x1;
+ SwapEndian((uint16_t*)dataTo, (uint16_t*)data, size >> 1);
+ }
+
+ if (size != frameSize)
+ memset(packet->m_data + size, 0, frameSize - IEC61937_DATA_OFFSET - size);
+
+ return frameSize;
+}
+
+int CAEPackIEC61937::PackPause(uint8_t *dest, unsigned int millis, unsigned int framesize, unsigned int samplerate, unsigned int rep_period, unsigned int encodedRate)
+{
+ int periodInBytes = rep_period * framesize;
+ double periodInTime = (double)rep_period / samplerate * 1000;
+ int periodsNeeded = millis / periodInTime;
+ int maxPeriods = MAX_IEC61937_PACKET / periodInBytes;
+ if (periodsNeeded > maxPeriods)
+ periodsNeeded = maxPeriods;
+ uint16_t gap = encodedRate * millis / 1000;
+
+ struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
+ packet->m_preamble1 = IEC61937_PREAMBLE1;
+ packet->m_preamble2 = IEC61937_PREAMBLE2;
+ packet->m_type = 3;
+ packet->m_length = 32;
+ memset(packet->m_data, 0, periodInBytes - 8);
+
+ for (int i=1; i<periodsNeeded; i++)
+ {
+ memcpy(dest+i*periodInBytes, dest, periodInBytes);
+ }
+
+ uint16_t *gapPtr = reinterpret_cast<uint16_t*>(packet->m_data);
+ *gapPtr = gap;
+
+ return periodsNeeded * periodInBytes;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h
new file mode 100644
index 0000000..5a83b65
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <list>
+#include <stdint.h>
+
+#define MAX_IEC61937_PACKET 61440
+#define IEC61937_DATA_OFFSET 8
+
+#define DTS1_FRAME_SIZE 512
+#define DTS2_FRAME_SIZE 1024
+#define DTS3_FRAME_SIZE 2048
+#define AC3_FRAME_SIZE 1536
+#define EAC3_FRAME_SIZE 6144
+#define TRUEHD_FRAME_SIZE 15360
+
+#define OUT_SAMPLESIZE 16
+#define OUT_CHANNELS 2
+#define OUT_FRAMESTOBYTES(a) ((a) * OUT_CHANNELS * (OUT_SAMPLESIZE>>3))
+
+class CAEPackIEC61937
+{
+public:
+ CAEPackIEC61937() = default;
+ typedef int (*PackFunc)(uint8_t *data, unsigned int size, uint8_t *dest);
+
+ static int PackAC3 (uint8_t *data, unsigned int size, uint8_t *dest);
+ static int PackEAC3 (uint8_t *data, unsigned int size, uint8_t *dest);
+ static int PackDTS_512 (uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
+ static int PackDTS_1024(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
+ static int PackDTS_2048(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
+ static int PackTrueHD (uint8_t *data, unsigned int size, uint8_t *dest);
+ static int PackDTSHD (uint8_t *data, unsigned int size, uint8_t *dest, unsigned int period);
+ static int PackPause(uint8_t *dest, unsigned int millis, unsigned int framesize, unsigned int samplerate, unsigned int rep_period, unsigned int encodedRate);
+private:
+
+ static int PackDTS(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian,
+ unsigned int frameSize, uint16_t type);
+
+ enum IEC61937DataType
+ {
+ IEC61937_TYPE_NULL = 0x00,
+ IEC61937_TYPE_AC3 = 0x01,
+ IEC61937_TYPE_DTS1 = 0x0B, /* 512 samples */
+ IEC61937_TYPE_DTS2 = 0x0C, /* 1024 samples */
+ IEC61937_TYPE_DTS3 = 0x0D, /* 2048 samples */
+ IEC61937_TYPE_DTSHD = 0x11,
+ IEC61937_TYPE_EAC3 = 0x15,
+ IEC61937_TYPE_TRUEHD = 0x16
+ };
+
+#ifdef __GNUC__
+ struct __attribute__((__packed__)) IEC61937Packet
+#else
+ __pragma(pack(push, 1))
+ struct IEC61937Packet
+#endif
+ {
+ uint16_t m_preamble1;
+ uint16_t m_preamble2;
+ uint16_t m_type;
+ uint16_t m_length;
+ uint8_t m_data[MAX_IEC61937_PACKET - IEC61937_DATA_OFFSET];
+ };
+#ifndef __GNUC__
+ __pragma(pack(pop))
+#endif
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AERingBuffer.h b/xbmc/cores/AudioEngine/Utils/AERingBuffer.h
new file mode 100644
index 0000000..8a2a1aa
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AERingBuffer.h
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#define AE_RING_BUFFER_OK 0;
+#define AE_RING_BUFFER_EMPTY 1;
+#define AE_RING_BUFFER_FULL 2;
+#define AE_RING_BUFFER_NOTAVAILABLE 3;
+
+//#define AE_RING_BUFFER_DEBUG
+
+#include "utils/log.h"
+#include "utils/MemUtils.h"
+
+#include <string.h>
+
+/**
+ * This buffer can be used by one read and one write thread at any one time
+ * without the risk of data corruption.
+ * If you intend to call the Reset() method, please use Locks.
+ * All other operations are thread-safe.
+ */
+class AERingBuffer {
+
+public:
+ AERingBuffer() = default;
+
+ AERingBuffer(unsigned int size, unsigned int planes = 1) { Create(size, planes); }
+
+ ~AERingBuffer()
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer::~AERingBuffer: Deleting buffer.");
+#endif
+ for (unsigned int i = 0; i < m_planes; i++)
+ KODI::MEMORY::AlignedFree(m_Buffer[i]);
+ delete[] m_Buffer;
+ }
+
+ /**
+ * Allocates space for buffer, and sets it's contents to 0.
+ *
+ * @return true on success, false otherwise
+ */
+ bool Create(int size, unsigned int planes = 1)
+ {
+ m_Buffer = new unsigned char*[planes];
+ for (unsigned int i = 0; i < planes; i++)
+ {
+ m_Buffer[i] = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(size, 16));
+ if (!m_Buffer[i])
+ return false;
+ memset(m_Buffer[i], 0, size);
+ }
+ m_iSize = size;
+ m_planes = planes;
+ return true;
+ }
+
+ /**
+ * Fills the buffer with zeros and resets the pointers.
+ * This method is not thread-safe, so before using this method
+ * please acquire a Lock()
+ */
+ void Reset() {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer::Reset: Buffer reset.");
+#endif
+ m_iWritten = 0;
+ m_iRead = 0;
+ m_iReadPos = 0;
+ m_iWritePos = 0;
+ }
+
+ /**
+ * Writes data to buffer.
+ * Attempt to write more bytes than available results in AE_RING_BUFFER_FULL.
+ *
+ * @return AE_RING_BUFFER_OK on success, otherwise an error code
+ */
+ int Write(unsigned char *src, unsigned int size, unsigned int plane = 0)
+ {
+ unsigned int space = GetWriteSize();
+
+ //do we have enough space for all the data?
+ if (size > space || plane >= m_planes)
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG,
+ "AERingBuffer: Not enough space, ignoring data. Requested: {} Available: {}", size,
+ space);
+#endif
+ return AE_RING_BUFFER_FULL;
+ }
+
+ //no wrapping?
+ if ( m_iSize > size + m_iWritePos )
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer: Written to: {} size: {} space before: {}", m_iWritePos,
+ size, space);
+#endif
+ memcpy(m_Buffer[plane] + m_iWritePos, src, size);
+ }
+ //need to wrap
+ else
+ {
+ unsigned int first = m_iSize - m_iWritePos;
+ unsigned int second = size - first;
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG,
+ "AERingBuffer: Written to (split) first: {} second: {} size: {} space before: {}",
+ first, second, size, space);
+#endif
+ memcpy(m_Buffer[plane] + m_iWritePos, src, first);
+ memcpy(m_Buffer[plane], src + first, second);
+ }
+ if (plane + 1 == m_planes)
+ WriteFinished(size);
+
+ return AE_RING_BUFFER_OK;
+ }
+
+ /**
+ * Reads data from buffer.
+ * Attempt to read more bytes than available results in RING_BUFFER_NOTAVAILABLE.
+ * Reading from empty buffer returns AE_RING_BUFFER_EMPTY
+ *
+ * @return AE_RING_BUFFER_OK on success, otherwise an error code
+ */
+ int Read(unsigned char *dest, unsigned int size, unsigned int plane = 0)
+ {
+ unsigned int space = GetReadSize();
+
+ //want to read more than we have written?
+ if( space == 0 )
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer: Can't read from empty buffer.");
+#endif
+ return AE_RING_BUFFER_EMPTY;
+ }
+
+ //want to read more than we have available
+ if( size > space || plane >= m_planes)
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer: Can't read {} bytes when we only have {}.", size, space);
+#endif
+ return AE_RING_BUFFER_NOTAVAILABLE;
+ }
+
+ //no wrapping?
+ if ( size + m_iReadPos < m_iSize )
+ {
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG, "AERingBuffer: Reading from: {} size: {} space before: {}", m_iWritePos,
+ size, space);
+#endif
+ if (dest)
+ memcpy(dest, m_Buffer[plane] + m_iReadPos, size);
+ }
+ //need to wrap
+ else
+ {
+ unsigned int first = m_iSize - m_iReadPos;
+ unsigned int second = size - first;
+#ifdef AE_RING_BUFFER_DEBUG
+ CLog::Log(LOGDEBUG,
+ "AERingBuffer: Reading from (split) first: {} second: {} size: {} space before: {}",
+ first, second, size, space);
+#endif
+ if (dest)
+ {
+ memcpy(dest, m_Buffer[plane] + m_iReadPos, first);
+ memcpy(dest + first, m_Buffer[plane], second);
+ }
+ }
+ if (plane + 1 == m_planes)
+ ReadFinished(size);
+
+ return AE_RING_BUFFER_OK;
+ }
+
+ /**
+ * Dumps the buffer.
+ */
+ void Dump()
+ {
+ unsigned char *bufferContents = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(m_iSize * m_planes + 1, 16));
+ unsigned char *dest = bufferContents;
+ for (unsigned int j = 0; j < m_planes; j++)
+ {
+ for (unsigned int i=0; i<m_iSize; i++)
+ {
+ if (i >= m_iReadPos && i<m_iWritePos)
+ *dest++ = m_Buffer[j][i];
+ else
+ *dest++ = '_';
+ }
+ }
+ bufferContents[m_iSize*m_planes] = '\0';
+ CLog::LogF(LOGDEBUG, "Buffer Content: {}", reinterpret_cast<const char*>(bufferContents));
+ KODI::MEMORY::AlignedFree(bufferContents);
+ }
+
+ /**
+ * Returns available space for writing to buffer.
+ * Attempt to write more bytes than available results in AE_RING_BUFFER_FULL.
+ */
+ unsigned int GetWriteSize()
+ {
+ return m_iSize - ( m_iWritten - m_iRead );
+ }
+
+ /**
+ * Returns available space for reading from buffer.
+ * Attempt to read more bytes than available results in AE_RING_BUFFER_EMPTY.
+ */
+ unsigned int GetReadSize()
+ {
+ return m_iWritten - m_iRead;
+ }
+
+ /**
+ * Returns the buffer size.
+ */
+ unsigned int GetMaxSize()
+ {
+ return m_iSize;
+ }
+
+ /**
+ * Returns the number of planes
+ */
+ unsigned int NumPlanes() const
+ {
+ return m_planes;
+ }
+private:
+ /**
+ * Increments the write pointer.
+ * Called at the end of writing to all planes.
+ */
+ void WriteFinished(unsigned int size)
+ {
+ if ( m_iSize > size + m_iWritePos )
+ m_iWritePos += size;
+ else // wrapping
+ m_iWritePos = size - (m_iSize - m_iWritePos);
+
+ //we can increase the write count now
+ m_iWritten+=size;
+ }
+
+ /**
+ * Increments the read pointer.
+ * Called at the end of reading to all planes.
+ */
+ void ReadFinished(unsigned int size)
+ {
+ if ( size + m_iReadPos < m_iSize )
+ m_iReadPos += size;
+ else
+ m_iReadPos = size - (m_iSize - m_iReadPos);
+
+ //we can increase the read count now
+ m_iRead+=size;
+ }
+
+ unsigned int m_iReadPos = 0;
+ unsigned int m_iWritePos = 0;
+ unsigned int m_iRead = 0;
+ unsigned int m_iWritten = 0;
+ unsigned int m_iSize = 0;
+ unsigned int m_planes = 0;
+ unsigned char** m_Buffer = nullptr;
+};
diff --git a/xbmc/cores/AudioEngine/Utils/AEStreamData.h b/xbmc/cores/AudioEngine/Utils/AEStreamData.h
new file mode 100644
index 0000000..a0624f4
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEStreamData.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/**
+ * Bit options to pass to IAE::MakeStream
+ */
+enum AEStreamOptions
+{
+ AESTREAM_FORCE_RESAMPLE = 1 << 0, /* force resample even if rates match */
+ AESTREAM_PAUSED = 1 << 1, /* create the stream paused */
+ AESTREAM_AUTOSTART = 1 << 2, /* autostart the stream when enough data is buffered */
+};
diff --git a/xbmc/cores/AudioEngine/Utils/AEStreamInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEStreamInfo.cpp
new file mode 100644
index 0000000..f12a025
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEStreamInfo.cpp
@@ -0,0 +1,865 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AEStreamInfo.h"
+
+#include "utils/log.h"
+
+#include <algorithm>
+#include <string.h>
+
+#define DTS_PREAMBLE_14BE 0x1FFFE800
+#define DTS_PREAMBLE_14LE 0xFF1F00E8
+#define DTS_PREAMBLE_16BE 0x7FFE8001
+#define DTS_PREAMBLE_16LE 0xFE7F0180
+#define DTS_PREAMBLE_HD 0x64582025
+#define DTS_PREAMBLE_XCH 0x5a5a5a5a
+#define DTS_PREAMBLE_XXCH 0x47004a03
+#define DTS_PREAMBLE_X96K 0x1d95f262
+#define DTS_PREAMBLE_XBR 0x655e315e
+#define DTS_PREAMBLE_LBR 0x0a801921
+#define DTS_PREAMBLE_XLL 0x41a29547
+#define DTS_SFREQ_COUNT 16
+#define MAX_EAC3_BLOCKS 6
+#define UNKNOWN_DTS_EXTENSION 255
+
+static const uint16_t AC3Bitrates[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
+ 192, 224, 256, 320, 384, 448, 512, 576, 640};
+static const uint16_t AC3FSCod[] = {48000, 44100, 32000, 0};
+static const uint8_t AC3BlkCod[] = {1, 2, 3, 6};
+static const uint8_t AC3Channels[] = {2, 1, 2, 3, 3, 4, 4, 5};
+static const uint8_t DTSChannels[] = {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8};
+static const uint8_t THDChanMap[] = {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1};
+
+static const uint32_t DTSSampleRates[DTS_SFREQ_COUNT] = {0, 8000, 16000, 32000, 64000, 128000,
+ 11025, 22050, 44100, 88200, 176400, 12000,
+ 24000, 48000, 96000, 192000};
+
+CAEStreamParser::CAEStreamParser() : m_syncFunc(&CAEStreamParser::DetectType)
+{
+ av_crc_init(m_crcTrueHD, 0, 16, 0x2D, sizeof(m_crcTrueHD));
+}
+
+double CAEStreamInfo::GetDuration() const
+{
+ double duration = 0;
+ switch (m_type)
+ {
+ case STREAM_TYPE_AC3:
+ duration = 0.032;
+ break;
+ case STREAM_TYPE_EAC3:
+ duration = 6144.0 / m_sampleRate / 4;
+ break;
+ case STREAM_TYPE_TRUEHD:
+ int rate;
+ if (m_sampleRate == 48000 || m_sampleRate == 96000 || m_sampleRate == 192000)
+ rate = 192000;
+ else
+ rate = 176400;
+ duration = 3840.0 / rate;
+ break;
+ case STREAM_TYPE_DTS_512:
+ case STREAM_TYPE_DTSHD_CORE:
+ case STREAM_TYPE_DTSHD:
+ case STREAM_TYPE_DTSHD_MA:
+ duration = 512.0 / m_sampleRate;
+ break;
+ case STREAM_TYPE_DTS_1024:
+ duration = 1024.0 / m_sampleRate;
+ break;
+ case STREAM_TYPE_DTS_2048:
+ duration = 2048.0 / m_sampleRate;
+ break;
+ default:
+ CLog::Log(LOGERROR, "CAEStreamInfo::GetDuration - invalid stream type");
+ break;
+ }
+ return duration * 1000;
+}
+
+bool CAEStreamInfo::operator==(const CAEStreamInfo& info) const
+{
+ if (m_type != info.m_type)
+ return false;
+ if (m_dataIsLE != info.m_dataIsLE)
+ return false;
+ if (m_repeat != info.m_repeat)
+ return false;
+ return true;
+}
+
+void CAEStreamParser::Reset()
+{
+ m_skipBytes = 0;
+ m_bufferSize = 0;
+ m_needBytes = 0;
+ m_hasSync = false;
+}
+
+int CAEStreamParser::AddData(uint8_t* data,
+ unsigned int size,
+ uint8_t** buffer,
+ unsigned int* bufferSize)
+{
+ if (size == 0)
+ {
+ if (bufferSize)
+ *bufferSize = 0;
+ return 0;
+ }
+
+ if (m_skipBytes)
+ {
+ unsigned int canSkip = std::min(size, m_skipBytes);
+ unsigned int room = sizeof(m_buffer) - m_bufferSize;
+ unsigned int copy = std::min(room, canSkip);
+
+ memcpy(m_buffer + m_bufferSize, data, copy);
+ m_bufferSize += copy;
+ m_skipBytes -= copy;
+
+ if (m_skipBytes)
+ {
+ if (bufferSize)
+ *bufferSize = 0;
+ return copy;
+ }
+
+ GetPacket(buffer, bufferSize);
+ return copy;
+ }
+ else
+ {
+ unsigned int consumed = 0;
+ unsigned int offset = 0;
+ unsigned int room = sizeof(m_buffer) - m_bufferSize;
+ while (true)
+ {
+ if (!size)
+ {
+ if (bufferSize)
+ *bufferSize = 0;
+ return consumed;
+ }
+
+ unsigned int copy = std::min(room, size);
+ memcpy(m_buffer + m_bufferSize, data, copy);
+ m_bufferSize += copy;
+ consumed += copy;
+ data += copy;
+ size -= copy;
+ room -= copy;
+
+ if (m_needBytes > m_bufferSize)
+ continue;
+
+ m_needBytes = 0;
+ offset = (this->*m_syncFunc)(m_buffer, m_bufferSize);
+
+ if (m_hasSync)
+ break;
+ else
+ {
+ // lost sync
+ m_syncFunc = &CAEStreamParser::DetectType;
+ m_info.m_type = CAEStreamInfo::STREAM_TYPE_NULL;
+ m_info.m_repeat = 1;
+
+ // if the buffer is full, or the offset < the buffer size
+ if (m_bufferSize == sizeof(m_buffer) || offset < m_bufferSize)
+ {
+ m_bufferSize -= offset;
+ room += offset;
+ memmove(m_buffer, m_buffer + offset, m_bufferSize);
+ }
+ }
+ }
+
+ // if we got here, we acquired sync on the buffer
+
+ // align the buffer
+ if (offset)
+ {
+ m_bufferSize -= offset;
+ memmove(m_buffer, m_buffer + offset, m_bufferSize);
+ }
+
+ // bytes to skip until the next packet
+ m_skipBytes = std::max(0, (int)m_fsize - (int)m_bufferSize);
+ if (m_skipBytes)
+ {
+ if (bufferSize)
+ *bufferSize = 0;
+ return consumed;
+ }
+
+ if (!m_needBytes)
+ GetPacket(buffer, bufferSize);
+ else if (bufferSize)
+ *bufferSize = 0;
+
+ return consumed;
+ }
+}
+
+void CAEStreamParser::GetPacket(uint8_t** buffer, unsigned int* bufferSize)
+{
+ // if the caller wants the packet
+ if (buffer)
+ {
+ // if it is dtsHD and we only want the core, just fetch that
+ unsigned int size = m_fsize;
+ if (m_info.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_CORE)
+ size = m_coreSize;
+
+ // make sure the buffer is allocated and big enough
+ if (!*buffer || !bufferSize || *bufferSize < size)
+ {
+ delete[] * buffer;
+ *buffer = new uint8_t[size];
+ }
+
+ // copy the data into the buffer and update the size
+ memcpy(*buffer, m_buffer, size);
+ if (bufferSize)
+ *bufferSize = size;
+ }
+
+ // remove the parsed data from the buffer
+ m_bufferSize -= m_fsize;
+ memmove(m_buffer, m_buffer + m_fsize, m_bufferSize);
+ m_fsize = 0;
+ m_coreSize = 0;
+}
+
+// SYNC FUNCTIONS
+
+// This function looks for sync words across the types in parallel, and only does an exhaustive
+// test if it finds a syncword. Once sync has been established, the relevant sync function sets
+// m_syncFunc to itself. This function will only be called again if total sync is lost, which
+// allows is to switch stream types on the fly much like a real receiver does.
+unsigned int CAEStreamParser::DetectType(uint8_t* data, unsigned int size)
+{
+ unsigned int skipped = 0;
+ unsigned int possible = 0;
+
+ while (size > 8)
+ {
+ // if it could be DTS
+ unsigned int header = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ if (header == DTS_PREAMBLE_14LE || header == DTS_PREAMBLE_14BE || header == DTS_PREAMBLE_16LE ||
+ header == DTS_PREAMBLE_16BE)
+ {
+ unsigned int skip = SyncDTS(data, size);
+ if (m_hasSync || m_needBytes)
+ return skipped + skip;
+ else
+ possible = skipped;
+ }
+
+ // if it could be AC3
+ if (data[0] == 0x0b && data[1] == 0x77)
+ {
+ unsigned int skip = SyncAC3(data, size);
+ if (m_hasSync || m_needBytes)
+ return skipped + skip;
+ else
+ possible = skipped;
+ }
+
+ // if it could be TrueHD
+ if (data[4] == 0xf8 && data[5] == 0x72 && data[6] == 0x6f && data[7] == 0xba)
+ {
+ unsigned int skip = SyncTrueHD(data, size);
+ if (m_hasSync)
+ return skipped + skip;
+ else
+ possible = skipped;
+ }
+
+ // move along one byte
+ --size;
+ ++skipped;
+ ++data;
+ }
+
+ return possible ? possible : skipped;
+}
+
+bool CAEStreamParser::TrySyncAC3(uint8_t* data,
+ unsigned int size,
+ bool resyncing,
+ bool wantEAC3dependent)
+{
+ if (size < 8)
+ return false;
+
+ // look for an ac3 sync word
+ if (data[0] != 0x0b || data[1] != 0x77)
+ return false;
+
+ uint8_t bsid = data[5] >> 3;
+ uint8_t acmod = data[6] >> 5;
+ uint8_t lfeon;
+
+ int8_t pos = 4;
+ if ((acmod & 0x1) && (acmod != 0x1))
+ pos -= 2;
+ if (acmod & 0x4)
+ pos -= 2;
+ if (acmod == 0x2)
+ pos -= 2;
+ if (pos < 0)
+ lfeon = (data[7] & 0x64) ? 1 : 0;
+ else
+ lfeon = ((data[6] >> pos) & 0x1) ? 1 : 0;
+
+ if (bsid > 0x11 || acmod > 7)
+ return false;
+
+ if (bsid <= 10)
+ {
+ // Normal AC-3
+
+ if (wantEAC3dependent)
+ return false;
+
+ uint8_t fscod = data[4] >> 6;
+ uint8_t frmsizecod = data[4] & 0x3F;
+ if (fscod == 3 || frmsizecod > 37)
+ return false;
+
+ // get the details we need to check crc1 and framesize
+ unsigned int bitRate = AC3Bitrates[frmsizecod >> 1];
+ unsigned int framesize = 0;
+ switch (fscod)
+ {
+ case 0:
+ framesize = bitRate * 2;
+ break;
+ case 1:
+ framesize = (320 * bitRate / 147 + (frmsizecod & 1 ? 1 : 0));
+ break;
+ case 2:
+ framesize = bitRate * 4;
+ break;
+ }
+
+ m_fsize = framesize << 1;
+ m_info.m_sampleRate = AC3FSCod[fscod];
+
+ // dont do extensive testing if we have not lost sync
+ if (m_info.m_type == CAEStreamInfo::STREAM_TYPE_AC3 && !resyncing)
+ return true;
+
+ // this may be the main stream of EAC3
+ unsigned int fsizeMain = m_fsize;
+ unsigned int reqBytes = fsizeMain + 8;
+ if (size < reqBytes)
+ {
+ // not enough data to check for E-AC3 dependent frame, request more
+ m_needBytes = reqBytes;
+ m_fsize = 0;
+ // no need to resync => return true
+ return true;
+ }
+ m_info.m_ac3FrameSize = fsizeMain;
+ if (TrySyncAC3(data + fsizeMain, size - fsizeMain, resyncing, true))
+ {
+ // concatenate the main and dependent frames
+ m_fsize += fsizeMain;
+ return true;
+ }
+
+ unsigned int crc_size;
+ // if we have enough data, validate the entire packet, else try to validate crc2 (5/8 of the packet)
+ if (framesize <= size)
+ crc_size = framesize - 1;
+ else
+ crc_size = (framesize >> 1) + (framesize >> 3) - 1;
+
+ if (crc_size <= size)
+ if (av_crc(av_crc_get_table(AV_CRC_16_ANSI), 0, &data[2], crc_size * 2))
+ return false;
+
+ // if we get here, we can sync
+ m_hasSync = true;
+ m_info.m_channels = AC3Channels[acmod] + lfeon;
+ m_syncFunc = &CAEStreamParser::SyncAC3;
+ m_info.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ m_info.m_ac3FrameSize += m_fsize;
+ m_info.m_repeat = 1;
+
+ CLog::Log(LOGINFO, "CAEStreamParser::TrySyncAC3 - AC3 stream detected ({} channels, {}Hz)",
+ m_info.m_channels, m_info.m_sampleRate);
+ return true;
+ }
+ else
+ {
+ // Enhanced AC-3
+ uint8_t strmtyp = data[2] >> 6;
+ if (strmtyp == 3)
+ return false;
+
+ if (strmtyp != 1 && wantEAC3dependent)
+ {
+ CLog::Log(LOGDEBUG,
+ "CAEStreamParser::TrySyncAC3 - Unexpected stream type: {} (wantEAC3dependent: {})",
+ strmtyp, wantEAC3dependent);
+ return false;
+ }
+
+ unsigned int framesize = (((data[2] & 0x7) << 8) | data[3]) + 1;
+ uint8_t fscod = (data[4] >> 6) & 0x3;
+ uint8_t cod = (data[4] >> 4) & 0x3;
+ uint8_t acmod = (data[4] >> 1) & 0x7;
+ uint8_t lfeon = data[4] & 0x1;
+ uint8_t blocks;
+
+ if (fscod == 0x3)
+ {
+ if (cod == 0x3)
+ return false;
+
+ blocks = 6;
+ m_info.m_sampleRate = AC3FSCod[cod] >> 1;
+ }
+ else
+ {
+ blocks = AC3BlkCod[cod];
+ m_info.m_sampleRate = AC3FSCod[fscod];
+ }
+
+ m_fsize = framesize << 1;
+ m_info.m_repeat = MAX_EAC3_BLOCKS / blocks;
+
+ // EAC3 can have a dependent stream too
+ if (!wantEAC3dependent)
+ {
+ unsigned int fsizeMain = m_fsize;
+ unsigned int reqBytes = fsizeMain + 8;
+ if (size < reqBytes)
+ {
+ // not enough data to check for E-AC3 dependent frame, request more
+ m_needBytes = reqBytes;
+ m_fsize = 0;
+ // no need to resync => return true
+ return true;
+ }
+ m_info.m_ac3FrameSize = fsizeMain;
+ if (TrySyncAC3(data + fsizeMain, size - fsizeMain, resyncing, true))
+ {
+ // concatenate the main and dependent frames
+ m_fsize += fsizeMain;
+ return true;
+ }
+ }
+
+ if (m_info.m_type == CAEStreamInfo::STREAM_TYPE_EAC3 && m_hasSync && !resyncing)
+ return true;
+
+ // if we get here, we can sync
+ m_hasSync = true;
+ m_info.m_channels = AC3Channels[acmod] + lfeon;
+ m_syncFunc = &CAEStreamParser::SyncAC3;
+ m_info.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ m_info.m_ac3FrameSize += m_fsize;
+
+ CLog::Log(LOGINFO, "CAEStreamParser::TrySyncAC3 - E-AC3 stream detected ({} channels, {}Hz)",
+ m_info.m_channels, m_info.m_sampleRate);
+ return true;
+ }
+}
+
+unsigned int CAEStreamParser::SyncAC3(uint8_t* data, unsigned int size)
+{
+ unsigned int skip = 0;
+
+ for (; size - skip > 7; ++skip, ++data)
+ {
+ bool resyncing = (skip != 0);
+ if (TrySyncAC3(data, size - skip, resyncing, false))
+ return skip;
+ }
+
+ // if we get here, the entire packet is invalid and we have lost sync
+ CLog::Log(LOGINFO, "CAEStreamParser::SyncAC3 - AC3 sync lost");
+ m_hasSync = false;
+ return skip;
+}
+
+unsigned int CAEStreamParser::SyncDTS(uint8_t* data, unsigned int size)
+{
+ if (size < 13)
+ {
+ if (m_needBytes < 13)
+ m_needBytes = 14;
+ return 0;
+ }
+
+ unsigned int skip = 0;
+ for (; size - skip > 13; ++skip, ++data)
+ {
+ unsigned int header = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ unsigned int hd_sync = 0;
+ unsigned int dtsBlocks;
+ unsigned int amode;
+ unsigned int sfreq;
+ unsigned int target_rate;
+ unsigned int extension = 0;
+ unsigned int ext_type = UNKNOWN_DTS_EXTENSION;
+ unsigned int lfe;
+ int bits;
+
+ switch (header)
+ {
+ // 14bit BE
+ case DTS_PREAMBLE_14BE:
+ if (data[4] != 0x07 || (data[5] & 0xf0) != 0xf0)
+ continue;
+ dtsBlocks = (((data[5] & 0x7) << 4) | ((data[6] & 0x3C) >> 2)) + 1;
+ m_fsize = (((((data[6] & 0x3) << 8) | data[7]) << 4) | ((data[8] & 0x3C) >> 2)) + 1;
+ amode = ((data[8] & 0x3) << 4) | ((data[9] & 0xF0) >> 4);
+ target_rate = ((data[10] & 0x3e) >> 1);
+ extension = ((data[11] & 0x1));
+ ext_type = ((data[11] & 0xe) >> 1);
+ sfreq = data[9] & 0xF;
+ lfe = (data[12] & 0x18) >> 3;
+ m_info.m_dataIsLE = false;
+ bits = 14;
+ break;
+
+ // 14bit LE
+ case DTS_PREAMBLE_14LE:
+ if (data[5] != 0x07 || (data[4] & 0xf0) != 0xf0)
+ continue;
+ dtsBlocks = (((data[4] & 0x7) << 4) | ((data[7] & 0x3C) >> 2)) + 1;
+ m_fsize = (((((data[7] & 0x3) << 8) | data[6]) << 4) | ((data[9] & 0x3C) >> 2)) + 1;
+ amode = ((data[9] & 0x3) << 4) | ((data[8] & 0xF0) >> 4);
+ target_rate = ((data[11] & 0x3e) >> 1);
+ extension = ((data[10] & 0x1));
+ ext_type = ((data[10] & 0xe) >> 1);
+ sfreq = data[8] & 0xF;
+ lfe = (data[13] & 0x18) >> 3;
+ m_info.m_dataIsLE = true;
+ bits = 14;
+ break;
+
+ // 16bit BE
+ case DTS_PREAMBLE_16BE:
+ dtsBlocks = (((data[4] & 0x1) << 7) | ((data[5] & 0xFC) >> 2)) + 1;
+ m_fsize = (((((data[5] & 0x3) << 8) | data[6]) << 4) | ((data[7] & 0xF0) >> 4)) + 1;
+ amode = ((data[7] & 0x0F) << 2) | ((data[8] & 0xC0) >> 6);
+ sfreq = (data[8] & 0x3C) >> 2;
+ target_rate = ((data[8] & 0x03) << 3) | ((data[9] & 0xe0) >> 5);
+ extension = (data[10] & 0x10) >> 4;
+ ext_type = (data[10] & 0xe0) >> 5;
+ lfe = (data[10] >> 1) & 0x3;
+ m_info.m_dataIsLE = false;
+ bits = 16;
+ break;
+
+ // 16bit LE
+ case DTS_PREAMBLE_16LE:
+ dtsBlocks = (((data[5] & 0x1) << 7) | ((data[4] & 0xFC) >> 2)) + 1;
+ m_fsize = (((((data[4] & 0x3) << 8) | data[7]) << 4) | ((data[6] & 0xF0) >> 4)) + 1;
+ amode = ((data[6] & 0x0F) << 2) | ((data[9] & 0xC0) >> 6);
+ sfreq = (data[9] & 0x3C) >> 2;
+ target_rate = ((data[9] & 0x03) << 3) | ((data[8] & 0xe0) >> 5);
+ extension = (data[11] & 0x10) >> 4;
+ ext_type = (data[11] & 0xe0) >> 5;
+ lfe = (data[11] >> 1) & 0x3;
+ m_info.m_dataIsLE = true;
+ bits = 16;
+ break;
+
+ default:
+ continue;
+ }
+
+ if (sfreq == 0 || sfreq >= DTS_SFREQ_COUNT)
+ continue;
+
+ // make sure the framesize is sane
+ if (m_fsize < 96 || m_fsize > 16384)
+ continue;
+
+ bool invalid = false;
+ CAEStreamInfo::DataType dataType;
+ switch (dtsBlocks << 5)
+ {
+ case 512:
+ dataType = CAEStreamInfo::STREAM_TYPE_DTS_512;
+ break;
+ case 1024:
+ dataType = CAEStreamInfo::STREAM_TYPE_DTS_1024;
+ break;
+ case 2048:
+ dataType = CAEStreamInfo::STREAM_TYPE_DTS_2048;
+ break;
+ default:
+ invalid = true;
+ break;
+ }
+
+ if (invalid)
+ continue;
+
+ // adjust the fsize for 14 bit streams
+ if (bits == 14)
+ m_fsize = m_fsize / 14 * 16;
+
+ // we need enough data to check for DTS-HD
+ if (size - skip < m_fsize + 10)
+ {
+ // we can assume DTS sync at this point
+ m_syncFunc = &CAEStreamParser::SyncDTS;
+ m_needBytes = m_fsize + 10;
+ m_fsize = 0;
+
+ return skip;
+ }
+
+ // look for DTS-HD
+ hd_sync = (data[m_fsize] << 24) | (data[m_fsize + 1] << 16) | (data[m_fsize + 2] << 8) |
+ data[m_fsize + 3];
+ if (hd_sync == DTS_PREAMBLE_HD)
+ {
+ int hd_size;
+ bool blownup = (data[m_fsize + 5] & 0x20) != 0;
+ if (blownup)
+ hd_size = (((data[m_fsize + 6] & 0x01) << 19) | (data[m_fsize + 7] << 11) |
+ (data[m_fsize + 8] << 3) | ((data[m_fsize + 9] & 0xe0) >> 5)) +
+ 1;
+ else
+ hd_size = (((data[m_fsize + 6] & 0x1f) << 11) | (data[m_fsize + 7] << 3) |
+ ((data[m_fsize + 8] & 0xe0) >> 5)) +
+ 1;
+
+ int header_size;
+ if (blownup)
+ header_size = (((data[m_fsize + 5] & 0x1f) << 7) | ((data[m_fsize + 6] & 0xfe) >> 1)) + 1;
+ else
+ header_size = (((data[m_fsize + 5] & 0x1f) << 3) | ((data[m_fsize + 6] & 0xe0) >> 5)) + 1;
+
+ hd_sync = data[m_fsize + header_size] << 24 | data[m_fsize + header_size + 1] << 16 |
+ data[m_fsize + header_size + 2] << 8 | data[m_fsize + header_size + 3];
+
+ // set the type according to core or not
+ if (m_coreOnly)
+ dataType = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ else if (hd_sync == DTS_PREAMBLE_XLL)
+ dataType = CAEStreamInfo::STREAM_TYPE_DTSHD_MA;
+ else if (hd_sync == DTS_PREAMBLE_XCH || hd_sync == DTS_PREAMBLE_XXCH ||
+ hd_sync == DTS_PREAMBLE_X96K || hd_sync == DTS_PREAMBLE_XBR ||
+ hd_sync == DTS_PREAMBLE_LBR)
+ dataType = CAEStreamInfo::STREAM_TYPE_DTSHD;
+ else
+ dataType = m_info.m_type;
+
+ m_coreSize = m_fsize;
+ m_fsize += hd_size;
+ }
+
+ unsigned int sampleRate = DTSSampleRates[sfreq];
+ if (!m_hasSync || skip || dataType != m_info.m_type || sampleRate != m_info.m_sampleRate ||
+ dtsBlocks != m_dtsBlocks)
+ {
+ m_hasSync = true;
+ m_info.m_type = dataType;
+ m_info.m_sampleRate = sampleRate;
+ m_dtsBlocks = dtsBlocks;
+ m_info.m_channels = DTSChannels[amode] + (lfe ? 1 : 0);
+ m_syncFunc = &CAEStreamParser::SyncDTS;
+ m_info.m_repeat = 1;
+
+ if (dataType == CAEStreamInfo::STREAM_TYPE_DTSHD_MA)
+ {
+ m_info.m_channels += 2; // FIXME: this needs to be read out, not sure how to do that yet
+ m_info.m_dtsPeriod = (192000 * (8 >> 1)) * (m_dtsBlocks << 5) / m_info.m_sampleRate;
+ }
+ else if (dataType == CAEStreamInfo::STREAM_TYPE_DTSHD)
+ {
+ m_info.m_dtsPeriod = (192000 * (2 >> 1)) * (m_dtsBlocks << 5) / m_info.m_sampleRate;
+ }
+ else
+ {
+ m_info.m_dtsPeriod =
+ (m_info.m_sampleRate * (2 >> 1)) * (m_dtsBlocks << 5) / m_info.m_sampleRate;
+ }
+
+ std::string type;
+ switch (dataType)
+ {
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ type = "dtsHD";
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ type = "dtsHD MA";
+ break;
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ type = "dtsHD (core)";
+ break;
+ default:
+ type = "dts";
+ break;
+ }
+
+ if (extension)
+ {
+ switch (ext_type)
+ {
+ case 0:
+ type += " XCH";
+ break;
+ case 2:
+ type += " X96";
+ break;
+ case 6:
+ type += " XXCH";
+ break;
+ default:
+ type += " ext unknown";
+ break;
+ }
+ }
+
+ CLog::Log(LOGINFO,
+ "CAEStreamParser::SyncDTS - {} stream detected ({} channels, {}Hz, {}bit {}, "
+ "period: {}, syncword: 0x{:x}, target rate: 0x{:x}, framesize {}))",
+ type, m_info.m_channels, m_info.m_sampleRate, bits, m_info.m_dataIsLE ? "LE" : "BE",
+ m_info.m_dtsPeriod, hd_sync, target_rate, m_fsize);
+ }
+
+ return skip;
+ }
+
+ // lost sync
+ CLog::Log(LOGINFO, "CAEStreamParser::SyncDTS - DTS sync lost");
+ m_hasSync = false;
+ return skip;
+}
+
+inline unsigned int CAEStreamParser::GetTrueHDChannels(const uint16_t chanmap)
+{
+ int channels = 0;
+ for (int i = 0; i < 13; ++i)
+ channels += THDChanMap[i] * ((chanmap >> i) & 1);
+ return channels;
+}
+
+unsigned int CAEStreamParser::SyncTrueHD(uint8_t* data, unsigned int size)
+{
+ unsigned int left = size;
+ unsigned int skip = 0;
+
+ // if MLP
+ for (; left; ++skip, ++data, --left)
+ {
+ // if we dont have sync and there is less the 8 bytes, then break out
+ if (!m_hasSync && left < 8)
+ return size;
+
+ // if its a major audio unit
+ uint16_t length = ((data[0] & 0x0F) << 8 | data[1]) << 1;
+ uint32_t syncword = ((((data[4] << 8 | data[5]) << 8) | data[6]) << 8) | data[7];
+ if (syncword == 0xf8726fba)
+ {
+ // we need 32 bytes to sync on a master audio unit
+ if (left < 32)
+ return skip;
+
+ // get the rate and ensure its valid
+ int rate = (data[8] & 0xf0) >> 4;
+ if (rate == 0xF)
+ continue;
+
+ unsigned int major_sync_size = 28;
+ if (data[29] & 1)
+ {
+ // extension(s) present, look up count
+ int extension_count = data[30] >> 4;
+ major_sync_size += 2 + extension_count * 2;
+ }
+
+ if (left < 4 + major_sync_size)
+ return skip;
+
+ // verify the crc of the audio unit
+ uint16_t crc = av_crc(m_crcTrueHD, 0, data + 4, major_sync_size - 4);
+ crc ^= (data[4 + major_sync_size - 3] << 8) | data[4 + major_sync_size - 4];
+ if (((data[4 + major_sync_size - 1] << 8) | data[4 + major_sync_size - 2]) != crc)
+ continue;
+
+ // get the sample rate and substreams, we have a valid master audio unit
+ m_info.m_sampleRate = (rate & 0x8 ? 44100 : 48000) << (rate & 0x7);
+ m_substreams = (data[20] & 0xF0) >> 4;
+
+ // get the number of encoded channels
+ uint16_t channel_map = ((data[10] & 0x1F) << 8) | data[11];
+ if (!channel_map)
+ channel_map = (data[9] << 1) | (data[10] >> 7);
+ m_info.m_channels = CAEStreamParser::GetTrueHDChannels(channel_map);
+
+ if (!m_hasSync)
+ CLog::Log(LOGINFO,
+ "CAEStreamParser::SyncTrueHD - TrueHD stream detected ({} channels, {}Hz)",
+ m_info.m_channels, m_info.m_sampleRate);
+
+ m_hasSync = true;
+ m_fsize = length;
+ m_info.m_type = CAEStreamInfo::STREAM_TYPE_TRUEHD;
+ m_syncFunc = &CAEStreamParser::SyncTrueHD;
+ m_info.m_repeat = 1;
+ return skip;
+ }
+ else
+ {
+ // we cant sink to a subframe until we have the information from a master audio unit
+ if (!m_hasSync)
+ continue;
+
+ // if there is not enough data left to verify the packet, just return the skip amount
+ if (left < (unsigned int)m_substreams * 4)
+ return skip;
+
+ // verify the parity
+ int p = 0;
+ uint8_t check = 0;
+ for (int i = -1; i < m_substreams; ++i)
+ {
+ check ^= data[p++];
+ check ^= data[p++];
+ if (i == -1 || data[p - 2] & 0x80)
+ {
+ check ^= data[p++];
+ check ^= data[p++];
+ }
+ }
+
+ // if the parity nibble does not match
+ if ((((check >> 4) ^ check) & 0xF) != 0xF)
+ {
+ // lost sync
+ m_hasSync = false;
+ CLog::Log(LOGINFO, "CAEStreamParser::SyncTrueHD - Sync Lost");
+ continue;
+ }
+ else
+ {
+ m_fsize = length;
+ return skip;
+ }
+ }
+ }
+
+ // lost sync
+ m_hasSync = false;
+ return skip;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEStreamInfo.h b/xbmc/cores/AudioEngine/Utils/AEStreamInfo.h
new file mode 100644
index 0000000..dff809d
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEStreamInfo.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEPackIEC61937.h"
+#include "AEChannelInfo.h"
+#include <stdint.h>
+
+/* ffmpeg re-defines this, so undef it to squash the warning */
+#undef restrict
+
+extern "C" {
+#include <libavutil/crc.h>
+}
+
+class CAEStreamInfo
+{
+public:
+ double GetDuration() const;
+ bool operator==(const CAEStreamInfo& info) const;
+
+ enum DataType
+ {
+ STREAM_TYPE_NULL,
+ STREAM_TYPE_AC3,
+ STREAM_TYPE_DTS_512,
+ STREAM_TYPE_DTS_1024,
+ STREAM_TYPE_DTS_2048,
+ STREAM_TYPE_DTSHD,
+ STREAM_TYPE_DTSHD_CORE,
+ STREAM_TYPE_EAC3,
+ STREAM_TYPE_MLP,
+ STREAM_TYPE_TRUEHD,
+ STREAM_TYPE_DTSHD_MA
+ };
+ DataType m_type = STREAM_TYPE_NULL;
+ unsigned int m_sampleRate;
+ unsigned int m_channels;
+ bool m_dataIsLE = true;
+ unsigned int m_dtsPeriod = 0;
+ unsigned int m_repeat = 0;
+ unsigned int m_ac3FrameSize = 0;
+};
+
+class CAEStreamParser
+{
+public:
+
+ CAEStreamParser();
+ ~CAEStreamParser() = default;
+
+ int AddData(uint8_t *data, unsigned int size, uint8_t **buffer = NULL, unsigned int *bufferSize = 0);
+
+ void SetCoreOnly(bool value) { m_coreOnly = value; }
+ unsigned int IsValid() const { return m_hasSync; }
+ unsigned int GetSampleRate() const { return m_info.m_sampleRate; }
+ unsigned int GetChannels() const { return m_info.m_channels; }
+ unsigned int GetFrameSize() const { return m_fsize; }
+ // unsigned int GetDTSBlocks() const { return m_dtsBlocks; }
+ unsigned int GetDTSPeriod() const { return m_info.m_dtsPeriod; }
+ unsigned int GetEAC3BlocksDiv() const { return m_info.m_repeat; }
+ enum CAEStreamInfo::DataType GetDataType() const { return m_info.m_type; }
+ bool IsLittleEndian() const { return m_info.m_dataIsLE; }
+ unsigned int GetBufferSize() const { return m_bufferSize; }
+ CAEStreamInfo& GetStreamInfo() { return m_info; }
+ void Reset();
+
+private:
+ uint8_t m_buffer[MAX_IEC61937_PACKET];
+ unsigned int m_bufferSize = 0;
+ unsigned int m_skipBytes = 0;
+
+ typedef unsigned int (CAEStreamParser::*ParseFunc)(uint8_t *data, unsigned int size);
+
+ CAEStreamInfo m_info;
+ bool m_coreOnly = false;
+ unsigned int m_needBytes = 0;
+ ParseFunc m_syncFunc;
+ bool m_hasSync = false;
+
+ unsigned int m_coreSize = 0; /* core size for dtsHD */
+ unsigned int m_dtsBlocks = 0;
+ unsigned int m_fsize = 0;
+ int m_substreams = 0; /* used for TrueHD */
+ AVCRC m_crcTrueHD[1024]; /* TrueHD crc table */
+
+ void GetPacket(uint8_t **buffer, unsigned int *bufferSize);
+ unsigned int DetectType(uint8_t *data, unsigned int size);
+ bool TrySyncAC3(uint8_t *data, unsigned int size, bool resyncing, bool wantEAC3dependent);
+ unsigned int SyncAC3(uint8_t *data, unsigned int size);
+ unsigned int SyncDTS(uint8_t *data, unsigned int size);
+ unsigned int SyncTrueHD(uint8_t *data, unsigned int size);
+
+ static unsigned int GetTrueHDChannels(const uint16_t chanmap);
+};
+
diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp
new file mode 100644
index 0000000..bfa2cf9
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#ifndef __STDC_LIMIT_MACROS
+ #define __STDC_LIMIT_MACROS
+#endif
+
+#include "AEUtil.h"
+#include "utils/log.h"
+#include "utils/TimeUtils.h"
+
+#include <cassert>
+
+#if defined(HAVE_SSE) && defined(__SSE__)
+#include <xmmintrin.h>
+#endif
+
+extern "C" {
+#include <libavutil/channel_layout.h>
+}
+
+void AEDelayStatus::SetDelay(double d)
+{
+ delay = d;
+ maxcorrection = d;
+ tick = CurrentHostCounter();
+}
+
+double AEDelayStatus::GetDelay() const
+{
+ double d = 0;
+ if (tick)
+ d = (double)(CurrentHostCounter() - tick) / CurrentHostFrequency();
+ if (d > maxcorrection)
+ d = maxcorrection;
+
+ return delay - d;
+}
+
+CAEChannelInfo CAEUtil::GuessChLayout(const unsigned int channels)
+{
+ CLog::Log(LOGWARNING, "CAEUtil::GuessChLayout - "
+ "This method should really never be used, please fix the code that called this");
+
+ CAEChannelInfo result;
+ if (channels < 1 || channels > 8)
+ return result;
+
+ switch (channels)
+ {
+ case 1: result = AE_CH_LAYOUT_1_0; break;
+ case 2: result = AE_CH_LAYOUT_2_0; break;
+ case 3: result = AE_CH_LAYOUT_3_0; break;
+ case 4: result = AE_CH_LAYOUT_4_0; break;
+ case 5: result = AE_CH_LAYOUT_5_0; break;
+ case 6: result = AE_CH_LAYOUT_5_1; break;
+ case 7: result = AE_CH_LAYOUT_7_0; break;
+ case 8: result = AE_CH_LAYOUT_7_1; break;
+ }
+
+ return result;
+}
+
+const char* CAEUtil::GetStdChLayoutName(const enum AEStdChLayout layout)
+{
+ if (layout < 0 || layout >= AE_CH_LAYOUT_MAX)
+ return "UNKNOWN";
+
+ static const char* layouts[AE_CH_LAYOUT_MAX] =
+ {
+ "1.0",
+ "2.0", "2.1", "3.0", "3.1", "4.0",
+ "4.1", "5.0", "5.1", "7.0", "7.1"
+ };
+
+ return layouts[layout];
+}
+
+unsigned int CAEUtil::DataFormatToBits(const enum AEDataFormat dataFormat)
+{
+ if (dataFormat < 0 || dataFormat >= AE_FMT_MAX)
+ return 0;
+
+ static const unsigned int formats[AE_FMT_MAX] =
+ {
+ 8, /* U8 */
+
+ 16, /* S16BE */
+ 16, /* S16LE */
+ 16, /* S16NE */
+
+ 32, /* S32BE */
+ 32, /* S32LE */
+ 32, /* S32NE */
+
+ 32, /* S24BE */
+ 32, /* S24LE */
+ 32, /* S24NE */
+ 32, /* S24NER */
+
+ 24, /* S24BE3 */
+ 24, /* S24LE3 */
+ 24, /* S24NE3 */
+
+ sizeof(double) << 3, /* DOUBLE */
+ sizeof(float ) << 3, /* FLOAT */
+
+ 8, /* RAW */
+
+ 8, /* U8P */
+ 16, /* S16NEP */
+ 32, /* S32NEP */
+ 32, /* S24NEP */
+ 32, /* S24NERP*/
+ 24, /* S24NE3P*/
+ sizeof(double) << 3, /* DOUBLEP */
+ sizeof(float ) << 3 /* FLOATP */
+ };
+
+ return formats[dataFormat];
+}
+
+unsigned int CAEUtil::DataFormatToUsedBits(const enum AEDataFormat dataFormat)
+{
+ if (dataFormat == AE_FMT_S24BE4 || dataFormat == AE_FMT_S24LE4 ||
+ dataFormat == AE_FMT_S24NE4 || dataFormat == AE_FMT_S24NE4MSB)
+ return 24;
+ else
+ return DataFormatToBits(dataFormat);
+}
+
+unsigned int CAEUtil::DataFormatToDitherBits(const enum AEDataFormat dataFormat)
+{
+ if (dataFormat == AE_FMT_S24NE4MSB)
+ return 8;
+ if (dataFormat == AE_FMT_S24NE3)
+ return -8;
+ else
+ return 0;
+}
+
+const char* CAEUtil::StreamTypeToStr(const enum CAEStreamInfo::DataType dataType)
+{
+ switch (dataType)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ return "STREAM_TYPE_AC3";
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ return "STREAM_TYPE_DTSHD";
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ return "STREAM_TYPE_DTSHD_MA";
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ return "STREAM_TYPE_DTSHD_CORE";
+ case CAEStreamInfo::STREAM_TYPE_DTS_1024:
+ return "STREAM_TYPE_DTS_1024";
+ case CAEStreamInfo::STREAM_TYPE_DTS_2048:
+ return "STREAM_TYPE_DTS_2048";
+ case CAEStreamInfo::STREAM_TYPE_DTS_512:
+ return "STREAM_TYPE_DTS_512";
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ return "STREAM_TYPE_EAC3";
+ case CAEStreamInfo::STREAM_TYPE_MLP:
+ return "STREAM_TYPE_MLP";
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ return "STREAM_TYPE_TRUEHD";
+
+ default:
+ return "STREAM_TYPE_NULL";
+ }
+}
+
+const char* CAEUtil::DataFormatToStr(const enum AEDataFormat dataFormat)
+{
+ if (dataFormat < 0 || dataFormat >= AE_FMT_MAX)
+ return "UNKNOWN";
+
+ static const char *formats[AE_FMT_MAX] =
+ {
+ "AE_FMT_U8",
+
+ "AE_FMT_S16BE",
+ "AE_FMT_S16LE",
+ "AE_FMT_S16NE",
+
+ "AE_FMT_S32BE",
+ "AE_FMT_S32LE",
+ "AE_FMT_S32NE",
+
+ "AE_FMT_S24BE4",
+ "AE_FMT_S24LE4",
+ "AE_FMT_S24NE4", /* S24 in 4 bytes */
+ "AE_FMT_S24NE4MSB",
+
+ "AE_FMT_S24BE3",
+ "AE_FMT_S24LE3",
+ "AE_FMT_S24NE3", /* S24 in 3 bytes */
+
+ "AE_FMT_DOUBLE",
+ "AE_FMT_FLOAT",
+
+ "AE_FMT_RAW",
+
+ /* planar formats */
+ "AE_FMT_U8P",
+ "AE_FMT_S16NEP",
+ "AE_FMT_S32NEP",
+ "AE_FMT_S24NE4P",
+ "AE_FMT_S24NE4MSBP",
+ "AE_FMT_S24NE3P",
+ "AE_FMT_DOUBLEP",
+ "AE_FMT_FLOATP"
+ };
+
+ return formats[dataFormat];
+}
+
+#if defined(HAVE_SSE) && defined(__SSE__)
+void CAEUtil::SSEMulArray(float *data, const float mul, uint32_t count)
+{
+ const __m128 m = _mm_set_ps1(mul);
+
+ /* work around invalid alignment */
+ while (((uintptr_t)data & 0xF) && count > 0)
+ {
+ data[0] *= mul;
+ ++data;
+ --count;
+ }
+
+ uint32_t even = count & ~0x3;
+ for (uint32_t i = 0; i < even; i+=4, data+=4)
+ {
+ __m128 to = _mm_load_ps(data);
+ *(__m128*)data = _mm_mul_ps (to, m);
+ }
+
+ if (even != count)
+ {
+ uint32_t odd = count - even;
+ if (odd == 1)
+ data[0] *= mul;
+ else
+ {
+ __m128 to;
+ if (odd == 2)
+ {
+ to = _mm_setr_ps(data[0], data[1], 0, 0);
+ __m128 ou = _mm_mul_ps(to, m);
+ data[0] = ((float*)&ou)[0];
+ data[1] = ((float*)&ou)[1];
+ }
+ else
+ {
+ to = _mm_setr_ps(data[0], data[1], data[2], 0);
+ __m128 ou = _mm_mul_ps(to, m);
+ data[0] = ((float*)&ou)[0];
+ data[1] = ((float*)&ou)[1];
+ data[2] = ((float*)&ou)[2];
+ }
+ }
+ }
+}
+
+void CAEUtil::SSEMulAddArray(float *data, float *add, const float mul, uint32_t count)
+{
+ const __m128 m = _mm_set_ps1(mul);
+
+ /* work around invalid alignment */
+ while ((((uintptr_t)data & 0xF) || ((uintptr_t)add & 0xF)) && count > 0)
+ {
+ data[0] += add[0] * mul;
+ ++add;
+ ++data;
+ --count;
+ }
+
+ uint32_t even = count & ~0x3;
+ for (uint32_t i = 0; i < even; i+=4, data+=4, add+=4)
+ {
+ __m128 ad = _mm_load_ps(add );
+ __m128 to = _mm_load_ps(data);
+ *(__m128*)data = _mm_add_ps (to, _mm_mul_ps(ad, m));
+ }
+
+ if (even != count)
+ {
+ uint32_t odd = count - even;
+ if (odd == 1)
+ data[0] += add[0] * mul;
+ else
+ {
+ __m128 ad;
+ __m128 to;
+ if (odd == 2)
+ {
+ ad = _mm_setr_ps(add [0], add [1], 0, 0);
+ to = _mm_setr_ps(data[0], data[1], 0, 0);
+ __m128 ou = _mm_add_ps(to, _mm_mul_ps(ad, m));
+ data[0] = ((float*)&ou)[0];
+ data[1] = ((float*)&ou)[1];
+ }
+ else
+ {
+ ad = _mm_setr_ps(add [0], add [1], add [2], 0);
+ to = _mm_setr_ps(data[0], data[1], data[2], 0);
+ __m128 ou = _mm_add_ps(to, _mm_mul_ps(ad, m));
+ data[0] = ((float*)&ou)[0];
+ data[1] = ((float*)&ou)[1];
+ data[2] = ((float*)&ou)[2];
+ }
+ }
+ }
+}
+#endif
+
+inline float CAEUtil::SoftClamp(const float x)
+{
+#if 1
+ /*
+ This is a rational function to approximate a tanh-like soft clipper.
+ It is based on the pade-approximation of the tanh function with tweaked coefficients.
+ See: http://www.musicdsp.org/showone.php?id=238
+ */
+ if (x < -3.0f)
+ return -1.0f;
+ else if (x > 3.0f)
+ return 1.0f;
+ float y = x * x;
+ return x * (27.0f + y) / (27.0f + 9.0f * y);
+#else
+ /* slower method using tanh, but more accurate */
+
+ static const double k = 0.9f;
+ /* perform a soft clamp */
+ if (x > k)
+ x = (float) (tanh((x - k) / (1 - k)) * (1 - k) + k);
+ else if (x < -k)
+ x = (float) (tanh((x + k) / (1 - k)) * (1 - k) - k);
+
+ /* hard clamp anything still outside the bounds */
+ if (x > 1.0f)
+ return 1.0f;
+ if (x < -1.0f)
+ return -1.0f;
+
+ /* return the final sample */
+ return x;
+#endif
+}
+
+void CAEUtil::ClampArray(float *data, uint32_t count)
+{
+#if !defined(HAVE_SSE) || !defined(__SSE__)
+ for (uint32_t i = 0; i < count; ++i)
+ data[i] = SoftClamp(data[i]);
+
+#else
+ const __m128 c1 = _mm_set_ps1(27.0f);
+ const __m128 c2 = _mm_set_ps1(27.0f + 9.0f);
+
+ /* work around invalid alignment */
+ while (((uintptr_t)data & 0xF) && count > 0)
+ {
+ data[0] = SoftClamp(data[0]);
+ ++data;
+ --count;
+ }
+
+ uint32_t even = count & ~0x3;
+ for (uint32_t i = 0; i < even; i+=4, data+=4)
+ {
+ /* tanh approx clamp */
+ __m128 dt = _mm_load_ps(data);
+ __m128 tmp = _mm_mul_ps(dt, dt);
+ *(__m128*)data = _mm_div_ps(
+ _mm_mul_ps(
+ dt,
+ _mm_add_ps(c1, tmp)
+ ),
+ _mm_add_ps(c2, tmp)
+ );
+ }
+
+ if (even != count)
+ {
+ uint32_t odd = count - even;
+ if (odd == 1)
+ data[0] = SoftClamp(data[0]);
+ else
+ {
+ __m128 dt;
+ __m128 tmp;
+ __m128 out;
+ if (odd == 2)
+ {
+ /* tanh approx clamp */
+ dt = _mm_setr_ps(data[0], data[1], 0, 0);
+ tmp = _mm_mul_ps(dt, dt);
+ out = _mm_div_ps(
+ _mm_mul_ps(
+ dt,
+ _mm_add_ps(c1, tmp)
+ ),
+ _mm_add_ps(c2, tmp)
+ );
+
+ data[0] = ((float*)&out)[0];
+ data[1] = ((float*)&out)[1];
+ }
+ else
+ {
+ /* tanh approx clamp */
+ dt = _mm_setr_ps(data[0], data[1], data[2], 0);
+ tmp = _mm_mul_ps(dt, dt);
+ out = _mm_div_ps(
+ _mm_mul_ps(
+ dt,
+ _mm_add_ps(c1, tmp)
+ ),
+ _mm_add_ps(c2, tmp)
+ );
+
+ data[0] = ((float*)&out)[0];
+ data[1] = ((float*)&out)[1];
+ data[2] = ((float*)&out)[2];
+ }
+ }
+ }
+#endif
+}
+
+bool CAEUtil::S16NeedsByteSwap(AEDataFormat in, AEDataFormat out)
+{
+ const AEDataFormat nativeFormat =
+#ifdef WORDS_BIGENDIAN
+ AE_FMT_S16BE;
+#else
+ AE_FMT_S16LE;
+#endif
+
+ if (in == AE_FMT_S16NE || (in == AE_FMT_RAW))
+ in = nativeFormat;
+ if (out == AE_FMT_S16NE || (out == AE_FMT_RAW))
+ out = nativeFormat;
+
+ return in != out;
+}
+
+uint64_t CAEUtil::GetAVChannelLayout(const CAEChannelInfo &info)
+{
+ uint64_t channelLayout = 0;
+ if (info.HasChannel(AE_CH_FL)) channelLayout |= AV_CH_FRONT_LEFT;
+ if (info.HasChannel(AE_CH_FR)) channelLayout |= AV_CH_FRONT_RIGHT;
+ if (info.HasChannel(AE_CH_FC)) channelLayout |= AV_CH_FRONT_CENTER;
+ if (info.HasChannel(AE_CH_LFE)) channelLayout |= AV_CH_LOW_FREQUENCY;
+ if (info.HasChannel(AE_CH_BL)) channelLayout |= AV_CH_BACK_LEFT;
+ if (info.HasChannel(AE_CH_BR)) channelLayout |= AV_CH_BACK_RIGHT;
+ if (info.HasChannel(AE_CH_FLOC)) channelLayout |= AV_CH_FRONT_LEFT_OF_CENTER;
+ if (info.HasChannel(AE_CH_FROC)) channelLayout |= AV_CH_FRONT_RIGHT_OF_CENTER;
+ if (info.HasChannel(AE_CH_BC)) channelLayout |= AV_CH_BACK_CENTER;
+ if (info.HasChannel(AE_CH_SL)) channelLayout |= AV_CH_SIDE_LEFT;
+ if (info.HasChannel(AE_CH_SR)) channelLayout |= AV_CH_SIDE_RIGHT;
+ if (info.HasChannel(AE_CH_TC)) channelLayout |= AV_CH_TOP_CENTER;
+ if (info.HasChannel(AE_CH_TFL)) channelLayout |= AV_CH_TOP_FRONT_LEFT;
+ if (info.HasChannel(AE_CH_TFC)) channelLayout |= AV_CH_TOP_FRONT_CENTER;
+ if (info.HasChannel(AE_CH_TFR)) channelLayout |= AV_CH_TOP_FRONT_RIGHT;
+ if (info.HasChannel(AE_CH_TBL)) channelLayout |= AV_CH_TOP_BACK_LEFT;
+ if (info.HasChannel(AE_CH_TBC)) channelLayout |= AV_CH_TOP_BACK_CENTER;
+ if (info.HasChannel(AE_CH_TBR)) channelLayout |= AV_CH_TOP_BACK_RIGHT;
+
+ return channelLayout;
+}
+
+CAEChannelInfo CAEUtil::GetAEChannelLayout(uint64_t layout)
+{
+ CAEChannelInfo channelLayout;
+ channelLayout.Reset();
+
+ if (layout & AV_CH_FRONT_LEFT) channelLayout += AE_CH_FL;
+ if (layout & AV_CH_FRONT_RIGHT) channelLayout += AE_CH_FR;
+ if (layout & AV_CH_FRONT_CENTER) channelLayout += AE_CH_FC;
+ if (layout & AV_CH_LOW_FREQUENCY) channelLayout += AE_CH_LFE;
+ if (layout & AV_CH_BACK_LEFT) channelLayout += AE_CH_BL;
+ if (layout & AV_CH_BACK_RIGHT) channelLayout += AE_CH_BR;
+ if (layout & AV_CH_FRONT_LEFT_OF_CENTER) channelLayout += AE_CH_FLOC;
+ if (layout & AV_CH_FRONT_RIGHT_OF_CENTER) channelLayout += AE_CH_FROC;
+ if (layout & AV_CH_BACK_CENTER) channelLayout += AE_CH_BC;
+ if (layout & AV_CH_SIDE_LEFT) channelLayout += AE_CH_SL;
+ if (layout & AV_CH_SIDE_RIGHT) channelLayout += AE_CH_SR;
+ if (layout & AV_CH_TOP_CENTER) channelLayout += AE_CH_TC;
+ if (layout & AV_CH_TOP_FRONT_LEFT) channelLayout += AE_CH_TFL;
+ if (layout & AV_CH_TOP_FRONT_CENTER) channelLayout += AE_CH_TFC;
+ if (layout & AV_CH_TOP_FRONT_RIGHT) channelLayout += AE_CH_TFR;
+ if (layout & AV_CH_TOP_BACK_LEFT) channelLayout += AE_CH_BL;
+ if (layout & AV_CH_TOP_BACK_CENTER) channelLayout += AE_CH_BC;
+ if (layout & AV_CH_TOP_BACK_RIGHT) channelLayout += AE_CH_BR;
+
+ return channelLayout;
+}
+
+AVSampleFormat CAEUtil::GetAVSampleFormat(AEDataFormat format)
+{
+ switch (format)
+ {
+ case AEDataFormat::AE_FMT_U8:
+ return AV_SAMPLE_FMT_U8;
+ case AEDataFormat::AE_FMT_S16NE:
+ return AV_SAMPLE_FMT_S16;
+ case AEDataFormat::AE_FMT_S32NE:
+ return AV_SAMPLE_FMT_S32;
+ case AEDataFormat::AE_FMT_S24NE4:
+ return AV_SAMPLE_FMT_S32;
+ case AEDataFormat::AE_FMT_S24NE4MSB:
+ return AV_SAMPLE_FMT_S32;
+ case AEDataFormat::AE_FMT_S24NE3:
+ return AV_SAMPLE_FMT_S32;
+ case AEDataFormat::AE_FMT_FLOAT:
+ return AV_SAMPLE_FMT_FLT;
+ case AEDataFormat::AE_FMT_DOUBLE:
+ return AV_SAMPLE_FMT_DBL;
+ case AEDataFormat::AE_FMT_U8P:
+ return AV_SAMPLE_FMT_U8P;
+ case AEDataFormat::AE_FMT_S16NEP:
+ return AV_SAMPLE_FMT_S16P;
+ case AEDataFormat::AE_FMT_S32NEP:
+ return AV_SAMPLE_FMT_S32P;
+ case AEDataFormat::AE_FMT_S24NE4P:
+ return AV_SAMPLE_FMT_S32P;
+ case AEDataFormat::AE_FMT_S24NE4MSBP:
+ return AV_SAMPLE_FMT_S32P;
+ case AEDataFormat::AE_FMT_S24NE3P:
+ return AV_SAMPLE_FMT_S32P;
+ case AEDataFormat::AE_FMT_FLOATP:
+ return AV_SAMPLE_FMT_FLTP;
+ case AEDataFormat::AE_FMT_DOUBLEP:
+ return AV_SAMPLE_FMT_DBLP;
+ case AEDataFormat::AE_FMT_RAW:
+ return AV_SAMPLE_FMT_U8;
+ default:
+ {
+ if (AE_IS_PLANAR(format))
+ return AV_SAMPLE_FMT_FLTP;
+ else
+ return AV_SAMPLE_FMT_FLT;
+ }
+ }
+}
+
+uint64_t CAEUtil::GetAVChannel(enum AEChannel aechannel)
+{
+ switch (aechannel)
+ {
+ case AE_CH_FL: return AV_CH_FRONT_LEFT;
+ case AE_CH_FR: return AV_CH_FRONT_RIGHT;
+ case AE_CH_FC: return AV_CH_FRONT_CENTER;
+ case AE_CH_LFE: return AV_CH_LOW_FREQUENCY;
+ case AE_CH_BL: return AV_CH_BACK_LEFT;
+ case AE_CH_BR: return AV_CH_BACK_RIGHT;
+ case AE_CH_FLOC: return AV_CH_FRONT_LEFT_OF_CENTER;
+ case AE_CH_FROC: return AV_CH_FRONT_RIGHT_OF_CENTER;
+ case AE_CH_BC: return AV_CH_BACK_CENTER;
+ case AE_CH_SL: return AV_CH_SIDE_LEFT;
+ case AE_CH_SR: return AV_CH_SIDE_RIGHT;
+ case AE_CH_TC: return AV_CH_TOP_CENTER;
+ case AE_CH_TFL: return AV_CH_TOP_FRONT_LEFT;
+ case AE_CH_TFC: return AV_CH_TOP_FRONT_CENTER;
+ case AE_CH_TFR: return AV_CH_TOP_FRONT_RIGHT;
+ case AE_CH_TBL: return AV_CH_TOP_BACK_LEFT;
+ case AE_CH_TBC: return AV_CH_TOP_BACK_CENTER;
+ case AE_CH_TBR: return AV_CH_TOP_BACK_RIGHT;
+ default:
+ return 0;
+ }
+}
+
+int CAEUtil::GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout)
+{
+ return av_get_channel_layout_channel_index(layout, GetAVChannel(aechannel));
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.h b/xbmc/cores/AudioEngine/Utils/AEUtil.h
new file mode 100644
index 0000000..034e115
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/AEUtil.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AEAudioFormat.h"
+#include "PlatformDefs.h"
+#include <math.h>
+
+extern "C" {
+#include <libavutil/samplefmt.h>
+}
+
+// AV sync options
+enum AVSync
+{
+ SYNC_DISCON = 0,
+ SYNC_RESAMPLE
+};
+
+struct AEDelayStatus
+{
+ void SetDelay(double d);
+ double GetDelay() const;
+
+ double delay = 0.0; // delay in sink currently
+ double maxcorrection = 0.0; // time correction must not be greater than sink delay
+ int64_t tick = 0; // timestamp when delay was calculated
+};
+
+/**
+ * @brief lockless consistency guaranteeer
+ *
+ * Requires write to be a higher priority thread
+ *
+ * use in writer:
+ * m_locker.enter();
+ * update_stuff();
+ * m_locker.leave();
+ *
+ * use in reader:
+ * CAESpinLock lock(m_locker);
+ * do {
+ * get_stuff();
+ * } while(lock.retry());
+ */
+
+class CAESpinSection
+{
+public:
+ void enter() { m_enter++; }
+ void leave() { m_leave = m_enter; }
+
+protected:
+ friend class CAESpinLock;
+ volatile unsigned int m_enter = 0;
+ volatile unsigned int m_leave = 0;
+};
+
+class CAESpinLock
+{
+public:
+ explicit CAESpinLock(CAESpinSection& section)
+ : m_section(section)
+ , m_begin(section.m_enter)
+ {}
+
+ bool retry()
+ {
+ if(m_section.m_enter != m_begin
+ || m_section.m_enter != m_section.m_leave)
+ {
+ m_begin = m_section.m_enter;
+ return true;
+ }
+ else
+ return false;
+ }
+
+private:
+ CAESpinSection& m_section;
+ unsigned int m_begin;
+};
+
+class CAEUtil
+{
+private:
+
+ static float SoftClamp(const float x);
+
+public:
+ static CAEChannelInfo GuessChLayout (const unsigned int channels);
+ static const char* GetStdChLayoutName(const enum AEStdChLayout layout);
+ static unsigned int DataFormatToBits (const enum AEDataFormat dataFormat);
+ static unsigned int DataFormatToUsedBits (const enum AEDataFormat dataFormat);
+ static unsigned int DataFormatToDitherBits(const enum AEDataFormat dataFormat);
+ static const char* DataFormatToStr (const enum AEDataFormat dataFormat);
+ static const char* StreamTypeToStr(const enum CAEStreamInfo::DataType dataType);
+
+ /*! \brief convert a volume percentage (as a proportion) to a dB gain
+ We assume a dB range of 60dB, i.e. assume that 0% volume corresponds
+ to a reduction of 60dB.
+ \param value the volume from 0..1
+ \return the corresponding gain in dB from -60dB .. 0dB.
+ \sa GainToScale
+ */
+ static inline float PercentToGain(const float value)
+ {
+ static const float db_range = 60.0f;
+ return (value - 1)*db_range;
+ }
+
+ /*! \brief convert a dB gain to volume percentage (as a proportion)
+ We assume a dB range of 60dB, i.e. assume that 0% volume corresponds
+ to a reduction of 60dB.
+ \param the corresponding gain in dB from -60dB .. 0dB.
+ \return value the volume from 0..1
+ \sa ScaleToGain
+ */
+ static inline float GainToPercent(const float gain)
+ {
+ static const float db_range = 60.0f;
+ return 1+(gain/db_range);
+ }
+
+ /*! \brief convert a dB gain to a scale factor for audio manipulation
+ Inverts gain = 20 log_10(scale)
+ \param dB the gain in decibels.
+ \return the scale factor (equivalent to a voltage multiplier).
+ \sa PercentToGain
+ */
+ static inline float GainToScale(const float dB)
+ {
+ float val = 0.0f;
+ // we need to make sure that our lowest db returns plain zero
+ if (dB > -60.0f)
+ val = pow(10.0f, dB/20);
+
+ // in order to not introduce computing overhead for nearly zero
+ // values of dB e.g. -0.01 or -0.001 we clamp to top
+ if (val >= 0.99f)
+ val = 1.0f;
+
+ return val;
+ }
+
+ /*! \brief convert a scale factor to dB gain for audio manipulation
+ Inverts GainToScale result
+ \param the scale factor (equivalent to a voltage multiplier).
+ \return dB the gain in decibels.
+ \sa GainToScale
+ */
+ static inline float ScaleToGain(const float scale)
+ {
+ return 20*log10(scale);
+ }
+
+ #if defined(HAVE_SSE) && defined(__SSE__)
+ static void SSEMulArray (float *data, const float mul, uint32_t count);
+ static void SSEMulAddArray (float *data, float *add, const float mul, uint32_t count);
+ #endif
+ static void ClampArray(float *data, uint32_t count);
+
+ static bool S16NeedsByteSwap(AEDataFormat in, AEDataFormat out);
+
+ static uint64_t GetAVChannelLayout(const CAEChannelInfo &info);
+ static CAEChannelInfo GetAEChannelLayout(uint64_t layout);
+ static AVSampleFormat GetAVSampleFormat(AEDataFormat format);
+ static uint64_t GetAVChannel(enum AEChannel aechannel);
+ static int GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout);
+};
diff --git a/xbmc/cores/CMakeLists.txt b/xbmc/cores/CMakeLists.txt
new file mode 100644
index 0000000..f0d6597
--- /dev/null
+++ b/xbmc/cores/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SOURCES DataCacheCore.cpp
+ FFmpeg.cpp
+ VideoSettings.cpp)
+
+set(HEADERS DataCacheCore.h
+ EdlEdit.h
+ FFmpeg.h
+ GameSettings.h
+ IPlayer.h
+ IPlayerCallback.h
+ MenuType.h
+ VideoSettings.h)
+
+core_add_library(cores)
diff --git a/xbmc/cores/DataCacheCore.cpp b/xbmc/cores/DataCacheCore.cpp
new file mode 100644
index 0000000..a8a0c4c
--- /dev/null
+++ b/xbmc/cores/DataCacheCore.cpp
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DataCacheCore.h"
+
+#include "ServiceBroker.h"
+#include "cores/EdlEdit.h"
+
+#include <mutex>
+#include <utility>
+
+CDataCacheCore::CDataCacheCore() :
+ m_playerVideoInfo {},
+ m_playerAudioInfo {},
+ m_contentInfo {},
+ m_renderInfo {},
+ m_stateInfo {}
+{
+}
+
+CDataCacheCore::~CDataCacheCore() = default;
+
+CDataCacheCore& CDataCacheCore::GetInstance()
+{
+ return CServiceBroker::GetDataCacheCore();
+}
+
+void CDataCacheCore::Reset()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_speed = 1.0;
+ m_stateInfo.m_tempo = 1.0;
+ m_stateInfo.m_stateSeeking = false;
+ m_stateInfo.m_renderGuiLayer = false;
+ m_stateInfo.m_renderVideoLayer = false;
+ m_playerStateChanged = false;
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+
+ m_contentInfo.Reset();
+ }
+}
+
+bool CDataCacheCore::HasAVInfoChanges()
+{
+ bool ret = m_hasAVInfoChanges;
+ m_hasAVInfoChanges = false;
+ return ret;
+}
+
+void CDataCacheCore::SignalVideoInfoChange()
+{
+ m_hasAVInfoChanges = true;
+}
+
+void CDataCacheCore::SignalAudioInfoChange()
+{
+ m_hasAVInfoChanges = true;
+}
+
+void CDataCacheCore::SignalSubtitleInfoChange()
+{
+ m_hasAVInfoChanges = true;
+}
+
+void CDataCacheCore::SetVideoDecoderName(std::string name, bool isHw)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.decoderName = std::move(name);
+ m_playerVideoInfo.isHwDecoder = isHw;
+}
+
+std::string CDataCacheCore::GetVideoDecoderName()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.decoderName;
+}
+
+bool CDataCacheCore::IsVideoHwDecoder()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.isHwDecoder;
+}
+
+
+void CDataCacheCore::SetVideoDeintMethod(std::string method)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.deintMethod = std::move(method);
+}
+
+std::string CDataCacheCore::GetVideoDeintMethod()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.deintMethod;
+}
+
+void CDataCacheCore::SetVideoPixelFormat(std::string pixFormat)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.pixFormat = std::move(pixFormat);
+}
+
+std::string CDataCacheCore::GetVideoPixelFormat()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.pixFormat;
+}
+
+void CDataCacheCore::SetVideoStereoMode(std::string mode)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.stereoMode = std::move(mode);
+}
+
+std::string CDataCacheCore::GetVideoStereoMode()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.stereoMode;
+}
+
+void CDataCacheCore::SetVideoDimensions(int width, int height)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.width = width;
+ m_playerVideoInfo.height = height;
+}
+
+int CDataCacheCore::GetVideoWidth()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.width;
+}
+
+int CDataCacheCore::GetVideoHeight()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.height;
+}
+
+void CDataCacheCore::SetVideoFps(float fps)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.fps = fps;
+}
+
+float CDataCacheCore::GetVideoFps()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.fps;
+}
+
+void CDataCacheCore::SetVideoDAR(float dar)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ m_playerVideoInfo.dar = dar;
+}
+
+float CDataCacheCore::GetVideoDAR()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+
+ return m_playerVideoInfo.dar;
+}
+
+void CDataCacheCore::SetVideoInterlaced(bool isInterlaced)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+ m_playerVideoInfo.m_isInterlaced = isInterlaced;
+}
+
+bool CDataCacheCore::IsVideoInterlaced()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoPlayerSection);
+ return m_playerVideoInfo.m_isInterlaced;
+}
+
+// player audio info
+void CDataCacheCore::SetAudioDecoderName(std::string name)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ m_playerAudioInfo.decoderName = std::move(name);
+}
+
+std::string CDataCacheCore::GetAudioDecoderName()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ return m_playerAudioInfo.decoderName;
+}
+
+void CDataCacheCore::SetAudioChannels(std::string channels)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ m_playerAudioInfo.channels = std::move(channels);
+}
+
+std::string CDataCacheCore::GetAudioChannels()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ return m_playerAudioInfo.channels;
+}
+
+void CDataCacheCore::SetAudioSampleRate(int sampleRate)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ m_playerAudioInfo.sampleRate = sampleRate;
+}
+
+int CDataCacheCore::GetAudioSampleRate()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ return m_playerAudioInfo.sampleRate;
+}
+
+void CDataCacheCore::SetAudioBitsPerSample(int bitsPerSample)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ m_playerAudioInfo.bitsPerSample = bitsPerSample;
+}
+
+int CDataCacheCore::GetAudioBitsPerSample()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioPlayerSection);
+
+ return m_playerAudioInfo.bitsPerSample;
+}
+
+void CDataCacheCore::SetEditList(const std::vector<EDL::Edit>& editList)
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ m_contentInfo.SetEditList(editList);
+}
+
+const std::vector<EDL::Edit>& CDataCacheCore::GetEditList() const
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ return m_contentInfo.GetEditList();
+}
+
+void CDataCacheCore::SetCuts(const std::vector<int64_t>& cuts)
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ m_contentInfo.SetCuts(cuts);
+}
+
+const std::vector<int64_t>& CDataCacheCore::GetCuts() const
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ return m_contentInfo.GetCuts();
+}
+
+void CDataCacheCore::SetSceneMarkers(const std::vector<int64_t>& sceneMarkers)
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ m_contentInfo.SetSceneMarkers(sceneMarkers);
+}
+
+const std::vector<int64_t>& CDataCacheCore::GetSceneMarkers() const
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ return m_contentInfo.GetSceneMarkers();
+}
+
+void CDataCacheCore::SetChapters(const std::vector<std::pair<std::string, int64_t>>& chapters)
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ m_contentInfo.SetChapters(chapters);
+}
+
+const std::vector<std::pair<std::string, int64_t>>& CDataCacheCore::GetChapters() const
+{
+ std::unique_lock<CCriticalSection> lock(m_contentSection);
+ return m_contentInfo.GetChapters();
+}
+
+void CDataCacheCore::SetRenderClockSync(bool enable)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ m_renderInfo.m_isClockSync = enable;
+}
+
+bool CDataCacheCore::IsRenderClockSync()
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ return m_renderInfo.m_isClockSync;
+}
+
+// player states
+void CDataCacheCore::SeekFinished(int64_t offset)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ m_stateInfo.m_lastSeekTime = std::chrono::system_clock::now();
+ m_stateInfo.m_lastSeekOffset = offset;
+}
+
+int64_t CDataCacheCore::GetSeekOffSet() const
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_stateInfo.m_lastSeekOffset;
+}
+
+bool CDataCacheCore::HasPerformedSeek(int64_t lastSecondInterval) const
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ if (m_stateInfo.m_lastSeekTime == std::chrono::time_point<std::chrono::system_clock>{})
+ {
+ return false;
+ }
+ return (std::chrono::system_clock::now() - m_stateInfo.m_lastSeekTime) <
+ std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::duration<int64_t>(lastSecondInterval));
+}
+
+void CDataCacheCore::SetStateSeeking(bool active)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_stateSeeking = active;
+ m_playerStateChanged = true;
+}
+
+bool CDataCacheCore::IsSeeking()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_stateSeeking;
+}
+
+void CDataCacheCore::SetSpeed(float tempo, float speed)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_tempo = tempo;
+ m_stateInfo.m_speed = speed;
+}
+
+float CDataCacheCore::GetSpeed()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_speed;
+}
+
+float CDataCacheCore::GetTempo()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_tempo;
+}
+
+void CDataCacheCore::SetFrameAdvance(bool fa)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_frameAdvance = fa;
+}
+
+bool CDataCacheCore::IsFrameAdvance()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_frameAdvance;
+}
+
+bool CDataCacheCore::IsPlayerStateChanged()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ bool ret(m_playerStateChanged);
+ m_playerStateChanged = false;
+
+ return ret;
+}
+
+void CDataCacheCore::SetGuiRender(bool gui)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_renderGuiLayer = gui;
+ m_playerStateChanged = true;
+}
+
+bool CDataCacheCore::GetGuiRender()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_renderGuiLayer;
+}
+
+void CDataCacheCore::SetVideoRender(bool video)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_stateInfo.m_renderVideoLayer = video;
+ m_playerStateChanged = true;
+}
+
+bool CDataCacheCore::GetVideoRender()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateInfo.m_renderVideoLayer;
+}
+
+void CDataCacheCore::SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ m_timeInfo.m_startTime = start;
+ m_timeInfo.m_time = current;
+ m_timeInfo.m_timeMin = min;
+ m_timeInfo.m_timeMax = max;
+}
+
+void CDataCacheCore::GetPlayTimes(time_t &start, int64_t &current, int64_t &min, int64_t &max)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ start = m_timeInfo.m_startTime;
+ current = m_timeInfo.m_time;
+ min = m_timeInfo.m_timeMin;
+ max = m_timeInfo.m_timeMax;
+}
+
+time_t CDataCacheCore::GetStartTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_timeInfo.m_startTime;
+}
+
+int64_t CDataCacheCore::GetPlayTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_timeInfo.m_time;
+}
+
+int64_t CDataCacheCore::GetMinTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_timeInfo.m_timeMin;
+}
+
+int64_t CDataCacheCore::GetMaxTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_timeInfo.m_timeMax;
+}
+
+float CDataCacheCore::GetPlayPercentage()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ // Note: To calculate accurate percentage, all time data must be consistent,
+ // which is the case for data cache core. Calculation can not be done
+ // outside of data cache core or a possibility to lock the data cache
+ // core from outside would be needed.
+ int64_t iTotalTime = m_timeInfo.m_timeMax - m_timeInfo.m_timeMin;
+ if (iTotalTime <= 0)
+ return 0;
+
+ return m_timeInfo.m_time * 100 / static_cast<float>(iTotalTime);
+}
diff --git a/xbmc/cores/DataCacheCore.h b/xbmc/cores/DataCacheCore.h
new file mode 100644
index 0000000..95a734b
--- /dev/null
+++ b/xbmc/cores/DataCacheCore.h
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "EdlEdit.h"
+#include "threads/CriticalSection.h"
+
+#include <atomic>
+#include <chrono>
+#include <string>
+#include <vector>
+
+class CDataCacheCore
+{
+public:
+ CDataCacheCore();
+ virtual ~CDataCacheCore();
+ static CDataCacheCore& GetInstance();
+ void Reset();
+ bool HasAVInfoChanges();
+ void SignalVideoInfoChange();
+ void SignalAudioInfoChange();
+ void SignalSubtitleInfoChange();
+
+ // player video info
+ void SetVideoDecoderName(std::string name, bool isHw);
+ std::string GetVideoDecoderName();
+ bool IsVideoHwDecoder();
+ void SetVideoDeintMethod(std::string method);
+ std::string GetVideoDeintMethod();
+ void SetVideoPixelFormat(std::string pixFormat);
+ std::string GetVideoPixelFormat();
+ void SetVideoStereoMode(std::string mode);
+ std::string GetVideoStereoMode();
+ void SetVideoDimensions(int width, int height);
+ int GetVideoWidth();
+ int GetVideoHeight();
+ void SetVideoFps(float fps);
+ float GetVideoFps();
+ void SetVideoDAR(float dar);
+ float GetVideoDAR();
+
+ /*!
+ * @brief Set if the video is interlaced in cache.
+ * @param isInterlaced Set true when the video is interlaced
+ */
+ void SetVideoInterlaced(bool isInterlaced);
+
+ /*!
+ * @brief Check if the video is interlaced from cache
+ * @return True if interlaced, otherwise false
+ */
+ bool IsVideoInterlaced();
+
+ // player audio info
+ void SetAudioDecoderName(std::string name);
+ std::string GetAudioDecoderName();
+ void SetAudioChannels(std::string channels);
+ std::string GetAudioChannels();
+ void SetAudioSampleRate(int sampleRate);
+ int GetAudioSampleRate();
+ void SetAudioBitsPerSample(int bitsPerSample);
+ int GetAudioBitsPerSample();
+
+ // content info
+
+ /*!
+ * @brief Set the EDL edit list to cache.
+ * @param editList The vector of edits to fill.
+ */
+ void SetEditList(const std::vector<EDL::Edit>& editList);
+
+ /*!
+ * @brief Get the EDL edit list in cache.
+ * @return The EDL edits or an empty vector if no edits exist.
+ */
+ const std::vector<EDL::Edit>& GetEditList() const;
+
+ /*!
+ * @brief Set the list of cut markers in cache.
+ * @return The list of cuts or an empty list if no cuts exist
+ */
+ void SetCuts(const std::vector<int64_t>& cuts);
+
+ /*!
+ * @brief Get the list of cut markers from cache.
+ * @return The list of cut markers or an empty vector if no cuts exist.
+ */
+ const std::vector<int64_t>& GetCuts() const;
+
+ /*!
+ * @brief Set the list of scene markers in cache.
+ * @return The list of scene markers or an empty list if no scene markers exist
+ */
+ void SetSceneMarkers(const std::vector<int64_t>& sceneMarkers);
+
+ /*!
+ * @brief Get the list of scene markers markers from cache.
+ * @return The list of scene markers or an empty vector if no scene exist.
+ */
+ const std::vector<int64_t>& GetSceneMarkers() const;
+
+ void SetChapters(const std::vector<std::pair<std::string, int64_t>>& chapters);
+
+ /*!
+ * @brief Get the chapter list in cache.
+ * @return The list of chapters or an empty vector if no chapters exist.
+ */
+ const std::vector<std::pair<std::string, int64_t>>& GetChapters() const;
+
+ // render info
+ void SetRenderClockSync(bool enabled);
+ bool IsRenderClockSync();
+
+ // player states
+ /*!
+ * @brief Notifies the cache core that a seek operation has finished
+ * @param offset - the seek offset
+ */
+ void SeekFinished(int64_t offset);
+
+ void SetStateSeeking(bool active);
+ bool IsSeeking();
+
+ /*!
+ * @brief Checks if a seek has been performed in the last provided seconds interval
+ * @param lastSecondInterval - the last elapsed second interval to check for a seek operation
+ * @return true if a seek was performed in the lastSecondInterval, false otherwise
+ */
+ bool HasPerformedSeek(int64_t lastSecondInterval) const;
+
+ /*!
+ * @brief Gets the last seek offset
+ * @return the last seek offset
+ */
+ int64_t GetSeekOffSet() const;
+
+ void SetSpeed(float tempo, float speed);
+ float GetSpeed();
+ float GetTempo();
+ void SetFrameAdvance(bool fa);
+ bool IsFrameAdvance();
+ bool IsPlayerStateChanged();
+ void SetGuiRender(bool gui);
+ bool GetGuiRender();
+ void SetVideoRender(bool video);
+ bool GetVideoRender();
+ void SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max);
+ void GetPlayTimes(time_t &start, int64_t &current, int64_t &min, int64_t &max);
+
+ /*!
+ * \brief Get the start time
+ *
+ * For a typical video this will be zero. For live TV, this is a reference time
+ * in units of time_t (UTC) from which time elapsed starts. Ideally this would
+ * be the start of the tv show but can be any other time as well.
+ */
+ time_t GetStartTime();
+
+ /*!
+ * \brief Get the current time of playback
+ *
+ * This is the time elapsed, in ms, since the start time.
+ */
+ int64_t GetPlayTime();
+
+ /*!
+ * \brief Get the current percentage of playback if a playback buffer is available.
+ *
+ * If there is no playback buffer, percentage will be 0.
+ */
+ float GetPlayPercentage();
+
+ /*!
+ * \brief Get the minimum time
+ *
+ * This will be zero for a typical video. With timeshift, this is the time,
+ * in ms, that the player can go back. This can be before the start time.
+ */
+ int64_t GetMinTime();
+
+ /*!
+ * \brief Get the maximum time
+ *
+ * This is the maximum time, in ms, that the player can skip forward. For a
+ * typical video, this will be the total length. For live TV without
+ * timeshift this is zero, and for live TV with timeshift this will be the
+ * buffer ahead.
+ */
+ int64_t GetMaxTime();
+
+protected:
+ std::atomic_bool m_hasAVInfoChanges = false;
+
+ CCriticalSection m_videoPlayerSection;
+ struct SPlayerVideoInfo
+ {
+ std::string decoderName;
+ bool isHwDecoder;
+ std::string deintMethod;
+ std::string pixFormat;
+ std::string stereoMode;
+ int width;
+ int height;
+ float fps;
+ float dar;
+ bool m_isInterlaced;
+ } m_playerVideoInfo;
+
+ CCriticalSection m_audioPlayerSection;
+ struct SPlayerAudioInfo
+ {
+ std::string decoderName;
+ std::string channels;
+ int sampleRate;
+ int bitsPerSample;
+ } m_playerAudioInfo;
+
+ mutable CCriticalSection m_contentSection;
+ struct SContentInfo
+ {
+ public:
+ /*!
+ * @brief Set the EDL edit list in cache.
+ * @param editList the list of edits to store in cache
+ */
+ void SetEditList(const std::vector<EDL::Edit>& editList) { m_editList = editList; }
+
+ /*!
+ * @brief Get the EDL edit list in cache.
+ * @return the list of edits in cache
+ */
+ const std::vector<EDL::Edit>& GetEditList() const { return m_editList; }
+
+ /*!
+ * @brief Save the list of cut markers in cache.
+ * @param cuts the list of cut markers to store in cache
+ */
+ void SetCuts(const std::vector<int64_t>& cuts) { m_cuts = cuts; }
+
+ /*!
+ * @brief Get the list of cut markers in cache.
+ * @return the list of cut markers in cache
+ */
+ const std::vector<int64_t>& GetCuts() const { return m_cuts; }
+
+ /*!
+ * @brief Save the list of scene markers in cache.
+ * @param sceneMarkers the list of scene markers to store in cache
+ */
+ void SetSceneMarkers(const std::vector<int64_t>& sceneMarkers)
+ {
+ m_sceneMarkers = sceneMarkers;
+ }
+
+ /*!
+ * @brief Get the list of scene markers in cache.
+ * @return the list of scene markers in cache
+ */
+ const std::vector<int64_t>& GetSceneMarkers() const { return m_sceneMarkers; }
+
+ /*!
+ * @brief Save the chapter list in cache.
+ * @param chapters the list of chapters to store in cache
+ */
+ void SetChapters(const std::vector<std::pair<std::string, int64_t>>& chapters)
+ {
+ m_chapters = chapters;
+ }
+
+ /*!
+ * @brief Get the list of chapters in cache.
+ * @return the list of chapters in cache
+ */
+ const std::vector<std::pair<std::string, int64_t>>& GetChapters() const { return m_chapters; }
+
+ /*!
+ * @brief Reset the content cache to the original values (all empty)
+ */
+ void Reset()
+ {
+ m_editList.clear();
+ m_chapters.clear();
+ m_cuts.clear();
+ m_sceneMarkers.clear();
+ }
+
+ private:
+ /*!< list of EDL edits */
+ std::vector<EDL::Edit> m_editList;
+ /*!< name and position for chapters */
+ std::vector<std::pair<std::string, int64_t>> m_chapters;
+ /*!< position for EDL cuts */
+ std::vector<int64_t> m_cuts;
+ /*!< position for EDL scene markers */
+ std::vector<int64_t> m_sceneMarkers;
+ } m_contentInfo;
+
+ CCriticalSection m_renderSection;
+ struct SRenderInfo
+ {
+ bool m_isClockSync;
+ } m_renderInfo;
+
+ mutable CCriticalSection m_stateSection;
+ bool m_playerStateChanged = false;
+ struct SStateInfo
+ {
+ bool m_stateSeeking;
+ bool m_renderGuiLayer;
+ bool m_renderVideoLayer;
+ float m_tempo;
+ float m_speed;
+ bool m_frameAdvance;
+ /*! Time point of the last seek operation */
+ std::chrono::time_point<std::chrono::system_clock> m_lastSeekTime{
+ std::chrono::time_point<std::chrono::system_clock>{}};
+ /*! Last seek offset */
+ int64_t m_lastSeekOffset{0};
+ } m_stateInfo;
+
+ struct STimeInfo
+ {
+ time_t m_startTime;
+ int64_t m_time;
+ int64_t m_timeMax;
+ int64_t m_timeMin;
+ } m_timeInfo = {};
+};
diff --git a/xbmc/cores/DllLoader/CMakeLists.txt b/xbmc/cores/DllLoader/CMakeLists.txt
new file mode 100644
index 0000000..313b8fe
--- /dev/null
+++ b/xbmc/cores/DllLoader/CMakeLists.txt
@@ -0,0 +1,38 @@
+set(SOURCES coff.cpp
+ dll.cpp
+ DllLoader.cpp
+ DllLoaderContainer.cpp
+ dll_tracker.cpp
+ dll_tracker_file.cpp
+ dll_tracker_library.cpp
+ dll_util.cpp
+ LibraryLoader.cpp)
+
+set(HEADERS coff.h
+ coffldr.h
+ dll.h
+ DllLoader.h
+ DllLoaderContainer.h
+ dll_tracker.h
+ dll_tracker_file.h
+ dll_tracker_library.h
+ dll_util.h
+ LibraryLoader.h)
+
+if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES mmap_anon.c
+ SoLoader.cpp)
+ list(APPEND HEADERS mmap_anon.h
+ SoLoader.h)
+ if(NOT CORE_SYSTEM_NAME STREQUAL freebsd)
+ list(APPEND SOURCES ldt_keeper.c)
+ list(APPEND HEADERS ldt_keeper.h)
+ endif()
+else()
+ list(APPEND SOURCES Win32DllLoader.cpp)
+ list(APPEND HEADERS Win32DllLoader.h)
+endif()
+
+add_definitions(-DAPI_DEBUG)
+
+core_add_library(dllloader)
diff --git a/xbmc/cores/DllLoader/DllLoader-linux.cpp b/xbmc/cores/DllLoader/DllLoader-linux.cpp
new file mode 100644
index 0000000..4d56e30
--- /dev/null
+++ b/xbmc/cores/DllLoader/DllLoader-linux.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DllLoader.h"
+#include "DllLoaderContainer.h"
+
+CoffLoader::CoffLoader() :
+ hModule (NULL ),
+ CoffFileHeader (NULL ),
+ OptionHeader (NULL ),
+ WindowsHeader (NULL ),
+ Directory (NULL ),
+ SectionHeader (NULL ),
+ SymTable (NULL ),
+ StringTable (NULL ),
+ SectionData (NULL ),
+ EntryAddress (0 ),
+ NumberOfSymbols (0 ),
+ SizeOfStringTable (0 ),
+ NumOfDirectories (0 ),
+ NumOfSections (0 ),
+ FileHeaderOffset (0 )
+{
+}
+
+CoffLoader::~CoffLoader()
+{
+}
+
+DllLoaderContainer::DllLoaderContainer()
+{
+}
+
+DllLoader* DllLoaderContainer::LoadModule(const char* sName, const char* sCurrentDir, bool bLoadSymbols)
+{
+ return NULL;
+}
+
+bool DllLoader::Load()
+{
+ return false;
+}
+
+void DllLoader::Unload()
+{
+}
+
+void DllLoaderContainer::ReleaseModule(DllLoader*& pDll)
+{
+}
+
+DllLoader::DllLoader(const char *dll, bool track, bool bSystemDll, bool bLoadSymbols, Export* exp)
+{
+}
+
+DllLoader::~DllLoader()
+{
+}
+
+int DllLoader::ResolveExport(const char* x, void** y)
+{
+}
+
+DllLoaderContainer g_dlls;
diff --git a/xbmc/cores/DllLoader/DllLoader.cpp b/xbmc/cores/DllLoader/DllLoader.cpp
new file mode 100644
index 0000000..d7953de
--- /dev/null
+++ b/xbmc/cores/DllLoader/DllLoader.cpp
@@ -0,0 +1,808 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <stdlib.h>
+#include <algorithm>
+
+#include "DllLoader.h"
+#include "DllLoaderContainer.h"
+#include "filesystem/SpecialProtocol.h"
+#include "dll_tracker.h"
+#include "dll_util.h"
+#include <limits>
+#include "utils/log.h"
+
+#ifdef TARGET_WINDOWS
+extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
+#else
+#define fopen_utf8 fopen
+#endif
+
+#include "commons/Exception.h"
+
+#define DLL_PROCESS_DETACH 0
+#define DLL_PROCESS_ATTACH 1
+#define DLL_THREAD_ATTACH 2
+#define DLL_THREAD_DETACH 3
+#define DLL_PROCESS_VERIFIER 4
+
+
+#ifndef APIENTRY
+#define APIENTRY __stdcall
+#endif
+
+// Entry point of a dll (DllMain)
+typedef int (APIENTRY *EntryFunc)(HINSTANCE hinstDLL, DWORD fdwReason, void* lpvReserved);
+
+
+#ifdef TARGET_POSIX
+/*
+ * This is a dirty hack.
+ * The win32 DLLs contain an alloca routine, that first probes the soon
+ * to be allocated new memory *below* the current stack pointer in 4KByte
+ * increments. After the mem probing below the current %esp, the stack
+ * pointer is finally decremented to make room for the "alloca"ed memory.
+ * Maybe the probing code is intended to extend the stack on a windows box.
+ * Anyway, the linux kernel does *not* extend the stack by simply accessing
+ * memory below %esp; it segfaults.
+ * The extend_stack_for_dll_alloca() routine just preallocates a big chunk
+ * of memory on the stack, for use by the DLLs alloca routine.
+ * Added the noinline attribute as e.g. gcc 3.2.2 inlines this function
+ * in a way that breaks it.
+ */
+static void __attribute__((noinline)) extend_stack_for_dll_alloca(void)
+{
+ volatile int* mem =(volatile int*)alloca(0x20000);
+ *mem=0x1234;
+}
+#endif
+
+
+DllLoader::DllLoader(const char *sDll, bool bTrack, bool bSystemDll, bool bLoadSymbols, Export* exps) : LibraryLoader(sDll)
+{
+ ImportDirTable = 0;
+ m_pExportHead = NULL;
+ m_pStaticExports = exps;
+ m_bTrack = bTrack;
+ m_bSystemDll = bSystemDll;
+ m_pDlls = NULL;
+
+
+ if(!bSystemDll)
+ {
+ // Initialize FS segment, important for quicktime dll's
+#if defined(USE_LDT_KEEPER)
+ m_ldt_fs = Setup_LDT_Keeper();
+#endif
+ }
+
+ DllLoaderContainer::RegisterDll(this);
+ if (m_bTrack) tracker_dll_add(this);
+ m_bLoadSymbols=bLoadSymbols;
+
+ m_bUnloadSymbols=false;
+
+ /* system dll's are never loaded in any way, so let's just use the pointer */
+ /* to this object as their base address */
+ if (m_bSystemDll)
+ hModule = (HMODULE)this;
+
+}
+
+DllLoader::~DllLoader()
+{
+ while (m_pExportHead)
+ {
+ ExportEntry* entry = m_pExportHead;
+ m_pExportHead = entry->next;
+
+ free(entry);
+ }
+
+ while (m_pDlls)
+ {
+ LoadedList* entry = m_pDlls;
+ m_pDlls = entry->pNext;
+ LibraryLoader* lib = entry->pDll;
+ if (entry->pDll) DllLoaderContainer::ReleaseModule(lib);
+ delete entry;
+ }
+
+ // can't unload a system dll, as this might be happening during xbmc destruction
+ if(!m_bSystemDll)
+ {
+ DllLoaderContainer::UnRegisterDll(this);
+
+#ifdef USE_LDT_KEEPER
+ Restore_LDT_Keeper(m_ldt_fs);
+#endif
+ }
+ if (m_bTrack) tracker_dll_free(this);
+
+ ImportDirTable = 0;
+
+ // hModule points to DllLoader in this case
+ if (m_bSystemDll)
+ hModule = NULL;
+}
+
+int DllLoader::Parse()
+{
+ int iResult = 0;
+
+ std::string strFileName= GetFileName();
+ FILE* fp = fopen_utf8(CSpecialProtocol::TranslatePath(strFileName).c_str(), "rb");
+
+ if (fp)
+ {
+ if (CoffLoader::ParseCoff(fp))
+ {
+ if(WindowsHeader)
+ tracker_dll_set_addr(this, (uintptr_t)hModule,
+ (uintptr_t)hModule + WindowsHeader->SizeOfImage - 1);
+ else
+ {
+ uintptr_t iMinAddr = std::numeric_limits<uintptr_t>::max();
+ uintptr_t iMaxAddr = 0;
+ // dll is loaded now, this means we also know the base address of it and its size
+ for (int i = 0; i < NumOfSections; ++i)
+ {
+ iMinAddr = std::min<uintptr_t>(iMinAddr,
+ (uintptr_t)SectionHeader[i].VirtualAddress);
+ iMaxAddr = std::max<uintptr_t>(iMaxAddr,
+ (uintptr_t)(SectionHeader[i].VirtualAddress +
+ SectionHeader[i].VirtualSize));
+ }
+ if(iMaxAddr > iMinAddr)
+ {
+ iMinAddr += (uintptr_t)hModule;
+ iMaxAddr += (uintptr_t)hModule;
+ tracker_dll_set_addr(this, iMinAddr, iMaxAddr - 1);
+ }
+ }
+ LoadExports();
+ iResult = 1;
+ }
+ fclose(fp);
+ }
+ if (iResult == 0)
+ {
+ m_bTrack = false;
+ }
+ return iResult;
+}
+
+void DllLoader::PrintImportLookupTable(unsigned long ImportLookupTable_RVA)
+{
+ unsigned long *Table = (unsigned long*)RVA2Data(ImportLookupTable_RVA);
+
+ while (*Table)
+ {
+ if (*Table & 0x80000000)
+ {
+ // Process Ordinal...
+ CLog::Log(LOGDEBUG, " Ordinal: {:01X}", *Table & 0x7fffffff);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, " Don't process Hint/Name Table yet...");
+ }
+ Table++;
+ }
+}
+
+void DllLoader::PrintImportTable(ImportDirTable_t *ImportDirTable)
+{
+ ImportDirTable_t *Imp = ImportDirTable;
+ int HavePrinted = 0;
+
+ CLog::Log(LOGDEBUG, "The Coff Image contains the following imports:");
+ while ( Imp->ImportLookupTable_RVA != 0 ||
+ Imp->TimeStamp != 0 ||
+ Imp->ForwarderChain != 0 ||
+ Imp->Name_RVA != 0 ||
+ Imp->ImportAddressTable_RVA != 0)
+ {
+ char *Name;
+ HavePrinted = 1;
+
+ Name = (char*)RVA2Data(Imp->Name_RVA);
+
+ CLog::Log(LOGDEBUG, " {}:", Name);
+ CLog::Log(LOGDEBUG, " ImportAddressTable: {:04X}", Imp->ImportAddressTable_RVA);
+ CLog::Log(LOGDEBUG, " ImportLookupTable: {:04X}", Imp->ImportLookupTable_RVA);
+ CLog::Log(LOGDEBUG, " TimeStamp: {:01X}", Imp->TimeStamp);
+ CLog::Log(LOGDEBUG, " Forwarder Chain: {:01X}", Imp->ForwarderChain);
+
+ PrintImportLookupTable(Imp->ImportLookupTable_RVA);
+ CLog::Log(LOGDEBUG, "");
+ Imp++;
+ }
+ if (!HavePrinted) CLog::Log(LOGDEBUG, "None.");
+}
+
+void DllLoader::PrintExportTable(ExportDirTable_t *ExportDirTable)
+{
+ char *Name = (char*)RVA2Data(ExportDirTable->Name_RVA);
+
+ unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
+ unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
+ unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);
+
+
+ CLog::Log(LOGDEBUG, "Export Table for {}:", Name);
+
+ CLog::Log(LOGDEBUG, "ExportFlags: {:04X}", ExportDirTable->ExportFlags);
+ CLog::Log(LOGDEBUG, "TimeStamp: {:04X}", ExportDirTable->TimeStamp);
+ CLog::Log(LOGDEBUG, "Major Ver: {:02X}", ExportDirTable->MajorVersion);
+ CLog::Log(LOGDEBUG, "Minor Ver: {:02X}", ExportDirTable->MinorVersion);
+ CLog::Log(LOGDEBUG, "Name RVA: {:04X}", ExportDirTable->Name_RVA);
+ CLog::Log(LOGDEBUG, "OrdinalBase {}", ExportDirTable->OrdinalBase);
+ CLog::Log(LOGDEBUG, "NumAddrTable {}", ExportDirTable->NumAddrTable);
+ CLog::Log(LOGDEBUG, "NumNamePtrs {}", ExportDirTable->NumNamePtrs);
+ CLog::Log(LOGDEBUG, "ExportAddressTable_RVA {:04X}", ExportDirTable->ExportAddressTable_RVA);
+ CLog::Log(LOGDEBUG, "NamePointerTable_RVA {:04X}", ExportDirTable->NamePointerTable_RVA);
+ CLog::Log(LOGDEBUG, "OrdinalTable_RVA {:04X}", ExportDirTable->OrdinalTable_RVA);
+
+ CLog::Log(LOGDEBUG, "Public Exports:");
+ CLog::Log(LOGDEBUG, " ordinal hint RVA name");
+ for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
+ {
+ char *Name = (char*)RVA2Data(NamePointerTable[i]);
+
+ CLog::Log(LOGDEBUG, " {}", OrdinalTable[i] + ExportDirTable->OrdinalBase);
+ CLog::Log(LOGDEBUG, " {}", OrdinalTable[i]);
+ CLog::Log(LOGDEBUG, " {:08X}", ExportAddressTable[OrdinalTable[i]]);
+ CLog::Log(LOGDEBUG, " {}", Name);
+ }
+}
+
+int DllLoader::ResolveImports(void)
+{
+ int bResult = 1;
+ if ( NumOfDirectories >= 2 && Directory[IMPORT_TABLE].Size > 0 )
+ {
+ ImportDirTable = (ImportDirTable_t*)RVA2Data(Directory[IMPORT_TABLE].RVA);
+
+#ifdef DUMPING_DATA
+ PrintImportTable(ImportDirTable);
+#endif
+
+ ImportDirTable_t *Imp = ImportDirTable;
+
+ while ( Imp->ImportLookupTable_RVA != 0 ||
+ Imp->TimeStamp != 0 ||
+ Imp->ForwarderChain != 0 ||
+ Imp->Name_RVA != 0 ||
+ Imp->ImportAddressTable_RVA != 0)
+ {
+ const char *Name = (const char*)RVA2Data(Imp->Name_RVA);
+
+ const char* FileName=ResolveReferencedDll(Name);
+ // If possible use the dll name WITH path to resolve exports. We could have loaded
+ // a dll with the same name as another dll but from a different directory
+ if (FileName) Name=FileName;
+
+ unsigned long *Table = (unsigned long*)RVA2Data(Imp->ImportLookupTable_RVA);
+ unsigned long *Addr = (unsigned long*)RVA2Data(Imp->ImportAddressTable_RVA);
+
+ while (*Table)
+ {
+ if (*Table & 0x80000000)
+ {
+ void *Fixup;
+ if ( !ResolveOrdinal(Name, *Table&0x7ffffff, &Fixup) )
+ {
+ bResult = 0;
+ char szBuf[128];
+ CLog::Log(LOGDEBUG, "Unable to resolve ordinal {} {}", Name, *Table & 0x7ffffff);
+ sprintf(szBuf, "%lu", *Table&0x7ffffff);
+ *Addr = create_dummy_function(Name, szBuf);
+ tracker_dll_data_track(this, *Addr);
+ }
+ else
+ {
+ *Addr = (unsigned long)Fixup; //woohoo!!
+ }
+ }
+ else
+ {
+ // We don't handle Hint/Name tables yet!!!
+ char *ImpName = (char*)RVA2Data(*Table + 2);
+
+ void *Fixup;
+ if ( !ResolveName(Name, ImpName, &Fixup) )
+ {
+ *Addr=get_win_function_address(Name, ImpName);
+ if(!*Addr)
+ {
+ CLog::Log(LOGDEBUG, "Unable to resolve {} {}", Name, ImpName);
+ *Addr = create_dummy_function(Name, ImpName);
+ tracker_dll_data_track(this, *Addr);
+ bResult = 0;
+ }
+ }
+ else
+ {
+ *Addr = (unsigned long)Fixup;
+ }
+ }
+ Table++;
+ Addr++;
+ }
+ Imp++;
+ }
+ }
+ return bResult;
+}
+
+const char* DllLoader::ResolveReferencedDll(const char* dll)
+{
+ DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::LoadModule(dll, GetPath(), m_bLoadSymbols));
+
+ if (!pDll)
+ {
+ CLog::Log(LOGDEBUG, "Unable to load referenced dll {} - Dll: {}", dll, GetFileName());
+ return NULL;
+ }
+ else if (!pDll->IsSystemDll())
+ {
+ LoadedList* entry=new LoadedList;
+ entry->pDll=pDll;
+ entry->pNext=m_pDlls;
+ m_pDlls=entry;
+ }
+
+ return pDll->GetFileName();
+}
+
+int DllLoader::LoadExports()
+{
+ if ( NumOfDirectories > EXPORT_TABLE && Directory[EXPORT_TABLE].Size > 0 )
+ {
+ ExportDirTable = (ExportDirTable_t*)RVA2Data(Directory[EXPORT_TABLE].RVA);
+
+#ifdef DUMPING_DATA
+ PrintExportTable(ExportDirTable);
+#endif
+
+ //! @todo Validate all pointers are valid. Is a zero RVA valid or not? I'd guess not as it would
+ //! point to the coff file header, thus not right.
+
+ unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
+ unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
+ unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);
+
+ for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
+ {
+ char *Name = (char*)RVA2Data(NamePointerTable[i]);
+ void* Addr = (void*)RVA2Data(ExportAddressTable[OrdinalTable[i]]);
+ AddExport(Name, OrdinalTable[i]+ExportDirTable->OrdinalBase, Addr);
+ }
+ }
+ return 0;
+}
+
+int DllLoader::ResolveExport(const char *sName, void **pAddr, bool logging)
+{
+ Export* pExport=GetExportByFunctionName(sName);
+
+ if (pExport)
+ {
+ if (m_bTrack && pExport->track_function)
+ *pAddr=(void*)pExport->track_function;
+ else
+ *pAddr=(void*)pExport->function;
+
+ return 1;
+ }
+
+ const char* sDllName = strrchr(GetFileName(), '\\');
+ if (sDllName) sDllName += 1;
+ else sDllName = GetFileName();
+
+ if (logging)
+ CLog::Log(LOGWARNING, "Unable to resolve: {} {}", sDllName, sName);
+ return 0;
+}
+
+int DllLoader::ResolveOrdinal(unsigned long ordinal, void **pAddr)
+{
+ Export* pExport=GetExportByOrdinal(ordinal);
+
+ if (pExport)
+ {
+ if (m_bTrack && pExport->track_function)
+ *pAddr=(void*)pExport->track_function;
+ else
+ *pAddr=(void*)pExport->function;
+
+ return 1;
+ }
+
+ const char* sDllName = strrchr(GetFileName(), '\\');
+ if (sDllName) sDllName += 1;
+ else sDllName = GetFileName();
+
+ CLog::Log(LOGWARNING, "Unable to resolve: {} {}", sDllName, ordinal);
+ return 0;
+}
+
+Export* DllLoader::GetExportByOrdinal(unsigned long ordinal)
+{
+ ExportEntry* entry = m_pExportHead;
+
+ while (entry)
+ {
+ if (ordinal == entry->exp.ordinal)
+ {
+ return &entry->exp;
+ }
+ entry = entry->next;
+ }
+
+ if( m_pStaticExports )
+ {
+ Export* exp = m_pStaticExports;
+ while(exp->function || exp->track_function || exp->name)
+ {
+ if (ordinal == exp->ordinal)
+ return exp;
+ exp++;
+ }
+ }
+
+ return NULL;
+}
+
+Export* DllLoader::GetExportByFunctionName(const char* sFunctionName)
+{
+ ExportEntry* entry = m_pExportHead;
+
+ while (entry)
+ {
+ if (entry->exp.name && strcmp(sFunctionName, entry->exp.name) == 0)
+ {
+ return &entry->exp;
+ }
+ entry = entry->next;
+ }
+
+ if( m_pStaticExports )
+ {
+ Export* exp = m_pStaticExports;
+ while(exp->function || exp->track_function || exp->name)
+ {
+ if (exp->name && strcmp(sFunctionName, exp->name) == 0)
+ return exp;
+ exp++;
+ }
+ }
+
+ return NULL;
+}
+
+int DllLoader::ResolveOrdinal(const char *sName, unsigned long ordinal, void **fixup)
+{
+ DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));
+
+ if (pDll)
+ {
+ Export* pExp = pDll->GetExportByOrdinal(ordinal);
+ if(pExp)
+ {
+ if (m_bTrack && pExp->track_function)
+ *fixup = (void*)(pExp->track_function);
+ else
+ *fixup = (void*)(pExp->function);
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int DllLoader::ResolveName(const char *sName, char* sFunction, void **fixup)
+{
+ DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));
+
+ if (pDll)
+ {
+ Export* pExp = pDll->GetExportByFunctionName(sFunction);
+ if(pExp)
+ {
+ if (m_bTrack && pExp->track_function)
+ *fixup = (void*)(pExp->track_function);
+ else
+ *fixup = (void*)(pExp->function);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void DllLoader::AddExport(unsigned long ordinal, void* function, void* track_function)
+{
+ ExportEntry* entry = (ExportEntry*)malloc(sizeof(ExportEntry));
+ if (!entry)
+ return;
+ entry->exp.function = function;
+ entry->exp.ordinal = ordinal;
+ entry->exp.track_function = track_function;
+ entry->exp.name = NULL;
+
+ entry->next = m_pExportHead;
+ m_pExportHead = entry;
+}
+
+void DllLoader::AddExport(char* sFunctionName, unsigned long ordinal, void* function, void* track_function)
+{
+ int len = sizeof(ExportEntry);
+
+ ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
+ if (!entry)
+ return;
+ entry->exp.function = function;
+ entry->exp.ordinal = ordinal;
+ entry->exp.track_function = track_function;
+ entry->exp.name = ((char*)(entry)) + len;
+ strcpy(const_cast<char*>(entry->exp.name), sFunctionName);
+
+ entry->next = m_pExportHead;
+ m_pExportHead = entry;
+}
+
+void DllLoader::AddExport(char* sFunctionName, void* function, void* track_function)
+{
+ int len = sizeof(ExportEntry);
+
+ ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
+ if (!entry)
+ return;
+ entry->exp.function = (void*)function;
+ entry->exp.ordinal = -1;
+ entry->exp.track_function = track_function;
+ entry->exp.name = ((char*)(entry)) + len;
+ strcpy(const_cast<char*>(entry->exp.name), sFunctionName);
+
+ entry->next = m_pExportHead;
+ m_pExportHead = entry;
+}
+
+bool DllLoader::Load()
+{
+ if (!Parse())
+ {
+ CLog::Log(LOGERROR, "Unable to open dll {}", GetFileName());
+ return false;
+ }
+
+ ResolveImports();
+ LoadSymbols();
+
+ // only execute DllMain if no EntryPoint is found
+ if (!EntryAddress)
+ ResolveExport("DllMain", (void**)&EntryAddress);
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_ATTACH at: 0x{:x} - Dll: {}",
+ pLoader->EntryAddress, sName);
+#endif
+
+ if(EntryAddress)
+ {
+ EntryFunc initdll = (EntryFunc)EntryAddress;
+ /* since we are handing execution over to unknown code, safeguard here */
+ try
+ {
+#ifdef TARGET_POSIX
+ extend_stack_for_dll_alloca();
+#endif
+ initdll((HINSTANCE)hModule, DLL_PROCESS_ATTACH , 0); //call "DllMain" with DLL_PROCESS_ATTACH
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_ATTACH called - Dll: {}", sName);
+#endif
+
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{} - Unhandled exception during DLL_PROCESS_ATTACH", __FUNCTION__);
+
+ // vp7vfw.dll throws a CUserException due to a missing export
+ // but the export isn't really needed for normal operation
+ // and dll works anyway, so let's ignore it
+
+ if (StringUtils::CompareNoCase(GetName(), "vp7vfw.dll") != 0)
+ return false;
+
+
+ CLog::Log(LOGDEBUG, "{} - Ignoring exception during DLL_PROCESS_ATTACH", __FUNCTION__);
+ }
+
+ // init function may have fixed up the export table
+ // this is what I expect should happens on PECompact2
+ // dll's if export table is compressed.
+ if(!m_pExportHead)
+ LoadExports();
+ }
+
+ return true;
+}
+
+void DllLoader::Unload()
+{
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_DETACH at: 0x{:x} - Dll: {}",
+ pDll->EntryAddress, pDll->GetFileName());
+#endif
+
+ //call "DllMain" with DLL_PROCESS_DETACH
+ if(EntryAddress)
+ {
+ EntryFunc initdll = (EntryFunc)EntryAddress;
+ initdll((HINSTANCE)hModule, DLL_PROCESS_DETACH , 0);
+ }
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_DETACH called - Dll: {}", pDll->GetFileName());
+#endif
+
+ if (m_bUnloadSymbols)
+ UnloadSymbols();
+}
+
+// This function is a hack to get symbols loaded for
+// dlls. The function FFinishImageLoad internally allocates
+// memory which is/can never be freed. And the dll can not be
+// unloaded.
+void DllLoader::LoadSymbols()
+{
+#ifdef ENABLE_SYMBOL_LOADING
+ if (!m_bLoadSymbols ) return;
+
+ // don't load debug symbols unless we have a debugger present
+ // seems these calls break on some bioses. i suppose it could
+ // be related to if the bios has debug capabilities.
+ if (!DmIsDebuggerPresent())
+ {
+ m_bLoadSymbols=false;
+ return;
+ }
+
+ LPVOID pBaseAddress=GetXbdmBaseAddress();
+
+ if (pBaseAddress)
+ {
+ CoffLoader dllxbdm;
+ if (dllxbdm.ParseHeaders(pBaseAddress))
+ {
+ int offset=GetFFinishImageLoadOffset(dllxbdm.WindowsHeader->CheckSum);
+
+ if (offset==0)
+ {
+ CLog::Log(LOGDEBUG,
+ "DllLoader: Unable to load symbols for {}. No offset for xbdm.dll with checksum "
+ "{:#08X} found",
+ GetName(), dllxbdm.WindowsHeader->CheckSum);
+ return;
+ }
+
+ // Get a function pointer to the unexported function FFinishImageLoad
+ fnFFinishImageLoad FFinishImageLoad=(fnFFinishImageLoad)((LPBYTE)pBaseAddress+offset);
+
+ // Prepare parameter for the function call
+ LDR_DATA_TABLE_ENTRY ldte;
+ LPLDR_DATA_TABLE_ENTRY pldteout;
+ ldte.DllBase=hModule; // Address where this dll is loaded into memory
+ char* szName=GetName(); // Name of this dll without path
+
+ try
+ {
+ // Call FFinishImageLoad to register this dll to the debugger and load its symbols.
+ FFinishImageLoad(&ldte, szName, &pldteout);
+ }
+ catch(...)
+ {
+ CLog::Log(LOGDEBUG, "DllLoader: Loading symbols for {} failed with an exception.",
+ GetName());
+ }
+ }
+ }
+ else
+ CLog::Log(LOGDEBUG, "DllLoader: Can't load symbols for {}. xbdm.dll is needed and not loaded",
+ GetName());
+
+#ifdef ENABLE_SYMBOL_UNLOADING
+ m_bUnloadSymbols=true; // Do this to allow unloading this dll from dllloadercontainer
+#endif
+
+#else
+ m_bLoadSymbols=false;
+#endif
+}
+
+// This function is even more a hack
+// It will remove the dll from the Debug manager
+// but vs.net does not unload the symbols (don't know why)
+// The dll can be loaded again after unloading.
+// This function leaks memory.
+void DllLoader::UnloadSymbols()
+{
+#ifdef ENABLE_SYMBOL_UNLOADING
+ ANSI_STRING name;
+ OBJECT_ATTRIBUTES attributes;
+ RtlInitAnsiString(&name, GetName());
+ InitializeObjectAttributes(&attributes, &name, OBJ_CASE_INSENSITIVE, NULL);
+
+ // Try to unload the symbols from vs.net debugger
+ DbgUnLoadImageSymbols(&name, (ULONG)hModule, 0xFFFFFFFF);
+
+ LPVOID pBaseAddress=GetXbdmBaseAddress();
+
+ if (pBaseAddress)
+ {
+ CoffLoader dllxbdm;
+ if (dllxbdm.ParseHeaders(pBaseAddress))
+ {
+ int offset=GetDmiOffset(dllxbdm.WindowsHeader->CheckSum);
+
+ if (offset==0)
+ {
+ CLog::Log(LOGDEBUG,
+ "DllLoader: Unable to unload symbols for {}. No offset for xbdm.dll with "
+ "checksum {:#08X} found",
+ GetName(), dllxbdm.WindowsHeader->CheckSum);
+ return;
+ }
+
+ try
+ {
+ std::wstring strNameW;
+ g_charsetConverter.utf8ToW(GetName(), strNameW);
+
+ // Get the address of the global struct g_dmi
+ // It is located inside the xbdm.dll and
+ // get the LoadedModuleList member (here the entry var)
+ // of the structure.
+ LPBYTE g_dmi=((LPBYTE)pBaseAddress)+offset;
+ LIST_ENTRY* entry=(LIST_ENTRY*)(g_dmi+4);
+
+ // Search for the dll we are unloading...
+ while (entry)
+ {
+ std::wstring baseName=(wchar_t*)((LDR_DATA_TABLE_ENTRY*)entry)->BaseDllName.Buffer;
+ if (baseName == strNameW)
+ {
+ // ...and remove it from the LoadedModuleList and free its memory.
+ LIST_ENTRY* back=entry->Blink;
+ LIST_ENTRY* front=entry->Flink;
+ back->Flink=front;
+ front->Blink=back;
+ DmFreePool(entry);
+ break;
+ }
+
+ entry=entry->Flink;
+ }
+ }
+ catch(...)
+ {
+ CLog::Log(LOGDEBUG, "DllLoader: Unloading symbols for {} failed with an exception.",
+ GetName());
+ }
+ }
+ }
+ else
+ CLog::Log(LOGDEBUG, "DllLoader: Can't unload symbols for {}. xbdm.dll is needed and not loaded",
+ GetName());
+#endif
+}
diff --git a/xbmc/cores/DllLoader/DllLoader.h b/xbmc/cores/DllLoader/DllLoader.h
new file mode 100644
index 0000000..79e019d
--- /dev/null
+++ b/xbmc/cores/DllLoader/DllLoader.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "coffldr.h"
+#include "LibraryLoader.h"
+
+// clang-format off
+#if defined(__linux__) && \
+ !defined(__aarch64__) && \
+ !defined(__alpha__) && \
+ !defined(__arc__) && \
+ !defined(__arm__) && \
+ !defined(__loongarch__) && \
+ !defined(__mips__) && \
+ !defined(__powerpc__) && \
+ !defined(__or1k__) && \
+ !defined(__riscv) && \
+ !defined(__SH4__) && \
+ !defined(__s390x__) && \
+ !defined(__sparc__) && \
+ !defined(__xtensa__)
+#define USE_LDT_KEEPER
+#include "ldt_keeper.h"
+#endif
+// clang-format on
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+class DllLoader;
+
+
+typedef struct Export
+{
+ const char* name;
+ unsigned long ordinal;
+ void* function;
+ void* track_function;
+} Export;
+
+typedef struct ExportEntry
+{
+ Export exp;
+ ExportEntry* next;
+} ExportEntry;
+
+typedef struct _LoadedList
+{
+ DllLoader* pDll;
+ _LoadedList* pNext;
+} LoadedList;
+
+class DllLoader : public CoffLoader, public LibraryLoader
+{
+public:
+ DllLoader(const char *dll, bool track = false, bool bSystemDll = false, bool bLoadSymbols = false, Export* exports = NULL);
+ ~DllLoader() override;
+
+ bool Load() override;
+ void Unload() override;
+
+ int ResolveExport(const char*, void** ptr, bool logging = true) override;
+ int ResolveOrdinal(unsigned long ordinal, void** ptr) override;
+ bool HasSymbols() override { return m_bLoadSymbols && !m_bUnloadSymbols; }
+ bool IsSystemDll() override { return m_bSystemDll; }
+ HMODULE GetHModule() override { return (HMODULE)hModule; }
+
+ Export* GetExportByFunctionName(const char* sFunctionName);
+ Export* GetExportByOrdinal(unsigned long ordinal);
+protected:
+ int Parse();
+ int ResolveImports();
+
+ void AddExport(unsigned long ordinal, void* function, void* track_function = NULL);
+ void AddExport(char* sFunctionName, unsigned long ordinal, void* function, void* track_function = NULL);
+ void AddExport(char* sFunctionName, void* function, void* track_function = NULL);
+ void SetExports(Export* exports) { m_pStaticExports = exports; }
+
+protected:
+ // Just pointers; don't delete...
+ ImportDirTable_t *ImportDirTable;
+ ExportDirTable_t *ExportDirTable;
+ bool m_bTrack;
+ bool m_bSystemDll; // true if this dll should not be removed
+ bool m_bLoadSymbols; // when true this dll should not be removed
+ bool m_bUnloadSymbols;
+ ExportEntry* m_pExportHead;
+ Export* m_pStaticExports;
+ LoadedList* m_pDlls;
+
+#ifdef USE_LDT_KEEPER
+ ldt_fs_t* m_ldt_fs;
+#endif
+
+ void PrintImportLookupTable(unsigned long ImportLookupTable_RVA);
+ void PrintImportTable(ImportDirTable_t *ImportDirTable);
+ void PrintExportTable(ExportDirTable_t *ExportDirTable);
+
+ int ResolveOrdinal(const char*, unsigned long, void**);
+ int ResolveName(const char*, char*, void **);
+ const char* ResolveReferencedDll(const char* dll);
+ int LoadExports();
+ void LoadSymbols();
+ static void UnloadSymbols();
+};
diff --git a/xbmc/cores/DllLoader/DllLoaderContainer.cpp b/xbmc/cores/DllLoader/DllLoaderContainer.cpp
new file mode 100644
index 0000000..e56b02f
--- /dev/null
+++ b/xbmc/cores/DllLoader/DllLoaderContainer.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DllLoaderContainer.h"
+#ifdef TARGET_POSIX
+#include "SoLoader.h"
+#endif
+#ifdef TARGET_WINDOWS
+#include "Win32DllLoader.h"
+#endif
+#include "DllLoader.h"
+#include "dll_tracker.h" // for python unload hack
+#include "filesystem/File.h"
+#include "utils/URIUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "URL.h"
+
+#if defined(TARGET_WINDOWS)
+#define ENV_PARTIAL_PATH \
+ "special://xbmcbin/;" \
+ "special://xbmcbin/system/;" \
+ "special://xbmcbin/system/python/;" \
+ "special://xbmc/;" \
+ "special://xbmc/system/;" \
+ "special://xbmc/system/python/"
+#else
+#define ENV_PARTIAL_PATH \
+ "special://xbmcbin/system/;" \
+ "special://xbmcbin/system/players/mplayer/;" \
+ "special://xbmcbin/system/players/VideoPlayer/;" \
+ "special://xbmcbin/system/players/paplayer/;" \
+ "special://xbmcbin/system/python/;" \
+ "special://xbmc/system/;" \
+ "special://xbmc/system/players/mplayer/;" \
+ "special://xbmc/system/players/VideoPlayer/;" \
+ "special://xbmc/system/players/paplayer/;" \
+ "special://xbmc/system/python/"
+#endif
+#if defined(TARGET_DARWIN)
+#define ENV_PATH ENV_PARTIAL_PATH \
+ ";special://frameworks/"
+#else
+#define ENV_PATH ENV_PARTIAL_PATH
+#endif
+
+//Define this to get logging on all calls to load/unload of dlls
+//#define LOGALL
+
+
+using namespace XFILE;
+
+LibraryLoader* DllLoaderContainer::m_dlls[64] = {};
+int DllLoaderContainer::m_iNrOfDlls = 0;
+bool DllLoaderContainer::m_bTrack = true;
+
+void DllLoaderContainer::Clear()
+{
+}
+
+HMODULE DllLoaderContainer::GetModuleAddress(const char* sName)
+{
+ return (HMODULE)GetModule(sName);
+}
+
+LibraryLoader* DllLoaderContainer::GetModule(const char* sName)
+{
+ for (int i = 0; i < m_iNrOfDlls && m_dlls[i] != NULL; i++)
+ {
+ if (StringUtils::CompareNoCase(m_dlls[i]->GetName(), sName) == 0)
+ return m_dlls[i];
+ if (!m_dlls[i]->IsSystemDll() &&
+ StringUtils::CompareNoCase(m_dlls[i]->GetFileName(), sName) == 0)
+ return m_dlls[i];
+ }
+
+ return NULL;
+}
+
+LibraryLoader* DllLoaderContainer::GetModule(const HMODULE hModule)
+{
+ for (int i = 0; i < m_iNrOfDlls && m_dlls[i] != NULL; i++)
+ {
+ if (m_dlls[i]->GetHModule() == hModule) return m_dlls[i];
+ }
+ return NULL;
+}
+
+LibraryLoader* DllLoaderContainer::LoadModule(const char* sName, const char* sCurrentDir/*=NULL*/, bool bLoadSymbols/*=false*/)
+{
+ LibraryLoader* pDll=NULL;
+
+ if (IsSystemDll(sName))
+ {
+ pDll = GetModule(sName);
+ }
+ else if (sCurrentDir)
+ {
+ std::string strPath=sCurrentDir;
+ strPath+=sName;
+ pDll = GetModule(strPath.c_str());
+ }
+
+ if (!pDll)
+ {
+ pDll = GetModule(sName);
+ }
+
+ if (!pDll)
+ {
+ pDll = FindModule(sName, sCurrentDir, bLoadSymbols);
+ }
+ else if (!pDll->IsSystemDll())
+ {
+ pDll->IncrRef();
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Already loaded Dll {} at 0x{:x}", pDll->GetFileName(), pDll);
+#endif
+
+ }
+
+ return pDll;
+}
+
+LibraryLoader* DllLoaderContainer::FindModule(const char* sName, const char* sCurrentDir, bool bLoadSymbols)
+{
+ if (URIUtils::IsInArchive(sName))
+ {
+ CURL url(sName);
+ std::string newName = "special://temp/";
+ newName += url.GetFileName();
+ CFile::Copy(sName, newName);
+ return FindModule(newName.c_str(), sCurrentDir, bLoadSymbols);
+ }
+
+ if (CURL::IsFullPath(sName))
+ { // Has a path, just try to load
+ return LoadDll(sName, bLoadSymbols);
+ }
+#ifdef TARGET_POSIX
+ else if (strcmp(sName, "xbmc.so") == 0)
+ return LoadDll(sName, bLoadSymbols);
+#endif
+ else if (sCurrentDir)
+ { // in the path of the parent dll?
+ std::string strPath=sCurrentDir;
+ strPath+=sName;
+
+ if (CFile::Exists(strPath))
+ return LoadDll(strPath.c_str(), bLoadSymbols);
+ }
+
+ // in environment variable?
+ std::vector<std::string> vecEnv;
+
+#if defined(TARGET_ANDROID)
+ std::string systemLibs = getenv("KODI_ANDROID_SYSTEM_LIBS");
+ vecEnv = StringUtils::Split(systemLibs, ':');
+ std::string localLibs = getenv("KODI_ANDROID_LIBS");
+ vecEnv.insert(vecEnv.begin(),localLibs);
+#else
+ vecEnv = StringUtils::Split(ENV_PATH, ';');
+#endif
+ LibraryLoader* pDll = NULL;
+
+ for (std::vector<std::string>::const_iterator i = vecEnv.begin(); i != vecEnv.end(); ++i)
+ {
+ std::string strPath = *i;
+ URIUtils::AddSlashAtEnd(strPath);
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Searching for the dll {} in directory {}", sName, strPath);
+#endif
+
+ strPath+=sName;
+
+ // Have we already loaded this dll
+ if ((pDll = GetModule(strPath.c_str())) != NULL)
+ return pDll;
+
+ if (CFile::Exists(strPath))
+ return LoadDll(strPath.c_str(), bLoadSymbols);
+ }
+
+ // can't find it in any of our paths - could be a system dll
+ if ((pDll = LoadDll(sName, bLoadSymbols)) != NULL)
+ return pDll;
+
+ CLog::Log(LOGDEBUG, "Dll {} was not found in path", sName);
+ return NULL;
+}
+
+void DllLoaderContainer::ReleaseModule(LibraryLoader*& pDll)
+{
+ if (!pDll)
+ return;
+ if (pDll->IsSystemDll())
+ {
+ CLog::Log(LOGFATAL, "{} is a system dll and should never be released", pDll->GetName());
+ return;
+ }
+
+ int iRefCount=pDll->DecrRef();
+ if (iRefCount==0)
+ {
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Releasing Dll {}", pDll->GetFileName());
+#endif
+
+ if (!pDll->HasSymbols())
+ {
+ pDll->Unload();
+ delete pDll;
+ pDll=NULL;
+ }
+ else
+ CLog::Log(LOGINFO, "{} has symbols loaded and can never be unloaded", pDll->GetName());
+ }
+#ifdef LOGALL
+ else
+ {
+ CLog::Log(LOGDEBUG, "Dll {} is still referenced with a count of {}", pDll->GetFileName(),
+ iRefCount);
+ }
+#endif
+}
+
+LibraryLoader* DllLoaderContainer::LoadDll(const char* sName, bool bLoadSymbols)
+{
+
+#ifdef LOGALL
+ CLog::Log(LOGDEBUG, "Loading dll {}", sName);
+#endif
+
+ LibraryLoader* pLoader;
+#ifdef TARGET_POSIX
+ pLoader = new SoLoader(sName, bLoadSymbols);
+#elif defined(TARGET_WINDOWS)
+ pLoader = new Win32DllLoader(sName, false);
+#else
+ pLoader = new DllLoader(sName, m_bTrack, false, bLoadSymbols);
+#endif
+
+ if (!pLoader)
+ {
+ CLog::Log(LOGERROR, "Unable to create dll {}", sName);
+ return NULL;
+ }
+
+ if (!pLoader->Load())
+ {
+ delete pLoader;
+ return NULL;
+ }
+
+ return pLoader;
+}
+
+bool DllLoaderContainer::IsSystemDll(const char* sName)
+{
+ for (int i = 0; i < m_iNrOfDlls && m_dlls[i] != NULL; i++)
+ {
+ if (m_dlls[i]->IsSystemDll() && StringUtils::CompareNoCase(m_dlls[i]->GetName(), sName) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+int DllLoaderContainer::GetNrOfModules()
+{
+ return m_iNrOfDlls;
+}
+
+LibraryLoader* DllLoaderContainer::GetModule(int iPos)
+{
+ if (iPos < m_iNrOfDlls) return m_dlls[iPos];
+ return NULL;
+}
+
+void DllLoaderContainer::RegisterDll(LibraryLoader* pDll)
+{
+ for (LibraryLoader*& dll : m_dlls)
+ {
+ if (dll == NULL)
+ {
+ dll = pDll;
+ m_iNrOfDlls++;
+ break;
+ }
+ }
+}
+
+void DllLoaderContainer::UnRegisterDll(LibraryLoader* pDll)
+{
+ if (pDll)
+ {
+ if (pDll->IsSystemDll())
+ {
+ CLog::Log(LOGFATAL, "{} is a system dll and should never be removed", pDll->GetName());
+ }
+ else
+ {
+ // remove from the list
+ bool bRemoved = false;
+ for (int i = 0; i < m_iNrOfDlls && m_dlls[i]; i++)
+ {
+ if (m_dlls[i] == pDll) bRemoved = true;
+ if (bRemoved && i + 1 < m_iNrOfDlls)
+ {
+ m_dlls[i] = m_dlls[i + 1];
+ }
+ }
+ if (bRemoved)
+ {
+ m_iNrOfDlls--;
+ m_dlls[m_iNrOfDlls] = NULL;
+ }
+ }
+ }
+}
+
+void DllLoaderContainer::UnloadPythonDlls()
+{
+ // unload all dlls that python could have loaded
+ for (int i = 0; i < m_iNrOfDlls && m_dlls[i] != NULL; i++)
+ {
+ const char* name = m_dlls[i]->GetName();
+ if (strstr(name, ".pyd") != NULL)
+ {
+ LibraryLoader* pDll = m_dlls[i];
+ ReleaseModule(pDll);
+ i = 0;
+ }
+ }
+
+}
diff --git a/xbmc/cores/DllLoader/DllLoaderContainer.h b/xbmc/cores/DllLoader/DllLoaderContainer.h
new file mode 100644
index 0000000..3769ff8
--- /dev/null
+++ b/xbmc/cores/DllLoader/DllLoaderContainer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "LibraryLoader.h"
+
+class DllLoaderContainer
+{
+public:
+ static void Clear();
+ static HMODULE GetModuleAddress(const char* sName);
+ static int GetNrOfModules();
+ static LibraryLoader* GetModule(int iPos);
+ static LibraryLoader* GetModule(const char* sName);
+ static LibraryLoader* GetModule(const HMODULE hModule);
+ static LibraryLoader* LoadModule(const char* sName, const char* sCurrentDir=NULL, bool bLoadSymbols=false);
+ static void ReleaseModule(LibraryLoader*& pDll);
+
+ static void RegisterDll(LibraryLoader* pDll);
+ static void UnRegisterDll(LibraryLoader* pDll);
+ static void UnloadPythonDlls();
+
+private:
+ static LibraryLoader* FindModule(const char* sName, const char* sCurrentDir, bool bLoadSymbols);
+ static LibraryLoader* LoadDll(const char* sName, bool bLoadSymbols);
+ static bool IsSystemDll(const char* sName);
+
+ static LibraryLoader* m_dlls[64];
+ static int m_iNrOfDlls;
+ static bool m_bTrack;
+};
diff --git a/xbmc/cores/DllLoader/LibraryLoader.cpp b/xbmc/cores/DllLoader/LibraryLoader.cpp
new file mode 100644
index 0000000..5689900
--- /dev/null
+++ b/xbmc/cores/DllLoader/LibraryLoader.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LibraryLoader.h"
+
+#include "utils/log.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+LibraryLoader::LibraryLoader(const std::string& libraryFile):
+ m_fileName(libraryFile)
+{
+ size_t pos = m_fileName.find_last_of("\\/");
+ if (pos != std::string::npos)
+ m_path = m_fileName.substr(0, pos);
+
+ m_iRefCount = 1;
+}
+
+LibraryLoader::~LibraryLoader() = default;
+
+const char *LibraryLoader::GetName() const
+{
+ size_t pos = m_fileName.find_last_of('/');
+ if (pos != std::string::npos)
+ return &m_fileName.at(pos + 1); // don't include /
+ return m_fileName.c_str();
+}
+
+const char *LibraryLoader::GetFileName() const
+{
+ return m_fileName.c_str();
+}
+
+const char *LibraryLoader::GetPath() const
+{
+ return m_path.c_str();
+}
+
+int LibraryLoader::IncrRef()
+{
+ m_iRefCount++;
+ return m_iRefCount;
+}
+
+int LibraryLoader::DecrRef()
+{
+ m_iRefCount--;
+ return m_iRefCount;
+}
+
+int LibraryLoader::ResolveOrdinal(unsigned long ordinal, void** ptr)
+{
+ CLog::Log(LOGWARNING, "{} - Unable to resolve {} in dll {}", __FUNCTION__, ordinal, GetName());
+ return 0;
+}
diff --git a/xbmc/cores/DllLoader/LibraryLoader.h b/xbmc/cores/DllLoader/LibraryLoader.h
new file mode 100644
index 0000000..a8db986
--- /dev/null
+++ b/xbmc/cores/DllLoader/LibraryLoader.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#ifdef TARGET_POSIX
+#include "PlatformDefs.h"
+#endif
+
+class LibraryLoader
+{
+public:
+ explicit LibraryLoader(const std::string& libraryFile);
+ virtual ~LibraryLoader();
+
+ virtual bool Load() = 0;
+ virtual void Unload() = 0;
+
+ virtual int ResolveExport(const char* symbol, void** ptr, bool logging = true) = 0;
+ virtual int ResolveOrdinal(unsigned long ordinal, void** ptr);
+ virtual bool IsSystemDll() = 0;
+ virtual HMODULE GetHModule() = 0;
+ virtual bool HasSymbols() = 0;
+
+ const char *GetName() const; // eg "mplayer.dll"
+ const char *GetFileName() const; // "special://xbmcbin/system/mplayer/players/mplayer.dll"
+ const char *GetPath() const; // "special://xbmcbin/system/mplayer/players/"
+
+ int IncrRef();
+ int DecrRef();
+ int GetRef();
+
+private:
+ LibraryLoader(const LibraryLoader&);
+ LibraryLoader& operator=(const LibraryLoader&);
+ std::string m_fileName;
+ std::string m_path;
+ int m_iRefCount;
+};
diff --git a/xbmc/cores/DllLoader/SoLoader.cpp b/xbmc/cores/DllLoader/SoLoader.cpp
new file mode 100644
index 0000000..2b5cbce
--- /dev/null
+++ b/xbmc/cores/DllLoader/SoLoader.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SoLoader.h"
+
+#include "filesystem/SpecialProtocol.h"
+#include "utils/log.h"
+
+#include <dlfcn.h>
+
+SoLoader::SoLoader(const std::string &so, bool bGlobal) : LibraryLoader(so)
+{
+ m_soHandle = NULL;
+ m_bGlobal = bGlobal;
+ m_bLoaded = false;
+}
+
+SoLoader::~SoLoader()
+{
+ if (m_bLoaded)
+ Unload();
+}
+
+bool SoLoader::Load()
+{
+ if (m_soHandle != NULL)
+ return true;
+
+ std::string strFileName= CSpecialProtocol::TranslatePath(GetFileName());
+ if (strFileName == "xbmc.so")
+ {
+ CLog::Log(LOGDEBUG, "Loading Internal Library");
+ m_soHandle = RTLD_DEFAULT;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Loading: {}", strFileName);
+ int flags = RTLD_LAZY;
+ m_soHandle = dlopen(strFileName.c_str(), flags);
+ if (!m_soHandle)
+ {
+ CLog::Log(LOGERROR, "Unable to load {}, reason: {}", strFileName, dlerror());
+ return false;
+ }
+ }
+ m_bLoaded = true;
+ return true;
+}
+
+void SoLoader::Unload()
+{
+
+ if (m_soHandle)
+ {
+ if (dlclose(m_soHandle) != 0)
+ CLog::Log(LOGERROR, "Unable to unload {}, reason: {}", GetName(), dlerror());
+ }
+ m_bLoaded = false;
+ m_soHandle = NULL;
+}
+
+int SoLoader::ResolveExport(const char* symbol, void** f, bool logging)
+{
+ if (!m_bLoaded && !Load())
+ {
+ if (logging)
+ CLog::Log(LOGWARNING, "Unable to resolve: {} {}, reason: so not loaded", GetName(), symbol);
+ return 0;
+ }
+
+ void* s = dlsym(m_soHandle, symbol);
+ if (!s)
+ {
+ if (logging)
+ CLog::Log(LOGWARNING, "Unable to resolve: {} {}, reason: {}", GetName(), symbol, dlerror());
+ return 0;
+ }
+
+ *f = s;
+ return 1;
+}
+
+bool SoLoader::IsSystemDll()
+{
+ return false;
+}
+
+HMODULE SoLoader::GetHModule()
+{
+ return m_soHandle;
+}
+
+bool SoLoader::HasSymbols()
+{
+ return false;
+}
diff --git a/xbmc/cores/DllLoader/SoLoader.h b/xbmc/cores/DllLoader/SoLoader.h
new file mode 100644
index 0000000..4adc7e9
--- /dev/null
+++ b/xbmc/cores/DllLoader/SoLoader.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#ifdef TARGET_POSIX
+#include "PlatformDefs.h"
+#endif
+#include "DllLoader.h"
+
+class SoLoader : public LibraryLoader
+{
+public:
+ SoLoader(const std::string &so, bool bGlobal = false);
+ ~SoLoader() override;
+
+ bool Load() override;
+ void Unload() override;
+
+ int ResolveExport(const char* symbol, void** ptr, bool logging = true) override;
+ bool IsSystemDll() override;
+ HMODULE GetHModule() override;
+ bool HasSymbols() override;
+
+private:
+ void* m_soHandle;
+ bool m_bGlobal;
+ bool m_bLoaded;
+};
diff --git a/xbmc/cores/DllLoader/Win32DllLoader.cpp b/xbmc/cores/DllLoader/Win32DllLoader.cpp
new file mode 100644
index 0000000..be0d4a6
--- /dev/null
+++ b/xbmc/cores/DllLoader/Win32DllLoader.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Win32DllLoader.h"
+
+#include "DllLoader.h"
+#include "DllLoaderContainer.h"
+#include "dll_tracker_file.h"
+#include "dll_tracker_library.h"
+#include "exports/emu_msvcrt.h"
+#include "filesystem/SpecialProtocol.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/win32/CharsetConverter.h"
+
+#include <limits>
+
+extern "C" FARPROC WINAPI dllWin32GetProcAddress(HMODULE hModule, LPCSTR function);
+
+//dllLoadLibraryA, dllFreeLibrary, dllGetProcAddress are from dllLoader,
+//they are wrapper functions of COFF/PE32 loader.
+extern "C" HMODULE WINAPI dllLoadLibraryA(LPCSTR libname);
+extern "C" BOOL WINAPI dllFreeLibrary(HINSTANCE hLibModule);
+
+// our exports
+Export win32_exports[] =
+{
+ { "LoadLibraryA", -1, (void*)dllLoadLibraryA, (void*)track_LoadLibraryA },
+ { "FreeLibrary", -1, (void*)dllFreeLibrary, (void*)track_FreeLibrary },
+// msvcrt
+ { "_close", -1, (void*)dll_close, (void*)track_close},
+ { "_lseek", -1, (void*)dll_lseek, NULL },
+ { "_read", -1, (void*)dll_read, NULL },
+ { "_write", -1, (void*)dll_write, NULL },
+ { "_lseeki64", -1, (void*)dll_lseeki64, NULL },
+ { "_open", -1, (void*)dll_open, (void*)track_open },
+ { "fflush", -1, (void*)dll_fflush, NULL },
+ { "fprintf", -1, (void*)dll_fprintf, NULL },
+ { "fwrite", -1, (void*)dll_fwrite, NULL },
+ { "putchar", -1, (void*)dll_putchar, NULL },
+ { "_fstat", -1, (void*)dll_fstat, NULL },
+ { "_mkdir", -1, (void*)dll_mkdir, NULL },
+ { "_stat", -1, (void*)dll_stat, NULL },
+ { "_fstat32", -1, (void*)dll_fstat, NULL },
+ { "_stat32", -1, (void*)dll_stat, NULL },
+ { "_findclose", -1, (void*)dll_findclose, NULL },
+ { "_findfirst", -1, (void*)dll_findfirst, NULL },
+ { "_findnext", -1, (void*)dll_findnext, NULL },
+ { "_findfirst64i32", -1, (void*)dll_findfirst64i32, NULL },
+ { "_findnext64i32", -1, (void*)dll_findnext64i32, NULL },
+ { "fclose", -1, (void*)dll_fclose, (void*)track_fclose},
+ { "feof", -1, (void*)dll_feof, NULL },
+ { "fgets", -1, (void*)dll_fgets, NULL },
+ { "fopen", -1, (void*)dll_fopen, (void*)track_fopen},
+ { "fopen_s", -1, (void*)dll_fopen_s, NULL },
+ { "putc", -1, (void*)dll_putc, NULL },
+ { "fputc", -1, (void*)dll_fputc, NULL },
+ { "fputs", -1, (void*)dll_fputs, NULL },
+ { "fread", -1, (void*)dll_fread, NULL },
+ { "fseek", -1, (void*)dll_fseek, NULL },
+ { "ftell", -1, (void*)dll_ftell, NULL },
+ { "getc", -1, (void*)dll_getc, NULL },
+ { "fgetc", -1, (void*)dll_getc, NULL },
+ { "rewind", -1, (void*)dll_rewind, NULL },
+ { "vfprintf", -1, (void*)dll_vfprintf, NULL },
+ { "fgetpos", -1, (void*)dll_fgetpos, NULL },
+ { "fsetpos", -1, (void*)dll_fsetpos, NULL },
+ { "_stati64", -1, (void*)dll_stati64, NULL },
+ { "_stat64", -1, (void*)dll_stat64, NULL },
+ { "_stat64i32", -1, (void*)dll_stat64i32, NULL },
+ { "_fstati64", -1, (void*)dll_fstati64, NULL },
+ { "_fstat64", -1, (void*)dll_fstat64, NULL },
+ { "_fstat64i32", -1, (void*)dll_fstat64i32, NULL },
+ { "_telli64", -1, (void*)dll_telli64, NULL },
+ { "_tell", -1, (void*)dll_tell, NULL },
+ { "_fileno", -1, (void*)dll_fileno, NULL },
+ { "ferror", -1, (void*)dll_ferror, NULL },
+ { "freopen", -1, (void*)dll_freopen, (void*)track_freopen},
+ { "fscanf", -1, (void*)dll_fscanf, NULL },
+ { "ungetc", -1, (void*)dll_ungetc, NULL },
+ { "_fdopen", -1, (void*)dll_fdopen, NULL },
+ { "clearerr", -1, (void*)dll_clearerr, NULL },
+ // for debugging
+ { "printf", -1, (void*)dllprintf, NULL },
+ { "vprintf", -1, (void*)dllvprintf, NULL },
+ { "perror", -1, (void*)dllperror, NULL },
+ { "puts", -1, (void*)dllputs, NULL },
+ // workarounds for non-win32 signals
+ { "signal", -1, (void*)dll_signal, NULL },
+
+ // libdvdnav + python need this (due to us using dll_putenv() to put stuff only?)
+ { "getenv", -1, (void*)dll_getenv, NULL },
+ { "_environ", -1, (void*)&dll__environ, NULL },
+ { "_open_osfhandle", -1, (void*)dll_open_osfhandle, NULL },
+
+ { NULL, -1, NULL, NULL }
+};
+
+Win32DllLoader::Win32DllLoader(const std::string& dll, bool isSystemDll)
+ : LibraryLoader(dll)
+ , bIsSystemDll(isSystemDll)
+{
+ m_dllHandle = NULL;
+ DllLoaderContainer::RegisterDll(this);
+}
+
+Win32DllLoader::~Win32DllLoader()
+{
+ if (m_dllHandle)
+ Unload();
+ DllLoaderContainer::UnRegisterDll(this);
+}
+
+bool Win32DllLoader::Load()
+{
+ using namespace KODI::PLATFORM::WINDOWS;
+
+ if (m_dllHandle != NULL)
+ return true;
+
+ std::string strFileName = GetFileName();
+ auto strDllW = ToW(CSpecialProtocol::TranslatePath(strFileName));
+
+#ifdef TARGET_WINDOWS_STORE
+ // The path cannot be an absolute path or a relative path that contains ".." in the path.
+ auto appPath = winrt::Windows::ApplicationModel::Package::Current().InstalledLocation().Path();
+ size_t len = appPath.size();
+
+ if (!appPath.empty() && wcsnicmp(appPath.c_str(), strDllW.c_str(), len) == 0)
+ {
+ if (strDllW.at(len) == '\\' || strDllW.at(len) == '/')
+ len++;
+ std::wstring relative = strDllW.substr(len);
+ m_dllHandle = LoadPackagedLibrary(relative.c_str(), 0);
+ }
+ else
+ m_dllHandle = LoadPackagedLibrary(strDllW.c_str(), 0);
+#else
+ m_dllHandle = LoadLibraryExW(strDllW.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+#endif
+
+ if (!m_dllHandle)
+ {
+ DWORD dw = GetLastError();
+ wchar_t* lpMsgBuf = NULL;
+ DWORD strLen = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPWSTR)&lpMsgBuf, 0, NULL);
+ if (strLen == 0)
+ strLen = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (LPWSTR)&lpMsgBuf, 0, NULL);
+
+ if (strLen != 0)
+ {
+ auto strMessage = FromW(lpMsgBuf, strLen);
+ CLog::Log(LOGERROR, "{}: Failed to load \"{}\" with error {}: \"{}\"", __FUNCTION__,
+ CSpecialProtocol::TranslatePath(strFileName), dw, strMessage);
+ }
+ else
+ CLog::Log(LOGERROR, "{}: Failed to load \"{}\" with error {}", __FUNCTION__,
+ CSpecialProtocol::TranslatePath(strFileName), dw);
+
+ LocalFree(lpMsgBuf);
+ return false;
+ }
+
+ // handle functions that the dll imports
+ if (NeedsHooking(strFileName.c_str()))
+ OverrideImports(strFileName);
+
+ return true;
+}
+
+void Win32DllLoader::Unload()
+{
+ // restore our imports
+ RestoreImports();
+
+ if (m_dllHandle)
+ {
+ if (!FreeLibrary(m_dllHandle))
+ CLog::Log(LOGERROR, "{} Unable to unload {}", __FUNCTION__, GetName());
+ }
+
+ m_dllHandle = NULL;
+}
+
+int Win32DllLoader::ResolveExport(const char* symbol, void** f, bool logging)
+{
+ if (!m_dllHandle && !Load())
+ {
+ if (logging)
+ CLog::Log(LOGWARNING, "{} - Unable to resolve: {} {}, reason: DLL not loaded", __FUNCTION__,
+ GetName(), symbol);
+ return 0;
+ }
+
+ void *s = GetProcAddress(m_dllHandle, symbol);
+
+ if (!s)
+ {
+ if (logging)
+ CLog::Log(LOGWARNING, "{} - Unable to resolve: {} {}", __FUNCTION__, GetName(), symbol);
+ return 0;
+ }
+
+ *f = s;
+ return 1;
+}
+
+bool Win32DllLoader::IsSystemDll()
+{
+ return bIsSystemDll;
+}
+
+HMODULE Win32DllLoader::GetHModule()
+{
+ return m_dllHandle;
+}
+
+bool Win32DllLoader::HasSymbols()
+{
+ return false;
+}
+
+void Win32DllLoader::OverrideImports(const std::string &dll)
+{
+ using KODI::PLATFORM::WINDOWS::ToW;
+ auto strdllW = ToW(CSpecialProtocol::TranslatePath(dll));
+ auto image_base = reinterpret_cast<BYTE*>(m_dllHandle);
+
+ if (!image_base)
+ {
+ CLog::Log(LOGERROR, "{} - unable to GetModuleHandle for dll {}", __FUNCTION__, dll);
+ return;
+ }
+
+ auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(image_base);
+ auto nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(image_base + dos_header->e_lfanew); // e_lfanew = value at 0x3c
+
+ auto imp_desc = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(
+ image_base + nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
+
+ if (!imp_desc)
+ {
+ CLog::Log(LOGERROR, "{} - unable to get import directory for dll {}", __FUNCTION__, dll);
+ return;
+ }
+
+ // loop over all imported dlls
+ for (int i = 0; imp_desc[i].Characteristics != 0; i++)
+ {
+ auto dllName = reinterpret_cast<char*>(image_base + imp_desc[i].Name);
+
+ // check whether this is one of our dll's.
+ if (NeedsHooking(dllName))
+ {
+ // this will do a loadlibrary on it, which should effectively make sure that it's hooked
+ // Note that the library has obviously already been loaded by the OS (as it's implicitly linked)
+ // so all this will do is insert our hook and make sure our DllLoaderContainer knows about it
+ auto hModule = dllLoadLibraryA(dllName);
+ if (hModule)
+ m_referencedDlls.push_back(hModule);
+ }
+
+ PIMAGE_THUNK_DATA orig_first_thunk = reinterpret_cast<PIMAGE_THUNK_DATA>(image_base + imp_desc[i].OriginalFirstThunk);
+ PIMAGE_THUNK_DATA first_thunk = reinterpret_cast<PIMAGE_THUNK_DATA>(image_base + imp_desc[i].FirstThunk);
+
+ // and then loop over all imported functions
+ for (int j = 0; orig_first_thunk[j].u1.Function != 0; j++)
+ {
+ void *fixup = NULL;
+ if (orig_first_thunk[j].u1.Function & 0x80000000)
+ ResolveOrdinal(dllName, (orig_first_thunk[j].u1.Ordinal & 0x7fffffff), &fixup);
+ else
+ { // resolve by name
+ PIMAGE_IMPORT_BY_NAME orig_imports_by_name = (PIMAGE_IMPORT_BY_NAME)(
+ image_base + orig_first_thunk[j].u1.AddressOfData);
+
+ ResolveImport(dllName, (char*)orig_imports_by_name->Name, &fixup);
+ }/*
+ if (!fixup)
+ { // create a dummy function for tracking purposes
+ PIMAGE_IMPORT_BY_NAME orig_imports_by_name = (PIMAGE_IMPORT_BY_NAME)(
+ image_base + orig_first_thunk[j].u1.AddressOfData);
+ fixup = CreateDummyFunction(dllName, (char*)orig_imports_by_name->Name);
+ }*/
+ if (fixup)
+ {
+ // save the old function
+ Import import;
+ import.table = &first_thunk[j].u1.Function;
+ import.function = first_thunk[j].u1.Function;
+ m_overriddenImports.push_back(import);
+
+ DWORD old_prot = 0;
+
+ // change to protection settings so we can write to memory area
+ VirtualProtect((PVOID)&first_thunk[j].u1.Function, 4, PAGE_EXECUTE_READWRITE, &old_prot);
+
+ // patch the address of function to point to our overridden version
+ first_thunk[j].u1.Function = (uintptr_t)fixup;
+
+ // reset to old settings
+ VirtualProtect((PVOID)&first_thunk[j].u1.Function, 4, old_prot, &old_prot);
+ }
+ }
+ }
+}
+
+bool Win32DllLoader::NeedsHooking(const char *dllName)
+{
+ if ( !StringUtils::EndsWithNoCase(dllName, "libdvdcss-2.dll")
+ && !StringUtils::EndsWithNoCase(dllName, "libdvdnav.dll"))
+ return false;
+
+ LibraryLoader *loader = DllLoaderContainer::GetModule(dllName);
+ if (loader)
+ {
+ // may have hooked this already (we can have repeats in the import table)
+ for (unsigned int i = 0; i < m_referencedDlls.size(); i++)
+ {
+ if (loader->GetHModule() == m_referencedDlls[i])
+ return false;
+ }
+ }
+ return true;
+}
+
+void Win32DllLoader::RestoreImports()
+{
+ // first unhook any referenced dll's
+ for (auto& module : m_referencedDlls)
+ dllFreeLibrary(module);
+ m_referencedDlls.clear();
+
+ for (auto& import : m_overriddenImports)
+ {
+ // change to protection settings so we can write to memory area
+ DWORD old_prot = 0;
+ VirtualProtect(import.table, 4, PAGE_EXECUTE_READWRITE, &old_prot);
+
+ *static_cast<uintptr_t *>(import.table) = import.function;
+
+ // reset to old settings
+ VirtualProtect(import.table, 4, old_prot, &old_prot);
+ }
+}
+
+bool FunctionNeedsWrapping(Export *exports, const char *functionName, void **fixup)
+{
+ Export *exp = exports;
+ while (exp->name)
+ {
+ if (strcmp(exp->name, functionName) == 0)
+ { //! @todo Should we be tracking stuff?
+ if (0)
+ *fixup = exp->track_function;
+ else
+ *fixup = exp->function;
+ return true;
+ }
+ exp++;
+ }
+ return false;
+}
+
+bool Win32DllLoader::ResolveImport(const char *dllName, const char *functionName, void **fixup)
+{
+ return FunctionNeedsWrapping(win32_exports, functionName, fixup);
+}
+
+bool Win32DllLoader::ResolveOrdinal(const char *dllName, unsigned long ordinal, void **fixup)
+{
+ Export *exp = win32_exports;
+ while (exp->name)
+ {
+ if (exp->ordinal == ordinal)
+ { //! @todo Should we be tracking stuff?
+ if (0)
+ *fixup = exp->track_function;
+ else
+ *fixup = exp->function;
+ return true;
+ }
+ exp++;
+ }
+ return false;
+}
+
+extern "C" FARPROC __stdcall dllWin32GetProcAddress(HMODULE hModule, LPCSTR function)
+{
+ // if the high-order word is zero, then lpProcName is the function's ordinal value
+ if (reinterpret_cast<uintptr_t>(function) > std::numeric_limits<WORD>::max())
+ {
+ // first check whether this function is one of the ones we need to wrap
+ void *fixup = NULL;
+ if (FunctionNeedsWrapping(win32_exports, function, &fixup))
+ return (FARPROC)fixup;
+ }
+
+ // Nope
+ return GetProcAddress(hModule, function);
+}
+
diff --git a/xbmc/cores/DllLoader/Win32DllLoader.h b/xbmc/cores/DllLoader/Win32DllLoader.h
new file mode 100644
index 0000000..f11eb54
--- /dev/null
+++ b/xbmc/cores/DllLoader/Win32DllLoader.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "LibraryLoader.h"
+
+#include <vector>
+
+class Win32DllLoader : public LibraryLoader
+{
+public:
+ class Import
+ {
+ public:
+ void *table;
+ uintptr_t function;
+ };
+
+ Win32DllLoader(const std::string& dll, bool isSystemDll);
+ ~Win32DllLoader();
+
+ virtual bool Load();
+ virtual void Unload();
+
+ virtual int ResolveExport(const char* symbol, void** ptr, bool logging = true);
+ virtual bool IsSystemDll();
+ virtual HMODULE GetHModule();
+ virtual bool HasSymbols();
+
+private:
+ void OverrideImports(const std::string &dll);
+ void RestoreImports();
+ static bool ResolveImport(const char *dllName, const char *functionName, void **fixup);
+ static bool ResolveOrdinal(const char *dllName, unsigned long ordinal, void **fixup);
+ bool NeedsHooking(const char *dllName);
+
+ HMODULE m_dllHandle;
+ bool bIsSystemDll;
+
+ std::vector<Import> m_overriddenImports;
+ std::vector<HMODULE> m_referencedDlls;
+};
+
diff --git a/xbmc/cores/DllLoader/coff.cpp b/xbmc/cores/DllLoader/coff.cpp
new file mode 100644
index 0000000..6e330c9
--- /dev/null
+++ b/xbmc/cores/DllLoader/coff.cpp
@@ -0,0 +1,997 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "coff.h"
+#include "coffldr.h"
+
+//#define DUMPING_DATA 1
+
+#ifndef __GNUC__
+#pragma warning (disable:4806)
+#endif
+
+#include "utils/log.h"
+#define printf(format, ...) CLog::Log(LOGDEBUG, format , ##__VA_ARGS__)
+
+const char *DATA_DIR_NAME[16] =
+ {
+ "Export Table",
+ "Import Table",
+ "Resource Table",
+ "Exception Table",
+ "Certificate Table",
+ "Base Relocation Table",
+ "Debug",
+ "Architecture",
+ "Global Ptr",
+ "TLS Table",
+ "Load Config Table",
+ "Bound Import",
+ "IAT",
+ "Delay Import Descriptor",
+ "COM+ Runtime Header",
+ "Reserved"
+ };
+
+
+CoffLoader::CoffLoader()
+{
+ CoffFileHeader = 0;
+ OptionHeader = 0;
+ WindowsHeader = 0;
+ Directory = 0;
+ SectionHeader = 0;
+ SymTable = 0;
+ StringTable = 0;
+ SectionData = 0;
+
+ NumberOfSymbols = 0;
+ SizeOfStringTable = 0;
+ NumOfDirectories = 0;
+ NumOfSections = 0;
+ FileHeaderOffset = 0;
+ EntryAddress = 0;
+ hModule = NULL;
+}
+
+CoffLoader::~CoffLoader()
+{
+ if ( hModule )
+ {
+#ifdef TARGET_POSIX
+ free(hModule);
+#else
+ VirtualFree(hModule, 0, MEM_RELEASE);
+#endif
+ hModule = NULL;
+ }
+ if ( SymTable )
+ {
+ delete [] SymTable;
+ SymTable = 0;
+ }
+ if ( StringTable )
+ {
+ delete [] StringTable;
+ StringTable = 0;
+ }
+ if ( SectionData )
+ {
+ delete [] SectionData;
+ SectionData = 0;
+ }
+}
+
+// Has nothing to do with the coff loader itself
+// it can be used to parse the headers of a dll
+// already loaded into memory
+int CoffLoader::ParseHeaders(void* hModule)
+{
+ if (strncmp((char*)hModule, "MZ", 2) != 0)
+ return 0;
+
+ int* Offset = (int*)((char*)hModule+0x3c);
+ if (*Offset <= 0)
+ return 0;
+
+ if (strncmp((char*)hModule+*Offset, "PE\0\0", 4) != 0)
+ return 0;
+
+ FileHeaderOffset = *Offset + 4;
+
+ CoffFileHeader = (COFF_FileHeader_t *) ( (char*)hModule + FileHeaderOffset );
+ NumOfSections = CoffFileHeader->NumberOfSections;
+
+ OptionHeader = (OptionHeader_t *) ( (char*)CoffFileHeader + sizeof(COFF_FileHeader_t) );
+ WindowsHeader = (WindowsHeader_t *) ( (char*)OptionHeader + OPTHDR_SIZE );
+ EntryAddress = OptionHeader->Entry;
+ NumOfDirectories = WindowsHeader->NumDirectories;
+
+ Directory = (Image_Data_Directory_t *) ( (char*)WindowsHeader + WINHDR_SIZE);
+ SectionHeader = (SectionHeader_t *) ( (char*)Directory + sizeof(Image_Data_Directory_t) * NumOfDirectories);
+
+ if (CoffFileHeader->MachineType != IMAGE_FILE_MACHINE_I386)
+ return 0;
+
+#ifdef DUMPING_DATA
+ PrintFileHeader(CoffFileHeader);
+#endif
+
+ if ( CoffFileHeader->SizeOfOptionHeader == 0 ) //not an image file, object file maybe
+ return 0;
+
+ // process Option Header
+ if (OptionHeader->Magic == OPTMAGIC_PE32P)
+ {
+ printf("PE32+ not supported\n");
+ return 0;
+ }
+ else if (OptionHeader->Magic == OPTMAGIC_PE32)
+ {
+
+#ifdef DUMPING_DATA
+ PrintOptionHeader(OptionHeader);
+ PrintWindowsHeader(WindowsHeader);
+#endif
+
+ }
+ else
+ {
+ //add error message
+ return 0;
+ }
+
+#ifdef DUMPING_DATA
+ for (int DirCount = 0; DirCount < NumOfDirectories; DirCount++)
+ {
+ printf("Data Directory %02d: %s\n", DirCount + 1, DATA_DIR_NAME[DirCount]);
+ printf(" RVA: %08X\n", Directory[DirCount].RVA);
+ printf(" Size: %08X\n\n", Directory[DirCount].Size);
+ }
+#endif
+
+ return 1;
+
+}
+
+int CoffLoader::LoadCoffHModule(FILE *fp)
+{
+ //test file signatures
+ char Sig[4];
+ rewind(fp);
+ memset(Sig, 0, sizeof(Sig));
+ if (!fread(Sig, 1, 2, fp) || strncmp(Sig, "MZ", 2) != 0)
+ return 0;
+
+ if (fseek(fp, 0x3c, SEEK_SET) != 0)
+ return 0;
+
+ int Offset = 0;
+ if (!fread(&Offset, sizeof(int), 1, fp) || (Offset <= 0))
+ return 0;
+
+ if (fseek(fp, Offset, SEEK_SET) != 0)
+ return 0;
+
+ memset(Sig, 0, sizeof(Sig));
+ if (!fread(Sig, 1, 4, fp) || strncmp(Sig, "PE\0\0", 4) != 0)
+ return 0;
+
+ Offset += 4;
+ FileHeaderOffset = Offset;
+
+ // Load and process Header
+ if (fseek(fp, FileHeaderOffset + sizeof(COFF_FileHeader_t) + OPTHDR_SIZE, SEEK_SET)) //skip to winows headers
+ return 0;
+
+ WindowsHeader_t tempWindowsHeader;
+ size_t readcount = fread(&tempWindowsHeader, 1, WINHDR_SIZE, fp);
+ if (readcount != WINHDR_SIZE) //test file size error
+ return 0;
+
+ // alloc aligned memory
+#ifdef TARGET_POSIX
+ hModule = malloc(tempWindowsHeader.SizeOfImage);
+#elif defined TARGET_WINDOWS_STORE
+ hModule = VirtualAllocFromApp(GetCurrentProcess(), tempWindowsHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+#else
+ hModule = VirtualAllocEx(GetCurrentProcess(), (PVOID)tempWindowsHeader.ImageBase, tempWindowsHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+ if (hModule == NULL)
+ hModule = VirtualAlloc(GetCurrentProcess(), tempWindowsHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+#endif
+ if (hModule == NULL)
+ return 0; //memory allocation fails
+
+ rewind(fp);
+ readcount = fread(hModule, 1, tempWindowsHeader.SizeOfHeaders, fp);
+ if (readcount != tempWindowsHeader.SizeOfHeaders) //file size error
+ return 0;
+
+ CoffFileHeader = (COFF_FileHeader_t *) ( (char*)hModule + FileHeaderOffset );
+ NumOfSections = CoffFileHeader->NumberOfSections;
+
+ OptionHeader = (OptionHeader_t *) ( (char*)CoffFileHeader + sizeof(COFF_FileHeader_t) );
+ WindowsHeader = (WindowsHeader_t *) ( (char*)OptionHeader + OPTHDR_SIZE );
+ EntryAddress = OptionHeader->Entry;
+ NumOfDirectories = WindowsHeader->NumDirectories;
+
+ Directory = (Image_Data_Directory_t *) ( (char*)WindowsHeader + WINHDR_SIZE);
+ SectionHeader = (SectionHeader_t *) ( (char*)Directory + sizeof(Image_Data_Directory_t) * NumOfDirectories);
+
+ if (CoffFileHeader->MachineType != IMAGE_FILE_MACHINE_I386)
+ return 0;
+
+#ifdef DUMPING_DATA
+ PrintFileHeader(CoffFileHeader);
+#endif
+
+ if ( CoffFileHeader->SizeOfOptionHeader == 0 ) //not an image file, object file maybe
+ return 0;
+
+ // process Option Header
+ if (OptionHeader->Magic == OPTMAGIC_PE32P)
+ {
+ printf("PE32+ not supported\n");
+ return 0;
+ }
+ else if (OptionHeader->Magic == OPTMAGIC_PE32)
+ {
+
+#ifdef DUMPING_DATA
+ PrintOptionHeader(OptionHeader);
+ PrintWindowsHeader(WindowsHeader);
+#endif
+
+ }
+ else
+ {
+ //add error message
+ return 0;
+ }
+
+#ifdef DUMPING_DATA
+ for (int DirCount = 0; DirCount < NumOfDirectories; DirCount++)
+ {
+ printf("Data Directory %02d: %s\n", DirCount + 1, DATA_DIR_NAME[DirCount]);
+ printf(" RVA: %08X\n", Directory[DirCount].RVA);
+ printf(" Size: %08X\n\n", Directory[DirCount].Size);
+ }
+#endif
+
+ return 1;
+
+}
+
+int CoffLoader::LoadSymTable(FILE *fp)
+{
+ int Offset = ftell(fp);
+ if (Offset < 0)
+ return 0;
+
+ if ( CoffFileHeader->PointerToSymbolTable == 0 )
+ return 1;
+
+ if (fseek(fp, CoffFileHeader->PointerToSymbolTable /* + CoffBeginOffset*/, SEEK_SET) != 0)
+ return 0;
+
+ SymbolTable_t *tmp = new SymbolTable_t[CoffFileHeader->NumberOfSymbols];
+ if (!tmp)
+ {
+ printf("Could not allocate memory for symbol table!\n");
+ return 0;
+ }
+ if (!fread((void *)tmp, CoffFileHeader->NumberOfSymbols, sizeof(SymbolTable_t), fp))
+ {
+ delete[] tmp;
+ return 0;
+ }
+ NumberOfSymbols = CoffFileHeader->NumberOfSymbols;
+ SymTable = tmp;
+ if (fseek(fp, Offset, SEEK_SET) != 0)
+ return 0;
+ return 1;
+}
+
+int CoffLoader::LoadStringTable(FILE *fp)
+{
+ int StringTableSize;
+ char *tmp = NULL;
+
+ int Offset = ftell(fp);
+ if (Offset < 0)
+ return 0;
+
+ if ( CoffFileHeader->PointerToSymbolTable == 0 )
+ return 1;
+
+ if (fseek(fp, CoffFileHeader->PointerToSymbolTable +
+ CoffFileHeader->NumberOfSymbols * sizeof(SymbolTable_t),
+ SEEK_SET) != 0)
+ return 0;
+
+ if (!fread(&StringTableSize, 1, sizeof(int), fp))
+ return 0;
+ StringTableSize -= 4;
+ if (StringTableSize != 0)
+ {
+ tmp = new char[StringTableSize];
+ if (tmp == NULL)
+ {
+ printf("Could not allocate memory for string table\n");
+ return 0;
+ }
+ if (!fread((void *)tmp, StringTableSize, sizeof(char), fp))
+ {
+ delete[] tmp;
+ return 0;
+ }
+ }
+ SizeOfStringTable = StringTableSize;
+ StringTable = tmp;
+ if (fseek(fp, Offset, SEEK_SET) != 0)
+ return 0;
+ return 1;
+}
+
+int CoffLoader::LoadSections(FILE *fp)
+{
+ NumOfSections = CoffFileHeader->NumberOfSections;
+
+ SectionData = new char * [NumOfSections];
+ if ( !SectionData )
+ return 0;
+
+ // Bobbin007: for debug dlls this check always fails
+
+ //////check VMA size!!!!!
+ //unsigned long vma_size = 0;
+ //for (int SctnCnt = 0; SctnCnt < NumOfSections; SctnCnt++)
+ //{
+ // SectionHeader_t *ScnHdr = (SectionHeader_t *)(SectionHeader + SctnCnt);
+ // vma_size = max(vma_size, ScnHdr->VirtualAddress + ScnHdr->SizeOfRawData);
+ // vma_size = max(vma_size, ScnHdr->VirtualAddress + ScnHdr->VirtualSize);
+ //}
+
+ //if (WindowsHeader->SizeOfImage < vma_size)
+ // return 0; //something wrong with file
+
+ for (int SctnCnt = 0; SctnCnt < NumOfSections; SctnCnt++)
+ {
+ SectionHeader_t *ScnHdr = SectionHeader + SctnCnt;
+ SectionData[SctnCnt] = ((char*)hModule + ScnHdr->VirtualAddress);
+
+ if (fseek(fp, ScnHdr->PtrToRawData, SEEK_SET) != 0)
+ return 0;
+
+ if (!fread(SectionData[SctnCnt], 1, ScnHdr->SizeOfRawData, fp))
+ return 0;
+
+#ifdef DUMPING_DATA
+ //debug blocks
+ char szBuf[128];
+ char namebuf[9];
+ for (int i = 0; i < 8; i++)
+ namebuf[i] = ScnHdr->Name[i];
+ namebuf[8] = '\0';
+ sprintf(szBuf, "Load code Sections %s Memory %p,Length %x\n", namebuf,
+ SectionData[SctnCnt], max(ScnHdr->VirtualSize, ScnHdr->SizeOfRawData));
+ OutputDebugString(szBuf);
+#endif
+
+ if ( ScnHdr->SizeOfRawData < ScnHdr->VirtualSize ) //initialize BSS data in the end of section
+ {
+ memset((char*)((long)(SectionData[SctnCnt]) + ScnHdr->SizeOfRawData), 0, ScnHdr->VirtualSize - ScnHdr->SizeOfRawData);
+ }
+
+ if (ScnHdr->Characteristics & IMAGE_SCN_CNT_BSS) //initialize whole .BSS section, pure .BSS is obsolete
+ {
+ memset(SectionData[SctnCnt], 0, ScnHdr->VirtualSize);
+ }
+
+#ifdef DUMPING_DATA
+ PrintSection(SectionHeader + SctnCnt, SectionData[SctnCnt]);
+#endif
+
+ }
+ return 1;
+}
+
+//FIXME: Add the Free Resources functions
+
+int CoffLoader::RVA2Section(unsigned long RVA)
+{
+ NumOfSections = CoffFileHeader->NumberOfSections;
+ for ( int i = 0; i < NumOfSections; i++)
+ {
+ if ( SectionHeader[i].VirtualAddress <= RVA )
+ {
+ if ( i + 1 != NumOfSections )
+ {
+ if ( RVA < SectionHeader[i + 1].VirtualAddress )
+ {
+ if ( SectionHeader[i].VirtualAddress + SectionHeader[i].VirtualSize <= RVA )
+ printf("Warning! Address outside of Section: %lx!\n", RVA);
+ // else
+ return i;
+ }
+ }
+ else
+ {
+ if ( SectionHeader[i].VirtualAddress + SectionHeader[i].VirtualSize <= RVA )
+ printf("Warning! Address outside of Section: %lx!\n", RVA);
+ // else
+ return i;
+ }
+ }
+ }
+ printf("RVA2Section lookup failure!\n");
+ return 0;
+}
+
+void* CoffLoader::RVA2Data(unsigned long RVA)
+{
+ int Sctn = RVA2Section(RVA);
+
+ if( RVA < SectionHeader[Sctn].VirtualAddress
+ || RVA >= SectionHeader[Sctn].VirtualAddress + SectionHeader[Sctn].VirtualSize)
+ {
+ // RVA2Section is lying, let's use base address of dll instead, only works if
+ // DLL has been loaded fully into memory, which we normally do
+ return (void*)(RVA + (unsigned long)hModule);
+ }
+ return SectionData[Sctn] + RVA - SectionHeader[Sctn].VirtualAddress;
+}
+
+unsigned long CoffLoader::Data2RVA(void* address)
+{
+ for ( int i = 0; i < CoffFileHeader->NumberOfSections; i++)
+ {
+ if(address >= SectionData[i] && address < SectionData[i] + SectionHeader[i].VirtualSize)
+ return (unsigned long)address - (unsigned long)SectionData[i] + SectionHeader[i].VirtualAddress;
+ }
+
+ // Section wasn't found, so use relative to main load of dll
+ return (unsigned long)address - (unsigned long)hModule;
+}
+
+char *CoffLoader::GetStringTblIndex(int index)
+{
+ char *table = StringTable;
+
+ while (index--)
+ table += strlen(table) + 1;
+ return table;
+}
+
+char *CoffLoader::GetStringTblOff(int Offset)
+{
+ return StringTable + Offset - 4;
+}
+
+char *CoffLoader::GetSymbolName(SymbolTable_t *sym)
+{
+ long long index = sym->Name.Offset;
+ int low = (int)(index & 0xFFFFFFFF);
+ int high = (int)((index >> 32) & 0xFFFFFFFF);
+
+ if (low == 0)
+ {
+ return GetStringTblOff(high);
+ }
+ else
+ {
+ static char shortname[9];
+ memset(shortname, 0, 9);
+ strncpy(shortname, (char *)sym->Name.ShortName, 8);
+ return shortname;
+ }
+}
+
+char *CoffLoader::GetSymbolName(int index)
+{
+ SymbolTable_t *sym = &(SymTable[index]);
+ return GetSymbolName(sym);
+}
+
+void CoffLoader::PrintStringTable(void)
+{
+ int size = SizeOfStringTable;
+ int index = 0;
+ char *table = StringTable;
+
+ printf("\nSTRING TABLE\n");
+ while (size)
+ {
+ printf("%2d: %s\n", index++, table);
+ size -= strlen(table) + 1;
+ table += strlen(table) + 1;
+ }
+ printf("\n");
+}
+
+
+void CoffLoader::PrintSymbolTable(void)
+{
+ int SymIndex;
+
+ printf("COFF SYMBOL TABLE\n");
+ for (SymIndex = 0; SymIndex < NumberOfSymbols; SymIndex++)
+ {
+ printf("%03X ", SymIndex);
+ printf("%08lX ", SymTable[SymIndex].Value);
+
+ if (SymTable[SymIndex].SectionNumber == IMAGE_SYM_ABSOLUTE)
+ printf("ABS ");
+ else if (SymTable[SymIndex].SectionNumber == IMAGE_SYM_DEBUG)
+ printf("DEBUG ");
+ else if (SymTable[SymIndex].SectionNumber == IMAGE_SYM_UNDEFINED)
+ printf("UNDEF ");
+ else
+ {
+ printf("SECT%d ", SymTable[SymIndex].SectionNumber);
+ if (SymTable[SymIndex].SectionNumber < 10)
+ printf(" ");
+ if (SymTable[SymIndex].SectionNumber < 100)
+ printf(" ");
+ }
+
+ if (SymTable[SymIndex].Type == 0)
+ printf("notype ");
+ else
+ {
+ printf("%X ", SymTable[SymIndex].Type);
+ if (SymTable[SymIndex].Type < 0x10)
+ printf(" ");
+ if (SymTable[SymIndex].Type < 0x100)
+ printf(" ");
+ if (SymTable[SymIndex].Type < 0x1000)
+ printf(" ");
+ }
+
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_END_OF_FUNCTION)
+ printf("End of Function ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_NULL)
+ printf("Null ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_AUTOMATIC)
+ printf("Automatic ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_EXTERNAL)
+ printf("External ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_STATIC)
+ printf("Static ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_REGISTER)
+ printf("Register ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_EXTERNAL_DEF)
+ printf("External Def ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_LABEL)
+ printf("Label ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_UNDEFINED_LABEL)
+ printf("Undefined Label ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_MEMBER_OF_STRUCT)
+ printf("Member Of Struct ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_ARGUMENT)
+ printf("Argument ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_STRUCT_TAG)
+ printf("Struct Tag ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_MEMBER_OF_UNION)
+ printf("Member Of Union ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_UNION_TAG)
+ printf("Union Tag ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_TYPE_DEFINITION)
+ printf("Type Definition ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_UNDEFINED_STATIC)
+ printf("Undefined Static ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_ENUM_TAG)
+ printf("Enum Tag ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_MEMBER_OF_ENUM)
+ printf("Member Of Enum ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_REGISTER_PARAM)
+ printf("Register Param ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_BIT_FIELD)
+ printf("Bit Field ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_BLOCK)
+ printf("Block ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_FUNCTION)
+ printf("Function ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_END_OF_STRUCT)
+ printf("End Of Struct ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_FILE)
+ printf("File ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_SECTION)
+ printf("Section ");
+ if (SymTable[SymIndex].StorageClass == IMAGE_SYM_CLASS_WEAK_EXTERNAL)
+ printf("Weak External ");
+
+ printf("| %s", GetSymbolName(SymIndex));
+
+ SymIndex += SymTable[SymIndex].NumberOfAuxSymbols;
+ printf("\n");
+ }
+ printf("\n");
+
+}
+
+void CoffLoader::PrintFileHeader(COFF_FileHeader_t *FileHeader)
+{
+ printf("COFF Header\n");
+ printf("------------------------------------------\n\n");
+
+ printf("MachineType: 0x%04X\n", FileHeader->MachineType);
+ printf("NumberOfSections: 0x%04X\n", FileHeader->NumberOfSections);
+ printf("TimeDateStamp: 0x%08lX\n",FileHeader->TimeDateStamp);
+ printf("PointerToSymbolTable: 0x%08lX\n",FileHeader->PointerToSymbolTable);
+ printf("NumberOfSymbols: 0x%08lX\n",FileHeader->NumberOfSymbols);
+ printf("SizeOfOptionHeader: 0x%04X\n", FileHeader->SizeOfOptionHeader);
+ printf("Characteristics: 0x%04X\n", FileHeader->Characteristics);
+
+ if (FileHeader->Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
+ printf(" IMAGE_FILE_RELOCS_STRIPPED\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)
+ printf(" IMAGE_FILE_EXECUTABLE_IMAGE\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_LINE_NUMS_STRIPPED)
+ printf(" IMAGE_FILE_LINE_NUMS_STRIPPED\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_LOCAL_SYMS_STRIPPED)
+ printf(" IMAGE_FILE_LOCAL_SYMS_STRIPPED\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_AGGRESSIVE_WS_TRIM)
+ printf(" IMAGE_FILE_AGGRESSIVE_WS_TRIM\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE)
+ printf(" IMAGE_FILE_LARGE_ADDRESS_AWARE\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_16BIT_MACHINE)
+ printf(" IMAGE_FILE_16BIT_MACHINE\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_BYTES_REVERSED_LO)
+ printf(" IMAGE_FILE_BYTES_REVERSED_LO\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_32BIT_MACHINE)
+ printf(" IMAGE_FILE_32BIT_MACHINE\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_DEBUG_STRIPPED)
+ printf(" IMAGE_FILE_DEBUG_STRIPPED\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP)
+ printf(" IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_SYSTEM)
+ printf(" IMAGE_FILE_SYSTEM\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_DLL)
+ printf(" IMAGE_FILE_DLL\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY)
+ printf(" IMAGE_FILE_UP_SYSTEM_ONLY\n");
+
+ if (FileHeader->Characteristics & IMAGE_FILE_BYTES_REVERSED_HI)
+ printf(" IMAGE_FILE_BYTES_REVERSED_HI\n");
+
+ printf("\n");
+}
+
+void CoffLoader::PrintOptionHeader(OptionHeader_t *OptHdr)
+{
+ printf("Option Header\n");
+ printf("------------------------------------------\n\n");
+
+ printf("Magic: 0x%04X\n", OptHdr->Magic);
+ printf("Linker Major Ver: 0x%02X\n", VERSION_MAJOR(OptHdr->LinkVersion));
+ printf("Linker Minor Ver: 0x%02X\n", VERSION_MINOR(OptHdr->LinkVersion));
+ printf("Code Size: 0x%08lX\n", OptHdr->CodeSize);
+ printf("Data Size: 0x%08lX\n", OptHdr->DataSize);
+ printf("BSS Size: 0x%08lX\n", OptHdr->BssSize);
+ printf("Entry: 0x%08lX\n", OptHdr->Entry);
+ printf("Code Base: 0x%08lX\n", OptHdr->CodeBase);
+ printf("Data Base: 0x%08lX\n", OptHdr->DataBase);
+ printf("\n");
+}
+
+void CoffLoader::PrintWindowsHeader(WindowsHeader_t *WinHdr)
+{
+ printf("Windows Specific Option Header\n");
+ printf("------------------------------------------\n\n");
+
+ printf("Image Base: 0x%08lX\n", WinHdr->ImageBase);
+ printf("Section Alignment: 0x%08lX\n", WinHdr->SectionAlignment);
+ printf("File Alignment: 0x%08lX\n", WinHdr->FileAlignment);
+ printf("OS Version: %d.%08d\n", BIGVERSION_MAJOR(WinHdr->OSVer), BIGVERSION_MINOR(WinHdr->OSVer));
+ printf("Image Version: %d.%08d\n", BIGVERSION_MAJOR(WinHdr->ImgVer), BIGVERSION_MINOR(WinHdr->ImgVer));
+ printf("SubSystem Version: %d.%08d\n", BIGVERSION_MAJOR(WinHdr->SubSysVer), BIGVERSION_MINOR(WinHdr->SubSysVer));
+ printf("Size of Image: 0x%08lX\n", WinHdr->SizeOfImage);
+ printf("Size of Headers: 0x%08lX\n", WinHdr->SizeOfHeaders);
+ printf("Checksum: 0x%08lX\n", WinHdr->CheckSum);
+ printf("Subsystem: 0x%04X\n", WinHdr->Subsystem);
+ printf("DLL Flags: 0x%04X\n", WinHdr->DLLFlags);
+ printf("Sizeof Stack Resv: 0x%08lX\n", WinHdr->SizeOfStackReserve);
+ printf("Sizeof Stack Comm: 0x%08lX\n", WinHdr->SizeOfStackCommit);
+ printf("Sizeof Heap Resv: 0x%08lX\n", WinHdr->SizeOfHeapReserve);
+ printf("Sizeof Heap Comm: 0x%08lX\n", WinHdr->SizeOfHeapCommit);
+ printf("Loader Flags: 0x%08lX\n", WinHdr->LoaderFlags);
+ printf("Num Directories: %ld\n", WinHdr->NumDirectories);
+ printf("\n");
+}
+
+void CoffLoader::PrintSection(SectionHeader_t* ScnHdr, const char* data)
+{
+ char SectionName[9];
+
+ strncpy(SectionName, (char *)ScnHdr->Name, 8);
+ SectionName[8] = 0;
+ printf("Section: %s\n", SectionName);
+ printf("------------------------------------------\n\n");
+
+ printf("Virtual Size: 0x%08lX\n", ScnHdr->VirtualSize);
+ printf("Virtual Address: 0x%08lX\n", ScnHdr->VirtualAddress);
+ printf("Sizeof Raw Data: 0x%08lX\n", ScnHdr->SizeOfRawData);
+ printf("Ptr To Raw Data: 0x%08lX\n", ScnHdr->PtrToRawData);
+ printf("Ptr To Relocations: 0x%08lX\n", ScnHdr->PtrToRelocations);
+ printf("Ptr To Line Nums: 0x%08lX\n", ScnHdr->PtrToLineNums);
+ printf("Num Relocations: 0x%04X\n", ScnHdr->NumRelocations);
+ printf("Num Line Numbers: 0x%04X\n", ScnHdr->NumLineNumbers);
+ printf("Characteristics: 0x%08lX\n", ScnHdr->Characteristics);
+ if (ScnHdr->Characteristics & IMAGE_SCN_CNT_CODE)
+ printf(" IMAGE_SCN_CNT_CODE\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_CNT_DATA)
+ printf(" IMAGE_SCN_CNT_DATA\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_CNT_BSS)
+ printf(" IMAGE_SCN_CNT_BSS\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_LNK_INFO)
+ printf(" IMAGE_SCN_LNK_INFO\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_LNK_REMOVE)
+ printf(" IMAGE_SCN_LNK_REMOVE\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_LNK_COMDAT)
+ printf(" IMAGE_SCN_LNK_COMDAT\n");
+
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_1BYTES)
+ printf(" IMAGE_SCN_ALIGN_1BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_2BYTES)
+ printf(" IMAGE_SCN_ALIGN_2BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_4BYTES)
+ printf(" IMAGE_SCN_ALIGN_4BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_8BYTES)
+ printf(" IMAGE_SCN_ALIGN_8BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_16BYTES)
+ printf(" IMAGE_SCN_ALIGN_16BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_32BYTES)
+ printf(" IMAGE_SCN_ALIGN_32BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_64BYTES)
+ printf(" IMAGE_SCN_ALIGN_64BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_128BYTES)
+ printf(" IMAGE_SCN_ALIGN_128BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_256BYTES)
+ printf(" IMAGE_SCN_ALIGN_256BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_512BYTES)
+ printf(" IMAGE_SCN_ALIGN_512BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_1024BYTES)
+ printf(" IMAGE_SCN_ALIGN_1024BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_2048BYTES)
+ printf(" IMAGE_SCN_ALIGN_2048BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_4096BYTES)
+ printf(" IMAGE_SCN_ALIGN_4096BYTES\n");
+ if ((ScnHdr->Characteristics & IMAGE_SCN_ALIGN_MASK) == IMAGE_SCN_ALIGN_8192BYTES)
+ printf(" IMAGE_SCN_ALIGN_8192BYTES\n");
+
+ if (ScnHdr->Characteristics & IMAGE_SCN_LNK_NRELOC_OVFL)
+ printf(" IMAGE_SCN_LNK_NRELOC_OVFL\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
+ printf(" IMAGE_SCN_MEM_DISCARDABLE\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
+ printf(" IMAGE_SCN_MEM_NOT_CACHED\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_NOT_PAGED)
+ printf(" IMAGE_SCN_MEM_NOT_PAGED\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_SHARED)
+ printf(" IMAGE_SCN_MEM_SHARED\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE)
+ printf(" IMAGE_SCN_MEM_EXECUTE\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_READ)
+ printf(" IMAGE_SCN_MEM_READ\n");
+ if (ScnHdr->Characteristics & IMAGE_SCN_MEM_WRITE)
+ printf(" IMAGE_SCN_MEM_WRITE\n");
+ printf("\n");
+
+ // Read the section Data, Relocations, & Line Nums
+ // Save the offset
+
+ if (ScnHdr->SizeOfRawData > 0)
+ {
+ unsigned int i;
+ // Print the Raw Data
+
+ printf("\nRAW DATA");
+ for (i = 0; i < ScnHdr->VirtualSize; i++)
+ {
+ if ((i % 16) == 0)
+ printf("\n %08X: ", i);
+ char ch = data[i];
+ printf("%02X ", (unsigned int)ch);
+ }
+ printf("\n\n");
+ }
+
+ /*
+ #if 0
+ if (ScnHdr->NumRelocations > 0)
+ {
+ // Print Section Relocations
+ ObjReloc_t ObjReloc;
+
+ fseek(fp, ScnHdr->PtrToRelocations/ * + CoffBeginOffset* /, SEEK_SET);
+ printf("RELOCATIONS\n");
+ printf(" Symbol Symbol\n");
+ printf(" Offset Type Index Name\n");
+ printf(" -------- -------- -------- ------\n");
+ for (int i = 0; i < ScnHdr->NumRelocations; i++)
+ {
+ fread(&ObjReloc, 1, sizeof(ObjReloc_t), fp);
+ printf(" %08X ", ObjReloc.VirtualAddress);
+
+ if (ObjReloc.Type == IMAGE_REL_I386_ABSOLUTE)
+ printf("ABSOLUTE ");
+ if (ObjReloc.Type == IMAGE_REL_I386_DIR16)
+ printf("DIR16 ");
+ if (ObjReloc.Type == IMAGE_REL_I386_REL16)
+ printf("REL16 ");
+ if (ObjReloc.Type == IMAGE_REL_I386_DIR32)
+ printf("DIR32 ");
+ if (ObjReloc.Type == IMAGE_REL_I386_DIR32NB)
+ printf("DIR32NB ");
+ if (ObjReloc.Type == IMAGE_REL_I386_SEG12)
+ printf("SEG12 ");
+ if (ObjReloc.Type == IMAGE_REL_I386_SECTION)
+ printf("SECTION ");
+ if (ObjReloc.Type == IMAGE_REL_I386_SECREL)
+ printf("SECREL ");
+ if (ObjReloc.Type == IMAGE_REL_I386_REL32)
+ printf("REL32 ");
+ printf("%8X ", ObjReloc.SymTableIndex);
+ printf("%s\n", GetSymbolName(ObjReloc.SymTableIndex));
+ }
+ printf("\n");
+ }
+
+ if (ScnHdr->NumLineNumbers > 0)
+ {
+ // Print The Line Number Info
+ LineNumbers_t LineNumber;
+ int LineCnt = 0;
+ int BaseLineNum = -1;
+
+ fseek(fp, ScnHdr->PtrToLineNums/ * + CoffBeginOffset* /, SEEK_SET);
+ printf("LINE NUMBERS");
+ for (int i = 0; i < ScnHdr->NumLineNumbers; i++)
+ {
+ int LNOffset = ftell(fp);
+
+ fread(&LineNumber, 1, sizeof(LineNumbers_t), fp);
+ if (LineNumber.LineNum == 0)
+ {
+ SymbolTable_t *Sym;
+ int SymIndex;
+
+ printf("\n");
+ SymIndex = LineNumber.Type.SymbolTableIndex;
+ Sym = &(SymTable[SymIndex]);
+ if (Sym->NumberOfAuxSymbols > 0)
+ {
+ Sym = &(SymTable[SymIndex+1]);
+ AuxFuncDef_t *FuncDef = (AuxFuncDef_t *)Sym;
+
+ if (FuncDef->PtrToLineNumber == LNOffset)
+ {
+ Sym = &(SymTable[FuncDef->TagIndex]);
+ if (Sym->NumberOfAuxSymbols > 0)
+ {
+ Sym = &(SymTable[FuncDef->TagIndex+1]);
+ AuxBfEf_t *Bf = (AuxBfEf_t *)Sym;
+ BaseLineNum = Bf->LineNumber;
+ }
+ }
+ }
+ printf(" Symbol Index: %8x ", SymIndex);
+ printf(" Base line number: %8d\n", BaseLineNum);
+ printf(" Symbol name = %s", GetSymbolName(SymIndex));
+ LineCnt = 0;
+ }
+ else
+ {
+ if ((LineCnt%4) == 0)
+ {
+ printf("\n ");
+ LineCnt = 0;
+ }
+ printf("%08X(%5d) ", LineNumber.Type.VirtualAddress,
+ LineNumber.LineNum + BaseLineNum);
+ LineCnt ++;
+ }
+ }
+ printf("\n");
+ }
+ #endif
+ */
+
+ printf("\n");
+}
+
+int CoffLoader::ParseCoff(FILE *fp)
+{
+ if ( !LoadCoffHModule(fp) )
+ {
+ printf("Failed to load/find COFF hModule header\n");
+ return 0;
+ }
+ if ( !LoadSymTable(fp) ||
+ !LoadStringTable(fp) ||
+ !LoadSections(fp) )
+ return 0;
+
+ PerformFixups();
+
+#ifdef DUMPING_DATA
+ PrintSymbolTable();
+ PrintStringTable();
+#endif
+ return 1;
+}
+
+void CoffLoader::PerformFixups(void)
+{
+ int FixupDataSize;
+ char *FixupData;
+ char *EndData;
+
+ EntryAddress = (unsigned long)RVA2Data(EntryAddress);
+
+ if( reinterpret_cast<void*>(WindowsHeader->ImageBase) == hModule )
+ return;
+
+ if ( !Directory )
+ return ;
+
+ if ( NumOfDirectories <= BASE_RELOCATION_TABLE )
+ return ;
+
+ if ( !Directory[BASE_RELOCATION_TABLE].Size )
+ return ;
+
+ FixupDataSize = Directory[BASE_RELOCATION_TABLE].Size;
+ FixupData = (char*)RVA2Data(Directory[BASE_RELOCATION_TABLE].RVA);
+ EndData = FixupData + FixupDataSize;
+
+ while (FixupData < EndData)
+ {
+ // Starting a new Fixup Block
+ unsigned long PageRVA = *((unsigned long*)FixupData);
+ FixupData += 4;
+ unsigned long BlockSize = *((unsigned long*)FixupData);
+ FixupData += 4;
+
+ BlockSize -= 8;
+ for (unsigned int i = 0; i < BlockSize / 2; i++)
+ {
+ unsigned short Fixup = *((unsigned short*)FixupData);
+ FixupData += 2;
+ int Type = (Fixup >> 12) & 0x0f;
+ Fixup &= 0xfff;
+ if (Type == IMAGE_REL_BASED_HIGHLOW)
+ {
+ unsigned long *Off = (unsigned long*)RVA2Data(Fixup + PageRVA);
+ *Off = (unsigned long)RVA2Data(*Off - WindowsHeader->ImageBase);
+ }
+ else if (Type == IMAGE_REL_BASED_ABSOLUTE)
+ {}
+ else
+ {
+ printf("Unsupported fixup type!!\n");
+ }
+ }
+ }
+}
diff --git a/xbmc/cores/DllLoader/coff.h b/xbmc/cores/DllLoader/coff.h
new file mode 100644
index 0000000..f421d43
--- /dev/null
+++ b/xbmc/cores/DllLoader/coff.h
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+//#pragma message("including coff.h")
+//
+// COFF -- Common Object File Format
+// Used commonly by Un*x and is embedded in Windows PE
+// file format.
+//
+
+// These structures must be packed
+#pragma pack(1)
+
+
+/*
+ * Some general purpose MACROs
+ */
+
+#define VERSION_MAJOR(x) ((unsigned int)((x)& 0xff))
+#define VERSION_MINOR(x) ((unsigned int)(((x)>8) &0xff))
+
+#define BIGVERSION_MAJOR(x) ((unsigned int)((x)& 0xffff))
+#define BIGVERSION_MINOR(x) ((unsigned int)(((x)>16) &0xffff))
+
+/*
+ * COFF File Header (Object & Image)
+ * Spec section 3.3
+ */
+
+typedef struct
+{
+ unsigned short MachineType; /* magic type */
+ unsigned short NumberOfSections; /* number of sections */
+ unsigned long TimeDateStamp; /* time & date stamp */
+ unsigned long PointerToSymbolTable; /* file pointer to symtab */
+ unsigned long NumberOfSymbols; /* number of symtab entries */
+ unsigned short SizeOfOptionHeader; /* sizeof(optional hdr) */
+ unsigned short Characteristics; /* flags */
+}
+COFF_FileHeader_t;
+
+/*
+ * Machine Types
+ * Spec section 3.3.1
+ * (only i386 relevant for us)
+ */
+
+#if 1
+
+#ifndef IMAGE_FILE_MACHINE_I386
+#define IMAGE_FILE_MACHINE_I386 0x14c
+#endif
+
+
+
+#define IMAGE_FILE_RELOCS_STRIPPED 0x0001
+#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002
+#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004
+#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008
+#define IMAGE_FILE_AGGRESSIVE_WS_TRIM 0x0010
+#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020
+#define IMAGE_FILE_16BIT_MACHINE 0x0040
+#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080
+#define IMAGE_FILE_32BIT_MACHINE 0x0100
+#define IMAGE_FILE_DEBUG_STRIPPED 0x0200
+#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400
+#define IMAGE_FILE_SYSTEM 0x1000
+#define IMAGE_FILE_DLL 0x2000
+#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000
+#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000
+
+#endif
+
+
+
+#define OPTMAGIC_PE32 0x010b
+#define OPTMAGIC_PE32P 0x020b
+
+#define OPTHDR_SIZE 28
+#define OPTHDR_SIZEP 24
+#define WINHDR_SIZE 68
+#define WINHDR_SIZEP 88
+
+/*
+ * Optional Header Standard Fields (Image Only)
+ * Spec section 3.4.1
+ */
+
+typedef struct
+{
+ unsigned short Magic;
+ unsigned short LinkVersion;
+ unsigned long CodeSize;
+ unsigned long DataSize;
+ unsigned long BssSize;
+ unsigned long Entry;
+ unsigned long CodeBase;
+ unsigned long DataBase;
+}
+OptionHeader_t;
+
+typedef struct
+{
+ unsigned short Magic;
+ unsigned short LinkVersion;
+ unsigned long CodeSize;
+ unsigned long DataSize;
+ unsigned long BssSize;
+ unsigned long Entry;
+ unsigned long CodeBase;
+}
+OptionHeaderPlus_t;
+
+/*
+ * Optional Header Windows NT-Specific Fields (Image Only)
+ * Spec section 3.4.2
+ */
+
+typedef struct
+{
+ unsigned long ImageBase;
+ unsigned long SectionAlignment;
+ unsigned long FileAlignment;
+ unsigned long OSVer;
+ unsigned long ImgVer;
+ unsigned long SubSysVer;
+ unsigned long Reserved;
+ unsigned long SizeOfImage;
+ unsigned long SizeOfHeaders;
+ unsigned long CheckSum;
+ unsigned short Subsystem;
+ unsigned short DLLFlags;
+ unsigned long SizeOfStackReserve;
+ unsigned long SizeOfStackCommit;
+ unsigned long SizeOfHeapReserve;
+ unsigned long SizeOfHeapCommit;
+ unsigned long LoaderFlags;
+ unsigned long NumDirectories;
+}
+WindowsHeader_t;
+
+typedef struct
+{
+ unsigned long long ImageBase;
+ unsigned long SectionAlignment;
+ unsigned long FileAlignment;
+ unsigned long OSVer;
+ unsigned long ImgVer;
+ unsigned long SubSysVer;
+ unsigned long Reserved;
+ unsigned long SizeOfImage;
+ unsigned long SizeOfHeaders;
+ unsigned long CheckSum;
+ unsigned short Subsystem;
+ unsigned short DLLFlags;
+ unsigned long long SizeOfStackReserve;
+ unsigned long long SizeOfStackCommit;
+ unsigned long long SizeOfHeapReserve;
+ unsigned long long SizeOfHeapCommit;
+ unsigned long LoaderFlags;
+ unsigned long NumDirectories;
+}
+WindowsHeaderPlus_t;
+
+/*
+#define IMAGE_SUBSYSTEM_UNKNOWN 0
+#define IMAGE_SUBSYSTEM_NATIVE 1
+#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2
+#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3
+#define IMAGE_SUBSYSTEM_POSIX_CUI 7
+#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9
+#define IMAGE_SUBSYSTEM_EFI_APPLICATION 10
+#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11
+#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12
+
+#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800
+#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000
+#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0X8000
+*/
+
+/*
+ * Optional Header Data Directories (Image Only)
+ * Spec section 3.4.3
+ */
+
+typedef struct
+{
+ unsigned long RVA;
+ unsigned long Size;
+}
+Image_Data_Directory_t;
+
+enum Directory_Items {
+ EXPORT_TABLE = 0,
+ IMPORT_TABLE,
+ RESOURCE_TABLE,
+ EXCEPTION_TABLE,
+ CERTIFICATE_TABLE,
+ BASE_RELOCATION_TABLE,
+ DEBUG_,
+ ARCHITECTURE,
+ GLOBAL_PTR,
+ TLS_TABLE,
+ LOAD_CONFIG_TABLE,
+ BOUND_IMPORT,
+ IAT,
+ DELAY_IMPORT_DESCRIPTOR,
+ COM_RUNTIME_HEADER,
+ RESERVED
+};
+
+/*
+ * Section Table (Section Headers)
+ * Spec section 4.
+ */
+
+
+typedef struct
+{
+ unsigned char Name[8];
+ unsigned long VirtualSize;
+ unsigned long VirtualAddress;
+ unsigned long SizeOfRawData;
+ unsigned long PtrToRawData;
+ unsigned long PtrToRelocations;
+ unsigned long PtrToLineNums;
+ unsigned short NumRelocations;
+ unsigned short NumLineNumbers;
+ unsigned long Characteristics;
+}
+SectionHeader_t;
+
+/*
+ * Section Flags (Characteristics)
+ * Spec section 4.1
+ */
+
+#define IMAGE_SCN_CNT_CODE 0x00000020
+#define IMAGE_SCN_CNT_DATA 0x00000040
+#define IMAGE_SCN_CNT_BSS 0x00000080
+#define IMAGE_SCN_LNK_INFO 0x00000200
+#define IMAGE_SCN_LNK_REMOVE 0x00000800
+#define IMAGE_SCN_LNK_COMDAT 0x00001000
+#define IMAGE_SCN_ALIGN_1BYTES 0x00100000
+#define IMAGE_SCN_ALIGN_2BYTES 0x00200000
+#define IMAGE_SCN_ALIGN_4BYTES 0x00300000
+#define IMAGE_SCN_ALIGN_8BYTES 0x00400000
+#define IMAGE_SCN_ALIGN_16BYTES 0x00500000
+#define IMAGE_SCN_ALIGN_32BYTES 0x00600000
+#define IMAGE_SCN_ALIGN_64BYTES 0x00700000
+#define IMAGE_SCN_ALIGN_128BYTES 0x00800000
+#define IMAGE_SCN_ALIGN_256BYTES 0x00900000
+#define IMAGE_SCN_ALIGN_512BYTES 0x00A00000
+#define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000
+#define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000
+#define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000
+#define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000
+#define IMAGE_SCN_ALIGN_MASK 0x00F00000
+#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000
+#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000
+#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000
+#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000
+#define IMAGE_SCN_MEM_SHARED 0x10000000
+#define IMAGE_SCN_MEM_EXECUTE 0x20000000
+#define IMAGE_SCN_MEM_READ 0x40000000
+#define IMAGE_SCN_MEM_WRITE 0x80000000
+
+/*
+ * COFF Relocations (Object Only)
+ * Spec section 5.2
+ */
+
+typedef struct
+{
+ unsigned long VirtualAddress;
+ unsigned long SymTableIndex;
+ unsigned short Type;
+}
+ObjReloc_t;
+
+/*
+ * COFF Relocation Type Indicators
+ * Spec section 5.2.1
+ */
+
+#define IMAGE_REL_I386_ABSOLUTE 0x0000
+#define IMAGE_REL_I386_DIR16 0x0001
+#define IMAGE_REL_I386_REL16 0x0002
+#define IMAGE_REL_I386_DIR32 0x0006
+#define IMAGE_REL_I386_DIR32NB 0x0007
+#define IMAGE_REL_I386_SEG12 0x0009
+#define IMAGE_REL_I386_SECTION 0x000A
+#define IMAGE_REL_I386_SECREL 0x000B
+#define IMAGE_REL_I386_REL32 0x0014
+
+/*
+ * COFF Line Numbers
+ * Spec section 5.3
+ */
+
+typedef struct
+{
+ union {
+ unsigned long SymbolTableIndex;
+ unsigned long VirtualAddress;
+ } Type;
+ unsigned short LineNum;
+}
+LineNumbers_t;
+
+/*
+ * COFF Symbol Table
+ * Spec section 5.4
+ */
+
+typedef struct
+{
+ union {
+ unsigned char ShortName[8];
+ unsigned long long Offset;
+ } Name;
+ unsigned long Value;
+ unsigned short SectionNumber;
+ unsigned short Type;
+ unsigned char StorageClass;
+ unsigned char NumberOfAuxSymbols;
+}
+SymbolTable_t;
+
+#if !defined(TARGET_WINDOWS)
+
+#define IMAGE_SYM_UNDEFINED 0
+#define IMAGE_SYM_ABSOLUTE 0xFFFF
+#define IMAGE_SYM_DEBUG 0xFFFE
+
+
+#define IMAGE_SYM_TYPE_NULL 0
+#define IMAGE_SYM_TYPE_VOID 1
+#define IMAGE_SYM_TYPE_CHAR 2
+#define IMAGE_SYM_TYPE_SHORT 3
+#define IMAGE_SYM_TYPE_INT 4
+#define IMAGE_SYM_TYPE_LONG 5
+#define IMAGE_SYM_TYPE_FLOAT 6
+#define IMAGE_SYM_TYPE_DOUBLE 7
+#define IMAGE_SYM_TYPE_STRUCT 8
+#define IMAGE_SYM_TYPE_UNION 9
+#define IMAGE_SYM_TYPE_ENUM 10
+#define IMAGE_SYM_TYPE_MOE 11
+#define IMAGE_SYM_TYPE_BYTE 12
+#define IMAGE_SYM_TYPE_WORD 13
+#define IMAGE_SYM_TYPE_UINT 14
+#define IMAGE_SYM_TYPE_DWORD 15
+
+#define IMAGE_SYM_DWORD_NULL 0
+#define IMAGE_SYM_DWORD_POINTER 1
+#define IMAGE_SYM_DWORD_FUNCTION 2
+#define IMAGE_SYM_DWORD_ARRAY 3
+
+
+#define IMAGE_SYM_CLASS_END_OF_FUNCTION 0xFF
+#define IMAGE_SYM_CLASS_NULL 0
+#define IMAGE_SYM_CLASS_AUTOMATIC 1
+#define IMAGE_SYM_CLASS_EXTERNAL 2
+#define IMAGE_SYM_CLASS_STATIC 3
+#define IMAGE_SYM_CLASS_REGISTER 4
+#define IMAGE_SYM_CLASS_EXTERNAL_DEF 5
+#define IMAGE_SYM_CLASS_LABEL 6
+#define IMAGE_SYM_CLASS_UNDEFINED_LABEL 7
+#define IMAGE_SYM_CLASS_MEMBER_OF_STRUCT 8
+#define IMAGE_SYM_CLASS_ARGUMENT 9
+#define IMAGE_SYM_CLASS_STRUCT_TAG 10
+#define IMAGE_SYM_CLASS_MEMBER_OF_UNION 11
+#define IMAGE_SYM_CLASS_UNION_TAG 12
+#define IMAGE_SYM_CLASS_TYPE_DEFINITION 13
+#define IMAGE_SYM_CLASS_UNDEFINED_STATIC 14
+#define IMAGE_SYM_CLASS_ENUM_TAG 15
+#define IMAGE_SYM_CLASS_MEMBER_OF_ENUM 16
+#define IMAGE_SYM_CLASS_REGISTER_PARAM 17
+#define IMAGE_SYM_CLASS_BIT_FIELD 18
+#define IMAGE_SYM_CLASS_BLOCK 100
+#define IMAGE_SYM_CLASS_FUNCTION 101
+#define IMAGE_SYM_CLASS_END_OF_STRUCT 102
+#define IMAGE_SYM_CLASS_FILE 103
+#define IMAGE_SYM_CLASS_SECTION 104
+#define IMAGE_SYM_CLASS_WEAK_EXTERNAL 105
+#endif
+
+typedef struct
+{
+ unsigned long TagIndex;
+ unsigned long TotalSize;
+ unsigned long PtrToLineNumber;
+ unsigned long PtrToNextFunc;
+ unsigned short unused;
+}
+AuxFuncDef_t;
+
+/*
+ * Symbol Auxiliary Record: .bf and .ef
+ * Spec section 5.5.2
+ */
+
+typedef struct
+{
+ unsigned long unused;
+ unsigned short LineNumber;
+ unsigned long unused1;
+ unsigned short unused2;
+ unsigned long PtrToNextFunc;
+ unsigned char unused3;
+}
+AuxBfEf_t;
+
+/*
+ * Export Section (Directory)
+ * Spec section 6.3
+ */
+
+/*
+ * Export Directory Table
+ * Spec section 6.3.1
+ */
+
+typedef struct
+{
+ unsigned long ExportFlags;
+ unsigned long TimeStamp;
+ unsigned short MajorVersion;
+ unsigned short MinorVersion;
+ unsigned long Name_RVA;
+ unsigned long OrdinalBase;
+ unsigned long NumAddrTable;
+ unsigned long NumNamePtrs;
+ unsigned long ExportAddressTable_RVA;
+ unsigned long NamePointerTable_RVA;
+ unsigned long OrdinalTable_RVA;
+}
+ExportDirTable_t;
+
+
+/*
+ * Import Section (Directory)
+ * Spec section 6.4
+ */
+
+/*
+ * Import Directory Table
+ * Spec Section 6.4.1
+ */
+
+typedef struct
+{
+ unsigned long ImportLookupTable_RVA;
+ unsigned long TimeStamp;
+ unsigned long ForwarderChain;
+ unsigned long Name_RVA;
+ unsigned long ImportAddressTable_RVA;
+}
+ImportDirTable_t;
+
+/*
+ * .reloc Relocation types
+ * spec section 6.6
+ */
+
+#if 1
+#define IMAGE_REL_BASED_ABSOLUTE 0
+#define IMAGE_REL_BASED_HIGH 1
+#define IMAGE_REL_BASED_LOW 2
+#define IMAGE_REL_BASED_HIGHLOW 3
+#define IMAGE_REL_BASED_HIGHADJ 4
+#define IMAGE_REL_BASED_MIPS_JMPADDR 5
+#define IMAGE_REL_BASED_SECTION 6
+#define IMAGE_REL_BASED_REL32 7
+#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
+#define IMAGE_REL_BASED_DIR64 10
+#define IMAGE_REL_BASED_HIGH3ADJ 11
+#endif
+
+
+
+
+#pragma pack()
+
diff --git a/xbmc/cores/DllLoader/coffldr.h b/xbmc/cores/DllLoader/coffldr.h
new file mode 100644
index 0000000..9da7801
--- /dev/null
+++ b/xbmc/cores/DllLoader/coffldr.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+//#pragma message("including coffldr.h")
+#include "coff.h"
+
+#include <stdio.h>
+
+class CoffLoader
+{
+public:
+ CoffLoader();
+ virtual ~CoffLoader();
+
+ int ParseCoff(FILE *fp);
+ int ParseHeaders(void* hModule);
+
+ void *hModule; //standard windows HINSTANCE handle hold the whole image
+ //Pointers to somewhere in hModule, do not free these pointers
+ COFF_FileHeader_t *CoffFileHeader;
+ OptionHeader_t *OptionHeader;
+ WindowsHeader_t *WindowsHeader;
+ Image_Data_Directory_t *Directory;
+ SectionHeader_t *SectionHeader;
+
+protected:
+
+ // Allocated structures... hModule now hold the master Memory handle
+ SymbolTable_t *SymTable;
+ char *StringTable;
+ char **SectionData;
+
+ unsigned long EntryAddress; //Initialize entry point
+
+ // Unnecessary data
+ // This is data that is used only during linking and is not necessary
+ // while the program is running in general
+
+ int NumberOfSymbols;
+ int SizeOfStringTable;
+ int NumOfDirectories;
+ int NumOfSections;
+ int FileHeaderOffset;
+
+ // Members for printing the structures
+ static void PrintFileHeader(COFF_FileHeader_t *FileHeader);
+ static void PrintWindowsHeader(WindowsHeader_t *WinHdr);
+ static void PrintOptionHeader(OptionHeader_t *OptHdr);
+ static void PrintSection(SectionHeader_t* ScnHdr, const char* data);
+ void PrintStringTable(void);
+ void PrintSymbolTable(void);
+
+ // Members for Loading the Different structures
+ int LoadCoffHModule(FILE * fp);
+ int LoadSymTable(FILE *fp);
+ int LoadStringTable(FILE *fp);
+ int LoadSections(FILE *fp);
+
+ // Members for access some of the Data
+
+ int RVA2Section(unsigned long RVA);
+ void* RVA2Data(unsigned long RVA);
+ unsigned long Data2RVA(void* address);
+
+ char *GetStringTblIndex(int index);
+ char *GetStringTblOff(int Offset);
+ char *GetSymbolName(SymbolTable_t *sym);
+ char *GetSymbolName(int index);
+
+ void PerformFixups(void);
+};
+
diff --git a/xbmc/cores/DllLoader/dll.cpp b/xbmc/cores/DllLoader/dll.cpp
new file mode 100644
index 0000000..9690529
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "dll.h"
+
+#include "DllLoader.h"
+#include "DllLoaderContainer.h"
+#include "dll_tracker.h"
+#include "dll_util.h"
+#include "filesystem/SpecialProtocol.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <climits>
+
+#define DEFAULT_DLLPATH "special://xbmc/system/players/mplayer/codecs/"
+#define HIGH_WORD(a) ((uintptr_t)(a) >> 16)
+#define LOW_WORD(a) ((unsigned short)(((uintptr_t)(a)) & MAXWORD))
+
+//#define API_DEBUG
+
+char* getpath(char *buf, const char *full)
+{
+ const char* pos;
+ if ((pos = strrchr(full, PATH_SEPARATOR_CHAR)))
+ {
+ strncpy(buf, full, pos - full + 1 );
+ buf[pos - full + 1] = 0;
+ return buf;
+ }
+ else
+ {
+ buf[0] = 0;
+ return buf;
+ }
+}
+
+extern "C" HMODULE __stdcall dllLoadLibraryExtended(const char* lib_file, const char* sourcedll)
+{
+ char libname[MAX_PATH + 1] = {};
+ char libpath[MAX_PATH + 1] = {};
+ LibraryLoader* dll = NULL;
+
+ /* extract name */
+ const char* p = strrchr(lib_file, PATH_SEPARATOR_CHAR);
+ if (p)
+ strncpy(libname, p+1, sizeof(libname) - 1);
+ else
+ strncpy(libname, lib_file, sizeof(libname) - 1);
+ libname[sizeof(libname) - 1] = '\0';
+
+ if( libname[0] == '\0' )
+ return NULL;
+
+ /* extract path */
+ getpath(libpath, lib_file);
+
+ if (sourcedll)
+ {
+ /* also check for invalid paths which begin with a \ */
+ if( libpath[0] == '\0' || libpath[0] == PATH_SEPARATOR_CHAR )
+ {
+ /* use calling dll's path as base address for this call */
+ getpath(libpath, sourcedll);
+
+ /* mplayer has all it's dlls in a codecs subdirectory */
+ if (strstr(sourcedll, "mplayer.dll"))
+ strcat(libpath, "codecs\\");
+ }
+ }
+
+ /* if we still don't have a path, use default path */
+ if( libpath[0] == '\0' )
+ strcpy(libpath, DEFAULT_DLLPATH);
+
+ /* msdn docs state */
+ /* "If no file name extension is specified in the lpFileName parameter, the default library extension .dll is appended. */
+ /* However, the file name string can include a trailing point character (.) to indicate that the module name has no extension." */
+ if( strrchr(libname, '.') == NULL )
+ strcat(libname, ".dll");
+ else if( libname[strlen(libname)-1] == '.' )
+ libname[strlen(libname)-1] = '\0';
+
+ dll = DllLoaderContainer::LoadModule(libname, libpath);
+
+ if (dll)
+ return (HMODULE)dll->GetHModule();
+
+ CLog::Log(LOGERROR, "LoadLibrary('{}') failed", libname);
+ return NULL;
+}
+
+extern "C" HMODULE __stdcall dllLoadLibraryA(const char* file)
+{
+ return dllLoadLibraryExtended(file, NULL);
+}
+
+#define DONT_RESOLVE_DLL_REFERENCES 0x00000001
+#define LOAD_LIBRARY_AS_DATAFILE 0x00000002
+#define LOAD_WITH_ALTERED_SEARCH_PATH 0x00000008
+#define LOAD_IGNORE_CODE_AUTHZ_LEVEL 0x00000010
+
+extern "C" HMODULE __stdcall dllLoadLibraryExExtended(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags, const char* sourcedll)
+{
+ char strFlags[512];
+ strFlags[0] = '\0';
+
+ if (dwFlags & DONT_RESOLVE_DLL_REFERENCES) strcat(strFlags, "\n - DONT_RESOLVE_DLL_REFERENCES");
+ if (dwFlags & LOAD_IGNORE_CODE_AUTHZ_LEVEL) strcat(strFlags, "\n - LOAD_IGNORE_CODE_AUTHZ_LEVEL");
+ if (dwFlags & LOAD_LIBRARY_AS_DATAFILE) strcat(strFlags, "\n - LOAD_LIBRARY_AS_DATAFILE");
+ if (dwFlags & LOAD_WITH_ALTERED_SEARCH_PATH) strcat(strFlags, "\n - LOAD_WITH_ALTERED_SEARCH_PATH");
+
+ CLog::Log(LOGDEBUG, "LoadLibraryExA called with flags: {}", strFlags);
+
+ return dllLoadLibraryExtended(lpLibFileName, sourcedll);
+}
+
+extern "C" HMODULE __stdcall dllLoadLibraryExA(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags)
+{
+ return dllLoadLibraryExExtended(lpLibFileName, hFile, dwFlags, NULL);
+}
+
+extern "C" int __stdcall dllFreeLibrary(HINSTANCE hLibModule)
+{
+ LibraryLoader* dllhandle = DllLoaderContainer::GetModule(hLibModule);
+
+ if( !dllhandle )
+ {
+ CLog::Log(LOGERROR, "{} - Invalid hModule specified", __FUNCTION__);
+ return 1;
+ }
+
+ // to make sure systems dlls are never deleted
+ if (dllhandle->IsSystemDll()) return 1;
+
+ DllLoaderContainer::ReleaseModule(dllhandle);
+
+ return 1;
+}
+
+extern "C" intptr_t (*__stdcall dllGetProcAddress(HMODULE hModule, const char* function))(void)
+{
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ void* address = NULL;
+ LibraryLoader* dll = DllLoaderContainer::GetModule(hModule);
+
+ if( !dll )
+ {
+ CLog::Log(LOGERROR, "{} - Invalid hModule specified", __FUNCTION__);
+ return NULL;
+ }
+
+ /* how can somebody get the stupid idea to create such a stupid function */
+ /* where you never know if the given pointer is a pointer or a value */
+ if( HIGH_WORD(function) == 0 && LOW_WORD(function) < 1000)
+ {
+ if( dll->ResolveOrdinal(LOW_WORD(function), &address) )
+ {
+ CLog::Log(LOGDEBUG, "{}({}({}), {}) => {}", __FUNCTION__, fmt::ptr(hModule), dll->GetName(),
+ LOW_WORD(function), fmt::ptr(address));
+ }
+ else if( dll->IsSystemDll() )
+ {
+ char ordinal[6] = {};
+ sprintf(ordinal, "%u", LOW_WORD(function));
+ address = (void*)create_dummy_function(dll->GetName(), ordinal);
+
+ /* add to tracklist if we are tracking this source dll */
+ DllTrackInfo* track = tracker_get_dlltrackinfo(loc);
+ if( track )
+ tracker_dll_data_track(track->pDll, (uintptr_t)address);
+
+ CLog::Log(LOGDEBUG, "{} - created dummy function {}!{}", __FUNCTION__, dll->GetName(),
+ ordinal);
+ }
+ else
+ {
+ address = NULL;
+ CLog::Log(LOGDEBUG, "{}({}({}), '{}') => {}", __FUNCTION__, fmt::ptr(hModule), dll->GetName(),
+ function, fmt::ptr(address));
+ }
+ }
+ else
+ {
+ if( dll->ResolveExport(function, &address) )
+ {
+ CLog::Log(LOGDEBUG, "{}({}({}), '{}') => {}", __FUNCTION__, fmt::ptr(hModule), dll->GetName(),
+ function, fmt::ptr(address));
+ }
+ else
+ {
+ DllTrackInfo* track = tracker_get_dlltrackinfo(loc);
+ /* some dll's require us to always return a function or it will fail, other's */
+ /* decide functionality depending on if the functions exist and may fail */
+ if (dll->IsSystemDll() && track &&
+ StringUtils::CompareNoCase(track->pDll->GetName(), "CoreAVCDecoder.ax") == 0)
+ {
+ address = (void*)create_dummy_function(dll->GetName(), function);
+ tracker_dll_data_track(track->pDll, (uintptr_t)address);
+ CLog::Log(LOGDEBUG, "{} - created dummy function {}!{}", __FUNCTION__, dll->GetName(),
+ function);
+ }
+ else
+ {
+ address = NULL;
+ CLog::Log(LOGDEBUG, "{}({}({}), '{}') => {}", __FUNCTION__, fmt::ptr(hModule),
+ dll->GetName(), function, fmt::ptr(address));
+ }
+ }
+ }
+
+ return (intptr_t(*)(void)) address;
+}
+
+extern "C" HMODULE WINAPI dllGetModuleHandleA(const char* lpModuleName)
+{
+ /*
+ If the file name extension is omitted, the default library extension .dll is appended.
+ The file name string can include a trailing point character (.) to indicate that the module name has no extension.
+ The string does not have to specify a path. When specifying a path, be sure to use backslashes (\), not forward slashes (/).
+ The name is compared (case independently)
+ If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file).
+ */
+
+ if( lpModuleName == NULL )
+ return NULL;
+
+ char* strModuleName = new char[strlen(lpModuleName) + 5];
+ strcpy(strModuleName, lpModuleName);
+
+ if (strrchr(strModuleName, '.') == 0) strcat(strModuleName, ".dll");
+
+ //CLog::Log(LOGDEBUG, "GetModuleHandleA({}) .. looking up", lpModuleName);
+
+ LibraryLoader *p = DllLoaderContainer::GetModule(strModuleName);
+ delete []strModuleName;
+
+ if (p)
+ {
+ //CLog::Log(LOGDEBUG, "GetModuleHandleA('{}') => 0x{:x}", lpModuleName, h);
+ return (HMODULE)p->GetHModule();
+ }
+
+ CLog::Log(LOGDEBUG, "GetModuleHandleA('{}') failed", lpModuleName);
+ return NULL;
+}
diff --git a/xbmc/cores/DllLoader/dll.h b/xbmc/cores/DllLoader/dll.h
new file mode 100644
index 0000000..5a0b605
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PlatformDefs.h"
+
+extern "C" HMODULE __stdcall dllLoadLibraryExtended(const char* file, const char* sourcedll);
+extern "C" HMODULE __stdcall dllLoadLibraryA(const char* file);
+extern "C" HMODULE __stdcall dllLoadLibraryExExtended(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags, const char* sourcedll);
+extern "C" HMODULE __stdcall dllLoadLibraryExA(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags);
+extern "C" int __stdcall dllFreeLibrary(HINSTANCE hLibModule);
+extern "C" intptr_t (*__stdcall dllGetProcAddress(HMODULE hModule, const char* function))(void);
+extern "C" HMODULE WINAPI dllGetModuleHandleA(const char* lpModuleName);
+
diff --git a/xbmc/cores/DllLoader/dll_tracker.cpp b/xbmc/cores/DllLoader/dll_tracker.cpp
new file mode 100644
index 0000000..252232d
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "dll_tracker.h"
+
+#include "DllLoader.h"
+#include "dll_tracker_file.h"
+#include "dll_tracker_library.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <stdlib.h>
+
+#ifdef _cplusplus
+extern "C"
+{
+#endif
+
+CCriticalSection g_trackerLock;
+TrackedDllList g_trackedDlls;
+
+void tracker_dll_add(DllLoader* pDll)
+{
+ DllTrackInfo* trackInfo = new DllTrackInfo;
+ trackInfo->pDll = pDll;
+ trackInfo->lMinAddr = 0;
+ trackInfo->lMaxAddr = 0;
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ g_trackedDlls.push_back(trackInfo);
+}
+
+void tracker_dll_free(DllLoader* pDll)
+{
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end();)
+ {
+ // NOTE: This code assumes that the same dll pointer can be in more than one
+ // slot of the vector g_trackedDlls. If it's not, then it can be
+ // simplified by returning after we've found the one we want, saving
+ // the iterator shuffling, and reducing potential bugs.
+ if ((*it)->pDll == pDll)
+ {
+ try
+ {
+ tracker_library_free_all(*it);
+ tracker_file_free_all(*it);
+ }
+ catch (...)
+ {
+ CLog::Log(LOGFATAL, "Error freeing tracked dll resources");
+ }
+ // free all functions which where created at the time we loaded the dll
+ DummyListIter dit = (*it)->dummyList.begin();
+ while (dit != (*it)->dummyList.end()) { free((void*)*dit); ++dit; }
+ (*it)->dummyList.clear();
+
+ delete (*it);
+ it = g_trackedDlls.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void tracker_dll_set_addr(const DllLoader* pDll, uintptr_t min, uintptr_t max)
+{
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end(); ++it)
+ {
+ if ((*it)->pDll == pDll)
+ {
+ (*it)->lMinAddr = min;
+ (*it)->lMaxAddr = max;
+ break;
+ }
+ }
+}
+
+const char* tracker_getdllname(uintptr_t caller)
+{
+ DllTrackInfo *track = tracker_get_dlltrackinfo(caller);
+ if(track)
+ return track->pDll->GetFileName();
+ return "";
+}
+
+DllTrackInfo* tracker_get_dlltrackinfo(uintptr_t caller)
+{
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end(); ++it)
+ {
+ if (caller >= (*it)->lMinAddr && caller <= (*it)->lMaxAddr)
+ {
+ return *it;
+ }
+ }
+
+ // crap not in any base address, check if it may be in virtual spaces
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end(); ++it)
+ {
+ for(VAllocListIter it2 = (*it)->virtualList.begin(); it2 != (*it)->virtualList.end(); ++it2)
+ {
+ if( it2->first <= caller && caller < it2->first + it2->second.size )
+ return *it;
+
+ }
+ }
+
+ return NULL;
+}
+
+DllTrackInfo* tracker_get_dlltrackinfo_byobject(const DllLoader* pDll)
+{
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end(); ++it)
+ {
+ if ((*it)->pDll == pDll)
+ {
+ return *it;
+ }
+ }
+ return NULL;
+}
+
+void tracker_dll_data_track(const DllLoader* pDll, uintptr_t addr)
+{
+ std::unique_lock<CCriticalSection> locktd(g_trackerLock);
+ for (TrackedDllsIter it = g_trackedDlls.begin(); it != g_trackedDlls.end(); ++it)
+ {
+ if (pDll == (*it)->pDll)
+ {
+ (*it)->dummyList.push_back((uintptr_t)addr);
+ break;
+ }
+ }
+}
+
+#ifdef _cplusplus
+}
+#endif
diff --git a/xbmc/cores/DllLoader/dll_tracker.h b/xbmc/cores/DllLoader/dll_tracker.h
new file mode 100644
index 0000000..68e1779
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "PlatformDefs.h"
+#ifdef TARGET_WINDOWS
+#if defined(TARGET_WINDOWS_STORE)
+#include <WinSock2.h>
+#endif
+#endif
+
+#include <list>
+#include <map>
+
+class DllLoader;
+
+struct AllocLenCaller
+{
+ size_t size;
+ uintptr_t calleraddr;
+};
+
+enum TrackedFileType
+{
+ FILE_XBMC_OPEN,
+ FILE_XBMC_FOPEN,
+ FILE_OPEN,
+ FILE_FOPEN
+};
+
+typedef struct _TrackedFile
+{
+ TrackedFileType type;
+ uintptr_t handle;
+ char* name;
+} TrackedFile;
+
+typedef std::map<uintptr_t, AllocLenCaller> DataList;
+typedef std::map<uintptr_t, AllocLenCaller>::iterator DataListIter;
+
+typedef std::list<TrackedFile*> FileList;
+typedef std::list<TrackedFile*>::iterator FileListIter;
+
+typedef std::list<HMODULE> DllList;
+typedef std::list<HMODULE>::iterator DllListIter;
+
+typedef std::list<uintptr_t> DummyList;
+typedef std::list<uintptr_t>::iterator DummyListIter;
+
+typedef std::list<SOCKET> SocketList;
+typedef std::list<SOCKET>::iterator SocketListIter;
+
+typedef std::list<HANDLE> HeapObjectList;
+typedef std::list<HANDLE>::iterator HeapObjectListIter;
+
+typedef std::map<uintptr_t, AllocLenCaller> VAllocList;
+typedef std::map<uintptr_t, AllocLenCaller>::iterator VAllocListIter;
+
+typedef struct _DllTrackInfo
+{
+ DllLoader* pDll;
+ uintptr_t lMinAddr;
+ uintptr_t lMaxAddr;
+
+ DataList dataList;
+
+ // list with dll's that are loaded by this dll
+ DllList dllList;
+
+ // for dummy functions that are created if no exported function could be found
+ DummyList dummyList;
+
+ FileList fileList;
+ SocketList socketList;
+
+ HeapObjectList heapobjectList;
+
+ VAllocList virtualList;
+} DllTrackInfo;
+
+class TrackedDllList : public std::list<DllTrackInfo*>, public CCriticalSection {};
+typedef std::list<DllTrackInfo*>::iterator TrackedDllsIter;
+
+#ifdef _cplusplus
+extern "C"
+{
+#endif
+
+extern CCriticalSection g_trackerLock;
+extern TrackedDllList g_trackedDlls;
+
+// add a dll for tracking
+void tracker_dll_add(DllLoader* pDll);
+
+// remove a dll, and free all its resources
+void tracker_dll_free(DllLoader* pDll);
+
+// sets the dll base address and size
+void tracker_dll_set_addr(const DllLoader* pDll, uintptr_t min, uintptr_t max);
+
+// returns the name from the dll that contains this address or "" if not found
+const char* tracker_getdllname(uintptr_t caller);
+
+// returns a function pointer if there is one available for it, or NULL if not ofund
+void* tracker_dll_get_function(DllLoader* pDll, char* sFunctionName);
+
+DllTrackInfo* tracker_get_dlltrackinfo_byobject(const DllLoader* pDll);
+
+DllTrackInfo* tracker_get_dlltrackinfo(uintptr_t caller);
+
+void tracker_dll_data_track(const DllLoader* pDll, uintptr_t addr);
+
+#ifdef TARGET_POSIX
+#define _ReturnAddress() __builtin_return_address(0)
+#endif
+
+#ifdef _cplusplus
+}
+#endif
+
+#ifndef TARGET_POSIX
+extern "C" void * _ReturnAddress(void);
+#pragma intrinsic(_ReturnAddress)
+#endif
+
diff --git a/xbmc/cores/DllLoader/dll_tracker_file.cpp b/xbmc/cores/DllLoader/dll_tracker_file.cpp
new file mode 100644
index 0000000..b5d1214
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker_file.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "dll_tracker_file.h"
+
+#include "DllLoader.h"
+#include "dll_tracker.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <stdlib.h>
+
+#ifdef TARGET_POSIX
+#define dll_open open
+#define dll_fopen fopen
+#define dll_close close
+#define dll_fclose fclose
+#define dll_freopen freopen
+#else
+#include "exports/emu_msvcrt.h"
+#include <io.h>
+#endif
+
+extern "C" void tracker_file_track(uintptr_t caller, uintptr_t handle, TrackedFileType type, const char* sFile)
+{
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(caller);
+ if (pInfo)
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ TrackedFile* file = new TrackedFile;
+ file->handle = handle;
+ file->type = type;
+ file->name = strdup(sFile);
+ pInfo->fileList.push_back(file);
+ }
+}
+
+extern "C" void tracker_file_free(uintptr_t caller, uintptr_t handle, TrackedFileType type)
+{
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(caller);
+ if (pInfo)
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ for (FileListIter it = pInfo->fileList.begin(); it != pInfo->fileList.end(); ++it)
+ {
+ TrackedFile* file = *it;
+ if (file->handle == handle && file->type == type)
+ {
+ free(file->name);
+ delete file;
+ pInfo->fileList.erase(it);
+ return;
+ }
+ }
+ }
+ CLog::Log(LOGWARNING, "unable to remove tracked file from tracker");
+}
+
+extern "C" void tracker_file_free_all(DllTrackInfo* pInfo)
+{
+ if (!pInfo->fileList.empty())
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ CLog::Log(LOGDEBUG, "{0}: Detected open files: {1}", pInfo->pDll->GetFileName(), pInfo->fileList.size());
+ for (FileListIter it = pInfo->fileList.begin(); it != pInfo->fileList.end(); ++it)
+ {
+ TrackedFile* file = *it;
+ CLog::Log(LOGDEBUG, "{}", file->name);
+ free(file->name);
+
+ if (file->type == FILE_XBMC_OPEN) dll_close(file->handle);
+ else if (file->type == FILE_XBMC_FOPEN) dll_fclose((FILE*)file->handle);
+ else if (file->type == FILE_OPEN) close(file->handle);
+ else if (file->type == FILE_FOPEN) fclose((FILE*)file->handle);
+
+ delete file;
+ }
+ }
+ pInfo->fileList.erase(pInfo->fileList.begin(), pInfo->fileList.end());
+}
+
+extern "C"
+{
+ int track_open(const char* sFileName, int iMode)
+ {
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ int fd = dll_open(sFileName, iMode);
+ if (fd >= 0) tracker_file_track(loc, fd, FILE_XBMC_OPEN, sFileName);
+ return fd;
+ }
+
+ int track_close(int fd)
+ {
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ tracker_file_free(loc, fd, FILE_XBMC_OPEN);
+ return dll_close(fd);
+ }
+
+ FILE* track_fopen(const char* sFileName, const char* mode)
+ {
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ FILE* fd = dll_fopen(sFileName, mode);
+ if (fd) tracker_file_track(loc, (uintptr_t)fd, FILE_XBMC_FOPEN, sFileName);
+ return fd;
+ }
+
+ int track_fclose(FILE* stream)
+ {
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ tracker_file_free(loc, (uintptr_t)stream, FILE_XBMC_FOPEN);
+ return dll_fclose(stream);
+ }
+
+ FILE* track_freopen(const char *path, const char *mode, FILE *stream)
+ {
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ tracker_file_free(loc, (uintptr_t)stream, FILE_XBMC_FOPEN);
+ stream = dll_freopen(path, mode, stream);
+ if (stream)
+ tracker_file_track(loc, (uintptr_t)stream, FILE_XBMC_FOPEN, path);
+ return stream;
+ }
+
+}
diff --git a/xbmc/cores/DllLoader/dll_tracker_file.h b/xbmc/cores/DllLoader/dll_tracker_file.h
new file mode 100644
index 0000000..e6be371
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker_file.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "dll_tracker.h"
+
+#include <stdio.h>
+
+extern "C" void tracker_file_track(uintptr_t caller, uintptr_t handle, TrackedFileType type, const char* sFile = "");
+extern "C" void tracker_file_free(uintptr_t caller, uintptr_t handle, TrackedFileType type);
+extern "C" void tracker_file_free_all(DllTrackInfo* pInfo);
+
+extern "C"
+{
+ int track_open(const char* sFileName, int iMode);
+ int track_close(int fd);
+ FILE* track_fopen(const char* sFileName, const char* mode);
+ int track_fclose(FILE* stream);
+ FILE* track_freopen(const char *path, const char *mode, FILE *stream);
+}
+
diff --git a/xbmc/cores/DllLoader/dll_tracker_library.cpp b/xbmc/cores/DllLoader/dll_tracker_library.cpp
new file mode 100644
index 0000000..8cfac13
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker_library.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "dll_tracker_library.h"
+
+#include "DllLoader.h"
+#include "DllLoaderContainer.h"
+#include "dll.h"
+#include "dll_tracker.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+extern "C" inline void tracker_library_track(uintptr_t caller, HMODULE hHandle)
+{
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(caller);
+ if (pInfo && hHandle)
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ pInfo->dllList.push_back(hHandle);
+ }
+}
+
+extern "C" inline void tracker_library_free(uintptr_t caller, HMODULE hHandle)
+{
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(caller);
+ if (pInfo && hHandle)
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ for (DllListIter it = pInfo->dllList.begin(); it != pInfo->dllList.end(); ++it)
+ {
+ if (*it == hHandle)
+ {
+ pInfo->dllList.erase(it);
+ break;
+ }
+ }
+ }
+}
+
+extern "C" void tracker_library_free_all(DllTrackInfo* pInfo)
+{
+ // unloading unloaded dll's
+ if (!pInfo->dllList.empty())
+ {
+ std::unique_lock<CCriticalSection> lock(g_trackerLock);
+ CLog::Log(LOGDEBUG,"{0}: Detected {1} unloaded dll's", pInfo->pDll->GetFileName(), pInfo->dllList.size());
+ for (DllListIter it = pInfo->dllList.begin(); it != pInfo->dllList.end(); ++it)
+ {
+ LibraryLoader* pDll = DllLoaderContainer::GetModule((HMODULE)*it);
+ if( !pDll)
+ {
+ CLog::Log(LOGERROR, "{} - Invalid module in tracker", __FUNCTION__);
+ return;
+ }
+
+ if (!pDll->IsSystemDll())
+ {
+ if (strlen(pDll->GetFileName()) > 0)
+ CLog::Log(LOGDEBUG, " : {}", pDll->GetFileName());
+ }
+ }
+
+ // now unload the dlls
+ for (DllListIter it = pInfo->dllList.begin(); it != pInfo->dllList.end(); ++it)
+ {
+ LibraryLoader* pDll = DllLoaderContainer::GetModule((HMODULE)*it);
+ if( !pDll)
+ {
+ CLog::Log(LOGERROR, "{} - Invalid module in tracker", __FUNCTION__);
+ return;
+ }
+
+ if (!pDll->IsSystemDll())
+ {
+ dllFreeLibrary((HMODULE)pDll->GetHModule());
+ }
+ }
+ }
+}
+
+extern "C" HMODULE __stdcall track_LoadLibraryA(const char* file)
+{
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(loc);
+ const char* path = NULL;
+ if (pInfo) path = pInfo->pDll->GetFileName();
+
+ HMODULE hHandle = dllLoadLibraryExtended(file, path);
+ tracker_library_track(loc, hHandle);
+
+ return hHandle;
+}
+
+extern "C" HMODULE __stdcall track_LoadLibraryExA(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags)
+{
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ DllTrackInfo* pInfo = tracker_get_dlltrackinfo(loc);
+ const char* path = NULL;
+ if (pInfo) path = pInfo->pDll->GetFileName();
+
+ HMODULE hHandle = dllLoadLibraryExExtended(lpLibFileName, hFile, dwFlags, path);
+ tracker_library_track(loc, hHandle);
+
+ return hHandle;
+}
+
+extern "C" int __stdcall track_FreeLibrary(HINSTANCE hLibModule)
+{
+ uintptr_t loc = (uintptr_t)_ReturnAddress();
+
+ tracker_library_free(loc, hLibModule);
+
+ return dllFreeLibrary(hLibModule);
+}
diff --git a/xbmc/cores/DllLoader/dll_tracker_library.h b/xbmc/cores/DllLoader/dll_tracker_library.h
new file mode 100644
index 0000000..6670d70
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_tracker_library.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "dll_tracker.h"
+
+extern "C" void tracker_library_free_all(DllTrackInfo* pInfo);
+
+extern "C" HMODULE __stdcall track_LoadLibraryA(const char* file);
+extern "C" HMODULE __stdcall track_LoadLibraryExA(const char* lpLibFileName, HANDLE hFile, DWORD dwFlags);
+extern "C" int __stdcall track_FreeLibrary(HINSTANCE hLibModule);
diff --git a/xbmc/cores/DllLoader/dll_util.cpp b/xbmc/cores/DllLoader/dll_util.cpp
new file mode 100644
index 0000000..b33c02e
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_util.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "utils/log.h"
+#include "dll_util.h"
+
+#ifdef TARGET_WINDOWS
+#include "platform/win32/CharsetConverter.h"
+#include <windows.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _cplusplus
+extern "C"
+{
+#endif
+
+static int iDllDummyOutputCall = 0;
+void dll_dummy_output(char* dllname, char* funcname)
+{
+ CLog::Log(LOGERROR, "{}: Unresolved function called ({}), Count number {}", dllname, funcname,
+ ++iDllDummyOutputCall);
+}
+
+// this piece of asm code only calls dll_dummy_output(s, s) and will return NULL
+unsigned char dummy_func[] = {
+ 0x55, // push ebp
+ 0x8b, 0xec, // mov ebp,esp
+ 0xa1, 0, 0, 0, 0, // mov eax,dword ptr [0 0 0 0]
+ 0x50, // push eax
+ 0xa1, 0, 0, 0, 0, // mov eax,dword ptr [0 0 0 0]
+ 0x50, // push eax
+ 0xff, 0x15, 0, 0, 0, 0, // call dword ptr[dll_dummy_output]
+ 0x83, 0xc4, 0x08, // add esp,8
+ 0x33, 0xc0, // xor eax,eax // return NULL
+ 0x5d, // pop ebp
+ 0xc3 // ret
+ };
+
+/* Create a new callable function
+ * This allocates a few bytes with the next content
+ *
+ * 1 function in assembly code (dummy_func)
+ * 2 datapointer (pointer to dll string)
+ * 3 datapointer (pointer to function string)
+ * 4 datapointer (pointer to function string)
+ * 5 string (string of chars representing dll name)
+ * 6 string (string of chars representing function name)
+ */
+uintptr_t create_dummy_function(const char* strDllName, const char* strFunctionName)
+{
+ size_t iFunctionSize = sizeof(dummy_func);
+ size_t iDllNameSize = strlen(strDllName) + 1;
+ size_t iFunctionNameSize = strlen(strFunctionName) + 1;
+
+ // allocate memory for function + strings + 3 x 4 bytes for three datapointers
+ char* pData = (char*)malloc(iFunctionSize + 12 + iDllNameSize + iFunctionNameSize);
+ if (!pData)
+ return 0;
+
+ char* offDataPointer1 = pData + iFunctionSize;
+ char* offDataPointer2 = pData + iFunctionSize + 4;
+ char* offDataPointer3 = pData + iFunctionSize + 8;
+ char* offStringDll = pData + iFunctionSize + 12;
+ char* offStringFunc = pData + iFunctionSize + 12 + iDllNameSize;
+
+ // 1 copy assembly code
+ memcpy(pData, dummy_func, iFunctionSize);
+
+ // insert pointers to datapointers into assembly code (fills 0x00000000 in dummy_func)
+ *(int*)(pData + 4) = (intptr_t)offDataPointer1;
+ *(int*)(pData + 10) = (intptr_t)offDataPointer2;
+ *(int*)(pData + 17) = (intptr_t)offDataPointer3;
+
+ // 2 fill datapointer with pointer to 5 (string)
+ *(int*)offDataPointer1 = (intptr_t)offStringFunc;
+ // 3 fill datapointer with pointer to 6 (string)
+ *(int*)offDataPointer2 = (intptr_t)offStringDll;
+ // 4 fill datapointer with pointer to dll_dummy_output
+ *(int*)offDataPointer3 = (intptr_t)dll_dummy_output;
+
+ // copy arguments to 5 (string) and 6 (string)
+ memcpy(offStringDll, strDllName, iDllNameSize);
+ memcpy(offStringFunc, strFunctionName, iFunctionNameSize);
+
+ return (uintptr_t)pData;
+}
+
+uintptr_t get_win_function_address(const char* strDllName, const char* strFunctionName)
+{
+#ifdef TARGET_WINDOWS_DESKTOP
+ using KODI::PLATFORM::WINDOWS::ToW;
+ auto strDllNameW = ToW(strDllName);
+ HMODULE handle = GetModuleHandle(strDllNameW.c_str());
+ if(handle == nullptr)
+ {
+ handle = LoadLibrary(strDllNameW.c_str());
+ }
+ if(handle != nullptr)
+ {
+ auto pGNSI = reinterpret_cast<uintptr_t>(GetProcAddress(handle, strFunctionName));
+ if(pGNSI != NULL)
+ return pGNSI;
+
+ FreeLibrary(handle);
+ }
+#endif
+ return 0;
+}
+
+#ifdef _cplusplus
+}
+#endif
diff --git a/xbmc/cores/DllLoader/dll_util.h b/xbmc/cores/DllLoader/dll_util.h
new file mode 100644
index 0000000..3b0b6c4
--- /dev/null
+++ b/xbmc/cores/DllLoader/dll_util.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#ifdef _cplusplus
+extern "C"
+{
+#endif
+
+uintptr_t create_dummy_function(const char* strDllName, const char* strFunctionName);
+uintptr_t get_win_function_address(const char* strDllName, const char* strFunctionName);
+
+#ifdef _cplusplus
+}
+#endif
+
diff --git a/xbmc/cores/DllLoader/exports/CMakeLists.txt b/xbmc/cores/DllLoader/exports/CMakeLists.txt
new file mode 100644
index 0000000..4039669
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/CMakeLists.txt
@@ -0,0 +1,33 @@
+set(SOURCES emu_dummy.cpp
+ emu_msvcrt.cpp)
+
+set(HEADERS emu_dummy.h
+ emu_msvcrt.h)
+
+core_add_library(dllexports)
+
+if(APPLE)
+ add_library(wrapper OBJECT wrapper.c)
+ add_custom_target(wrapper.def ALL ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/wrapper_mach_alias wrapper.def)
+ set_target_properties(wrapper PROPERTIES FOLDER "Build Utilities")
+ set_target_properties(wrapper.def PROPERTIES FOLDER "Build Utilities")
+ add_dependencies(wrapper.def wrapper)
+elseif(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
+ add_options(C ALL_BUILDS "-fPIC")
+ add_library(wrapper OBJECT wrapper.c)
+
+ if(USE_LTO)
+ add_custom_target(wrapper.def ALL ${CMAKE_NM} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/wrapper.dir/wrapper.c.o | grep __wrap | awk '{ printf(\"%s \", \$\$3) }' | sed \"s/^/${CMAKE_C_COMPILE_OPTIONS_IPO} /\" | sed \"s/___wrap_/__wrap_/g\" | sed \"s/__wrap_/-Wl,-wrap,/g\" > wrapper.def && test -s wrapper.def)
+ else()
+ add_custom_target(wrapper.def ALL ${CMAKE_NM} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/wrapper.dir/wrapper.c.o | grep __wrap | awk '{ printf(\"%s \", \$\$3) }' | sed \"s/___wrap_/__wrap_/g\" | sed \"s/__wrap_/-Wl,-wrap,/g\" > wrapper.def && test -s wrapper.def)
+ endif()
+
+ if(CORE_SYSTEM_NAME STREQUAL android)
+ add_custom_command(TARGET wrapper.def COMMAND echo \"-L${DEPENDS_PATH}/lib/dummy-lib${APP_NAME_LC} -l${APP_NAME_LC}\" >> wrapper.def)
+ endif()
+
+ set_target_properties(wrapper PROPERTIES FOLDER "Build Utilities")
+ set_target_properties(wrapper.def PROPERTIES FOLDER "Build Utilities")
+ add_dependencies(wrapper.def wrapper)
+endif()
+
diff --git a/xbmc/cores/DllLoader/exports/emu_dummy.cpp b/xbmc/cores/DllLoader/exports/emu_dummy.cpp
new file mode 100644
index 0000000..813c901
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/emu_dummy.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "emu_dummy.h"
+
+#include "utils/log.h"
+
+extern "C" void not_implement( const char* debuginfo)
+{
+ if (debuginfo)
+ {
+ CLog::Log(LOGDEBUG, "{}", debuginfo);
+ }
+}
+
diff --git a/xbmc/cores/DllLoader/exports/emu_dummy.h b/xbmc/cores/DllLoader/exports/emu_dummy.h
new file mode 100644
index 0000000..f49e214
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/emu_dummy.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void not_implement( const char* );
+
+#ifdef __cplusplus
+}
+#endif
+
+
diff --git a/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp b/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp
new file mode 100644
index 0000000..3f555db
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp
@@ -0,0 +1,2074 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <math.h>
+#include <mutex>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef TARGET_POSIX
+#include <io.h>
+#include <direct.h>
+#include <process.h>
+#include <errno.h>
+#else
+#if !defined(TARGET_DARWIN) && !defined(TARGET_FREEBSD)
+#include <mntent.h>
+#endif
+#endif
+#include <sys/stat.h>
+#include <sys/types.h>
+#if !defined(TARGET_FREEBSD) && (!defined(TARGET_ANDROID) && defined(__LP64__))
+#include <sys/timeb.h>
+#endif
+#ifdef HAS_DVD_DRIVE
+ #ifdef TARGET_POSIX
+ #include <sys/ioctl.h>
+ #if defined(TARGET_DARWIN)
+ #include <IOKit/storage/IODVDMediaBSDClient.h>
+ #elif !defined(TARGET_FREEBSD)
+ #include <linux/cdrom.h>
+ #endif
+ #endif
+#endif
+#include <fcntl.h>
+#include <time.h>
+#include <signal.h>
+#ifdef TARGET_POSIX
+#include "PlatformDefs.h" // for __stat64
+#endif
+#include "CompileInfo.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "emu_dummy.h"
+#include "emu_msvcrt.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "filesystem/SpecialProtocol.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "util/EmuFileWrapper.h"
+#include "utils/log.h"
+#ifndef TARGET_POSIX
+#include "utils/CharsetConverter.h"
+#include "utils/URIUtils.h"
+#endif
+#if !defined(TARGET_WINDOWS)
+#include <dlfcn.h>
+#endif
+#include "platform/Environment.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+
+#if defined(TARGET_WINDOWS)
+#include "platform/win32/CharsetConverter.h"
+#endif
+
+using namespace XFILE;
+
+struct SDirData
+{
+ CFileItemList items;
+ int curr_index;
+ struct dirent *last_entry;
+ SDirData()
+ {
+ curr_index = -1;
+ last_entry = NULL;
+ }
+};
+
+#define MAX_OPEN_DIRS 10
+static SDirData vecDirsOpen[MAX_OPEN_DIRS];
+bool bVecDirsInited = false;
+extern void update_cache_dialog(const char* tmp);
+
+#define EMU_MAX_ENVIRONMENT_ITEMS 100
+static char *dll__environ_imp[EMU_MAX_ENVIRONMENT_ITEMS + 1];
+extern "C" char **dll__environ;
+char **dll__environ = dll__environ_imp;
+
+CCriticalSection dll_cs_environ;
+
+extern "C" void __stdcall init_emu_environ()
+{
+ memset(dll__environ, 0, EMU_MAX_ENVIRONMENT_ITEMS + 1);
+
+ // python
+#if defined(TARGET_WINDOWS_DESKTOP)
+ using KODI::PLATFORM::WINDOWS::FromW;
+ // fill our array with the windows system vars
+ LPTSTR lpszVariable;
+ LPTCH lpvEnv = NULL;
+ lpvEnv = GetEnvironmentStrings();
+ if (lpvEnv != NULL)
+ {
+ lpszVariable = (LPTSTR) lpvEnv;
+ while (*lpszVariable)
+ {
+ dll_putenv(FromW(lpszVariable).c_str());
+ lpszVariable += lstrlen(lpszVariable) + 1;
+ }
+ FreeEnvironmentStrings(lpvEnv);
+ }
+ dll_putenv("OS=win32");
+#elif defined(TARGET_WINDOWS_STORE)
+ dll_putenv("OS=win10");
+#elif defined(TARGET_DARWIN)
+ dll_putenv("OS=darwin");
+#elif defined(TARGET_POSIX)
+ dll_putenv("OS=linux");
+#else
+ dll_putenv("OS=unknown");
+#endif
+
+ // check if we are running as real xbmc.app or just binary
+ if (!CUtil::GetFrameworksPath(true).empty())
+ {
+ // using external python, it's build looking for xxx/lib/python(VERSIONMAJOR.MINOR)
+ // so point it to frameworks which is where python is located
+ dll_putenv(("PYTHONPATH=" +
+ CSpecialProtocol::TranslatePath("special://frameworks")).c_str());
+ dll_putenv(("PYTHONHOME=" +
+ CSpecialProtocol::TranslatePath("special://frameworks")).c_str());
+ dll_putenv(("PATH=.;" +
+ CSpecialProtocol::TranslatePath("special://xbmc") + ";" +
+ CSpecialProtocol::TranslatePath("special://frameworks")).c_str());
+ }
+ else
+ {
+ dll_putenv(("PYTHONPATH=" +
+ CSpecialProtocol::TranslatePath("special://xbmc/system/python/DLLs") + ";" +
+ CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib")).c_str());
+ dll_putenv(("PYTHONHOME=" +
+ CSpecialProtocol::TranslatePath("special://xbmc/system/python")).c_str());
+ dll_putenv(("PATH=.;" + CSpecialProtocol::TranslatePath("special://xbmc") + ";" +
+ CSpecialProtocol::TranslatePath("special://xbmc/system/python")).c_str());
+ }
+
+#if defined(TARGET_ANDROID)
+ std::string apkPath = getenv("KODI_ANDROID_APK");
+ apkPath += "/assets/python" + CCompileInfo::GetPythonVersion();
+ dll_putenv(("PYTHONHOME=" + apkPath).c_str());
+ dll_putenv("PYTHONOPTIMIZE=");
+ dll_putenv("PYTHONNOUSERSITE=1");
+ dll_putenv("PYTHONPATH=");
+#else
+ dll_putenv("PYTHONOPTIMIZE=1");
+#endif
+
+ //dll_putenv("PYTHONCASEOK=1");
+ //dll_putenv("PYTHONDEBUG=1");
+ //dll_putenv("PYTHONVERBOSE=2"); // "1" for normal verbose, "2" for more verbose ?
+ //dll_putenv("PYTHONDUMPREFS=1");
+ //dll_putenv("THREADDEBUG=1");
+ //dll_putenv("PYTHONMALLOCSTATS=1");
+ //dll_putenv("PYTHONY2K=1");
+ dll_putenv("TEMP=special://temp/temp"); // for python tempdir
+
+ // libdvdnav
+ dll_putenv("DVDREAD_NOKEYS=1");
+ //dll_putenv("DVDREAD_VERBOSE=1");
+ //dll_putenv("DVDREAD_USE_DIRECT=1");
+
+ // libdvdcss
+ dll_putenv("DVDCSS_METHOD=key");
+ dll_putenv("DVDCSS_VERBOSE=3");
+ dll_putenv("DVDCSS_CACHE=special://masterprofile/cache");
+}
+
+extern "C" void __stdcall update_emu_environ()
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ // Use a proxy, if the GUI was configured as such
+ if (settings->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY)
+ && !settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER).empty()
+ && settings->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT) > 0
+ && settings->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE) == 0)
+ {
+ std::string strProxy;
+ if (!settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME).empty() &&
+ !settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD).empty())
+ {
+ strProxy = StringUtils::Format(
+ "{}:{}@", settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME),
+ settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD));
+ }
+
+ strProxy += settings->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER);
+ strProxy +=
+ StringUtils::Format(":{}", settings->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT));
+
+ CEnvironment::setenv( "HTTP_PROXY", "http://" + strProxy, true );
+ CEnvironment::setenv( "HTTPS_PROXY", "http://" + strProxy, true );
+ }
+ else
+ {
+ // is there a better way to delete an environment variable?
+ // this works but leaves the variable
+ dll_putenv( "HTTP_PROXY=" );
+ dll_putenv( "HTTPS_PROXY=" );
+ }
+}
+
+extern "C" void __stdcall cleanup_emu_environ()
+{
+ for (int i = 0; i < EMU_MAX_ENVIRONMENT_ITEMS; i++)
+ {
+ free(dll__environ[i]);
+ dll__environ[i] = NULL;
+ }
+}
+
+static int convert_fmode(const char* mode)
+{
+ int iMode = O_BINARY;
+ if (strstr(mode, "r+"))
+ iMode |= O_RDWR;
+ else if (strchr(mode, 'r'))
+ iMode |= _O_RDONLY;
+ if (strstr(mode, "w+"))
+ iMode |= O_RDWR | _O_TRUNC;
+ else if (strchr(mode, 'w'))
+ iMode |= _O_WRONLY | O_CREAT;
+ return iMode;
+}
+
+#ifdef TARGET_WINDOWS
+static void to_finddata64i32(_wfinddata64i32_t *wdata, _finddata64i32_t *data)
+{
+ std::string strname;
+ g_charsetConverter.wToUTF8(wdata->name, strname);
+ size_t size = sizeof(data->name) / sizeof(char);
+ strncpy(data->name, strname.c_str(), size);
+ if (size)
+ data->name[size - 1] = '\0';
+ data->attrib = wdata->attrib;
+ data->time_create = wdata->time_create;
+ data->time_access = wdata->time_access;
+ data->time_write = wdata->time_write;
+ data->size = wdata->size;
+}
+
+static void to_wfinddata64i32(_finddata64i32_t *data, _wfinddata64i32_t *wdata)
+{
+ std::wstring strwname;
+ g_charsetConverter.utf8ToW(data->name, strwname, false);
+ size_t size = sizeof(wdata->name) / sizeof(wchar_t);
+ wcsncpy(wdata->name, strwname.c_str(), size);
+ if (size)
+ wdata->name[size - 1] = '\0';
+ wdata->attrib = data->attrib;
+ wdata->time_create = data->time_create;
+ wdata->time_access = data->time_access;
+ wdata->time_write = data->time_write;
+ wdata->size = data->size;
+}
+#endif
+
+extern "C"
+{
+ void dll_sleep(unsigned long imSec) { KODI::TIME::Sleep(std::chrono::milliseconds(imSec)); }
+
+ // FIXME, XXX, !!!!!!
+ void dllReleaseAll( )
+ {
+ // close all open dirs...
+ if (bVecDirsInited)
+ {
+ for (SDirData& dir : vecDirsOpen)
+ {
+ dir.items.Clear();
+ }
+ bVecDirsInited = false;
+ }
+ }
+
+ void* dllmalloc(size_t size)
+ {
+ void* pBlock = malloc(size);
+ if (!pBlock)
+ {
+ CLog::Log(LOGFATAL, "malloc {0} bytes failed, crash imminent", size);
+ }
+ return pBlock;
+ }
+
+ void dllfree( void* pPtr )
+ {
+ free(pPtr);
+ }
+
+ void* dllcalloc(size_t num, size_t size)
+ {
+ void* pBlock = calloc(num, size);
+ if (!pBlock)
+ {
+ CLog::Log(LOGFATAL, "calloc {0} bytes failed, crash imminent", size);
+ }
+ return pBlock;
+ }
+
+ void* dllrealloc( void *memblock, size_t size )
+ {
+ void* pBlock = realloc(memblock, size);
+ if (!pBlock)
+ {
+ CLog::Log(LOGFATAL, "realloc {0} bytes failed, crash imminent", size);
+ }
+ return pBlock;
+ }
+
+ void dllexit(int iCode)
+ {
+ not_implement("msvcrt.dll fake function exit() called\n"); //warning
+ }
+
+ void dllabort()
+ {
+ not_implement("msvcrt.dll fake function abort() called\n"); //warning
+ }
+
+ void* dll__dllonexit(PFV input, PFV ** start, PFV ** end)
+ {
+ //ported from WINE code
+ PFV *tmp;
+ int len;
+
+ if (!start || !*start || !end || !*end)
+ {
+ //FIXME("bad table\n");
+ return NULL;
+ }
+
+ len = (*end - *start);
+
+ if (++len <= 0)
+ return NULL;
+
+ tmp = (PFV*) realloc (*start, len * sizeof(tmp) );
+ if (!tmp)
+ return NULL;
+ *start = tmp;
+ *end = tmp + len;
+ tmp[len - 1] = input;
+ return (void *)input;
+
+ //wrong handling, this function is used for register functions
+ //that called before exit use _initterm functions.
+
+ //dllReleaseAll( );
+ //return TRUE;
+ }
+
+ _onexit_t dll_onexit(_onexit_t func)
+ {
+ not_implement("msvcrt.dll fake function dll_onexit() called\n");
+
+ // register to dll unload list
+ // return func if successfully added to the dll unload list
+ return NULL;
+ }
+
+ int dllputs(const char* szLine)
+ {
+ if (!szLine[0]) return EOF;
+ if (szLine[strlen(szLine) - 1] != '\n')
+ CLog::Log(LOGDEBUG, " msg: {}", szLine);
+ else
+ CLog::Log(LOGDEBUG, " msg: {}", szLine);
+
+ // return a non negative value
+ return 0;
+ }
+
+ int dllprintf(const char *format, ...)
+ {
+ va_list va;
+ static char tmp[2048];
+ va_start(va, format);
+ _vsnprintf(tmp, 2048, format, va);
+ va_end(va);
+ tmp[2048 - 1] = 0;
+ CLog::Log(LOGDEBUG, " msg: {}", tmp);
+
+ return strlen(tmp);
+ }
+
+ char *dll_fullpath(char *absPath, const char *relPath, size_t maxLength)
+ {
+ unsigned int len = strlen(relPath);
+ if (len > maxLength && absPath != NULL) return NULL;
+
+ // dll has to make sure it uses the correct path for now
+ if (len > 1 && relPath[1] == ':')
+ {
+ if (absPath == NULL) absPath = dll_strdup(relPath);
+ else
+ {
+ strncpy(absPath, relPath, maxLength);
+ if (maxLength != 0)
+ absPath[maxLength-1] = '\0';
+ }
+ return absPath;
+ }
+ if (!strncmp(relPath, "\\Device\\Cdrom0", 14))
+ {
+ // needed?
+ if (absPath == NULL) absPath = strdup(relPath);
+ else
+ {
+ strncpy(absPath, relPath, maxLength);
+ if (maxLength != 0)
+ absPath[maxLength-1] = '\0';
+ }
+ return absPath;
+ }
+
+ not_implement("msvcrt.dll incomplete function _fullpath(...) called\n"); //warning
+ return NULL;
+ }
+
+ FILE* dll_popen(const char *command, const char *mode)
+ {
+ not_implement("msvcrt.dll fake function _popen(...) called\n"); //warning
+ return NULL;
+ }
+
+ void *dll_dlopen(const char *filename, int flag)
+ {
+#if !defined(TARGET_WINDOWS)
+ return dlopen(filename, flag);
+#else
+ return NULL;
+#endif
+ }
+
+ int dll_pclose(FILE *stream)
+ {
+ not_implement("msvcrt.dll fake function _pclose(...) called\n"); //warning
+ return 0;
+ }
+
+ FILE* dll_fdopen(int fd, const char* mode)
+ {
+ EmuFileObject* o = g_emuFileWrapper.GetFileObjectByDescriptor(fd);
+ if (o)
+ {
+ if(!o->used)
+ return NULL;
+
+ int nmode = convert_fmode(mode);
+ if( (o->mode & nmode) != nmode)
+ CLog::Log(LOGWARNING, "dll_fdopen - mode 0x{:x} differs from fd mode 0x{:x}", nmode,
+ o->mode);
+ return reinterpret_cast<FILE*>(o);
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ return _fdopen(fd, mode);
+ }
+
+ not_implement("msvcrt.dll incomplete function _fdopen(...) called\n");
+ return NULL;
+ }
+
+ int dll_open(const char* szFileName, int iMode)
+ {
+ char str[1024];
+ int size = sizeof(str);
+ // move to CFile classes
+ if (strncmp(szFileName, "\\Device\\Cdrom0", 14) == 0)
+ {
+ // replace "\\Device\\Cdrom0" with "D:"
+ strncpy(str, "D:", size);
+ if (size)
+ {
+ str[size-1] = '\0';
+ strncat(str, szFileName + 14, size - strlen(str));
+ }
+ }
+ else
+ {
+ strncpy(str, szFileName, size);
+ if (size)
+ str[size-1] = '\0';
+ }
+
+ CFile* pFile = new CFile();
+ bool bWrite = false;
+ if ((iMode & O_RDWR) || (iMode & O_WRONLY))
+ bWrite = true;
+ bool bOverwrite=false;
+ if ((iMode & _O_TRUNC) || (iMode & O_CREAT))
+ bOverwrite = true;
+ // currently always overwrites
+ bool bResult;
+
+ // We need to validate the path here as some calls from ie. libdvdnav
+ // or the python DLLs have malformed slashes on Win32
+ // (-> E:\test\VIDEO_TS/VIDEO_TS.BUP))
+ if (bWrite)
+ bResult = pFile->OpenForWrite(CUtil::ValidatePath(str), bOverwrite);
+ else
+ bResult = pFile->Open(CUtil::ValidatePath(str), READ_TRUNCATED);
+
+ if (bResult)
+ {
+ EmuFileObject* object = g_emuFileWrapper.RegisterFileObject(pFile);
+ if (object == NULL)
+ {
+ pFile->Close();
+ delete pFile;
+ return -1;
+ }
+ object->mode = iMode;
+ FILE* f = reinterpret_cast<FILE*>(object);
+ return g_emuFileWrapper.GetDescriptorByStream(f);
+ }
+ delete pFile;
+ return -1;
+ }
+
+ FILE* dll_freopen(const char *path, const char *mode, FILE *stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ dll_fclose(stream);
+ return dll_fopen(path, mode);
+ }
+
+ // error
+ // close stream and return NULL
+ dll_fclose(stream);
+ return NULL;
+ }
+
+ int dll_read(int fd, void* buffer, unsigned int uiSize)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ errno = 0;
+ const ssize_t ret = pFile->Read(buffer, uiSize);
+ if (ret < 0)
+ {
+ const int err = errno; // help compiler to optimize, "errno" can be macro
+ if (err == 0 ||
+ (err != EAGAIN && err != EINTR && err != EIO && err != EOVERFLOW && err != EWOULDBLOCK &&
+ err != ECONNRESET && err != ENOTCONN && err != ETIMEDOUT &&
+ err != ENOBUFS && err != ENOMEM && err != ENXIO))
+ errno = EIO; // exact errno is unknown or incorrect, use default error number
+
+ return -1;
+ }
+ return ret;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ return read(fd, buffer, uiSize);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ errno = EBADF;
+ return -1;
+ }
+
+ int dll_write(int fd, const void* buffer, unsigned int uiSize)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ errno = 0;
+ const ssize_t ret = pFile->Write(buffer, uiSize);
+ if (ret < 0)
+ {
+ const int err = errno; // help compiler to optimize, "errno" can be macro
+ if (err == 0 ||
+ (err != EAGAIN && err != EFBIG && err != EINTR && err != EIO && err != ENOSPC && err != EPIPE && err != EWOULDBLOCK &&
+ err != ECONNRESET &&
+ err != ENOBUFS && err != ENXIO &&
+ err != EACCES && err != ENETDOWN && err != ENETUNREACH))
+ errno = EIO; // exact errno is unknown or incorrect, use default error number
+
+ return -1;
+ }
+ return ret;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ return write(fd, buffer, uiSize);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ errno = EBADF;
+ return -1;
+ }
+
+ int dll_fstat64(int fd, struct __stat64 *buf)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ return pFile->Stat(buf);
+ else if (IS_STD_DESCRIPTOR(fd))
+#if defined(TARGET_WINDOWS)
+ return _fstat64(fd, buf);
+#else
+ return fstat64(fd, buf);
+#endif
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ int dll_close(int fd)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ g_emuFileWrapper.UnRegisterFileObjectByDescriptor(fd);
+
+ pFile->Close();
+ delete pFile;
+ return 0;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd) && fd >= 0)
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ return close(fd);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ __off64_t dll_lseeki64(int fd, __off64_t lPos, int iWhence)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ lPos = pFile->Seek(lPos, iWhence);
+ return lPos;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ // not supported: return lseeki64(fd, lPos, iWhence);
+ CLog::Log(LOGWARNING, "msvcrt.dll: dll_lseeki64 called, TODO: add 'int64 -> long' type checking"); //warning
+ return static_cast<long long>(lseek(fd, (long)lPos, iWhence));
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1ll;
+ }
+
+ __off_t dll_lseek(int fd, __off_t lPos, int iWhence)
+ {
+ if (g_emuFileWrapper.DescriptorIsEmulatedFile(fd))
+ {
+ return (__off_t)dll_lseeki64(fd, lPos, iWhence);
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ return lseek(fd, lPos, iWhence);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ void dll_rewind(FILE* stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ dll_lseeki64(fd, 0, SEEK_SET);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------
+ void dll_flockfile(FILE *stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ g_emuFileWrapper.LockFileObjectByDescriptor(fd);
+ return;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ }
+
+ int dll_ftrylockfile(FILE *stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ if (g_emuFileWrapper.TryLockFileObjectByDescriptor(fd))
+ return 0;
+ return -1;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ void dll_funlockfile(FILE *stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ g_emuFileWrapper.UnlockFileObjectByDescriptor(fd);
+ return;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ }
+
+ int dll_fclose(FILE * stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ return dll_close(fd) == 0 ? 0 : EOF;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+#ifndef TARGET_POSIX
+ // should be moved to CFile classes
+ intptr_t dll_findfirst(const char *file, struct _finddata_t *data)
+ {
+ struct _finddata64i32_t data64i32;
+ intptr_t ret = dll_findfirst64i32(file, &data64i32);
+ if (ret != -1)
+ {
+ int size = sizeof(data->name);
+ strncpy(data->name, data64i32.name, size);
+ if (size)
+ data->name[size - 1] = '\0';
+ data->size = (_fsize_t)data64i32.size;
+ data->time_write = (time_t)data64i32.time_write;
+ data->time_access = (time_t)data64i32.time_access;
+ }
+ return ret;
+ }
+
+ intptr_t dll_findfirst64i32(const char *file, struct _finddata64i32_t *data)
+ {
+ char str[1024];
+ int size = sizeof(str);
+ CURL url(CSpecialProtocol::TranslatePath(file));
+ if (url.IsLocal())
+ {
+ // move to CFile classes
+ if (strncmp(file, "\\Device\\Cdrom0", 14) == 0)
+ {
+ // replace "\\Device\\Cdrom0" with "D:"
+ strncpy(str, "D:", size);
+ if (size)
+ {
+ str[size - 1] = '\0';
+ strncat(str, file + 14, size - strlen(str));
+ }
+ }
+ else
+ {
+ strncpy(str, file, size);
+ if (size)
+ str[size - 1] = '\0';
+ }
+
+ // Make sure the slashes are correct & translate the path
+ struct _wfinddata64i32_t wdata;
+ std::wstring strwfile;
+ g_charsetConverter.utf8ToW(CUtil::ValidatePath(CSpecialProtocol::TranslatePath(str)), strwfile, false);
+ intptr_t ret = _wfindfirst64i32(strwfile.c_str(), &wdata);
+ if (ret != -1)
+ to_finddata64i32(&wdata, data);
+ return ret;
+ }
+ // non-local files. handle through IDirectory-class - only supports '*.bah' or '*.*'
+ std::string strURL(file);
+ std::string strMask;
+ if (url.GetFileName().find("*.*") != std::string::npos)
+ {
+ std::string strReplaced = url.GetFileName();
+ StringUtils::Replace(strReplaced, "*.*","");
+ url.SetFileName(strReplaced);
+ }
+ else if (url.GetFileName().find("*.") != std::string::npos)
+ {
+ strMask = URIUtils::GetExtension(url.GetFileName());
+ url.SetFileName(url.GetFileName().substr(0, url.GetFileName().find("*.")));
+ }
+ else if (url.GetFileName().find("*") != std::string::npos)
+ {
+ std::string strReplaced = url.GetFileName();
+ StringUtils::Replace(strReplaced, "*","");
+ url.SetFileName(strReplaced);
+ }
+ int iDirSlot=0; // locate next free directory
+ while ((iDirSlot < MAX_OPEN_DIRS) && (vecDirsOpen[iDirSlot].curr_index != -1)) iDirSlot++;
+ if (iDirSlot >= MAX_OPEN_DIRS)
+ return -1; // no free slots
+ strURL = url.Get();
+ bVecDirsInited = true;
+ vecDirsOpen[iDirSlot].items.Clear();
+ XFILE::CDirectory::GetDirectory(strURL, vecDirsOpen[iDirSlot].items, strMask, DIR_FLAG_DEFAULTS);
+ if (vecDirsOpen[iDirSlot].items.Size())
+ {
+ int size = sizeof(data->name);
+ strncpy(data->name,vecDirsOpen[iDirSlot].items[0]->GetLabel().c_str(), size);
+ if (size)
+ data->name[size - 1] = '\0';
+ data->size = static_cast<_fsize_t>(vecDirsOpen[iDirSlot].items[0]->m_dwSize);
+ data->time_write = 0;
+ data->time_access = 0;
+ vecDirsOpen[iDirSlot].curr_index = 0;
+ return (intptr_t)&vecDirsOpen[iDirSlot];
+ }
+ vecDirsOpen[iDirSlot].curr_index = -1;
+ return -1; // whatever != NULL
+ }
+
+ // should be moved to CFile classes
+ int dll_findnext(intptr_t f, _finddata_t* data)
+ {
+ struct _finddata64i32_t data64i32;
+ int ret = dll_findnext64i32(f, &data64i32);
+ if (ret == 0)
+ {
+ int size = sizeof(data->name);
+ strncpy(data->name, data64i32.name, size);
+ if (size)
+ data->name[size - 1] = '\0';
+ data->size = (_fsize_t)data64i32.size;
+ data->time_write = (time_t)data64i32.time_write;
+ data->time_access = (time_t)data64i32.time_access;
+ }
+ return ret;
+ }
+
+ int dll_findnext64i32(intptr_t f, _finddata64i32_t* data)
+ {
+ int found = MAX_OPEN_DIRS;
+ for (int i = 0; i < MAX_OPEN_DIRS; i++)
+ {
+ if (f == (intptr_t)&vecDirsOpen[i] && vecDirsOpen[i].curr_index != -1)
+ {
+ found = i;
+ break;
+ }
+ }
+ if (found >= MAX_OPEN_DIRS)
+ {
+ struct _wfinddata64i32_t wdata;
+ to_wfinddata64i32(data, &wdata);
+ intptr_t ret = _wfindnext64i32(f, &wdata); // local dir
+ if (ret != -1)
+ to_finddata64i32(&wdata, data);
+ return ret;
+ }
+
+ // we have a valid data structure. get next item!
+ int iItem = vecDirsOpen[found].curr_index;
+ if (iItem+1 < vecDirsOpen[found].items.Size()) // we have a winner!
+ {
+ int size = sizeof(data->name);
+ strncpy(data->name,vecDirsOpen[found].items[iItem+1]->GetLabel().c_str(), size);
+ if (size)
+ data->name[size - 1] = '\0';
+ data->size = static_cast<_fsize_t>(vecDirsOpen[found].items[iItem+1]->m_dwSize);
+ vecDirsOpen[found].curr_index++;
+ return 0;
+ }
+
+ vecDirsOpen[found].items.Clear();
+ return -1;
+ }
+
+ int dll_findclose(intptr_t handle)
+ {
+ int found = MAX_OPEN_DIRS;
+ for (int i = 0; i < MAX_OPEN_DIRS; i++)
+ {
+ if (handle == (intptr_t)&vecDirsOpen[i] && vecDirsOpen[i].curr_index != -1)
+ {
+ found = i;
+ break;
+ }
+ }
+ if (found >= MAX_OPEN_DIRS)
+ return _findclose(handle);
+
+ vecDirsOpen[found].items.Clear();
+ vecDirsOpen[found].curr_index = -1;
+ return 0;
+ }
+
+ void dll__security_error_handler(int code, void *data)
+ {
+ //NOTE: __security_error_handler has been removed in VS2005 and up
+ CLog::Log(LOGERROR, "security_error, code {}", code);
+ }
+
+#endif
+
+ DIR *dll_opendir(const char *file)
+ {
+ CURL url(CSpecialProtocol::TranslatePath(file));
+ if (url.IsLocal())
+ { // Make sure the slashes are correct & translate the path
+ return opendir(CUtil::ValidatePath(url.Get().c_str()).c_str());
+ }
+
+ // locate next free directory
+ int iDirSlot=0;
+ while ((iDirSlot<MAX_OPEN_DIRS) && (vecDirsOpen[iDirSlot].curr_index != -1)) iDirSlot++;
+ if (iDirSlot >= MAX_OPEN_DIRS)
+ {
+ CLog::Log(LOGDEBUG, "Dll: Max open dirs reached");
+ return NULL; // no free slots
+ }
+
+ bVecDirsInited = true;
+ vecDirsOpen[iDirSlot].items.Clear();
+
+ if (XFILE::CDirectory::GetDirectory(url.Get(), vecDirsOpen[iDirSlot].items, "", DIR_FLAG_DEFAULTS))
+ {
+ vecDirsOpen[iDirSlot].curr_index = 0;
+ return (DIR *)&vecDirsOpen[iDirSlot];
+ }
+ else
+ return NULL;
+ }
+
+ struct dirent *dll_readdir(DIR *dirp)
+ {
+ if (!dirp)
+ return NULL;
+
+ bool emulated(false);
+ for (const SDirData& dir : vecDirsOpen)
+ {
+ if (dirp == (DIR*)&dir)
+ {
+ emulated = true;
+ break;
+ }
+ }
+ if (!emulated)
+ return readdir(dirp); // local dir
+
+ // dirp is actually a SDirData*
+ SDirData* dirData = reinterpret_cast<SDirData*>(dirp);
+ if (dirData->last_entry)
+ free(dirData->last_entry);
+ struct dirent *entry = NULL;
+ entry = (dirent*) malloc(sizeof(*entry));
+ if (dirData->curr_index < dirData->items.Size() + 2)
+ { // simulate the '.' and '..' dir entries
+ if (dirData->curr_index == 0)
+ strncpy(entry->d_name, ".\0", 2);
+ else if (dirData->curr_index == 1)
+ strncpy(entry->d_name, "..\0", 3);
+ else
+ {
+ strncpy(entry->d_name, dirData->items[dirData->curr_index - 2]->GetLabel().c_str(), sizeof(entry->d_name));
+ entry->d_name[sizeof(entry->d_name)-1] = '\0'; // null-terminate any truncated paths
+ }
+ dirData->last_entry = entry;
+ dirData->curr_index++;
+ return entry;
+ }
+ free(entry);
+ return NULL;
+ }
+
+ int dll_closedir(DIR *dirp)
+ {
+ bool emulated(false);
+ for (const SDirData& dir : vecDirsOpen)
+ {
+ if (dirp == (DIR*)&dir)
+ {
+ emulated = true;
+ break;
+ }
+ }
+ if (!emulated)
+ return closedir(dirp);
+
+ SDirData* dirData = reinterpret_cast<SDirData*>(dirp);
+ dirData->items.Clear();
+ if (dirData->last_entry)
+ {
+ dirData->last_entry = NULL;
+ }
+ dirData->curr_index = -1;
+ return 0;
+ }
+
+ void dll_rewinddir(DIR *dirp)
+ {
+ bool emulated(false);
+ for (const SDirData& dir : vecDirsOpen)
+ {
+ if (dirp == (DIR*)&dir)
+ {
+ emulated = true;
+ break;
+ }
+ }
+ if (!emulated)
+ {
+ rewinddir(dirp);
+ return;
+ }
+
+ SDirData* dirData = reinterpret_cast<SDirData*>(dirp);
+ if (dirData->last_entry)
+ {
+ dirData->last_entry = NULL;
+ }
+ dirData->curr_index = 0;
+ }
+
+ char* dll_fgets(char* pszString, int num ,FILE * stream)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ if (pFile->GetPosition() < pFile->GetLength())
+ {
+ bool bRead = pFile->ReadString(pszString, num);
+ if (bRead)
+ {
+ return pszString;
+ }
+ }
+ else return NULL; //eof
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return NULL;
+ }
+
+ int dll_feof(FILE * stream)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ if (pFile->GetPosition() < pFile->GetLength()) return 0;
+ else return 1;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return 1; // eof by default
+ }
+
+ int dll_fread(void * buffer, size_t size, size_t count, FILE * stream)
+ {
+ if (size == 0 || count == 0)
+ return 0;
+
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ size_t read = 0;
+ const size_t bufSize = size * count;
+ do // fread() must read all data until buffer is filled or eof/error occurs
+ {
+ const ssize_t r = pFile->Read(((int8_t*)buffer) + read, bufSize - read);
+ if (r <= 0)
+ break;
+ read += r;
+ } while (bufSize > read);
+ return read / size;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return 0;
+ }
+
+ int dll_fgetc(FILE* stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ // it is a emulated file
+ unsigned char buf;
+
+ if (dll_fread(&buf, 1, 1, stream) <= 0)
+ return EOF;
+
+ return (int)buf;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+ int dll_getc(FILE* stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ // This routine is normally implemented as a macro with the same result as fgetc().
+ return dll_fgetc(stream);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+ FILE* dll_fopen(const char* filename, const char* mode)
+ {
+ FILE* file = NULL;
+#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
+ if (strcmp(filename, _PATH_MOUNTED) == 0
+ || strcmp(filename, _PATH_MNTTAB) == 0)
+ {
+ CLog::Log(LOGINFO,
+ "{} - something opened the mount file, let's hope it knows what it's doing",
+ __FUNCTION__);
+ return fopen(filename, mode);
+ }
+#endif
+ int fd = dll_open(filename, convert_fmode(mode));
+ if (fd >= 0)
+ {
+ file = g_emuFileWrapper.GetStreamByDescriptor(fd);
+ }
+
+ return file;
+ }
+
+ int dll_fopen_s(FILE** pFile, const char * filename, const char * mode)
+ {
+ if (pFile == NULL || filename == NULL || mode == NULL)
+ return EINVAL;
+
+ *pFile = dll_fopen(filename, mode);
+ if (*pFile == NULL)
+ return errno;
+
+ return 0;
+ }
+
+ int dll_putc(int c, FILE *stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream) || IS_STD_STREAM(stream))
+ {
+ return dll_fputc(c, stream);
+ }
+ return EOF;
+ }
+
+ int dll_putchar(int c)
+ {
+ return dll_putc(c, stdout);
+ }
+
+ int dll_fputc(int character, FILE* stream)
+ {
+ if (IS_STDOUT_STREAM(stream) || IS_STDERR_STREAM(stream) || !IS_VALID_STREAM(stream))
+ {
+ unsigned char tmp[2] = { (unsigned char)character, 0 };
+ dllputs((char *)tmp);
+ return character;
+ }
+ else
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ unsigned char c = (unsigned char)character;
+ int iItemsWritten = dll_write(fd, &c, 1);
+ if (iItemsWritten == 1)
+ return character;
+ }
+ }
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+ int dll_fputs(const char * szLine, FILE* stream)
+ {
+ if (IS_STDOUT_STREAM(stream) || IS_STDERR_STREAM(stream) || !IS_VALID_STREAM(stream))
+ {
+ dllputs(szLine);
+ return 0;
+ }
+ else
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ size_t len = strlen(szLine);
+ return dll_fwrite(static_cast<const void*>(szLine), sizeof(char), len, stream);
+ }
+ }
+
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+ int dll_fseek64(FILE* stream, off64_t offset, int origin)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ if (dll_lseeki64(fd, offset, origin) != -1)
+ {
+ return 0;
+ }
+ else return -1;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ int dll_fseek(FILE *stream, long offset, int origin)
+ {
+ return dll_fseek64(stream, offset, origin);
+ }
+
+ int dll_ungetc(int c, FILE* stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ // it is a emulated file
+ int d;
+ if (dll_fseek(stream, -1, SEEK_CUR)!=0)
+ return EOF;
+ d = dll_fgetc(stream);
+ if (d == EOF)
+ return EOF;
+
+ dll_fseek(stream, -1, SEEK_CUR);
+ if (c != d)
+ {
+ CLog::Log(LOGWARNING, "{}: c != d", __FUNCTION__);
+ d = fputc(c, stream);
+ if (d != c)
+ CLog::Log(LOGERROR, "{}: Write failed!", __FUNCTION__);
+ else
+ dll_fseek(stream, -1, SEEK_CUR);
+ }
+ return d;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EOF;
+ }
+
+ long dll_ftell(FILE *stream)
+ {
+ return (long)dll_ftell64(stream);
+ }
+
+ off64_t dll_ftell64(FILE *stream)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ return (off64_t)pFile->GetPosition();
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ long dll_tell(int fd)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ return (long)pFile->GetPosition();
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+#ifndef TARGET_POSIX
+ return tell(fd);
+#else
+ return lseek(fd, 0, SEEK_CUR);
+#endif
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ long long dll_telli64(int fd)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ return static_cast<long long>(pFile->GetPosition());
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+ // not supported return telli64(fd);
+ CLog::Log(LOGWARNING, "msvcrt.dll: dll_telli64 called, TODO: add 'int64 -> long' type checking"); //warning
+#ifndef TARGET_POSIX
+ return static_cast<long long>(tell(fd));
+#elif defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+ return lseek(fd, 0, SEEK_CUR);
+#else
+ return lseek64(fd, 0, SEEK_CUR);
+#endif
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return -1;
+ }
+
+ size_t dll_fwrite(const void * buffer, size_t size, size_t count, FILE* stream)
+ {
+ if (size == 0 || count == 0)
+ return 0;
+
+ if (IS_STDOUT_STREAM(stream) || IS_STDERR_STREAM(stream) || !IS_VALID_STREAM(stream))
+ {
+ char* buf = (char*)malloc(size * count + 1);
+ if (buf)
+ {
+ memcpy(buf, buffer, size * count);
+ buf[size * count] = 0; // string termination
+
+ CLog::Log(LOGDEBUG, "{}", buf);
+
+ free(buf);
+ return count;
+ }
+ }
+ else
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ size_t written = 0;
+ const size_t bufSize = size * count;
+ do // fwrite() must write all data until whole buffer is written or error occurs
+ {
+ const ssize_t w = pFile->Write(((const int8_t*)buffer) + written, bufSize - written);
+ if (w <= 0)
+ break;
+ written += w;
+ } while (bufSize > written);
+ return written / size;
+ }
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return 0;
+ }
+
+ int dll_fflush(FILE* stream)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ pFile->Flush();
+ return 0;
+ }
+
+ // std stream, no need to flush
+ return 0;
+ }
+
+ int dll_ferror(FILE* stream)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ // unimplemented
+ return 0;
+ }
+ else if (IS_STD_STREAM(stream))
+ return 0;
+ else
+ return ferror(stream);
+ }
+
+ int dllvprintf(const char *format, va_list va)
+ {
+ std::string buffer = StringUtils::FormatV(format, va);
+ CLog::Log(LOGDEBUG, " msg: {}", buffer);
+ return buffer.length();
+ }
+
+ int dll_vfprintf(FILE *stream, const char *format, va_list va)
+ {
+ static char tmp[2048];
+
+ if (_vsnprintf(tmp, 2048, format, va) == -1)
+ {
+ CLog::Log(LOGWARNING, "dll_vfprintf: Data lost due to undersized buffer");
+ }
+ tmp[2048 - 1] = 0;
+
+ if (IS_STDOUT_STREAM(stream) || IS_STDERR_STREAM(stream) || !IS_VALID_STREAM(stream))
+ {
+ CLog::Log(LOGINFO, " msg: {}", tmp);
+ return strlen(tmp);
+ }
+ else
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+ int len = strlen(tmp);
+ // replace all '\n' occurrences with '\r\n'...
+ char tmp2[2048];
+ int j = 0;
+ for (int i = 0; i < len; i++)
+ {
+ if (j == 2047)
+ { // out of space
+ if (i != len-1)
+ CLog::Log(LOGWARNING, "dll_fprintf: Data lost due to undersized buffer");
+ break;
+ }
+ if (tmp[i] == '\n' && ((i > 0 && tmp[i-1] != '\r') || i == 0) && j < 2047 - 2)
+ { // need to add a \r
+ tmp2[j++] = '\r';
+ tmp2[j++] = '\n';
+ }
+ else
+ { // just add the character as-is
+ tmp2[j++] = tmp[i];
+ }
+ }
+ // terminate string
+ tmp2[j] = 0;
+ len = strlen(tmp2);
+ pFile->Write(tmp2, len);
+ return len;
+ }
+ }
+
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return strlen(tmp);
+ }
+
+ int dll_fscanf(FILE* stream, const char* format, ...)
+ {
+ CLog::Log(LOGERROR, "{} is not implemented", __FUNCTION__);
+ return -1;
+ }
+
+ int dll_fprintf(FILE* stream, const char* format, ...)
+ {
+ int res;
+ va_list va;
+ va_start(va, format);
+ res = dll_vfprintf(stream, format, va);
+ va_end(va);
+ return res;
+ }
+
+ int dll_fgetpos(FILE* stream, fpos_t* pos)
+ {
+ fpos64_t tmpPos = {};
+ int ret;
+
+ ret = dll_fgetpos64(stream, &tmpPos);
+#if !defined(TARGET_POSIX) || defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+ *pos = (fpos_t)tmpPos;
+#else
+ pos->__pos = (off_t)tmpPos.__pos;
+#endif
+ return ret;
+ }
+
+ int dll_fgetpos64(FILE *stream, fpos64_t *pos)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(stream);
+ if (pFile != NULL)
+ {
+#if !defined(TARGET_POSIX) || defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+ *pos = pFile->GetPosition();
+#else
+ pos->__pos = pFile->GetPosition();
+#endif
+ return 0;
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EINVAL;
+ }
+
+ int dll_fsetpos64(FILE* stream, const fpos64_t* pos)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+#if !defined(TARGET_POSIX) || defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+ if (dll_lseeki64(fd, *pos, SEEK_SET) >= 0)
+#else
+ if (dll_lseeki64(fd, (__off64_t)pos->__pos, SEEK_SET) >= 0)
+#endif
+ {
+ return 0;
+ }
+ else
+ {
+ return EINVAL;
+ }
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EINVAL;
+ }
+
+ int dll_fsetpos(FILE* stream, const fpos_t* pos)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ fpos64_t tmpPos;
+#if !defined(TARGET_POSIX) || defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+ tmpPos= *pos;
+#else
+ tmpPos.__pos = (off64_t)(pos->__pos);
+#endif
+ return dll_fsetpos64(stream, &tmpPos);
+ }
+ CLog::Log(LOGERROR, "{} emulated function failed", __FUNCTION__);
+ return EINVAL;
+ }
+
+ int dll_fileno(FILE* stream)
+ {
+ int fd = g_emuFileWrapper.GetDescriptorByStream(stream);
+ if (fd >= 0)
+ {
+ return fd;
+ }
+ else if (IS_STDIN_STREAM(stream))
+ {
+ return 0;
+ }
+ else if (IS_STDOUT_STREAM(stream))
+ {
+ return 1;
+ }
+ else if (IS_STDERR_STREAM(stream))
+ {
+ return 2;
+ }
+ else
+ {
+ return fileno(stream);
+ }
+
+ return -1;
+ }
+
+ void dll_clearerr(FILE* stream)
+ {
+ if (g_emuFileWrapper.StreamIsEmulatedFile(stream))
+ {
+ // not implemented
+ }
+ }
+
+ char* dll_strdup( const char* str)
+ {
+ char* pdup;
+ pdup = strdup(str);
+ return pdup;
+ }
+
+ //Critical Section has been fixed in EMUkernel32.cpp
+
+ int dll_initterm(PFV* start, const PFV* end) //pncrt.dll
+ {
+ PFV * temp;
+ for (temp = start; temp < end; temp ++)
+ if (*temp)
+ (*temp)(); //call initial function table.
+ return 0;
+ }
+
+ //SLOW CODE SHOULD BE REVISED
+ int dll_stat(const char *path, struct stat *buffer)
+ {
+ if (!StringUtils::CompareNoCase(path, "shout://", 8)) // don't stat shoutcast
+ return -1;
+ if (!StringUtils::CompareNoCase(path, "mms://", 6)) // don't stat mms
+ return -1;
+
+#ifdef TARGET_POSIX
+ if (!StringUtils::CompareNoCase(path, "D:") || !StringUtils::CompareNoCase(path, "D:\\"))
+ {
+ buffer->st_mode = S_IFDIR;
+ return 0;
+ }
+#endif
+ if (!StringUtils::CompareNoCase(path, "\\Device\\Cdrom0") ||
+ !StringUtils::CompareNoCase(path, "\\Device\\Cdrom0\\"))
+ {
+ buffer->st_mode = _S_IFDIR;
+ return 0;
+ }
+
+ struct __stat64 tStat;
+ if (CFile::Stat(path, &tStat) == 0)
+ {
+ CUtil::Stat64ToStat(buffer, &tStat);
+ return 0;
+ }
+ // errno is set by file.Stat(...)
+ return -1;
+ }
+
+ int dll_stati64(const char *path, struct _stati64 *buffer)
+ {
+ struct __stat64 a;
+ memset(&a, 0, sizeof(a));
+
+ if(dll_stat64(path, &a) == 0)
+ {
+ CUtil::Stat64ToStatI64(buffer, &a);
+ return 0;
+ }
+ return -1;
+ }
+
+ int dll_stat64(const char *path, struct __stat64 *buffer)
+ {
+ if (!StringUtils::CompareNoCase(path, "shout://", 8)) // don't stat shoutcast
+ return -1;
+ if (!StringUtils::CompareNoCase(path, "mms://", 6)) // don't stat mms
+ return -1;
+
+#ifdef TARGET_POSIX
+ if (!StringUtils::CompareNoCase(path, "D:") || !StringUtils::CompareNoCase(path, "D:\\"))
+ {
+ buffer->st_mode = _S_IFDIR;
+ return 0;
+ }
+#endif
+ if (!StringUtils::CompareNoCase(path, "\\Device\\Cdrom0") ||
+ !StringUtils::CompareNoCase(path, "\\Device\\Cdrom0\\"))
+ {
+ buffer->st_mode = _S_IFDIR;
+ return 0;
+ }
+
+ return CFile::Stat(path, buffer);
+ }
+
+#ifdef TARGET_WINDOWS
+ int dll_stat64i32(const char *path, struct _stat64i32 *buffer)
+ {
+ struct __stat64 a;
+ if(dll_stat64(path, &a) == 0)
+ {
+ CUtil::Stat64ToStat64i32(buffer, &a);
+ return 0;
+ }
+ return -1;
+ }
+#endif
+
+ int dll_fstat(int fd, struct stat* buffer)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ struct __stat64 tStat;
+ if (pFile->Stat(&tStat) == 0)
+ {
+ CUtil::Stat64ToStat(buffer, &tStat);
+ return 0;
+ }
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ return fstat(fd, buffer);
+ }
+
+ // fstat on stdin, stdout or stderr should fail
+ // this is what python expects
+ return -1;
+ }
+
+ int dll_fstati64(int fd, struct _stati64 *buffer)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ CLog::Log(LOGINFO, "Stating open file");
+
+ buffer->st_size = pFile->GetLength();
+ buffer->st_mode = _S_IFREG;
+ return 0;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ CLog::Log(LOGWARNING, "msvcrt.dll: dll_fstati64 called, TODO: add 'int64 <-> long' type checking"); //warning
+ // need to use fstat and convert everything
+ struct stat temp;
+ int res = fstat(fd, &temp);
+ if (res == 0)
+ {
+ CUtil::StatToStatI64(buffer, &temp);
+ }
+ return res;
+ }
+
+ // fstat on stdin, stdout or stderr should fail
+ // this is what python expects
+ return -1;
+ }
+
+#ifdef TARGET_WINDOWS
+ int dll_fstat64i32(int fd, struct _stat64i32 *buffer)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ struct __stat64 tStat = {};
+ if (pFile->Stat(&tStat) == 0)
+ {
+ CUtil::Stat64ToStat64i32(buffer, &tStat);
+ return 0;
+ }
+ return -1;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ CLog::Log(LOGWARNING, "msvcrt.dll: dll_fstati64 called, TODO: add 'int64 <-> long' type checking"); //warning
+ // need to use fstat and convert everything
+ struct __stat64 temp;
+ int res = _fstat64(fd, &temp);
+ if (res == 0)
+ {
+ CUtil::Stat64ToStat64i32(buffer, &temp);
+ }
+ return res;
+ }
+
+ // fstat on stdin, stdout or stderr should fail
+ // this is what python expects
+ return -1;
+ }
+#endif
+
+ int dll_setmode ( int handle, int mode )
+ {
+ not_implement("msvcrt.dll fake function dll_setmode() called\n");
+ return -1;
+ }
+
+ void dllperror(const char* s)
+ {
+ if (s)
+ {
+ CLog::Log(LOGERROR, "perror: {}", s);
+ }
+ }
+
+ char* dllstrerror(int iErr)
+ {
+ static char szError[32];
+ sprintf(szError, "err:%i", iErr);
+ return (char*)szError;
+ }
+
+ int dll_mkdir(const char* dir)
+ {
+ if (!dir) return -1;
+
+ // Make sure the slashes are correct & translate the path
+ std::string strPath = CUtil::ValidatePath(CSpecialProtocol::TranslatePath(dir));
+#ifndef TARGET_POSIX
+ std::wstring strWPath;
+ g_charsetConverter.utf8ToW(strPath, strWPath, false);
+ return _wmkdir(strWPath.c_str());
+#else
+ return mkdir(strPath.c_str(), 0755);
+#endif
+ }
+
+ const char* dll_getcwd(char *buffer, int maxlen)
+ {
+ not_implement("msvcrt.dll fake function dll_getcwd() called\n");
+ return "special://xbmc/";
+ }
+
+ int dll_putenv(const char* envstring)
+ {
+ bool added = false;
+
+ if (envstring != NULL)
+ {
+ const char *value_start = strchr(envstring, '=');
+
+ if (value_start != NULL)
+ {
+ char var[64];
+ int size = strlen(envstring) + 1;
+ char *value = (char*)malloc(size);
+
+ if (!value)
+ return -1;
+ value[0] = 0;
+
+ memcpy(var, envstring, value_start - envstring);
+ var[value_start - envstring] = 0;
+ char* temp = var;
+ while (*temp)
+ {
+ *temp = (char)toupper(*temp);
+ temp++;
+ }
+
+ strncpy(value, value_start + 1, size);
+ if (size)
+ value[size - 1] = '\0';
+
+ {
+ std::unique_lock<CCriticalSection> lock(dll_cs_environ);
+
+ char** free_position = NULL;
+ for (int i = 0; i < EMU_MAX_ENVIRONMENT_ITEMS && free_position == NULL; i++)
+ {
+ if (dll__environ[i] != NULL)
+ {
+ // we only support overwriting the old values
+ if (StringUtils::CompareNoCase(dll__environ[i], var, strlen(var)) == 0)
+ {
+ // free it first
+ free(dll__environ[i]);
+ dll__environ[i] = NULL;
+ free_position = &dll__environ[i];
+ }
+ }
+ else
+ {
+ free_position = &dll__environ[i];
+ }
+ }
+
+ if (free_position != NULL)
+ {
+ // free position, copy value
+ size = strlen(var) + strlen(value) + 2;
+ *free_position = (char*)malloc(size); // for '=' and 0 termination
+ if ((*free_position))
+ {
+ strncpy(*free_position, var, size);
+ (*free_position)[size - 1] = '\0';
+ strncat(*free_position, "=", size - strlen(*free_position));
+ strncat(*free_position, value, size - strlen(*free_position));
+ added = true;
+ }
+ }
+
+ }
+
+ free(value);
+ }
+ }
+
+ return added ? 0 : -1;
+ }
+
+ char* dll_getenv(const char* szKey)
+ {
+ char* value = NULL;
+
+ {
+ std::unique_lock<CCriticalSection> lock(dll_cs_environ);
+
+ update_emu_environ();//apply any changes
+
+ for (int i = 0; i < EMU_MAX_ENVIRONMENT_ITEMS && value == NULL; i++)
+ {
+ if (dll__environ[i])
+ {
+ if (StringUtils::CompareNoCase(dll__environ[i], szKey, strlen(szKey)) == 0)
+ {
+ // found it
+ value = dll__environ[i] + strlen(szKey) + 1;
+ }
+ }
+ }
+ }
+
+ if (value != NULL)
+ {
+ return value;
+ }
+
+ return NULL;
+ }
+
+ int dll_ctype(int i)
+ {
+ not_implement("msvcrt.dll fake function dll_ctype() called\n");
+ return 0;
+ }
+
+ int dll_system(const char *command)
+ {
+ not_implement("msvcrt.dll fake function dll_system() called\n");
+ return 0; //system(command);
+ }
+
+ void (__cdecl * dll_signal(int sig, void (__cdecl *func)(int)))(int)
+ {
+#if defined(TARGET_WINDOWS)
+ //vs2008 asserts for known signals, return err for everything unknown to windows.
+ if (sig == 5 || sig == 7 || sig == 9 || sig == 10 || sig == 12 || sig == 14 || sig == 18 || sig == 19 || sig == 20)
+ return SIG_ERR;
+#endif
+ return signal(sig, func);
+ }
+
+ int dll_getpid()
+ {
+ return 1;
+ }
+
+ int dll__commit(int fd)
+ {
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (pFile != NULL)
+ {
+ pFile->Flush();
+ return 0;
+ }
+ else if (!IS_STD_DESCRIPTOR(fd))
+ {
+ // it might be something else than a file, or the file is not emulated
+ // let the operating system handle it
+#ifndef TARGET_POSIX
+ return _commit(fd);
+#else
+ return fsync(fd);
+#endif
+ }
+
+ // std stream, no need to flush
+ return 0;
+ }
+
+ char*** dll___p__environ()
+ {
+ static char*** t = &dll__environ;
+ return (char***)&t;
+ }
+
+#ifdef TARGET_POSIX
+#if defined(TARGET_ANDROID)
+ volatile int * __cdecl dll_errno(void)
+ {
+ return &errno;
+ }
+#else
+ int * __cdecl dll_errno(void)
+ {
+ return &errno;
+ }
+#endif
+
+ int __cdecl dll_ioctl(int fd, unsigned long int request, va_list va)
+ {
+ int ret;
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByDescriptor(fd);
+ if (!pFile)
+ return -1;
+
+#if defined(HAS_DVD_DRIVE) && !defined(TARGET_FREEBSD)
+#if !defined(TARGET_DARWIN)
+ if(request == DVD_READ_STRUCT || request == DVD_AUTH)
+#else
+ if(request == DKIOCDVDSENDKEY || request == DKIOCDVDREPORTKEY || request == DKIOCDVDREADSTRUCTURE)
+#endif
+ {
+ void *p1 = va_arg(va, void*);
+ SNativeIoControl d;
+ d.request = request;
+ d.param = p1;
+ ret = pFile->IoControl(IOCTRL_NATIVE, &d);
+ if(ret<0)
+ CLog::Log(LOGWARNING, "{} - {} request failed with error [{}] {}", __FUNCTION__, request,
+ errno, strerror(errno));
+ }
+ else
+#endif
+ {
+ CLog::Log(LOGWARNING, "{} - Unknown request type {}", __FUNCTION__, request);
+ ret = -1;
+ }
+ return ret;
+ }
+#endif
+
+ int dll_setvbuf(FILE *stream, char *buf, int type, size_t size)
+ {
+ CLog::Log(LOGWARNING, "{} - May not be implemented correctly", __FUNCTION__);
+ return 0;
+ }
+
+ struct mntent *dll_getmntent(FILE *fp)
+ {
+ if (!fp)
+ return nullptr;
+
+#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
+ struct mntent* mountPoint = getmntent(fp);
+ if (mountPoint)
+ return mountPoint;
+
+ // warn if this is a kodi vfs file not associated with a mountpoint
+ CFile* pFile = g_emuFileWrapper.GetFileXbmcByStream(fp);
+ if (pFile)
+ {
+ CLog::LogF(LOGWARNING, "getmntent is not implemented for our virtual filesystem");
+ }
+ return nullptr;
+#else
+ CLog::LogF(LOGWARNING, "Unimplemented function called");
+ return nullptr;
+#endif
+ }
+
+ struct mntent* dll_getmntent_r(FILE* fp, struct mntent* result, char* buffer, int bufsize)
+ {
+ if (!fp || !result || !buffer)
+ return nullptr;
+
+#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
+ struct mntent* mountPoint = getmntent_r(fp, result, buffer, bufsize);
+ if (mountPoint)
+ return mountPoint;
+#endif
+ return nullptr;
+ }
+
+ // this needs to be wrapped, since dll's have their own file
+ // descriptor list, but we always use app's list with our wrappers
+ int __cdecl dll_open_osfhandle(intptr_t _OSFileHandle, int _Flags)
+ {
+#ifdef TARGET_WINDOWS
+ return _open_osfhandle(_OSFileHandle, _Flags);
+#else
+ return -1;
+#endif
+ }
+
+}
diff --git a/xbmc/cores/DllLoader/exports/emu_msvcrt.h b/xbmc/cores/DllLoader/exports/emu_msvcrt.h
new file mode 100644
index 0000000..cee233b
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/emu_msvcrt.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "PlatformDefs.h"
+
+#ifdef TARGET_POSIX
+#define _onexit_t void*
+#endif
+
+#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+typedef off_t __off_t;
+typedef int64_t off64_t;
+typedef off64_t __off64_t;
+typedef fpos_t fpos64_t;
+#endif
+
+#ifdef TARGET_WINDOWS
+#include "platform/win32/dirent.h"
+#else
+#include <dirent.h>
+#endif
+
+typedef void ( *PFV)(void);
+
+#define __IS_STDIN_STREAM(stream) (stream == stdin || fileno(stream) == fileno(stdin) || fileno(stream) == 0)
+#define __IS_STDOUT_STREAM(stream) (stream == stdout || fileno(stream) == fileno(stdout) || fileno(stream) == 1)
+#define __IS_STDERR_STREAM(stream) (stream == stderr || fileno(stream) == fileno(stderr) || fileno(stream) == 2)
+#define IS_STDIN_STREAM(stream) (stream != NULL && __IS_STDIN_STREAM(stream))
+#define IS_STDOUT_STREAM(stream) (stream != NULL && __IS_STDOUT_STREAM(stream))
+#define IS_STDERR_STREAM(stream) (stream != NULL && __IS_STDERR_STREAM(stream))
+#define IS_VALID_STREAM(stream) (stream != nullptr)
+
+
+#define IS_STD_STREAM(stream) (stream != NULL && (__IS_STDIN_STREAM(stream) || __IS_STDOUT_STREAM(stream) || __IS_STDERR_STREAM(stream)))
+
+#define IS_STDIN_DESCRIPTOR(fd) (fd == 0)
+#define IS_STDOUT_DESCRIPTOR(fd) (fd == 1)
+#define IS_STDERR_DESCRIPTOR(fd) (fd == 2)
+
+#define IS_STD_DESCRIPTOR(fd) (IS_STDIN_DESCRIPTOR(fd) || IS_STDOUT_DESCRIPTOR(fd) || IS_STDERR_DESCRIPTOR(fd))
+
+
+extern "C"
+{
+ char* dll_strdup( const char* str);
+ void dll_sleep(unsigned long imSec);
+ void InitFiles();
+ void dllReleaseAll( );
+ void* dllmalloc(size_t size);
+ void dllfree( void* pPtr );
+ void* dllcalloc( size_t num, size_t size );
+ void* dllrealloc( void *memblock, size_t size );
+ void dllexit(int iCode);
+ void dllabort();
+ void* dll__dllonexit(PFV input, PFV ** start, PFV ** end);
+ _onexit_t dll_onexit(_onexit_t func);
+ int dllputs(const char* szLine);
+ int dll_putchar(int c);
+ int dll_putc(int c, FILE *stream);
+ int dllprintf( const char *format, ... );
+ int dllvprintf(const char *format, va_list va);
+ char *dll_fullpath(char *absPath, const char *relPath, size_t maxLength);
+ FILE *dll_popen(const char *command, const char *mode);
+ int dll_pclose(FILE *stream);
+ FILE* dll_fdopen(int i, const char* file);
+ int dll_open(const char* szFileName, int iMode);
+ int dll_read(int fd, void* buffer, unsigned int uiSize);
+ int dll_write(int fd, const void* buffer, unsigned int uiSize);
+ int dll_close(int fd);
+ __off64_t dll_lseeki64(int fd, __off64_t lPos, int iWhence);
+ __off_t dll_lseek(int fd, __off_t lPos, int iWhence);
+ char* dll_getenv(const char* szKey);
+ int dll_fclose (FILE * stream);
+#ifndef TARGET_POSIX
+ intptr_t dll_findfirst(const char *file, struct _finddata_t *data);
+ int dll_findnext(intptr_t f, _finddata_t* data);
+ int dll_findclose(intptr_t handle);
+ intptr_t dll_findfirst64i32(const char *file, struct _finddata64i32_t *data);
+ int dll_findnext64i32(intptr_t f, _finddata64i32_t* data);
+ void dll__security_error_handler(int code, void *data);
+#endif
+ DIR *dll_opendir(const char *filename);
+ struct dirent *dll_readdir(DIR *dirp);
+ int dll_closedir(DIR *dirp);
+ void dll_rewinddir(DIR *dirp);
+ char * dll_fgets (char* pszString, int num , FILE * stream);
+ int dll_fgetc (FILE* stream);
+ int dll_feof (FILE * stream);
+ int dll_fread (void * buffer, size_t size, size_t count, FILE * stream);
+ int dll_getc (FILE * stream);
+ FILE * dll_fopen(const char * filename, const char * mode);
+ int dll_fopen_s(FILE** pFile, const char * filename, const char * mode);
+ int dll_fputc (int character, FILE * stream);
+ int dll_putcchar (int character);
+ int dll_fputs (const char * szLine , FILE* stream);
+ int dll_fseek ( FILE * stream , long offset , int origin );
+ int dll_fseek64(FILE *stream, off64_t offset, int origin);
+ int dll_ungetc (int c, FILE * stream);
+ long dll_ftell(FILE *stream);
+ off64_t dll_ftell64(FILE *stream);
+ long dll_tell ( int fd );
+ long long dll_telli64 ( int fd );
+ size_t dll_fwrite ( const void * buffer, size_t size, size_t count, FILE * stream );
+ int dll_fflush (FILE * stream);
+ int dll_ferror (FILE * stream);
+ int dll_vfprintf(FILE *stream, const char *format, va_list va);
+ int dll_fprintf(FILE* stream , const char * format, ...);
+ int dll_fgetpos(FILE* stream, fpos_t* pos);
+ int dll_fgetpos64(FILE *stream, fpos64_t *pos);
+ int dll_fsetpos(FILE* stream, const fpos_t* pos);
+ int dll_fsetpos64(FILE* stream, const fpos64_t* pos);
+ int dll_fileno(FILE* stream);
+ void dll_rewind(FILE* stream);
+ void dll_clearerr(FILE* stream);
+ int dll_initterm(PFV* start, const PFV* end);
+ uintptr_t dll_beginthread(void( *start_address )( void * ),unsigned stack_size,void *arglist);
+ int dll_stati64(const char *path, struct _stati64 *buffer);
+ int dll_stat64(const char *path, struct __stat64 *buffer);
+#ifdef TARGET_WINDOWS
+ int dll_stat64i32(const char *path, struct _stat64i32 *buffer);
+#endif
+ int dll_stat(const char *path, struct stat *buffer);
+ int dll_fstat(int fd, struct stat *buffer);
+ int dll_fstati64(int fd, struct _stati64 *buffer);
+ int dll_setmode(int handle, int mode );
+ void dllperror(const char* s);
+ char* dllstrerror(int iErr);
+ int dll_mkdir(const char* dir);
+ const char* dll_getcwd(char *buffer, int maxlen);
+ int dll_putenv(const char* envstring);
+ int dll_ctype(int i);
+ int dll_system(const char *command);
+ void (__cdecl * dll_signal(int sig, void (__cdecl *func)(int)))(int);
+ int dll_getpid();
+ int dll__commit(int fd);
+ char*** dll___p__environ();
+ FILE* dll_freopen(const char *path, const char *mode, FILE *stream);
+ int dll_fscanf(FILE *stream, const char *format , ...);
+ void dll_flockfile(FILE *file);
+ int dll_ftrylockfile(FILE *file);
+ void dll_funlockfile(FILE *file);
+ int dll_fstat64(int fd, struct __stat64 *buf);
+#ifdef TARGET_WINDOWS
+ int dll_fstat64i32(int fd, struct _stat64i32 *buffer);
+ int dll_open_osfhandle(intptr_t _OSFileHandle, int _Flags);
+#endif
+ int dll_setvbuf(FILE *stream, char *buf, int type, size_t size);
+
+#if defined(TARGET_ANDROID)
+ volatile int * __cdecl dll_errno(void);
+#elif defined(TARGET_POSIX)
+ int * __cdecl dll_errno(void);
+#endif
+
+ extern char **dll__environ;
+}
+
diff --git a/xbmc/cores/DllLoader/exports/kernel32.def b/xbmc/cores/DllLoader/exports/kernel32.def
new file mode 100755
index 0000000..b23a9be
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/kernel32.def
@@ -0,0 +1,25 @@
+LIBRARY kernel32.dll
+
+VERSION 1.0
+
+EXPORTS
+ AddAtomA
+ FindAtomA
+ GetAtomNameA
+ CreateThread
+ FindClose
+ FindFirstFileA
+ FindNextFileA
+ GetFileAttributesA
+ GetLastError
+ GetModuleFileNameA
+ GetNumberOfConsoleInputEvents
+ GetStdHandle
+ ReadConsoleInputA
+ SetUnhandledExceptionFilter
+ Sleep
+ TerminateThread
+ GetCurrentThread
+ QueryPerformanceCounter
+ QueryPerformanceFrequency
+ SetThreadPriority \ No newline at end of file
diff --git a/xbmc/cores/DllLoader/exports/msvcrt.def b/xbmc/cores/DllLoader/exports/msvcrt.def
new file mode 100755
index 0000000..73a63b4
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/msvcrt.def
@@ -0,0 +1,123 @@
+LIBRARY msvcrt.dll
+
+VERSION 1.0
+
+EXPORTS
+ _close
+ _lseek
+ _read
+ _write
+ __dllonexit
+ __mb_cur_max
+ _assert
+ _errno
+ _ftime
+ _iob
+ _isctype
+ _lseeki64
+ _open
+ _snprintf
+ _stricmp
+ _strnicmp
+ _vsnprintf
+ abort
+ atof
+ atoi
+ cos
+ cosh
+ exp
+ fflush
+ floor
+ fprintf
+ free
+ frexp
+ fwrite
+ gmtime
+ ldexp
+ localtime
+ log
+ log10
+ malloc
+ memcpy
+ memmove
+ memset
+ mktime
+ perror
+ printf
+ putchar
+ puts
+ qsort
+ realloc
+ sin
+ sinh
+ sprintf
+ sqrt
+ sscanf
+ strchr
+ strcmp
+ strcpy
+ strlen
+ strncpy
+ strrchr
+ strtod
+ strtok
+ strtol
+ strtoul
+ tan
+ tanh
+ time
+ toupper
+ _memccpy
+ _fstat
+ _memccpy
+ _mkdir
+ _pclose
+ _popen
+ _sleep
+ _stat
+ _strdup
+ _swab
+ _findclose
+ _findfirst
+ _findnext
+ _fullpath
+ _pctype
+ calloc
+ ceil
+ ctime
+ exit
+ fclose
+ feof
+ fgets
+ fopen
+ fputc
+ fputs
+ fread
+ fseek
+ ftell
+ getc
+ getenv
+ putc
+ rand
+ remove
+ rewind
+ setlocale
+ signal
+ srand
+ strcat
+ strcoll
+ strerror
+ strncat
+ strncmp
+ strpbrk
+ strstr
+ tolower
+ strspn
+ strcspn
+ fsetpos
+ fgetpos
+ _fstati64
+ _stati64
+ _tell
+ _telli64
+ _setmode \ No newline at end of file
diff --git a/xbmc/cores/DllLoader/exports/pncrt.def b/xbmc/cores/DllLoader/exports/pncrt.def
new file mode 100755
index 0000000..ba34b22
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/pncrt.def
@@ -0,0 +1,114 @@
+LIBRARY msvcrt.dll
+
+VERSION 1.0
+
+EXPORTS
+ _close
+ _lseek
+ _read
+ _write
+ __dllonexit
+ __mb_cur_max
+ _assert
+ _errno
+ _ftime
+ _iob
+ _isctype
+ _lseeki64
+ _open
+ _snprintf
+ _stricmp
+ _strnicmp
+ _vsnprintf
+ abort
+ atof
+ atoi
+ cos
+ cosh
+ exp
+ fflush
+ floor
+ fprintf
+ free
+ frexp
+ fwrite
+ gmtime
+ ldexp
+ localtime
+ log
+ log10
+ malloc
+ memcpy
+ memmove
+ memset
+ mktime
+ perror
+ printf
+ putchar
+ puts
+ qsort
+ realloc
+ sin
+ sinh
+ sprintf
+ sqrt
+ sscanf
+ strchr
+ strcmp
+ strcpy
+ strlen
+ strncpy
+ strrchr
+ strtod
+ strtok
+ strtol
+ strtoul
+ tan
+ tanh
+ time
+ toupper
+ _memccpy
+ _fstat
+ _memccpy
+ _mkdir
+ _pclose
+ _popen
+ _sleep
+ _stat
+ _strdup
+ _swab
+ _findclose
+ _findfirst
+ _findnext
+ _fullpath
+ _pctype
+ calloc
+ ceil
+ ctime
+ exit
+ fclose
+ feof
+ fgets
+ fopen
+ fputc
+ fputs
+ fread
+ fseek
+ ftell
+ getc
+ getenv
+ putc
+ rand
+ remove
+ rewind
+ setlocale
+ signal
+ srand
+ strcat
+ strcoll
+ strerror
+ strncat
+ strncmp
+ strpbrk
+ strstr
+ tolower \ No newline at end of file
diff --git a/xbmc/cores/DllLoader/exports/util/CMakeLists.txt b/xbmc/cores/DllLoader/exports/util/CMakeLists.txt
new file mode 100644
index 0000000..3fa6f31
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/util/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES EmuFileWrapper.cpp)
+
+set(HEADERS EmuFileWrapper.h)
+
+core_add_library(exports_utils)
diff --git a/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.cpp b/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.cpp
new file mode 100644
index 0000000..40d6b61
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "EmuFileWrapper.h"
+
+#include "filesystem/File.h"
+
+#include <mutex>
+
+CEmuFileWrapper g_emuFileWrapper;
+
+namespace
+{
+
+constexpr bool isValidFilePtr(const FILE* f)
+{
+ return (f != nullptr);
+}
+
+}
+CEmuFileWrapper::CEmuFileWrapper()
+{
+ // since we always use dlls we might just initialize it directly
+ for (EmuFileObject& file : m_files)
+ {
+ memset(&file, 0, sizeof(EmuFileObject));
+ file.used = false;
+ file.fd = -1;
+ }
+}
+
+CEmuFileWrapper::~CEmuFileWrapper()
+{
+ CleanUp();
+}
+
+void CEmuFileWrapper::CleanUp()
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ for (EmuFileObject& file : m_files)
+ {
+ if (file.used)
+ {
+ file.file_xbmc->Close();
+ delete file.file_xbmc;
+
+ if (file.file_lock)
+ {
+ delete file.file_lock;
+ file.file_lock = nullptr;
+ }
+ file.used = false;
+ file.fd = -1;
+ }
+ }
+}
+
+EmuFileObject* CEmuFileWrapper::RegisterFileObject(XFILE::CFile* pFile)
+{
+ EmuFileObject* object = nullptr;
+
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+
+ for (int i = 0; i < MAX_EMULATED_FILES; i++)
+ {
+ if (!m_files[i].used)
+ {
+ // found a free location
+ object = &m_files[i];
+ object->used = true;
+ object->file_xbmc = pFile;
+ object->fd = (i + FILE_WRAPPER_OFFSET);
+ object->file_lock = new CCriticalSection();
+ break;
+ }
+ }
+
+ return object;
+}
+
+void CEmuFileWrapper::UnRegisterFileObjectByDescriptor(int fd)
+{
+ int i = fd - FILE_WRAPPER_OFFSET;
+ if (! (i >= 0 && i < MAX_EMULATED_FILES))
+ return;
+
+ if (!m_files[i].used)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+
+ // we assume the emulated function already deleted the CFile object
+ if (m_files[i].file_lock)
+ {
+ delete m_files[i].file_lock;
+ m_files[i].file_lock = nullptr;
+ }
+ m_files[i].used = false;
+ m_files[i].fd = -1;
+}
+
+void CEmuFileWrapper::UnRegisterFileObjectByStream(FILE* stream)
+{
+ if (isValidFilePtr(stream))
+ {
+ EmuFileObject* o = reinterpret_cast<EmuFileObject*>(stream);
+ return UnRegisterFileObjectByDescriptor(o->fd);
+ }
+}
+
+void CEmuFileWrapper::LockFileObjectByDescriptor(int fd)
+{
+ int i = fd - FILE_WRAPPER_OFFSET;
+ if (i >= 0 && i < MAX_EMULATED_FILES)
+ {
+ if (m_files[i].used)
+ {
+ m_files[i].file_lock->lock();
+ }
+ }
+}
+
+bool CEmuFileWrapper::TryLockFileObjectByDescriptor(int fd)
+{
+ int i = fd - FILE_WRAPPER_OFFSET;
+ if (i >= 0 && i < MAX_EMULATED_FILES)
+ {
+ if (m_files[i].used)
+ {
+ return m_files[i].file_lock->try_lock();
+ }
+ }
+ return false;
+}
+
+void CEmuFileWrapper::UnlockFileObjectByDescriptor(int fd)
+{
+ int i = fd - FILE_WRAPPER_OFFSET;
+ if (i >= 0 && i < MAX_EMULATED_FILES)
+ {
+ if (m_files[i].used)
+ {
+ m_files[i].file_lock->unlock();
+ }
+ }
+}
+
+EmuFileObject* CEmuFileWrapper::GetFileObjectByDescriptor(int fd)
+{
+ int i = fd - FILE_WRAPPER_OFFSET;
+ if (i >= 0 && i < MAX_EMULATED_FILES)
+ {
+ if (m_files[i].used)
+ {
+ return &m_files[i];
+ }
+ }
+ return nullptr;
+}
+
+EmuFileObject* CEmuFileWrapper::GetFileObjectByStream(FILE* stream)
+{
+ if (isValidFilePtr(stream))
+ {
+ EmuFileObject* o = reinterpret_cast<EmuFileObject*>(stream);
+ return GetFileObjectByDescriptor(o->fd);
+ }
+
+ return nullptr;
+}
+
+XFILE::CFile* CEmuFileWrapper::GetFileXbmcByDescriptor(int fd)
+{
+ auto object = GetFileObjectByDescriptor(fd);
+ if (object != nullptr && object->used)
+ {
+ return object->file_xbmc;
+ }
+ return nullptr;
+}
+
+XFILE::CFile* CEmuFileWrapper::GetFileXbmcByStream(FILE* stream)
+{
+ if (isValidFilePtr(stream))
+ {
+ EmuFileObject* object = reinterpret_cast<EmuFileObject*>(stream);
+ if (object != nullptr && object->used)
+ {
+ return object->file_xbmc;
+ }
+ }
+ return nullptr;
+}
+
+int CEmuFileWrapper::GetDescriptorByStream(FILE* stream)
+{
+ if (isValidFilePtr(stream))
+ {
+ EmuFileObject* obj = reinterpret_cast<EmuFileObject*>(stream);
+ int i = obj->fd - FILE_WRAPPER_OFFSET;
+ if (i >= 0 && i < MAX_EMULATED_FILES)
+ {
+ return i + FILE_WRAPPER_OFFSET;
+ }
+ }
+ return -1;
+}
+
+FILE* CEmuFileWrapper::GetStreamByDescriptor(int fd)
+{
+ auto object = GetFileObjectByDescriptor(fd);
+ if (object != nullptr && object->used)
+ {
+ return reinterpret_cast<FILE*>(object);
+ }
+ return nullptr;
+}
+
+bool CEmuFileWrapper::StreamIsEmulatedFile(FILE* stream)
+{
+ if (isValidFilePtr(stream))
+ {
+ EmuFileObject* obj = reinterpret_cast<EmuFileObject*>(stream);
+ return DescriptorIsEmulatedFile(obj->fd);
+ }
+ return false;
+}
diff --git a/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.h b/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.h
new file mode 100644
index 0000000..a8a70fa
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/util/EmuFileWrapper.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <stdio.h>
+
+#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) && !defined(TARGET_FREEBSD) && !defined(TARGET_ANDROID) && !defined(__UCLIBC__)
+#define _file _fileno
+#elif defined(__UCLIBC__)
+#define _file __filedes
+#endif
+
+#define MAX_EMULATED_FILES 50
+#define FILE_WRAPPER_OFFSET 0x00000200
+
+namespace XFILE
+{
+ class CFile;
+}
+
+typedef struct stEmuFileObject
+{
+ XFILE::CFile* file_xbmc;
+ CCriticalSection *file_lock;
+ int mode;
+ //Stick this last to avoid 3-7 bytes of padding
+ bool used;
+ int fd;
+} EmuFileObject;
+
+class CEmuFileWrapper
+{
+public:
+ CEmuFileWrapper();
+ ~CEmuFileWrapper();
+
+ /**
+ * Only to be called when shutting down xbmc
+ */
+ void CleanUp();
+
+ EmuFileObject* RegisterFileObject(XFILE::CFile* pFile);
+ void UnRegisterFileObjectByDescriptor(int fd);
+ void UnRegisterFileObjectByStream(FILE* stream);
+ void LockFileObjectByDescriptor(int fd);
+ bool TryLockFileObjectByDescriptor(int fd);
+ void UnlockFileObjectByDescriptor(int fd);
+ EmuFileObject* GetFileObjectByDescriptor(int fd);
+ EmuFileObject* GetFileObjectByStream(FILE* stream);
+ XFILE::CFile* GetFileXbmcByDescriptor(int fd);
+ XFILE::CFile* GetFileXbmcByStream(FILE* stream);
+ static int GetDescriptorByStream(FILE* stream);
+ FILE* GetStreamByDescriptor(int fd);
+ static constexpr bool DescriptorIsEmulatedFile(int fd)
+ {
+ return fd >= FILE_WRAPPER_OFFSET && fd < FILE_WRAPPER_OFFSET + MAX_EMULATED_FILES;
+ }
+ static bool StreamIsEmulatedFile(FILE* stream);
+private:
+ EmuFileObject m_files[MAX_EMULATED_FILES];
+ CCriticalSection m_criticalSection;
+};
+
+extern CEmuFileWrapper g_emuFileWrapper;
+
diff --git a/xbmc/cores/DllLoader/exports/winMM.def b/xbmc/cores/DllLoader/exports/winMM.def
new file mode 100755
index 0000000..c389b3d
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/winMM.def
@@ -0,0 +1,6 @@
+LIBRARY WINMM.DLL
+
+VERSION 1.0
+
+EXPORTS
+ timeGetTime \ No newline at end of file
diff --git a/xbmc/cores/DllLoader/exports/wrapper.c b/xbmc/cores/DllLoader/exports/wrapper.c
new file mode 100644
index 0000000..455ec0b
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/wrapper.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+//
+// To recreate wrapper.def:
+//
+// bash# (echo -n "-Wl"; grep __wrap wrapper.c | grep -v bash | sed "s/.*__wrap_//g" | sed "s/(.*//g" | awk '{printf(",-wrap,%s",$0);}') > wrapper.def
+//
+#include <sys/types.h>
+#include <sys/stat.h>
+#if !defined(TARGET_ANDROID)
+#include <sys/statvfs.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <dirent.h>
+#include <dlfcn.h>
+
+#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_ANDROID)
+typedef off_t __off_t;
+typedef int64_t off64_t;
+typedef off64_t __off64_t;
+typedef fpos_t fpos64_t;
+#define stat64 stat
+#endif
+
+#ifdef TARGET_POSIX
+#define _stat stat
+#endif
+
+struct mntent;
+
+void* dllmalloc(size_t );
+void* dllcalloc( size_t , size_t );
+void* dllrealloc(void*, size_t);
+void dllfree(void*);
+
+int dll_open(const char* szFileName, int iMode);
+int dll_write(int fd, const void* buffer, unsigned int uiSize);
+int dll_read(int fd, void* buffer, unsigned int uiSize);
+off_t dll_lseek(int fd, __off_t lPos, int iWhence);
+__off64_t dll_lseeki64(int fd, __off64_t lPos, int iWhence);
+int dll_close(int fd);
+
+FILE * dll_fopen (const char * filename, const char * mode);
+FILE* dll_freopen(const char *path, const char *mode, FILE *stream);
+FILE* dll_fdopen(int i, const char* file);
+int dll_fclose (FILE * stream);
+int dll_ferror (FILE * stream);
+int dll_feof (FILE * stream);
+int dll_fileno(FILE* stream);
+void dll_clearerr(FILE* stream);
+int dll_fread (void * buffer, size_t size, size_t count, FILE * stream);
+size_t dll_fwrite ( const void * buffer, size_t size, size_t count, FILE * stream );
+int dll_fflush (FILE * stream);
+int dll_fputc (int character, FILE * stream);
+int dll_fputs (const char * szLine , FILE* stream);
+int dll_putc(int c, FILE *stream);
+int dll_fseek ( FILE * stream , long offset , int origin );
+int dll_fseek64(FILE *stream, off64_t offset, int origin);
+long dll_ftell(FILE *stream);
+off64_t dll_ftell64(FILE *stream);
+void dll_rewind(FILE* stream);
+int dll_fgetpos(FILE* stream, fpos_t* pos);
+int dll_fgetpos64(FILE *stream, fpos64_t *pos);
+int dll_fsetpos(FILE* stream, const fpos_t* pos);
+int dll_fsetpos64(FILE* stream, const fpos64_t* pos);
+DIR* dll_opendir(const char* name);
+struct dirent* dll_readdir(DIR* dirp);
+int dll_closedir(DIR* dirp);
+void dll_rewinddir(DIR* dirp);
+int dll_fprintf(FILE* stream , const char * format, ...);
+int dllprintf(const char *format, ...);
+int dll_vfprintf(FILE *stream, const char *format, va_list va);
+int dll_fgetc (FILE* stream);
+char * dll_fgets (char* pszString, int num , FILE * stream);
+int dll_getc (FILE * stream);
+int dll_ungetc (int c, FILE * stream);
+int dll_ioctl(int d, unsigned long int request, va_list va);
+int dll_stat(const char *path, struct _stat *buffer);
+int dll_stat64(const char *path, struct stat64 *buffer);
+void dll_flockfile(FILE *file);
+int dll_ftrylockfile(FILE *file);
+void dll_funlockfile(FILE *file);
+int dll_fstat64(int fd, struct stat64 *buf);
+int dll_fstat(int fd, struct _stat *buf);
+FILE* dll_popen(const char *command, const char *mode);
+void* dll_dlopen(const char *filename, int flag);
+int dll_setvbuf(FILE *stream, char *buf, int type, size_t size);
+struct mntent *dll_getmntent(FILE *fp);
+struct mntent* dll_getmntent_r(FILE* fp, struct mntent* result, char* buffer, int bufsize);
+
+void *__wrap_dlopen(const char *filename, int flag)
+{
+ return dlopen(filename, flag);
+}
+
+FILE *__wrap_popen(const char *command, const char *mode)
+{
+ return dll_popen(command, mode);
+}
+
+void* __wrap_calloc( size_t num, size_t size )
+{
+ return dllcalloc(num, size);
+}
+
+void* __wrap_malloc(size_t size)
+{
+ return dllmalloc(size);
+}
+
+void* __wrap_realloc( void *memblock, size_t size )
+{
+ return dllrealloc(memblock, size);
+}
+
+void __wrap_free( void* pPtr )
+{
+ dllfree(pPtr);
+}
+
+int __wrap_open(const char *file, int oflag, ...)
+{
+ return dll_open(file, oflag);
+}
+
+int __wrap_open64(const char *file, int oflag, ...)
+{
+ return dll_open(file, oflag);
+}
+
+int __wrap_close(int fd)
+{
+ return dll_close(fd);
+}
+
+ssize_t __wrap_write(int fd, const void *buf, size_t count)
+{
+ return dll_write(fd, buf, count);
+}
+
+ssize_t __wrap_read(int fd, void *buf, size_t count)
+{
+ return dll_read(fd, buf, count);
+}
+
+__off_t __wrap_lseek(int filedes, __off_t offset, int whence)
+{
+ return dll_lseek(filedes, offset, whence);
+}
+
+__off64_t __wrap_lseek64(int filedes, __off64_t offset, int whence)
+{
+ __off64_t seekRes = dll_lseeki64(filedes, offset, whence);
+ return seekRes;
+}
+
+int __wrap_fclose(FILE *fp)
+{
+ return dll_fclose(fp);
+}
+
+int __wrap_ferror(FILE *stream)
+{
+ return dll_ferror(stream);
+}
+
+void __wrap_clearerr(FILE *stream)
+{
+ dll_clearerr(stream);
+}
+
+int __wrap_feof(FILE *stream)
+{
+ return dll_feof(stream);
+}
+
+int __wrap_fileno(FILE *stream)
+{
+ return dll_fileno(stream);
+}
+
+FILE *__wrap_fopen(const char *path, const char *mode)
+{
+ return dll_fopen(path, mode);
+}
+
+FILE *__wrap_fopen64(const char *path, const char *mode)
+{
+ return dll_fopen(path, mode);
+}
+
+FILE *__wrap_fdopen(int filedes, const char *mode)
+{
+ return dll_fdopen(filedes, mode);
+}
+
+FILE *__wrap_freopen(const char *path, const char *mode, FILE *stream)
+{
+ return dll_freopen(path, mode, stream);
+}
+
+size_t __wrap_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+ return dll_fread(ptr, size, nmemb, stream);
+}
+
+size_t __wrap_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+ return dll_fwrite(ptr, size, nmemb, stream);
+}
+
+int __wrap_fflush(FILE *stream)
+{
+ return dll_fflush(stream);
+}
+
+int __wrap_fputc(int c, FILE *stream)
+{
+ return dll_fputc(c, stream);
+}
+
+int __wrap_fputs(const char *s, FILE *stream)
+{
+ return dll_fputs(s, stream);
+}
+
+int __wrap__IO_putc(int c, FILE *stream)
+{
+ return dll_putc(c, stream);
+}
+
+int __wrap_fseek(FILE *stream, long offset, int whence)
+{
+ return dll_fseek(stream, offset, whence);
+}
+
+int __wrap_fseeko64(FILE *stream, off64_t offset, int whence)
+{
+ return dll_fseek64(stream, offset, whence);
+}
+
+long __wrap_ftell(FILE *stream)
+{
+ return dll_ftell(stream);
+}
+
+off64_t __wrap_ftello64(FILE *stream)
+{
+ return dll_ftell64(stream);
+}
+
+void __wrap_rewind(FILE *stream)
+{
+ dll_rewind(stream);
+}
+
+int __wrap_fgetpos(FILE *stream, fpos_t *pos)
+{
+ return dll_fgetpos(stream, pos);
+}
+
+int __wrap_fgetpos64(FILE *stream, fpos64_t *pos)
+{
+ return dll_fgetpos64(stream, pos);
+}
+
+int __wrap_fsetpos(FILE *stream, fpos_t *pos)
+{
+ return dll_fsetpos(stream, pos);
+}
+
+int __wrap_fsetpos64(FILE *stream, fpos64_t *pos)
+{
+ return dll_fsetpos64(stream, pos);
+}
+
+DIR * __wrap_opendir(const char *name)
+{
+ return dll_opendir(name);
+}
+
+struct dirent * __wrap_readdir(DIR* dirp)
+{
+ return dll_readdir(dirp);
+}
+
+struct dirent * __wrap_readdir64(DIR* dirp)
+{
+ return dll_readdir(dirp);
+}
+
+int __wrap_closedir(DIR* dirp)
+{
+ return dll_closedir(dirp);
+}
+
+void __wrap_rewinddir(DIR* dirp)
+{
+ dll_rewinddir(dirp);
+}
+
+int __wrap_fprintf(FILE *stream, const char *format, ...)
+{
+ int res;
+ va_list va;
+ va_start(va, format);
+ res = dll_vfprintf(stream, format, va);
+ va_end(va);
+ return res;
+}
+
+int __wrap_vfprintf(FILE *stream, const char *format, va_list ap)
+{
+ return dll_vfprintf(stream, format, ap);
+}
+
+int __wrap_printf(const char *format, ...)
+{
+ int res;
+ va_list va;
+ va_start(va, format);
+ res = dll_vfprintf(stdout, format, va);
+ va_end(va);
+ return res;
+}
+
+int __wrap_fgetc(FILE *stream)
+{
+ return dll_fgetc(stream);
+}
+
+char *__wrap_fgets(char *s, int size, FILE *stream)
+{
+ return dll_fgets(s, size, stream);
+}
+
+int __wrap__IO_getc(FILE *stream)
+{
+ return dll_getc(stream);
+}
+
+int __wrap__IO_getc_unlocked(FILE *stream)
+{
+ return dll_getc(stream);
+}
+
+int __wrap_getc_unlocked(FILE *stream)
+{
+ return dll_getc(stream);
+}
+
+int __wrap_ungetc(int c, FILE *stream)
+{
+ return dll_ungetc(c, stream);
+}
+
+int __wrap_getc(FILE *stream)
+{
+ return dll_getc(stream);
+}
+
+int __wrap_ioctl(int d, unsigned long int request, ...)
+{
+ int res;
+ va_list va;
+ va_start(va, request);
+ res = dll_ioctl(d, request, va);
+ va_end(va);
+ return res;
+}
+
+int __wrap__stat(const char *path, struct _stat *buffer)
+{
+ return dll_stat(path, buffer);
+}
+
+int __wrap_stat(const char *path, struct _stat *buffer)
+{
+ return dll_stat(path, buffer);
+}
+
+int __wrap_stat64(const char* path, struct stat64* buffer)
+{
+ return dll_stat64(path, buffer);
+}
+
+int __wrap___xstat(int __ver, const char *__filename, struct stat *__stat_buf)
+{
+ return dll_stat(__filename, __stat_buf);
+}
+
+int __wrap___xstat64(int __ver, const char *__filename, struct stat64 *__stat_buf)
+{
+ return dll_stat64(__filename, __stat_buf);
+}
+
+int __wrap___lxstat64(int __ver, const char *__filename, struct stat64 *__stat_buf)
+{
+ return dll_stat64(__filename, __stat_buf);
+}
+
+void __wrap_flockfile(FILE *file)
+{
+ dll_flockfile(file);
+}
+
+int __wrap_ftrylockfile(FILE *file)
+{
+ return dll_ftrylockfile(file);
+}
+
+void __wrap_funlockfile(FILE *file)
+{
+ dll_funlockfile(file);
+}
+
+int __wrap___fxstat64(int ver, int fd, struct stat64 *buf)
+{
+ return dll_fstat64(fd, buf);
+}
+
+int __wrap___fxstat(int ver, int fd, struct stat *buf)
+{
+ return dll_fstat(fd, buf);
+}
+
+int __wrap_fstat(int fd, struct _stat *buf)
+{
+ return dll_fstat(fd, buf);
+}
+
+int __wrap_fstat64(int fd, struct stat64* buf)
+{
+ return dll_fstat64(fd, buf);
+}
+
+int __wrap_setvbuf(FILE *stream, char *buf, int type, size_t size)
+{
+ return dll_setvbuf(stream, buf, type, size);
+}
+
+struct mntent *__wrap_getmntent(FILE *fp)
+{
+#ifdef TARGET_POSIX
+ return dll_getmntent(fp);
+#endif
+ return NULL;
+}
+
+struct mntent* __wrap_getmntent_r(FILE* fp, struct mntent* result, char* buffer, int bufsize)
+{
+#ifdef TARGET_POSIX
+ return dll_getmntent_r(fp, result, buffer, bufsize);
+#endif
+ return NULL;
+}
+
+// GCC 4.3 in Ubuntu 8.10 defines _FORTIFY_SOURCE=2 which means, that fread, read etc
+// are actually #defines which are inlined when compiled with -O. Those defines
+// actually call __*chk (for example, __fread_chk). We need to bypass this whole
+// thing to actually call our wrapped functions.
+#if _FORTIFY_SOURCE > 1
+
+size_t __wrap___fread_chk(void * ptr, size_t ptrlen, size_t size, size_t n, FILE * stream)
+{
+ return dll_fread(ptr, size, n, stream);
+}
+
+int __wrap___printf_chk(int flag, const char *format, ...)
+{
+ int res;
+ va_list va;
+ va_start(va, format);
+ res = dll_vfprintf(stdout, format, va);
+ va_end(va);
+ return res;
+}
+
+int __wrap___vfprintf_chk(FILE* stream, int flag, const char *format, va_list ap)
+{
+ return dll_vfprintf(stream, format, ap);
+}
+
+int __wrap___fprintf_chk(FILE * stream, int flag, const char *format, ...)
+{
+ int res;
+ va_list va;
+ va_start(va, format);
+ res = dll_vfprintf(stream, format, va);
+ va_end(va);
+ return res;
+}
+
+char *__wrap___fgets_chk(char *s, size_t size, int n, FILE *stream)
+{
+ return dll_fgets(s, n, stream);
+}
+
+size_t __wrap___read_chk(int fd, void *buf, size_t nbytes, size_t buflen)
+{
+ return dll_read(fd, buf, nbytes);
+}
+
+#endif
diff --git a/xbmc/cores/DllLoader/exports/wrapper_mach_alias b/xbmc/cores/DllLoader/exports/wrapper_mach_alias
new file mode 100644
index 0000000..88ff675
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/wrapper_mach_alias
@@ -0,0 +1,62 @@
+# List of wrapper aliases for Mach-O
+___wrap_clearerr _clearerr
+___wrap_close _close
+___wrap_fclose _fclose
+___wrap_fdopen _fdopen
+___wrap_feof _feof
+___wrap_ferror _ferror
+___wrap_fflush _fflush
+___wrap_fgetc _fgetc
+___wrap_fgetpos _fgetpos
+___wrap_fgets _fgets
+___wrap_fileno _fileno
+___wrap_flockfile _flockfile
+___wrap_fopen _fopen
+___wrap_fopen64 _fopen64
+___wrap_fprintf _fprintf
+___wrap_fputc _fputc
+___wrap_fputs _fputs
+___wrap_fread _fread
+___wrap_freopen _freopen
+___wrap_fseek _fseek
+___wrap_fsetpos _fsetpos
+___wrap_fstatvfs64 _fstatvfs64
+___wrap_ftell _ftell
+___wrap_ftrylockfile _ftrylockfile
+___wrap_funlockfile _funlockfile
+___wrap_fwrite _fwrite
+___wrap_ioctl _ioctl
+___wrap_lseek _lseek
+___wrap_lseek64 _lseek64
+___wrap_open _open
+___wrap_open64 _open64
+___wrap_popen _popen
+___wrap_printf _printf
+___wrap_read _read
+___wrap_opendir _opendir
+___wrap_readdir _readdir
+___wrap_closedir _closedir
+___wrap_rewinddir _rewinddir
+___wrap_rewind _rewind
+___wrap__stat _stat
+___wrap_fstat _fstat
+___wrap_ungetc _ungetc
+___wrap_vfprintf _vfprintf
+___wrap_write _write
+___wrap__IO_putc _putc
+___wrap__IO_getc _getc
+___wrap__IO_getc_unlocked _getc_unlocked
+___wrap_fwrite _fwrite$UNIX2003
+___wrap_close _close$UNIX2003
+#___wrap_fcntl _fcntl$UNIX2003
+___wrap_open _open$UNIX2003
+___wrap_fopen _fopen$UNIX2003
+___wrap_fputs _fputs$UNIX2003
+___wrap_read _read$UNIX2003
+___wrap_write _write$UNIX2003
+___wrap_fstat _fstat$INODE64
+___wrap__stat _stat$INODE64
+___wrap_opendir _opendir$INODE64$UNIX2003
+___wrap_closedir _closedir$UNIX2003
+___wrap_readdir _readdir$INODE64
+___wrap_opendir _opendir$INODE64
diff --git a/xbmc/cores/DllLoader/exports/ws2_32.def b/xbmc/cores/DllLoader/exports/ws2_32.def
new file mode 100755
index 0000000..ac7712e
--- /dev/null
+++ b/xbmc/cores/DllLoader/exports/ws2_32.def
@@ -0,0 +1,25 @@
+LIBRARY WS2_32.DLL
+
+VERSION 1.0
+
+EXPORTS
+ WSACleanup
+ WSAGetLastError
+ WSAStartup
+ bind
+ closesocket
+ connect
+ gethostbyname
+ getsockopt
+ htonl
+ htons
+ inet_addr
+ inet_ntoa
+ ioctlsocket
+ ntohl
+ recv
+ select
+ send
+ sendto
+ setsockopt
+ socket \ No newline at end of file
diff --git a/xbmc/cores/DllLoader/ldt_keeper.c b/xbmc/cores/DllLoader/ldt_keeper.c
new file mode 100644
index 0000000..325d50c
--- /dev/null
+++ b/xbmc/cores/DllLoader/ldt_keeper.c
@@ -0,0 +1,282 @@
+/**
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file MUST be in main library because LDT must
+ * be modified before program creates first thread
+ * - avifile includes this file from C++ code
+ * and initializes it at the start of player!
+ * it might sound like a hack and it really is - but
+ * as aviplay is decoding video with more than just one
+ * thread currently it's necessary to do it this way
+ * this might change in the future
+ */
+
+/* applied some modification to make make our xine friend more happy */
+
+/*
+ * Modified for use with MPlayer, detailed changelog at
+ * http://svn.mplayerhq.hu/mplayer/trunk/
+ * $Id: ldt_keeper.c 22733 2007-03-18 22:18:11Z nicodvb $
+ */
+
+// clang-format off
+#if !defined(__aarch64__) && \
+ !defined(__alpha__) &&\
+ !defined(__arc__) &&\
+ !defined(__arm__) && \
+ !defined(__loongarch__) && \
+ !defined(__mips__) && \
+ !defined(__or1k__) && \
+ !defined(__powerpc__) && \
+ !defined(__ppc__) && \
+ !defined(__riscv) && \
+ !defined(__SH4__) && \
+ !defined(__s390x__) && \
+ !defined(__sparc__) && \
+ !defined(__xtensa__)
+// clang-format on
+
+#include "ldt_keeper.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mmap_anon.h"
+#if defined( __linux__ ) && !defined(__powerpc__)
+#include <asm/unistd.h>
+#include <asm/ldt.h>
+/* prototype it here, so we won't depend on kernel headers */
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if defined(TARGET_ANDROID) && (defined(__i386__) || defined(__x86_64__)) && !defined(modify_ldt)
+#define modify_ldt(a,b,c) syscall( __NR_modify_ldt, a, b, c);
+#else
+int modify_ldt(int func, void *ptr, unsigned long bytecount);
+#endif
+#ifdef __cplusplus
+}
+#endif
+#else
+#if defined(__NetBSD__) || defined(TARGET_FREEBSD) || defined(__OpenBSD__) || defined(__DragonFly__)
+#include <machine/segments.h>
+#include <machine/sysarch.h>
+#endif
+#if defined(TARGET_DARWIN)
+#include <i386/user_ldt.h>
+#endif
+
+#ifdef __svr4__
+#include <sys/segment.h>
+#include <sys/sysi86.h>
+
+/* solaris x86: add missing prototype for sysi86(), but only when sysi86(int, void*) is known to be valid */
+#ifdef HAVE_SYSI86_iv
+#ifdef __cplusplus
+extern "C" {
+#endif
+int sysi86(int, void*);
+#ifdef __cplusplus
+}
+#endif
+#endif
+
+#ifndef NUMSYSLDTS /* SunOS 2.5.1 does not define NUMSYSLDTS */
+#define NUMSYSLDTS 6 /* Let's hope the SunOS 5.8 value is OK */
+#endif
+
+#define TEB_SEL_IDX NUMSYSLDTS
+#endif
+
+#define LDT_ENTRIES 8192
+#define LDT_ENTRY_SIZE 8
+#pragma pack(4)
+struct user_desc {
+ unsigned int entry_number;
+ unsigned long base_addr;
+ unsigned int limit;
+ unsigned int seg_32bit:1;
+ unsigned int contents:2;
+ unsigned int read_exec_only:1;
+ unsigned int limit_in_pages:1;
+ unsigned int seg_not_present:1;
+};
+
+#define MODIFY_LDT_CONTENTS_DATA 0
+#define MODIFY_LDT_CONTENTS_STACK 1
+#define MODIFY_LDT_CONTENTS_CODE 2
+#endif
+
+
+/* user level (privilege level: 3) ldt (1<<2) segment selector */
+#define LDT_SEL(idx) ((idx) << 3 | 1 << 2 | 3)
+
+/* i got this value from wine sources, it's the first free LDT entry */
+#if (defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)) && defined(LDT_AUTO_ALLOC)
+#define TEB_SEL_IDX LDT_AUTO_ALLOC
+#define USE_LDT_AA
+#endif
+
+#ifndef TEB_SEL_IDX
+#define TEB_SEL_IDX 17
+#endif
+
+static unsigned int fs_ldt = TEB_SEL_IDX;
+
+
+/**
+ * here is a small logical problem with Restore for multithreaded programs -
+ * in C++ we use static class for this...
+ */
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void Setup_FS_Segment(void)
+{
+ unsigned int ldt_desc = LDT_SEL(fs_ldt);
+
+ __asm__ __volatile__(
+ "movl %0,%%eax; movw %%ax, %%fs" : : "r" (ldt_desc)
+ :"eax"
+ );
+}
+
+#if defined(__NetBSD__) || defined(TARGET_FREEBSD) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(TARGET_DARWIN)
+static void LDT_EntryToBytes( unsigned long *buffer, const struct user_desc *content )
+{
+ *buffer++ = ((content->base_addr & 0x0000ffff) << 16) |
+ (content->limit & 0x0ffff);
+ *buffer = (content->base_addr & 0xff000000) |
+ ((content->base_addr & 0x00ff0000)>>16) |
+ (content->limit & 0xf0000) |
+ (content->contents << 10) |
+ ((content->read_exec_only == 0) << 9) |
+ ((content->seg_32bit != 0) << 22) |
+ ((content->limit_in_pages != 0) << 23) |
+ 0xf000;
+}
+#endif
+
+void* fs_seg=0;
+
+ldt_fs_t* Setup_LDT_Keeper(void)
+{
+ struct user_desc array;
+ int ret;
+ int sret;
+ ldt_fs_t* ldt_fs = (ldt_fs_t*) malloc(sizeof(ldt_fs_t));
+
+ if (!ldt_fs)
+ return NULL;
+
+#if defined(TARGET_DARWIN)
+ if (getenv("DYLD_BIND_AT_LAUNCH") == NULL)
+ printf("DYLD_BIND_AT_LAUNCH");
+#endif // TARGET_DARWIN
+
+ sret = sysconf(_SC_PAGE_SIZE);
+ if (sret == -1)
+ {
+ perror("ERROR: Couldn't allocate memory for fs segment");
+ free(ldt_fs);
+ return NULL;
+ }
+
+ fs_seg = ldt_fs->fs_seg = mmap_anon(NULL, sret, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+ if (ldt_fs->fs_seg == (void*)-1)
+ {
+ perror("ERROR: Couldn't allocate memory for fs segment");
+ free(ldt_fs);
+ return NULL;
+ }
+ *(void**)((char*)ldt_fs->fs_seg + 0x18) = ldt_fs->fs_seg;
+ memset(&array, 0, sizeof(array));
+ array.base_addr = (long)ldt_fs->fs_seg;
+ array.entry_number = TEB_SEL_IDX;
+ array.limit = array.base_addr+sysconf(_SC_PAGE_SIZE) - 1;
+ array.seg_32bit = 1;
+ array.read_exec_only = 0;
+ array.seg_not_present = 0;
+ array.contents=MODIFY_LDT_CONTENTS_DATA;
+ array.limit_in_pages = 0;
+#ifdef __linux__
+ /* ret=LDT_Modify(0x1, &array, sizeof(struct user_desc)); */
+ ret = modify_ldt(0x1, &array, sizeof(struct user_desc));
+ if (ret < 0)
+ {
+ perror("install_fs");
+ printf("Couldn't install fs segment, expect segfault\n");
+ }
+#endif /*linux*/
+
+#if defined(__NetBSD__) || defined(TARGET_FREEBSD) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(TARGET_DARWIN)
+ {
+ unsigned long d[2];
+
+ LDT_EntryToBytes( d, &array );
+#ifdef USE_LDT_AA
+ ret = i386_set_ldt(LDT_AUTO_ALLOC, (const union ldt_entry *)d, 1);
+ array.entry_number = ret;
+ fs_ldt = ret;
+#else
+ ret = i386_set_ldt(array.entry_number, (const union ldt_entry *)d, 1);
+#endif
+ if (ret < 0)
+ {
+ perror("install_fs");
+ printf("Couldn't install fs segment, expect segfault\n");
+ printf("Did you reconfigure the kernel with \"options USER_LDT\"?\n");
+#ifdef __OpenBSD__
+ printf("On newer OpenBSD systems did you set machdep.userldt to 1?\n");
+#endif
+ }
+ }
+#endif // __NetBSD__ || TARGET_FREEBSD || __OpenBSD__ || __DragonFly__ || TARGET_DARWIN
+
+#if defined(__svr4__)
+ {
+ struct ssd ssd;
+ ssd.sel = LDT_SEL(TEB_SEL_IDX);
+ ssd.bo = array.base_addr;
+ ssd.ls = array.limit - array.base_addr;
+ ssd.acc1 = ((array.read_exec_only == 0) << 1) |
+ (array.contents << 2) |
+ 0xf0; /* P(resent) | DPL3 | S */
+ ssd.acc2 = 0x4; /* byte limit, 32-bit segment */
+ if (sysi86(SI86DSCR, &ssd) < 0)
+ {
+ perror("sysi86(SI86DSCR)");
+ printf("Couldn't install fs segment, expect segfault\n");
+ }
+ }
+#endif
+
+ Setup_FS_Segment();
+
+ ldt_fs->prev_struct = malloc(8);
+ array.base_addr = (uintptr_t)(ldt_fs->prev_struct);
+
+ return ldt_fs;
+}
+
+void Restore_LDT_Keeper(ldt_fs_t* ldt_fs)
+{
+ if (ldt_fs == NULL || ldt_fs->fs_seg == 0)
+ return;
+ if (ldt_fs->prev_struct)
+ free(ldt_fs->prev_struct);
+
+ int sret = sysconf(_SC_PAGE_SIZE);
+ if (sret != -1)
+ munmap((char*)ldt_fs->fs_seg, sret);
+ ldt_fs->fs_seg = 0;
+ free(ldt_fs);
+}
+
+#endif
diff --git a/xbmc/cores/DllLoader/ldt_keeper.h b/xbmc/cores/DllLoader/ldt_keeper.h
new file mode 100644
index 0000000..f6a262f
--- /dev/null
+++ b/xbmc/cores/DllLoader/ldt_keeper.h
@@ -0,0 +1,38 @@
+/**
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file MUST be in main library because LDT must
+ * be modified before program creates first thread
+ * - avifile includes this file from C++ code
+ * and initializes it at the start of player!
+ * it might sound like a hack and it really is - but
+ * as aviplay is decoding video with more than just one
+ * thread currently it's necessary to do it this way
+ * this might change in the future
+ */
+
+/* applied some modification to make make our xine friend more happy */
+
+/*
+ * Modified for use with MPlayer, detailed changelog at
+ * http://svn.mplayerhq.hu/mplayer/trunk/
+ * $Id: ldt_keeper.c 22733 2007-03-18 22:18:11Z nicodvb $
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct {
+ void* fs_seg;
+ char* prev_struct;
+ int fd;
+} ldt_fs_t;
+
+void Setup_FS_Segment(void);
+ldt_fs_t* Setup_LDT_Keeper(void);
+void Restore_LDT_Keeper(ldt_fs_t* ldt_fs);
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/xbmc/cores/DllLoader/mmap_anon.c b/xbmc/cores/DllLoader/mmap_anon.c
new file mode 100644
index 0000000..5bb13cf
--- /dev/null
+++ b/xbmc/cores/DllLoader/mmap_anon.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+/**
+ * \file mmap_anon.c
+ * \brief Provide a compatible anonymous space mapping function
+ */
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+/*
+ * mmap() anonymous space, depending on the system's mmap() style. On systems
+ * that use the /dev/zero mapping idiom, zerofd will be set to the file descriptor
+ * of the opened /dev/zero.
+ */
+
+ /**
+ * \brief mmap() anonymous space, depending on the system's mmap() style. On systems
+ * that use the /dev/zero mapping idiom, zerofd will be set to the file descriptor
+ * of the opened /dev/zero.
+ *
+ * \param addr address to map at.
+ * \param len number of bytes from addr to be mapped.
+ * \param prot protections (region accessibility).
+ * \param flags specifies the type of the mapped object.
+ * \param offset start mapping at byte offset.
+ * \param zerofd
+ * \return a pointer to the mapped region upon successful completion, -1 otherwise.
+ */
+void *mmap_anon(void *addr, size_t len, int prot, int flags, off_t offset)
+{
+ void *result;
+
+#ifndef MAP_ANONYMOUS
+ int fd;
+#endif
+
+ /* From loader/ext.c:
+ * "Linux EINVAL's on us if we don't pass MAP_PRIVATE to an anon mmap"
+ * Therefore we preserve the same behavior on all platforms, ie. no
+ * shared mappings of anon space (if the concepts are supported). */
+#if defined(MAP_SHARED) && defined(MAP_PRIVATE)
+ flags = (flags & ~MAP_SHARED) | MAP_PRIVATE;
+#endif /* defined(MAP_SHARED) && defined(MAP_PRIVATE) */
+
+#ifdef MAP_ANONYMOUS
+ /* BSD-style anonymous mapping */
+ result = mmap(addr, len, prot, flags | MAP_ANONYMOUS, -1, offset);
+#else
+ /* SysV-style anonymous mapping */
+ fd = open("/dev/zero", O_RDWR);
+ if(fd < 0){
+ perror( "Cannot open /dev/zero for READ+WRITE. Check permissions! error: ");
+ return NULL;
+ }
+
+ result = mmap(addr, len, prot, flags, fd, offset);
+ close(fd);
+#endif /* MAP_ANONYMOUS */
+
+ return result;
+}
diff --git a/xbmc/cores/DllLoader/mmap_anon.h b/xbmc/cores/DllLoader/mmap_anon.h
new file mode 100644
index 0000000..291ccf8
--- /dev/null
+++ b/xbmc/cores/DllLoader/mmap_anon.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+void *mmap_anon(void *, size_t, int, int, off_t);
+
diff --git a/xbmc/cores/EdlEdit.h b/xbmc/cores/EdlEdit.h
new file mode 100644
index 0000000..a06ed15
--- /dev/null
+++ b/xbmc/cores/EdlEdit.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace EDL
+{
+
+constexpr int EDL_ACTION_NONE = -1;
+
+enum class Action
+{
+ CUT = 0,
+ MUTE = 1,
+ SCENE = 2,
+ COMM_BREAK = 3
+};
+
+struct Edit
+{
+ int start = 0; // ms
+ int end = 0; // ms
+ Action action = Action::CUT;
+};
+
+} // namespace EDL
diff --git a/xbmc/cores/ExternalPlayer/CMakeLists.txt b/xbmc/cores/ExternalPlayer/CMakeLists.txt
new file mode 100644
index 0000000..69c71d9
--- /dev/null
+++ b/xbmc/cores/ExternalPlayer/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ExternalPlayer.cpp)
+
+set(HEADERS ExternalPlayer.h)
+
+core_add_library(externalplayer)
diff --git a/xbmc/cores/ExternalPlayer/ExternalPlayer.cpp b/xbmc/cores/ExternalPlayer/ExternalPlayer.cpp
new file mode 100644
index 0000000..b027e21
--- /dev/null
+++ b/xbmc/cores/ExternalPlayer/ExternalPlayer.cpp
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ExternalPlayer.h"
+
+#include "CompileInfo.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/DataCacheCore.h"
+#include "dialogs/GUIDialogOK.h"
+#include "filesystem/MusicDatabaseFile.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "threads/SystemClock.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+#include "video/Bookmark.h"
+#include "windowing/WinSystem.h"
+#if defined(TARGET_WINDOWS)
+ #include "utils/CharsetConverter.h"
+ #include <Windows.h>
+#endif
+#if defined(TARGET_ANDROID)
+ #include "platform/android/activity/XBMCApp.h"
+#endif
+
+// If the process ends in less than this time (ms), we assume it's a launcher
+// and wait for manual intervention before continuing
+#define LAUNCHER_PROCESS_TIME 2000
+// Time (ms) we give a process we sent a WM_QUIT to close before terminating
+#define PROCESS_GRACE_TIME 3000
+// Default time after which the item's playcount is incremented
+#define DEFAULT_PLAYCOUNT_MIN_TIME 10
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+extern HWND g_hWnd;
+#endif
+
+CExternalPlayer::CExternalPlayer(IPlayerCallback& callback)
+ : IPlayer(callback),
+ CThread("ExternalPlayer")
+{
+ m_bAbortRequest = false;
+ m_bIsPlaying = false;
+ m_playbackStartTime = {};
+ m_speed = 1;
+ m_time = 0;
+
+ m_hideconsole = false;
+ m_warpcursor = WARP_NONE;
+ m_hidexbmc = false;
+ m_islauncher = false;
+ m_playCountMinTime = DEFAULT_PLAYCOUNT_MIN_TIME;
+ m_playOneStackItem = false;
+
+ m_dialog = NULL;
+#if defined(TARGET_WINDOWS_DESKTOP)
+ m_xPos = 0;
+ m_yPos = 0;
+
+ memset(&m_processInfo, 0, sizeof(m_processInfo));
+#endif
+}
+
+CExternalPlayer::~CExternalPlayer()
+{
+ CloseFile();
+}
+
+bool CExternalPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
+{
+ try
+ {
+ m_file = file;
+ m_bIsPlaying = true;
+ m_time = 0;
+ m_playbackStartTime = std::chrono::steady_clock::now();
+ m_launchFilename = file.GetDynPath();
+ CLog::Log(LOGINFO, "{}: {}", __FUNCTION__, m_launchFilename);
+ Create();
+
+ return true;
+ }
+ catch(...)
+ {
+ m_bIsPlaying = false;
+ CLog::Log(LOGERROR, "{} - Exception thrown", __FUNCTION__);
+ return false;
+ }
+}
+
+bool CExternalPlayer::CloseFile(bool reopen)
+{
+ m_bAbortRequest = true;
+
+ if (m_dialog && m_dialog->IsActive()) m_dialog->Close();
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+ if (m_bIsPlaying && m_processInfo.hProcess)
+ {
+ TerminateProcess(m_processInfo.hProcess, 1);
+ }
+#endif
+
+ return true;
+}
+
+bool CExternalPlayer::IsPlaying() const
+{
+ return m_bIsPlaying;
+}
+
+void CExternalPlayer::Process()
+{
+ std::string mainFile = m_launchFilename;
+ std::string archiveContent;
+
+ if (m_args.find("{0}") == std::string::npos)
+ {
+ // Unwind archive names
+ CURL url(m_launchFilename);
+ if (url.IsProtocol("zip") || url.IsProtocol("rar") /* || url.IsProtocol("iso9660") ??*/ || url.IsProtocol("udf"))
+ {
+ mainFile = url.GetHostName();
+ archiveContent = url.GetFileName();
+ }
+ if (url.IsProtocol("musicdb"))
+ mainFile = CMusicDatabaseFile::TranslateUrl(url);
+ if (url.IsProtocol("bluray"))
+ {
+ CURL base(url.GetHostName());
+ if (base.IsProtocol("udf"))
+ {
+ mainFile = base.GetHostName(); /* image file */
+ archiveContent = base.GetFileName();
+ }
+ else
+ mainFile = URIUtils::AddFileToFolder(base.Get(), url.GetFileName());
+ }
+ }
+
+ if (!m_filenameReplacers.empty())
+ {
+ for (unsigned int i = 0; i < m_filenameReplacers.size(); i++)
+ {
+ std::vector<std::string> vecSplit = StringUtils::Split(m_filenameReplacers[i], " , ");
+
+ // something is wrong, go to next substitution
+ if (vecSplit.size() != 4)
+ continue;
+
+ std::string strMatch = vecSplit[0];
+ StringUtils::Replace(strMatch, ",,",",");
+ bool bCaseless = vecSplit[3].find('i') != std::string::npos;
+ CRegExp regExp(bCaseless, CRegExp::autoUtf8);
+
+ if (!regExp.RegComp(strMatch.c_str()))
+ { // invalid regexp - complain in logs
+ CLog::Log(LOGERROR, "{}: Invalid RegExp:'{}'", __FUNCTION__, strMatch);
+ continue;
+ }
+
+ if (regExp.RegFind(mainFile) > -1)
+ {
+ std::string strPat = vecSplit[1];
+ StringUtils::Replace(strPat, ",,",",");
+
+ if (!regExp.RegComp(strPat.c_str()))
+ { // invalid regexp - complain in logs
+ CLog::Log(LOGERROR, "{}: Invalid RegExp:'{}'", __FUNCTION__, strPat);
+ continue;
+ }
+
+ std::string strRep = vecSplit[2];
+ StringUtils::Replace(strRep, ",,",",");
+ bool bGlobal = vecSplit[3].find('g') != std::string::npos;
+ bool bStop = vecSplit[3].find('s') != std::string::npos;
+ int iStart = 0;
+ while ((iStart = regExp.RegFind(mainFile, iStart)) > -1)
+ {
+ int iLength = regExp.GetFindLen();
+ mainFile = mainFile.substr(0, iStart) + regExp.GetReplaceString(strRep) + mainFile.substr(iStart + iLength);
+ if (!bGlobal)
+ break;
+ }
+ CLog::Log(LOGINFO, "{}: File matched:'{}' (RE='{}',Rep='{}') new filename:'{}'.",
+ __FUNCTION__, strMatch, strPat, strRep, mainFile);
+ if (bStop) break;
+ }
+ }
+ }
+
+ CLog::Log(LOGINFO, "{}: Player : {}", __FUNCTION__, m_filename);
+ CLog::Log(LOGINFO, "{}: File : {}", __FUNCTION__, mainFile);
+ CLog::Log(LOGINFO, "{}: Content: {}", __FUNCTION__, archiveContent);
+ CLog::Log(LOGINFO, "{}: Args : {}", __FUNCTION__, m_args);
+ CLog::Log(LOGINFO, "{}: Start", __FUNCTION__);
+
+ // make sure we surround the arguments with quotes where necessary
+ std::string strFName;
+ std::string strFArgs;
+#if defined(TARGET_WINDOWS_DESKTOP)
+ // W32 batch-file handline
+ if (StringUtils::EndsWith(m_filename, ".bat") || StringUtils::EndsWith(m_filename, ".cmd"))
+ {
+ // MSDN says you just need to do this, but cmd's handing of spaces and
+ // quotes is soo broken it seems to work much better if you just omit
+ // lpApplicationName and enclose the module in lpCommandLine in quotes
+ //strFName = "cmd.exe";
+ //strFArgs = "/c ";
+ }
+ else
+#endif
+ strFName = m_filename;
+
+ strFArgs.append("\"");
+ strFArgs.append(m_filename);
+ strFArgs.append("\" ");
+ strFArgs.append(m_args);
+
+ int nReplaced = StringUtils::Replace(strFArgs, "{0}", mainFile);
+
+ if (!nReplaced)
+ nReplaced = StringUtils::Replace(strFArgs, "{1}", mainFile) + StringUtils::Replace(strFArgs, "{2}", archiveContent);
+
+ if (!nReplaced)
+ {
+ strFArgs.append(" \"");
+ strFArgs.append(mainFile);
+ strFArgs.append("\"");
+ }
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+ if (m_warpcursor)
+ {
+ GetCursorPos(&m_ptCursorpos);
+ int x = 0;
+ int y = 0;
+ switch (m_warpcursor)
+ {
+ case WARP_BOTTOM_RIGHT:
+ x = GetSystemMetrics(SM_CXSCREEN);
+ case WARP_BOTTOM_LEFT:
+ y = GetSystemMetrics(SM_CYSCREEN);
+ break;
+ case WARP_TOP_RIGHT:
+ x = GetSystemMetrics(SM_CXSCREEN);
+ break;
+ case WARP_CENTER:
+ x = GetSystemMetrics(SM_CXSCREEN) / 2;
+ y = GetSystemMetrics(SM_CYSCREEN) / 2;
+ break;
+ }
+ CLog::Log(LOGINFO, "{}: Warping cursor to ({},{})", __FUNCTION__, x, y);
+ SetCursorPos(x,y);
+ }
+
+ LONG currentStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
+#endif
+
+ if (m_hidexbmc && !m_islauncher)
+ {
+ CLog::Log(LOGINFO, "{}: Hiding {} window", __FUNCTION__, CCompileInfo::GetAppName());
+ CServiceBroker::GetWinSystem()->Hide();
+ }
+#if defined(TARGET_WINDOWS_DESKTOP)
+ else if (currentStyle & WS_EX_TOPMOST)
+ {
+ CLog::Log(LOGINFO, "{}: Lowering {} window", __FUNCTION__, CCompileInfo::GetAppName());
+ SetWindowPos(g_hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_ASYNCWINDOWPOS);
+ }
+
+ CLog::Log(LOGDEBUG, "{}: Unlocking foreground window", __FUNCTION__);
+ LockSetForegroundWindow(LSFW_UNLOCK);
+#endif
+
+ m_playbackStartTime = std::chrono::steady_clock::now();
+
+ /* Suspend AE temporarily so exclusive or hog-mode sinks */
+ /* don't block external player's access to audio device */
+ CServiceBroker::GetActiveAE()->Suspend();
+ // wait for AE has completed suspended
+ XbmcThreads::EndTime<> timer(2000ms);
+ while (!timer.IsTimePast() && !CServiceBroker::GetActiveAE()->IsSuspended())
+ {
+ CThread::Sleep(50ms);
+ }
+ if (timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "{}: AudioEngine did not suspend before launching external player",
+ __FUNCTION__);
+ }
+
+ m_callback.OnPlayBackStarted(m_file);
+ m_callback.OnAVStarted(m_file);
+
+ bool ret = true;
+#if defined(TARGET_WINDOWS_DESKTOP)
+ ret = ExecuteAppW32(strFName.c_str(),strFArgs.c_str());
+#elif defined(TARGET_ANDROID)
+ ret = ExecuteAppAndroid(m_filename.c_str(), mainFile.c_str());
+#elif defined(TARGET_POSIX) && !defined(TARGET_DARWIN_EMBEDDED)
+ ret = ExecuteAppLinux(strFArgs.c_str());
+#endif
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_playbackStartTime);
+
+ if (ret && (m_islauncher || duration.count() < LAUNCHER_PROCESS_TIME))
+ {
+ if (m_hidexbmc)
+ {
+ CLog::Log(LOGINFO, "{}: {} cannot stay hidden for a launcher process", __FUNCTION__,
+ CCompileInfo::GetAppName());
+ CServiceBroker::GetWinSystem()->Show(false);
+ }
+
+ {
+ m_dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogOK>(WINDOW_DIALOG_OK);
+ m_dialog->SetHeading(CVariant{23100});
+ m_dialog->SetLine(1, CVariant{23104});
+ m_dialog->SetLine(2, CVariant{23105});
+ m_dialog->SetLine(3, CVariant{23106});
+ }
+
+ if (!m_bAbortRequest)
+ m_dialog->Open();
+ }
+
+ m_bIsPlaying = false;
+ CLog::Log(LOGINFO, "{}: Stop", __FUNCTION__);
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+ CServiceBroker::GetWinSystem()->Restore();
+
+ if (currentStyle & WS_EX_TOPMOST)
+ {
+ CLog::Log(LOGINFO, "{}: Showing {} window TOPMOST", __FUNCTION__, CCompileInfo::GetAppName());
+ SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS);
+ SetForegroundWindow(g_hWnd);
+ }
+ else
+#endif
+ {
+ CLog::Log(LOGINFO, "{}: Showing {} window", __FUNCTION__, CCompileInfo::GetAppName());
+ CServiceBroker::GetWinSystem()->Show();
+ }
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+ if (m_warpcursor)
+ {
+ m_xPos = 0;
+ m_yPos = 0;
+ if (&m_ptCursorpos != 0)
+ {
+ m_xPos = (m_ptCursorpos.x);
+ m_yPos = (m_ptCursorpos.y);
+ }
+ CLog::Log(LOGINFO, "{}: Restoring cursor to ({},{})", __FUNCTION__, m_xPos, m_yPos);
+ SetCursorPos(m_xPos,m_yPos);
+ }
+#endif
+
+ CBookmark bookmark;
+ bookmark.totalTimeInSeconds = 1;
+ bookmark.timeInSeconds = (duration.count() / 1000 >= m_playCountMinTime) ? 1 : 0;
+ bookmark.player = m_name;
+ m_callback.OnPlayerCloseFile(m_file, bookmark);
+
+ /* Resume AE processing of XBMC native audio */
+ if (!CServiceBroker::GetActiveAE()->Resume())
+ {
+ CLog::Log(LOGFATAL, "{}: Failed to restart AudioEngine after return from external player",
+ __FUNCTION__);
+ }
+
+ // We don't want to come back to an active screensaver
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->ResetScreenSaver();
+ appPower->WakeUpScreenSaverAndDPMS();
+
+ if (!ret || (m_playOneStackItem && g_application.CurrentFileItem().IsStack()))
+ m_callback.OnPlayBackStopped();
+ else
+ m_callback.OnPlayBackEnded();
+}
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+bool CExternalPlayer::ExecuteAppW32(const char* strPath, const char* strSwitches)
+{
+ CLog::Log(LOGINFO, "{}: {} {}", __FUNCTION__, strPath, strSwitches);
+
+ STARTUPINFOW si = {};
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = m_hideconsole ? SW_HIDE : SW_SHOW;
+
+ std::wstring WstrPath, WstrSwitches;
+ g_charsetConverter.utf8ToW(strPath, WstrPath, false);
+ g_charsetConverter.utf8ToW(strSwitches, WstrSwitches, false);
+
+ if (m_bAbortRequest) return false;
+
+ BOOL ret = CreateProcessW(WstrPath.empty() ? NULL : WstrPath.c_str(),
+ (LPWSTR) WstrSwitches.c_str(), NULL, NULL, FALSE, NULL,
+ NULL, NULL, &si, &m_processInfo);
+
+ if (ret == FALSE)
+ {
+ DWORD lastError = GetLastError();
+ CLog::Log(LOGINFO, "{} - Failure: {}", __FUNCTION__, lastError);
+ }
+ else
+ {
+ int res = WaitForSingleObject(m_processInfo.hProcess, INFINITE);
+
+ switch (res)
+ {
+ case WAIT_OBJECT_0:
+ CLog::Log(LOGINFO, "{}: WAIT_OBJECT_0", __FUNCTION__);
+ break;
+ case WAIT_ABANDONED:
+ CLog::Log(LOGINFO, "{}: WAIT_ABANDONED", __FUNCTION__);
+ break;
+ case WAIT_TIMEOUT:
+ CLog::Log(LOGINFO, "{}: WAIT_TIMEOUT", __FUNCTION__);
+ break;
+ case WAIT_FAILED:
+ CLog::Log(LOGINFO, "{}: WAIT_FAILED ({})", __FUNCTION__, GetLastError());
+ ret = FALSE;
+ break;
+ }
+
+ CloseHandle(m_processInfo.hThread);
+ m_processInfo.hThread = 0;
+ CloseHandle(m_processInfo.hProcess);
+ m_processInfo.hProcess = 0;
+ }
+ return (ret == TRUE);
+}
+#endif
+
+#if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN_EMBEDDED) && defined(TARGET_POSIX)
+bool CExternalPlayer::ExecuteAppLinux(const char* strSwitches)
+{
+ CLog::Log(LOGINFO, "{}: {}", __FUNCTION__, strSwitches);
+
+ int ret = system(strSwitches);
+ if (ret != 0)
+ {
+ CLog::Log(LOGINFO, "{}: Failure: {}", __FUNCTION__, ret);
+ }
+
+ return (ret == 0);
+}
+#endif
+
+#if defined(TARGET_ANDROID)
+bool CExternalPlayer::ExecuteAppAndroid(const char* strSwitches,const char* strPath)
+{
+ CLog::Log(LOGINFO, "{}: {}", __FUNCTION__, strSwitches);
+
+ bool ret = CXBMCApp::StartActivity(strSwitches, "android.intent.action.VIEW", "video/*", strPath);
+
+ if (!ret)
+ {
+ CLog::Log(LOGINFO, "{}: Failure", __FUNCTION__);
+ }
+
+ return (ret == 0);
+}
+#endif
+
+void CExternalPlayer::Pause()
+{
+}
+
+bool CExternalPlayer::HasVideo() const
+{
+ return true;
+}
+
+bool CExternalPlayer::HasAudio() const
+{
+ return false;
+}
+
+bool CExternalPlayer::CanSeek() const
+{
+ return false;
+}
+
+void CExternalPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
+{
+}
+
+void CExternalPlayer::SeekPercentage(float iPercent)
+{
+}
+
+void CExternalPlayer::SetAVDelay(float fValue)
+{
+}
+
+float CExternalPlayer::GetAVDelay()
+{
+ return 0.0f;
+}
+
+void CExternalPlayer::SetSubTitleDelay(float fValue)
+{
+}
+
+float CExternalPlayer::GetSubTitleDelay()
+{
+ return 0.0;
+}
+
+void CExternalPlayer::SeekTime(int64_t iTime)
+{
+}
+
+void CExternalPlayer::SetSpeed(float speed)
+{
+ m_speed = speed;
+ CDataCacheCore::GetInstance().SetSpeed(1.0, speed);
+}
+
+bool CExternalPlayer::SetPlayerState(const std::string& state)
+{
+ return true;
+}
+
+bool CExternalPlayer::Initialize(TiXmlElement* pConfig)
+{
+ XMLUtils::GetString(pConfig, "filename", m_filename);
+ if (m_filename.length() > 0)
+ {
+ CLog::Log(LOGINFO, "ExternalPlayer Filename: {}", m_filename);
+ }
+ else
+ {
+ std::string xml;
+ xml<<*pConfig;
+ CLog::Log(LOGERROR, "ExternalPlayer Error: filename element missing from: {}", xml);
+ return false;
+ }
+
+ XMLUtils::GetString(pConfig, "args", m_args);
+ XMLUtils::GetBoolean(pConfig, "playonestackitem", m_playOneStackItem);
+ XMLUtils::GetBoolean(pConfig, "islauncher", m_islauncher);
+ XMLUtils::GetBoolean(pConfig, "hidexbmc", m_hidexbmc);
+ if (!XMLUtils::GetBoolean(pConfig, "hideconsole", m_hideconsole))
+ {
+#ifdef TARGET_WINDOWS_DESKTOP
+ // Default depends on whether player is a batch file
+ m_hideconsole = StringUtils::EndsWith(m_filename, ".bat");
+#endif
+ }
+
+ bool bHideCursor;
+ if (XMLUtils::GetBoolean(pConfig, "hidecursor", bHideCursor) && bHideCursor)
+ m_warpcursor = WARP_BOTTOM_RIGHT;
+
+ std::string warpCursor;
+ if (XMLUtils::GetString(pConfig, "warpcursor", warpCursor) && !warpCursor.empty())
+ {
+ if (warpCursor == "bottomright") m_warpcursor = WARP_BOTTOM_RIGHT;
+ else if (warpCursor == "bottomleft") m_warpcursor = WARP_BOTTOM_LEFT;
+ else if (warpCursor == "topleft") m_warpcursor = WARP_TOP_LEFT;
+ else if (warpCursor == "topright") m_warpcursor = WARP_TOP_RIGHT;
+ else if (warpCursor == "center") m_warpcursor = WARP_CENTER;
+ else
+ {
+ warpCursor = "none";
+ CLog::Log(LOGWARNING, "ExternalPlayer: invalid value for warpcursor: {}", warpCursor);
+ }
+ }
+
+ XMLUtils::GetInt(pConfig, "playcountminimumtime", m_playCountMinTime, 1, INT_MAX);
+
+ CLog::Log(
+ LOGINFO,
+ "ExternalPlayer Tweaks: hideconsole ({}), hidexbmc ({}), islauncher ({}), warpcursor ({})",
+ m_hideconsole ? "true" : "false", m_hidexbmc ? "true" : "false",
+ m_islauncher ? "true" : "false", warpCursor);
+
+#ifdef TARGET_WINDOWS_DESKTOP
+ m_filenameReplacers.push_back("^smb:// , / , \\\\ , g");
+ m_filenameReplacers.push_back("^smb:\\\\\\\\ , smb:(\\\\\\\\[^\\\\]*\\\\) , \\1 , ");
+#endif
+
+ TiXmlElement* pReplacers = pConfig->FirstChildElement("replacers");
+ while (pReplacers)
+ {
+ GetCustomRegexpReplacers(pReplacers, m_filenameReplacers);
+ pReplacers = pReplacers->NextSiblingElement("replacers");
+ }
+
+ return true;
+}
+
+void CExternalPlayer::GetCustomRegexpReplacers(TiXmlElement *pRootElement,
+ std::vector<std::string>& settings)
+{
+ int iAction = 0; // overwrite
+ // for backward compatibility
+ const char* szAppend = pRootElement->Attribute("append");
+ if ((szAppend && StringUtils::CompareNoCase(szAppend, "yes") == 0))
+ iAction = 1;
+ // action takes precedence if both attributes exist
+ const char* szAction = pRootElement->Attribute("action");
+ if (szAction)
+ {
+ iAction = 0; // overwrite
+ if (StringUtils::CompareNoCase(szAction, "append") == 0)
+ iAction = 1; // append
+ else if (StringUtils::CompareNoCase(szAction, "prepend") == 0)
+ iAction = 2; // prepend
+ }
+ if (iAction == 0)
+ settings.clear();
+
+ TiXmlElement* pReplacer = pRootElement->FirstChildElement("replacer");
+ int i = 0;
+ while (pReplacer)
+ {
+ if (pReplacer->FirstChild())
+ {
+ const char* szGlobal = pReplacer->Attribute("global");
+ const char* szStop = pReplacer->Attribute("stop");
+ bool bGlobal = szGlobal && StringUtils::CompareNoCase(szGlobal, "true") == 0;
+ bool bStop = szStop && StringUtils::CompareNoCase(szStop, "true") == 0;
+
+ std::string strMatch;
+ std::string strPat;
+ std::string strRep;
+ XMLUtils::GetString(pReplacer,"match",strMatch);
+ XMLUtils::GetString(pReplacer,"pat",strPat);
+ XMLUtils::GetString(pReplacer,"rep",strRep);
+
+ if (!strPat.empty() && !strRep.empty())
+ {
+ CLog::Log(LOGDEBUG," Registering replacer:");
+ CLog::Log(LOGDEBUG, " Match:[{}] Pattern:[{}] Replacement:[{}]", strMatch, strPat,
+ strRep);
+ CLog::Log(LOGDEBUG, " Global:[{}] Stop:[{}]", bGlobal ? "true" : "false",
+ bStop ? "true" : "false");
+ // keep literal commas since we use comma as a separator
+ StringUtils::Replace(strMatch, ",",",,");
+ StringUtils::Replace(strPat, ",",",,");
+ StringUtils::Replace(strRep, ",",",,");
+
+ std::string strReplacer = strMatch + " , " + strPat + " , " + strRep + " , " + (bGlobal ? "g" : "") + (bStop ? "s" : "");
+ if (iAction == 2)
+ settings.insert(settings.begin() + i++, 1, strReplacer);
+ else
+ settings.push_back(strReplacer);
+ }
+ else
+ {
+ // error message about missing tag
+ if (strPat.empty())
+ CLog::Log(LOGERROR," Missing <Pat> tag");
+ else
+ CLog::Log(LOGERROR," Missing <Rep> tag");
+ }
+ }
+
+ pReplacer = pReplacer->NextSiblingElement("replacer");
+ }
+}
diff --git a/xbmc/cores/ExternalPlayer/ExternalPlayer.h b/xbmc/cores/ExternalPlayer/ExternalPlayer.h
new file mode 100644
index 0000000..c8f39d5
--- /dev/null
+++ b/xbmc/cores/ExternalPlayer/ExternalPlayer.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "cores/IPlayer.h"
+#include "threads/Thread.h"
+
+#include <string>
+#include <vector>
+
+class CGUIDialogOK;
+
+class CExternalPlayer : public IPlayer, public CThread
+{
+public:
+ enum WARP_CURSOR { WARP_NONE = 0, WARP_TOP_LEFT, WARP_TOP_RIGHT, WARP_BOTTOM_RIGHT, WARP_BOTTOM_LEFT, WARP_CENTER };
+
+ explicit CExternalPlayer(IPlayerCallback& callback);
+ ~CExternalPlayer() override;
+ bool Initialize(TiXmlElement* pConfig) override;
+ bool OpenFile(const CFileItem& file, const CPlayerOptions &options) override;
+ bool CloseFile(bool reopen = false) override;
+ bool IsPlaying() const override;
+ void Pause() override;
+ bool HasVideo() const override;
+ bool HasAudio() const override;
+ bool CanSeek() const override;
+ void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) override;
+ void SeekPercentage(float iPercent) override;
+ void SetVolume(float volume) override {}
+ void SetDynamicRangeCompression(long drc) override {}
+ void SetAVDelay(float fValue = 0.0f) override;
+ float GetAVDelay() override;
+
+ void SetSubTitleDelay(float fValue = 0.0f) override;
+ float GetSubTitleDelay() override;
+
+ void SeekTime(int64_t iTime) override;
+ void SetSpeed(float speed) override;
+ void DoAudioWork() override {}
+
+ bool SetPlayerState(const std::string& state) override;
+
+#if defined(TARGET_WINDOWS_DESKTOP)
+ bool ExecuteAppW32(const char* strPath, const char* strSwitches);
+ //static void CALLBACK AppFinished(void* closure, BOOLEAN TimerOrWaitFired);
+#elif defined(TARGET_ANDROID)
+ bool ExecuteAppAndroid(const char* strSwitches,const char* strPath);
+#elif defined(TARGET_POSIX)
+ bool ExecuteAppLinux(const char* strSwitches);
+#endif
+
+private:
+ void GetCustomRegexpReplacers(TiXmlElement *pRootElement, std::vector<std::string>& settings);
+ void Process() override;
+
+ bool m_bAbortRequest;
+ bool m_bIsPlaying;
+ std::chrono::time_point<std::chrono::steady_clock> m_playbackStartTime;
+ float m_speed;
+ int m_time;
+ std::string m_launchFilename;
+#if defined(TARGET_WINDOWS_DESKTOP)
+ POINT m_ptCursorpos;
+ PROCESS_INFORMATION m_processInfo;
+#endif
+ CGUIDialogOK* m_dialog;
+#if defined(TARGET_WINDOWS_DESKTOP)
+ int m_xPos;
+ int m_yPos;
+#endif
+ std::string m_filename;
+ std::string m_args;
+ bool m_hideconsole;
+ bool m_hidexbmc;
+ bool m_islauncher;
+ bool m_playOneStackItem;
+ WARP_CURSOR m_warpcursor;
+ int m_playCountMinTime;
+ std::vector<std::string> m_filenameReplacers;
+ CFileItem m_file;
+};
diff --git a/xbmc/cores/FFmpeg.cpp b/xbmc/cores/FFmpeg.cpp
new file mode 100644
index 0000000..03a29f7
--- /dev/null
+++ b/xbmc/cores/FFmpeg.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "cores/FFmpeg.h"
+
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <map>
+#include <mutex>
+
+static thread_local CFFmpegLog* CFFmpegLogTls;
+
+void CFFmpegLog::SetLogLevel(int level)
+{
+ CFFmpegLog::ClearLogLevel();
+ CFFmpegLog *log = new CFFmpegLog();
+ log->level = level;
+ CFFmpegLogTls = log;
+}
+
+int CFFmpegLog::GetLogLevel()
+{
+ CFFmpegLog* log = CFFmpegLogTls;
+ if (!log)
+ return -1;
+ return log->level;
+}
+
+void CFFmpegLog::ClearLogLevel()
+{
+ CFFmpegLog* log = CFFmpegLogTls;
+ CFFmpegLogTls = nullptr;
+ if (log)
+ delete log;
+}
+
+static CCriticalSection m_logSection;
+std::map<const CThread*, std::string> g_logbuffer;
+
+void ff_flush_avutil_log_buffers(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_logSection);
+ /* Loop through the logbuffer list and remove any blank buffers
+ If the thread using the buffer is still active, it will just
+ add a new buffer next time it writes to the log */
+ std::map<const CThread*, std::string>::iterator it;
+ for (it = g_logbuffer.begin(); it != g_logbuffer.end(); )
+ if ((*it).second.empty())
+ g_logbuffer.erase(it++);
+ else
+ ++it;
+}
+
+void ff_avutil_log(void* ptr, int level, const char* format, va_list va)
+{
+ std::unique_lock<CCriticalSection> lock(m_logSection);
+ const CThread* threadId = CThread::GetCurrentThread();
+ std::string &buffer = g_logbuffer[threadId];
+
+ AVClass* avc= ptr ? *(AVClass**)ptr : NULL;
+
+ int maxLevel = AV_LOG_WARNING;
+ if (CFFmpegLog::GetLogLevel() > 0)
+ maxLevel = AV_LOG_INFO;
+
+ if (level > maxLevel && !CServiceBroker::GetLogging().CanLogComponent(LOGFFMPEG))
+ return;
+ else if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel <= LOG_LEVEL_NORMAL)
+ return;
+
+ int type;
+ switch (level)
+ {
+ case AV_LOG_INFO:
+ type = LOGINFO;
+ break;
+
+ case AV_LOG_ERROR:
+ type = LOGERROR;
+ break;
+
+ case AV_LOG_DEBUG:
+ default:
+ type = LOGDEBUG;
+ break;
+ }
+
+ std::string message = StringUtils::FormatV(format, va);
+ std::string prefix = StringUtils::Format("ffmpeg[{}]: ", fmt::ptr(threadId));
+ if (avc)
+ {
+ if (avc->item_name)
+ prefix += std::string("[") + avc->item_name(ptr) + "] ";
+ else if (avc->class_name)
+ prefix += std::string("[") + avc->class_name + "] ";
+ }
+
+ buffer += message;
+ int pos, start = 0;
+ while ((pos = buffer.find_first_of('\n', start)) >= 0)
+ {
+ if (pos > start)
+ CLog::Log(type, "{}{}", prefix, buffer.substr(start, pos - start));
+ start = pos+1;
+ }
+ buffer.erase(0, start);
+}
+
diff --git a/xbmc/cores/FFmpeg.h b/xbmc/cores/FFmpeg.h
new file mode 100644
index 0000000..e1194d1
--- /dev/null
+++ b/xbmc/cores/FFmpeg.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ServiceBroker.h"
+#include "utils/CPUInfo.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#include <libavutil/log.h>
+#include <libavutil/ffversion.h>
+#include <libavfilter/avfilter.h>
+#include <libpostproc/postprocess.h>
+}
+
+inline int PPCPUFlags()
+{
+ unsigned int cpuFeatures = CServiceBroker::GetCPUInfo()->GetCPUFeatures();
+ int flags = 0;
+
+ if (cpuFeatures & CPU_FEATURE_MMX)
+ flags |= PP_CPU_CAPS_MMX;
+ if (cpuFeatures & CPU_FEATURE_MMX2)
+ flags |= PP_CPU_CAPS_MMX2;
+ if (cpuFeatures & CPU_FEATURE_3DNOW)
+ flags |= PP_CPU_CAPS_3DNOW;
+ if (cpuFeatures & CPU_FEATURE_ALTIVEC)
+ flags |= PP_CPU_CAPS_ALTIVEC;
+
+ return flags;
+}
+
+// callback used for logging
+void ff_avutil_log(void* ptr, int level, const char* format, va_list va);
+void ff_flush_avutil_log_buffers(void);
+
+class CFFmpegLog
+{
+public:
+ static void SetLogLevel(int level);
+ static int GetLogLevel();
+ static void ClearLogLevel();
+ int level;
+};
+
diff --git a/xbmc/cores/GameSettings.h b/xbmc/cores/GameSettings.h
new file mode 100644
index 0000000..a6b34c6
--- /dev/null
+++ b/xbmc/cores/GameSettings.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+
+// NOTE: Only append
+enum class SCALINGMETHOD
+{
+ AUTO = 0,
+ NEAREST = 1,
+ LINEAR = 2,
+ MAX = LINEAR
+};
+
+/*!
+ * \ingroup games
+ * \brief Methods for stretching the game to the viewing area
+ */
+enum class STRETCHMODE
+{
+ /*!
+ * \brief Show the game at its normal aspect ratio
+ */
+ Normal,
+
+ /*!
+ * \brief Stretch the game to maintain a 4:3 aspect ratio
+ */
+ Stretch4x3,
+
+ /*!
+ * \brief Stretch the game to fill the viewing area
+ */
+ Fullscreen,
+
+ /*!
+ * \brief Show the game at its original size (humorous for old consoles
+ * on 4K TVs)
+ */
+ Original,
+};
+
+constexpr const char* STRETCHMODE_NORMAL_ID = "normal";
+constexpr const char* STRETCHMODE_STRETCH_4_3_ID = "4:3";
+constexpr const char* STRETCHMODE_FULLSCREEN_ID = "fullscreen";
+constexpr const char* STRETCHMODE_ORIGINAL_ID = "original";
+
+enum class RENDERFEATURE
+{
+ ROTATION,
+ STRETCH,
+ ZOOM,
+ PIXEL_RATIO,
+};
+
+}
+}
diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h
new file mode 100644
index 0000000..706f9c1
--- /dev/null
+++ b/xbmc/cores/IPlayer.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IPlayerCallback.h"
+#include "Interface/StreamInfo.h"
+#include "MenuType.h"
+#include "VideoSettings.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#define CURRENT_STREAM -1
+#define CAPTUREFLAG_CONTINUOUS 0x01 //after a render is done, render a new one immediately
+#define CAPTUREFLAG_IMMEDIATELY 0x02 //read out immediately after render, this can cause a busy wait
+#define CAPTUREFORMAT_BGRA 0x01
+
+struct TextCacheStruct_t;
+class TiXmlElement;
+class CStreamDetails;
+class CAction;
+class IPlayerCallback;
+
+class CPlayerOptions
+{
+public:
+ CPlayerOptions()
+ {
+ starttime = 0LL;
+ startpercent = 0LL;
+ fullscreen = false;
+ videoOnly = false;
+ preferStereo = false;
+ }
+ double starttime; /* start time in seconds */
+ double startpercent; /* start time in percent */
+ std::string state; /* potential playerstate to restore to */
+ bool fullscreen; /* player is allowed to switch to fullscreen */
+ bool videoOnly; /* player is not allowed to play audio streams, video streams only */
+ bool preferStereo; /* prefer stereo streams when selecting initial audio stream*/
+};
+
+class CFileItem;
+
+enum IPlayerAudioCapabilities
+{
+ IPC_AUD_ALL,
+ IPC_AUD_OFFSET,
+ IPC_AUD_AMP,
+ IPC_AUD_SELECT_STREAM,
+ IPC_AUD_OUTPUT_STEREO,
+ IPC_AUD_SELECT_OUTPUT
+};
+
+enum IPlayerSubtitleCapabilities
+{
+ IPC_SUBS_ALL,
+ IPC_SUBS_SELECT,
+ IPC_SUBS_EXTERNAL,
+ IPC_SUBS_OFFSET
+};
+
+enum ERENDERFEATURE
+{
+ RENDERFEATURE_GAMMA,
+ RENDERFEATURE_BRIGHTNESS,
+ RENDERFEATURE_CONTRAST,
+ RENDERFEATURE_NOISE,
+ RENDERFEATURE_SHARPNESS,
+ RENDERFEATURE_NONLINSTRETCH,
+ RENDERFEATURE_ROTATION,
+ RENDERFEATURE_STRETCH,
+ RENDERFEATURE_ZOOM,
+ RENDERFEATURE_VERTICAL_SHIFT,
+ RENDERFEATURE_PIXEL_RATIO,
+ RENDERFEATURE_POSTPROCESS,
+ RENDERFEATURE_TONEMAP
+};
+
+class IPlayer
+{
+public:
+ explicit IPlayer(IPlayerCallback& callback) : m_callback(callback) {}
+ virtual ~IPlayer() = default;
+ virtual bool Initialize(TiXmlElement* pConfig) { return true; }
+ virtual bool OpenFile(const CFileItem& file, const CPlayerOptions& options){ return false;}
+ virtual bool QueueNextFile(const CFileItem &file) { return false; }
+ virtual void OnNothingToQueueNotify() {}
+ virtual bool CloseFile(bool reopen = false) = 0;
+ virtual bool IsPlaying() const { return false;}
+ virtual bool CanPause() const { return true; }
+ virtual void Pause() = 0;
+ virtual bool HasVideo() const = 0;
+ virtual bool HasAudio() const = 0;
+ virtual bool HasGame() const { return false; }
+ virtual bool HasRDS() const { return false; }
+ virtual bool HasID3() const { return false; }
+ virtual bool IsPassthrough() const { return false;}
+ virtual bool CanSeek() const { return true; }
+ virtual void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) = 0;
+ virtual bool SeekScene(bool bPlus = true) {return false;}
+ virtual void SeekPercentage(float fPercent = 0){}
+ virtual float GetCachePercentage() const { return 0; }
+ virtual void SetMute(bool bOnOff){}
+ virtual void SetVolume(float volume){}
+ virtual void SetDynamicRangeCompression(long drc){}
+
+ virtual void SetAVDelay(float fValue = 0.0f) {}
+ virtual float GetAVDelay() { return 0.0f; }
+
+ virtual void SetSubTitleDelay(float fValue = 0.0f) {}
+ virtual float GetSubTitleDelay() { return 0.0f; }
+ virtual int GetSubtitleCount() const { return 0; }
+ virtual int GetSubtitle() { return -1; }
+ virtual void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const {}
+ virtual void SetSubtitle(int iStream) {}
+ virtual bool GetSubtitleVisible() const { return false; }
+ virtual void SetSubtitleVisible(bool bVisible) {}
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ virtual void SetSubtitleVerticalPosition(int value, bool save) {}
+
+ /** \brief Adds the subtitle(s) provided by the given file to the available player streams
+ * and actives the first of the added stream(s). E.g., vob subs can contain multiple streams.
+ * \param[in] strSubPath The full path of the subtitle file.
+ */
+ virtual void AddSubtitle(const std::string& strSubPath) {}
+
+ virtual int GetAudioStreamCount() const { return 0; }
+ virtual int GetAudioStream() { return -1; }
+ virtual void SetAudioStream(int iStream) {}
+ virtual void GetAudioStreamInfo(int index, AudioStreamInfo& info) const {}
+
+ virtual int GetVideoStream() const { return -1; }
+ virtual int GetVideoStreamCount() const { return 0; }
+ virtual void GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const {}
+ virtual void SetVideoStream(int iStream) {}
+
+ virtual int GetPrograms(std::vector<ProgramInfo>& programs) { return 0; }
+ virtual void SetProgram(int progId) {}
+ virtual int GetProgramsCount() const { return 0; }
+
+ virtual bool HasTeletextCache() const { return false; }
+ virtual std::shared_ptr<TextCacheStruct_t> GetTeletextCache() { return nullptr; }
+ virtual void LoadPage(int p, int sp, unsigned char* buffer) {}
+
+ virtual int GetChapterCount() const { return 0; }
+ virtual int GetChapter() const { return -1; }
+ virtual void GetChapterName(std::string& strChapterName, int chapterIdx = -1) const {}
+ virtual int64_t GetChapterPos(int chapterIdx = -1) const { return 0; }
+ virtual int SeekChapter(int iChapter) { return -1; }
+// virtual bool GetChapterInfo(int chapter, SChapterInfo &info) { return false; }
+
+ virtual void SeekTime(int64_t iTime = 0) {}
+ /*
+ \brief seek relative to current time, returns false if not implemented by player
+ \param iTime The time in milliseconds to seek. A positive value will seek forward, a negative backward.
+ \return True if the player supports relative seeking, otherwise false
+ */
+ virtual bool SeekTimeRelative(int64_t iTime) { return false; }
+
+ /*!
+ \brief Sets the current time. This
+ can be used for injecting the current time.
+ This is not to be confused with a seek. It just
+ can be used if endless streams contain multiple
+ tracks in reality (like with airtunes)
+ */
+ virtual void SetTime(int64_t time) { }
+
+ /*!
+ \brief Set the total time in milliseconds
+ this can be used for injecting the duration in case
+ its not available in the underlaying decoder (airtunes for example)
+ */
+ virtual void SetTotalTime(int64_t time) { }
+ virtual void SetSpeed(float speed) = 0;
+ virtual void SetTempo(float tempo) {}
+ virtual bool SupportsTempo() const { return false; }
+ virtual void FrameAdvance(int frames) {}
+
+ //Returns true if not playback (paused or stopped being filled)
+ virtual bool IsCaching() const { return false; }
+ //Cache filled in Percent
+ virtual int GetCacheLevel() const { return -1; }
+
+ virtual bool IsInMenu() const { return false; }
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ virtual MenuType GetSupportedMenuType() const { return MenuType::NONE; }
+
+ virtual void DoAudioWork() {}
+ virtual bool OnAction(const CAction& action) { return false; }
+
+ //returns a state that is needed for resuming from a specific time
+ virtual std::string GetPlayerState() { return ""; }
+ virtual bool SetPlayerState(const std::string& state) { return false; }
+
+ virtual void GetAudioCapabilities(std::vector<int>& audioCaps) const
+ {
+ audioCaps.assign(1, IPC_AUD_ALL);
+ }
+ /*!
+ \brief define the subtitle capabilities of the player
+ */
+ virtual void GetSubtitleCapabilities(std::vector<int>& subCaps) const
+ {
+ subCaps.assign(1, IPC_SUBS_ALL);
+ }
+
+ /*!
+ \brief hook into render loop of render thread
+ */
+ virtual void Render(bool clear, uint32_t alpha = 255, bool gui = true) {}
+ virtual void FlushRenderer() {}
+ virtual void SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch) {}
+ virtual float GetRenderAspectRatio() const { return 1.0; }
+ virtual void TriggerUpdateResolution() {}
+ virtual bool IsRenderingVideo() const { return false; }
+
+ virtual bool Supports(EINTERLACEMETHOD method) const { return false; }
+ virtual EINTERLACEMETHOD GetDeinterlacingMethodDefault() const
+ {
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE;
+ }
+ virtual bool Supports(ESCALINGMETHOD method) const { return false; }
+ virtual bool Supports(ERENDERFEATURE feature) const { return false; }
+
+ virtual unsigned int RenderCaptureAlloc() { return 0; }
+ virtual void RenderCaptureRelease(unsigned int captureId) {}
+ virtual void RenderCapture(unsigned int captureId,
+ unsigned int width,
+ unsigned int height,
+ int flags)
+ {
+ }
+ virtual bool RenderCaptureGetPixels(unsigned int captureId,
+ unsigned int millis,
+ uint8_t* buffer,
+ unsigned int size)
+ {
+ return false;
+ }
+
+ // video and audio settings
+ virtual CVideoSettings GetVideoSettings() const { return CVideoSettings(); }
+ virtual void SetVideoSettings(CVideoSettings& settings) {}
+
+ /*!
+ * \brief Check if any players are playing a game
+ *
+ * \return True if at least one player has an input device attached to the
+ * game, false otherwise
+ */
+ virtual bool HasGameAgent() const { return false; }
+
+ std::string m_name;
+ std::string m_type;
+
+protected:
+ IPlayerCallback& m_callback;
+};
diff --git a/xbmc/cores/IPlayerCallback.h b/xbmc/cores/IPlayerCallback.h
new file mode 100644
index 0000000..3c1d99b
--- /dev/null
+++ b/xbmc/cores/IPlayerCallback.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "VideoSettings.h"
+
+#include <stdint.h>
+
+class CFileItem;
+class CBookmark;
+
+class IPlayerCallback
+{
+public:
+ virtual ~IPlayerCallback() = default;
+ virtual void OnPlayBackEnded() = 0;
+ virtual void OnPlayBackStarted(const CFileItem &file) = 0;
+ virtual void OnPlayerCloseFile(const CFileItem& file, const CBookmark& bookmark) {}
+ virtual void OnPlayBackPaused() {}
+ virtual void OnPlayBackResumed() {}
+ virtual void OnPlayBackStopped() = 0;
+ virtual void OnPlayBackError() = 0;
+ virtual void OnQueueNextItem() = 0;
+ virtual void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) {}
+ virtual void OnPlayBackSeekChapter(int iChapter) {}
+ virtual void OnPlayBackSpeedChanged(int iSpeed) {}
+ virtual void OnAVChange() {}
+ virtual void OnAVStarted(const CFileItem& file) {}
+ virtual void RequestVideoSettings(const CFileItem& fileItem) {}
+ virtual void StoreVideoSettings(const CFileItem& fileItem, const CVideoSettings& vs) {}
+};
diff --git a/xbmc/cores/MenuType.h b/xbmc/cores/MenuType.h
new file mode 100644
index 0000000..221d59d
--- /dev/null
+++ b/xbmc/cores/MenuType.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+* \brief Represents a Menu type (e.g. dvd menus, bluray menus, etc)
+*/
+enum class MenuType
+{
+ /*! No supported menu */
+ NONE,
+ /*! Supports native menus (e.g. those provided natively by blurays or dvds) */
+ NATIVE,
+ /*! Application specific menu such as the simplified menu for blurays */
+ SIMPLIFIED
+};
diff --git a/xbmc/cores/RetroPlayer/CMakeLists.txt b/xbmc/cores/RetroPlayer/CMakeLists.txt
new file mode 100644
index 0000000..3e78c54
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SOURCES RetroPlayer.cpp
+ RetroPlayerAutoSave.cpp
+ RetroPlayerInput.cpp
+ RetroPlayerUtils.cpp
+)
+
+set(HEADERS RetroPlayer.h
+ RetroPlayerAutoSave.h
+ RetroPlayerInput.h
+ RetroPlayerTypes.h
+ RetroPlayerUtils.h
+)
+
+core_add_library(retroplayer)
diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
new file mode 100644
index 0000000..030b851
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
@@ -0,0 +1,698 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayer.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "RetroPlayerAutoSave.h"
+#include "RetroPlayerInput.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "cores/DataCacheCore.h"
+#include "cores/IPlayerCallback.h"
+#include "cores/RetroPlayer/cheevos/Cheevos.h"
+#include "cores/RetroPlayer/guibridge/GUIGameMessenger.h"
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guiplayback/GUIPlaybackControl.h"
+#include "cores/RetroPlayer/playback/IPlayback.h"
+#include "cores/RetroPlayer/playback/RealtimePlayback.h"
+#include "cores/RetroPlayer/playback/ReversiblePlayback.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RPRenderManager.h"
+#include "cores/RetroPlayer/savestates/ISavestate.h"
+#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "cores/RetroPlayer/streams/RPStreamManager.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "games/GameServices.h"
+#include "games/GameSettings.h"
+#include "games/GameUtils.h"
+#include "games/addons/GameClient.h"
+#include "games/addons/input/GameClientInput.h"
+#include "games/tags/GameInfoTag.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "interfaces/AnnouncementManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "utils/JobManager.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace GAME;
+using namespace RETRO;
+
+CRetroPlayer::CRetroPlayer(IPlayerCallback& callback)
+ : IPlayer(callback), m_gameServices(CServiceBroker::GetGameServices())
+{
+ ResetPlayback();
+ CServiceBroker::GetWinSystem()->RegisterRenderLoop(this);
+}
+
+CRetroPlayer::~CRetroPlayer()
+{
+ CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this);
+ CloseFile();
+}
+
+bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options)
+{
+ CFileItem fileCopy(file);
+
+ std::string savestatePath;
+
+ // When playing a game, set the game client that we'll use to open the game.
+ // This will prompt the user to select a savestate if there are any.
+ // If there are no savestates, or the user wants to create a new savestate
+ // it will prompt the user to select a game client
+ if (!GAME::CGameUtils::FillInGameClient(fileCopy, savestatePath))
+ {
+ CLog::Log(LOGINFO,
+ "RetroPlayer[PLAYER]: No compatible game client selected, aborting playback");
+ return false;
+ }
+
+ // Check if we should open in standalone mode
+ const bool bStandalone = fileCopy.GetPath().empty();
+
+ m_processInfo.reset(CRPProcessInfo::CreateInstance());
+ if (!m_processInfo)
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to create - no process info registered");
+ return false;
+ }
+
+ m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore());
+ m_processInfo->ResetInfo();
+
+ m_guiMessenger = std::make_unique<CGUIGameMessenger>(*m_processInfo);
+ m_renderManager.reset(new CRPRenderManager(*m_processInfo));
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (IsPlaying())
+ CloseFile();
+
+ PrintGameInfo(fileCopy);
+
+ bool bSuccess = false;
+
+ std::string gameClientId = fileCopy.GetGameInfoTag()->GetGameClient();
+
+ ADDON::AddonPtr addon;
+ if (gameClientId.empty())
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't play game, no game client was passed!");
+ }
+ else if (!CServiceBroker::GetAddonMgr().GetAddon(gameClientId, addon, ADDON::AddonType::GAMEDLL,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't find add-on {} for game file!", gameClientId);
+ }
+ else
+ {
+ m_gameClient = std::static_pointer_cast<CGameClient>(addon);
+ if (m_gameClient->Initialize())
+ {
+ m_streamManager.reset(new CRPStreamManager(*m_renderManager, *m_processInfo));
+
+ // Initialize input
+ m_input = std::make_unique<CRetroPlayerInput>(CServiceBroker::GetPeripherals(),
+ *m_processInfo, m_gameClient);
+ m_input->StartAgentManager();
+
+ if (!bStandalone)
+ {
+ std::string redactedPath = CURL::GetRedacted(fileCopy.GetPath());
+ CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening: {}", redactedPath);
+ bSuccess = m_gameClient->OpenFile(fileCopy, *m_streamManager, m_input.get());
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening standalone");
+ bSuccess = m_gameClient->OpenStandalone(*m_streamManager, m_input.get());
+ }
+
+ if (bSuccess)
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Using game client {}", gameClientId);
+ else
+ CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to open file using {}", gameClientId);
+ }
+ else
+ CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to initialize {}", gameClientId);
+ }
+
+ if (bSuccess && !bStandalone)
+ {
+ CSavestateDatabase savestateDb;
+
+ std::unique_ptr<ISavestate> save = CSavestateDatabase::AllocateSavestate();
+ if (savestateDb.GetSavestate(savestatePath, *save))
+ {
+ // Check if game client is the same
+ if (save->GameClientID() != m_gameClient->ID())
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(save->GameClientID(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ // Warn the user that continuing with a different game client will
+ // overwrite the save
+ bool dummy;
+ if (!CGUIDialogYesNo::ShowAndGetInput(
+ 438, StringUtils::Format(g_localizeStrings.Get(35217), addon->Name()), dummy, 222,
+ 35218, 0))
+ bSuccess = false;
+ }
+ }
+ }
+ }
+
+ if (bSuccess)
+ {
+ // Switch to fullscreen
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
+
+ m_cheevos = std::make_shared<CCheevos>(m_gameClient.get(),
+ m_gameServices.GameSettings().GetRAUsername(),
+ m_gameServices.GameSettings().GetRAToken());
+
+ m_cheevos->EnableRichPresence();
+
+ // Initialize gameplay
+ CreatePlayback(savestatePath);
+ RegisterWindowCallbacks();
+ m_playbackControl.reset(new CGUIPlaybackControl(*this));
+ m_callback.OnPlayBackStarted(fileCopy);
+ m_callback.OnAVStarted(fileCopy);
+ if (!bStandalone)
+ m_autoSave.reset(new CRetroPlayerAutoSave(*this, m_gameServices.GameSettings()));
+
+ // Set video framerate
+ m_processInfo->SetVideoFps(static_cast<float>(m_gameClient->GetFrameRate()));
+ }
+ else
+ {
+ m_input.reset();
+ m_streamManager.reset();
+ if (m_gameClient)
+ m_gameClient->Unload();
+ m_gameClient.reset();
+ }
+
+ return bSuccess;
+}
+
+bool CRetroPlayer::CloseFile(bool reopen /* = false */)
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing file");
+
+ m_autoSave.reset();
+
+ UnregisterWindowCallbacks();
+
+ m_playbackControl.reset();
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (m_gameClient && m_gameServices.GameSettings().AutosaveEnabled())
+ {
+ std::string savePath = m_playback->CreateSavestate(true);
+ if (!savePath.empty())
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Saved state to {}", CURL::GetRedacted(savePath));
+ else
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Failed to save state at close");
+ }
+
+ m_playback.reset();
+
+ if (m_input)
+ m_input->StopAgentManager();
+
+ m_cheevos.reset();
+
+ if (m_gameClient)
+ m_gameClient->CloseFile();
+
+ m_input.reset();
+
+ m_streamManager.reset();
+
+ if (m_gameClient)
+ m_gameClient->Unload();
+ m_gameClient.reset();
+
+ m_renderManager.reset();
+ m_processInfo.reset();
+
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Playback ended");
+ m_callback.OnPlayBackEnded();
+
+ return true;
+}
+
+bool CRetroPlayer::IsPlaying() const
+{
+ if (m_gameClient)
+ return m_gameClient->IsPlaying();
+ return false;
+}
+
+bool CRetroPlayer::CanPause() const
+{
+ return m_playback->CanPause();
+}
+
+void CRetroPlayer::Pause()
+{
+ if (!CanPause())
+ return;
+
+ float speed;
+
+ if (m_playback->GetSpeed() == 0.0)
+ speed = 1.0f;
+ else
+ speed = 0.0f;
+
+ SetSpeed(speed);
+}
+
+bool CRetroPlayer::CanSeek() const
+{
+ return m_playback->CanSeek();
+}
+
+void CRetroPlayer::Seek(bool bPlus /* = true */,
+ bool bLargeStep /* = false */,
+ bool bChapterOverride /* = false */)
+{
+ if (!CanSeek())
+ return;
+
+ if (m_gameClient)
+ {
+ //! @todo
+ /*
+ if (bPlus)
+ {
+ if (bLargeStep)
+ m_playback->BigSkipForward();
+ else
+ m_playback->SmallSkipForward();
+ }
+ else
+ {
+ if (bLargeStep)
+ m_playback->BigSkipBackward();
+ else
+ m_playback->SmallSkipBackward();
+ }
+ */
+ }
+}
+
+void CRetroPlayer::SeekPercentage(float fPercent /* = 0 */)
+{
+ if (!CanSeek())
+ return;
+
+ if (fPercent < 0.0f)
+ fPercent = 0.0f;
+ else if (fPercent > 100.0f)
+ fPercent = 100.0f;
+
+ uint64_t totalTime = GetTotalTime();
+ if (totalTime != 0)
+ SeekTime(static_cast<int64_t>(totalTime * fPercent / 100.0f));
+}
+
+float CRetroPlayer::GetCachePercentage() const
+{
+ const float cacheMs = static_cast<float>(m_playback->GetCacheTimeMs());
+ const float totalMs = static_cast<float>(m_playback->GetTotalTimeMs());
+
+ if (totalMs != 0.0f)
+ return cacheMs / totalMs * 100.0f;
+
+ return 0.0f;
+}
+
+void CRetroPlayer::SetMute(bool bOnOff)
+{
+ if (m_streamManager)
+ m_streamManager->EnableAudio(!bOnOff);
+}
+
+void CRetroPlayer::SeekTime(int64_t iTime /* = 0 */)
+{
+ if (!CanSeek())
+ return;
+
+ m_playback->SeekTimeMs(static_cast<unsigned int>(iTime));
+}
+
+bool CRetroPlayer::SeekTimeRelative(int64_t iTime)
+{
+ if (!CanSeek())
+ return false;
+
+ SeekTime(GetTime() + iTime);
+
+ return true;
+}
+
+uint64_t CRetroPlayer::GetTime()
+{
+ return m_playback->GetTimeMs();
+}
+
+uint64_t CRetroPlayer::GetTotalTime()
+{
+ return m_playback->GetTotalTimeMs();
+}
+
+void CRetroPlayer::SetSpeed(float speed)
+{
+ if (m_playback->GetSpeed() != static_cast<double>(speed))
+ {
+ if (speed == 1.0f)
+ m_callback.OnPlayBackResumed();
+ else if (speed == 0.0f)
+ m_callback.OnPlayBackPaused();
+
+ SetSpeedInternal(static_cast<double>(speed));
+
+ if (speed == 0.0f)
+ {
+ const int dialogId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog();
+ if (dialogId == WINDOW_FULLSCREEN_GAME)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Opening OSD via speed change ({:f})", speed);
+ OpenOSD();
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via speed change ({:f})", speed);
+ CloseOSD();
+ }
+ }
+}
+
+bool CRetroPlayer::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PLAYER_RESET:
+ {
+ if (m_gameClient)
+ {
+ float speed = static_cast<float>(m_playback->GetSpeed());
+
+ m_playback->SetSpeed(0.0);
+
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Sending reset command via ACTION_PLAYER_RESET");
+ m_cheevos->ResetRuntime();
+ m_gameClient->Input().HardwareReset();
+
+ // If rewinding or paused, begin playback
+ if (speed <= 0.0f)
+ speed = 1.0f;
+
+ SetSpeed(speed);
+ }
+ return true;
+ }
+ case ACTION_SHOW_OSD:
+ {
+ if (m_gameClient)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via ACTION_SHOW_OSD");
+ CloseOSD();
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+std::string CRetroPlayer::GetPlayerState()
+{
+ std::string savestatePath;
+
+ if (m_autoSave)
+ {
+ savestatePath = m_playback->CreateSavestate(true);
+ if (savestatePath.empty())
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Continuing without saving");
+ m_autoSave.reset();
+ }
+ }
+ return savestatePath;
+}
+
+bool CRetroPlayer::SetPlayerState(const std::string& state)
+{
+ return m_playback->LoadSavestate(state);
+}
+
+void CRetroPlayer::FrameMove()
+{
+ if (m_renderManager)
+ m_renderManager->FrameMove();
+
+ if (m_playbackControl)
+ m_playbackControl->FrameMove();
+
+ if (m_processInfo)
+ m_processInfo->SetPlayTimes(0, GetTime(), 0, GetTotalTime());
+}
+
+void CRetroPlayer::Render(bool clear, uint32_t alpha /* = 255 */, bool gui /* = true */)
+{
+ // Performed by callbacks
+}
+
+bool CRetroPlayer::IsRenderingVideo() const
+{
+ return true;
+}
+
+bool CRetroPlayer::HasGameAgent() const
+{
+ if (m_gameClient)
+ return m_gameClient->Input().HasAgent();
+
+ return false;
+}
+
+std::string CRetroPlayer::GameClientID() const
+{
+ if (m_gameClient)
+ return m_gameClient->ID();
+
+ return "";
+}
+
+std::string CRetroPlayer::GetPlayingGame() const
+{
+ if (m_gameClient)
+ return m_gameClient->GetGamePath();
+
+ return "";
+}
+
+std::string CRetroPlayer::CreateSavestate(bool autosave)
+{
+ if (m_playback)
+ return m_playback->CreateSavestate(autosave);
+
+ return "";
+}
+
+bool CRetroPlayer::UpdateSavestate(const std::string& savestatePath)
+{
+ if (m_playback)
+ return !m_playback->CreateSavestate(false, savestatePath).empty();
+
+ return false;
+}
+
+bool CRetroPlayer::LoadSavestate(const std::string& savestatePath)
+{
+ if (m_playback)
+ return m_playback->LoadSavestate(savestatePath);
+
+ return false;
+}
+
+void CRetroPlayer::FreeSavestateResources(const std::string& savestatePath)
+{
+ if (m_renderManager)
+ m_renderManager->ClearVideoFrame(savestatePath);
+}
+
+void CRetroPlayer::CloseOSDCallback()
+{
+ CloseOSD();
+}
+
+void CRetroPlayer::SetPlaybackSpeed(double speed)
+{
+ if (m_playback)
+ {
+ if (m_playback->GetSpeed() != speed)
+ {
+ if (speed == 1.0)
+ {
+ IPlayerCallback* callback = &m_callback;
+ CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackResumed(); },
+ CJob::PRIORITY_NORMAL);
+ }
+ else if (speed == 0.0)
+ {
+ IPlayerCallback* callback = &m_callback;
+ CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackPaused(); },
+ CJob::PRIORITY_NORMAL);
+ }
+ }
+ }
+
+ SetSpeedInternal(speed);
+}
+
+void CRetroPlayer::EnableInput(bool bEnable)
+{
+ if (m_input)
+ m_input->EnableInput(bEnable);
+}
+
+bool CRetroPlayer::IsAutoSaveEnabled() const
+{
+ return m_playback->GetSpeed() > 0.0;
+}
+
+std::string CRetroPlayer::CreateAutosave()
+{
+ return m_playback->CreateSavestate(true);
+}
+
+void CRetroPlayer::SetSpeedInternal(double speed)
+{
+ OnSpeedChange(speed);
+
+ if (speed == 0.0)
+ m_playback->PauseAsync();
+ else
+ m_playback->SetSpeed(speed);
+}
+
+void CRetroPlayer::OnSpeedChange(double newSpeed)
+{
+ m_streamManager->EnableAudio(newSpeed == 1.0);
+ m_input->SetSpeed(newSpeed);
+ m_renderManager->SetSpeed(newSpeed);
+ m_processInfo->SetSpeed(static_cast<float>(newSpeed));
+}
+
+void CRetroPlayer::CreatePlayback(const std::string& savestatePath)
+{
+ if (m_gameClient->RequiresGameLoop())
+ {
+ m_playback->Deinitialize();
+ m_playback = std::make_unique<CReversiblePlayback>(
+ m_gameClient.get(), *m_renderManager, m_cheevos.get(), *m_guiMessenger,
+ m_gameClient->GetFrameRate(), m_gameClient->GetSerializeSize());
+ }
+ else
+ ResetPlayback();
+
+ if (!savestatePath.empty())
+ {
+ const bool bStandalone = m_gameClient->GetGamePath().empty();
+ if (!bStandalone)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Loading savestate");
+
+ if (!SetPlayerState(savestatePath))
+ CLog::Log(LOGERROR, "RetroPlayer[SAVE]: Failed to load savestate");
+ }
+ }
+
+ m_playback->Initialize();
+}
+
+void CRetroPlayer::ResetPlayback()
+{
+ // Called from the constructor, m_playback might not be initialized
+ if (m_playback)
+ m_playback->Deinitialize();
+
+ m_playback.reset(new CRealtimePlayback);
+}
+
+void CRetroPlayer::OpenOSD()
+{
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_OSD);
+}
+
+void CRetroPlayer::CloseOSD()
+{
+ CServiceBroker::GetGUI()->GetWindowManager().CloseDialogs(true);
+}
+
+void CRetroPlayer::RegisterWindowCallbacks()
+{
+ m_gameServices.GameRenderManager().RegisterPlayer(m_renderManager->GetGUIRenderTargetFactory(),
+ m_renderManager.get(), this);
+}
+
+void CRetroPlayer::UnregisterWindowCallbacks()
+{
+ m_gameServices.GameRenderManager().UnregisterPlayer();
+}
+
+void CRetroPlayer::PrintGameInfo(const CFileItem& file) const
+{
+ const CGameInfoTag* tag = file.GetGameInfoTag();
+ if (tag)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------");
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game tag loaded");
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: URL: {}", tag->GetURL());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Title: {}", tag->GetTitle());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Platform: {}", tag->GetPlatform());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Genres: {}",
+ StringUtils::Join(tag->GetGenres(), ", "));
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Developer: {}", tag->GetDeveloper());
+ if (tag->GetYear() > 0)
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Year: {}", tag->GetYear());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game Code: {}", tag->GetID());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Region: {}", tag->GetRegion());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Publisher: {}", tag->GetPublisher());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Format: {}", tag->GetFormat());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Cartridge type: {}", tag->GetCartridgeType());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game client: {}", tag->GetGameClient());
+ CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------");
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.h b/xbmc/cores/RetroPlayer/RetroPlayer.h
new file mode 100644
index 0000000..fc383c9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayer.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RetroPlayerAutoSave.h"
+#include "cores/IPlayer.h"
+#include "cores/RetroPlayer/guibridge/IGameCallback.h"
+#include "cores/RetroPlayer/playback/IPlaybackControl.h"
+#include "games/GameTypes.h"
+#include "guilib/DispResource.h"
+#include "threads/CriticalSection.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameServices;
+}
+
+namespace RETRO
+{
+class CCheevos;
+class CGUIGameMessenger;
+class CRetroPlayerInput;
+class CRPProcessInfo;
+class CRPRenderManager;
+class CRPStreamManager;
+class IPlayback;
+
+class CRetroPlayer : public IPlayer,
+ public IRenderLoop,
+ public IGameCallback,
+ public IPlaybackCallback,
+ public IAutoSaveCallback
+{
+public:
+ explicit CRetroPlayer(IPlayerCallback& callback);
+ ~CRetroPlayer() override;
+
+ // implementation of IPlayer
+ bool OpenFile(const CFileItem& file, const CPlayerOptions& options) override;
+ bool CloseFile(bool reopen = false) override;
+ bool IsPlaying() const override;
+ bool CanPause() const override;
+ void Pause() override;
+ bool HasVideo() const override { return true; }
+ bool HasAudio() const override { return true; }
+ bool HasGame() const override { return true; }
+ bool CanSeek() const override;
+ void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) override;
+ void SeekPercentage(float fPercent = 0) override;
+ float GetCachePercentage() const override;
+ void SetMute(bool bOnOff) override;
+ void SeekTime(int64_t iTime = 0) override;
+ bool SeekTimeRelative(int64_t iTime) override;
+ void SetSpeed(float speed) override;
+ bool OnAction(const CAction& action) override;
+ std::string GetPlayerState() override;
+ bool SetPlayerState(const std::string& state) override;
+ void FrameMove() override;
+ void Render(bool clear, uint32_t alpha = 255, bool gui = true) override;
+ bool IsRenderingVideo() const override;
+ bool HasGameAgent() const override;
+
+ // Implementation of IGameCallback
+ std::string GameClientID() const override;
+ std::string GetPlayingGame() const override;
+ std::string CreateSavestate(bool autosave) override;
+ bool UpdateSavestate(const std::string& savestatePath) override;
+ bool LoadSavestate(const std::string& savestatePath) override;
+ void FreeSavestateResources(const std::string& savestatePath) override;
+ void CloseOSDCallback() override;
+
+ // Implementation of IPlaybackCallback
+ void SetPlaybackSpeed(double speed) override;
+ void EnableInput(bool bEnable) override;
+
+ // Implementation of IAutoSaveCallback
+ bool IsAutoSaveEnabled() const override;
+ std::string CreateAutosave() override;
+
+private:
+ void SetSpeedInternal(double speed);
+
+ /*!
+ * \brief Called when the speed changes
+ * \param newSpeed The new speed, possibly equal to the previous speed
+ */
+ void OnSpeedChange(double newSpeed);
+
+ // Playback functions
+ void CreatePlayback(const std::string& savestatePath);
+ void ResetPlayback();
+
+ /*!
+ * \brief Opens the OSD
+ */
+ void OpenOSD();
+
+ /*!
+ * \brief Closes the OSD and shows the FullscreenGame window
+ */
+ void CloseOSD();
+
+ void RegisterWindowCallbacks();
+ void UnregisterWindowCallbacks();
+
+ /**
+ * \brief Dump game information (if any) to the debug log.
+ */
+ void PrintGameInfo(const CFileItem& file) const;
+
+ uint64_t GetTime();
+ uint64_t GetTotalTime();
+
+ // Construction parameters
+ GAME::CGameServices& m_gameServices;
+
+ // Subsystems
+ std::unique_ptr<CRPProcessInfo> m_processInfo;
+ std::unique_ptr<CGUIGameMessenger> m_guiMessenger;
+ std::unique_ptr<CRPRenderManager> m_renderManager;
+ std::unique_ptr<CRPStreamManager> m_streamManager;
+ std::unique_ptr<CRetroPlayerInput> m_input;
+ std::unique_ptr<IPlayback> m_playback;
+ std::unique_ptr<IPlaybackControl> m_playbackControl;
+ std::unique_ptr<CRetroPlayerAutoSave> m_autoSave;
+ std::shared_ptr<CCheevos> m_cheevos;
+
+ // Game parameters
+ GAME::GameClientPtr m_gameClient;
+
+ // Synchronization parameters
+ CCriticalSection m_mutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.cpp b/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.cpp
new file mode 100644
index 0000000..872bfa8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerAutoSave.h"
+
+#include "URL.h"
+#include "games/GameSettings.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr auto AUTOSAVE_DURATION_SECS = 10s; // Auto-save every 10 seconds
+}
+
+CRetroPlayerAutoSave::CRetroPlayerAutoSave(IAutoSaveCallback& callback,
+ GAME::CGameSettings& settings)
+ : CThread("CRetroPlayerAutoSave"), m_callback(callback), m_settings(settings)
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Initializing autosave");
+
+ Create(false);
+}
+
+CRetroPlayerAutoSave::~CRetroPlayerAutoSave()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Deinitializing autosave");
+
+ StopThread();
+}
+
+void CRetroPlayerAutoSave::Process()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Autosave thread started");
+
+ while (!m_bStop)
+ {
+ CThread::Sleep(AUTOSAVE_DURATION_SECS);
+
+ if (m_bStop)
+ break;
+
+ if (!m_settings.AutosaveEnabled())
+ continue;
+
+ if (m_callback.IsAutoSaveEnabled())
+ {
+ std::string savePath = m_callback.CreateAutosave();
+ if (!savePath.empty())
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Saved state to {}", CURL::GetRedacted(savePath));
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Autosave thread ended");
+}
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.h b/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.h
new file mode 100644
index 0000000..f34ab09
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerAutoSave.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Thread.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClient;
+class CGameSettings;
+} // namespace GAME
+
+namespace RETRO
+{
+class IAutoSaveCallback
+{
+public:
+ virtual ~IAutoSaveCallback() = default;
+
+ virtual bool IsAutoSaveEnabled() const = 0;
+ virtual std::string CreateAutosave() = 0;
+};
+
+class CRetroPlayerAutoSave : protected CThread
+{
+public:
+ explicit CRetroPlayerAutoSave(IAutoSaveCallback& callback, GAME::CGameSettings& settings);
+
+ ~CRetroPlayerAutoSave() override;
+
+protected:
+ // implementation of CThread
+ void Process() override;
+
+private:
+ // Construction parameters
+ IAutoSaveCallback& m_callback;
+ GAME::CGameSettings& m_settings;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerDefines.h b/xbmc/cores/RetroPlayer/RetroPlayerDefines.h
new file mode 100644
index 0000000..4d84641
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerDefines.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#define GAME_STREAM_VIDEO_ID 1
+#define GAME_STREAM_AUDIO_ID 2
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerInput.cpp b/xbmc/cores/RetroPlayer/RetroPlayerInput.cpp
new file mode 100644
index 0000000..0e6f813
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerInput.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerInput.h"
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "peripherals/EventPollHandle.h"
+#include "peripherals/Peripherals.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRetroPlayerInput::CRetroPlayerInput(PERIPHERALS::CPeripherals& peripheralManager,
+ CRPProcessInfo& processInfo,
+ GAME::GameClientPtr gameClient)
+ : m_peripheralManager(peripheralManager),
+ m_processInfo(processInfo),
+ m_gameClient(std::move(gameClient))
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[INPUT]: Initializing input");
+
+ m_inputPollHandle = m_peripheralManager.RegisterEventPoller();
+}
+
+CRetroPlayerInput::~CRetroPlayerInput()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[INPUT]: Deinitializing input");
+
+ m_inputPollHandle.reset();
+}
+
+void CRetroPlayerInput::StartAgentManager()
+{
+ if (!m_bAgentManagerStarted)
+ {
+ m_bAgentManagerStarted = true;
+ m_processInfo.GetRenderContext().StartAgentManager(m_gameClient);
+ }
+}
+
+void CRetroPlayerInput::StopAgentManager()
+{
+ if (m_bAgentManagerStarted)
+ {
+ m_bAgentManagerStarted = false;
+ m_processInfo.GetRenderContext().StopAgentManager();
+ }
+}
+
+void CRetroPlayerInput::SetSpeed(double speed)
+{
+ if (speed != 0)
+ m_inputPollHandle->Activate();
+ else
+ m_inputPollHandle->Deactivate();
+}
+
+void CRetroPlayerInput::EnableInput(bool bEnabled)
+{
+ m_bEnabled = bEnabled;
+}
+
+void CRetroPlayerInput::PollInput()
+{
+ m_inputPollHandle->HandleEvents(true);
+}
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerInput.h b/xbmc/cores/RetroPlayer/RetroPlayerInput.h
new file mode 100644
index 0000000..eb243b5
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerInput.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/GameTypes.h"
+#include "games/addons/GameClientCallbacks.h"
+#include "peripherals/PeripheralTypes.h"
+
+namespace PERIPHERALS
+{
+class CPeripherals;
+}
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfo;
+
+class CRetroPlayerInput : public GAME::IGameInputCallback
+{
+public:
+ CRetroPlayerInput(PERIPHERALS::CPeripherals& peripheralManager,
+ CRPProcessInfo& processInfo,
+ GAME::GameClientPtr gameClient);
+ ~CRetroPlayerInput() override;
+
+ // Lifecycle functions
+ void StartAgentManager();
+ void StopAgentManager();
+
+ // Input functions
+ void SetSpeed(double speed);
+ void EnableInput(bool bEnabled);
+
+ // implementation of IGameInputCallback
+ bool AcceptsInput() const override { return m_bEnabled; }
+ void PollInput() override;
+
+private:
+ // Construction parameters
+ PERIPHERALS::CPeripherals& m_peripheralManager;
+ CRPProcessInfo& m_processInfo;
+
+ // Input variables
+ PERIPHERALS::EventPollHandlePtr m_inputPollHandle;
+ bool m_bEnabled = false;
+
+ // Game parameters
+ const GAME::GameClientPtr m_gameClient;
+ bool m_bAgentManagerStarted{false};
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerTypes.h b/xbmc/cores/RetroPlayer/RetroPlayerTypes.h
new file mode 100644
index 0000000..af14900
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerTypes.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IRenderBufferPool;
+using RenderBufferPoolPtr = std::shared_ptr<IRenderBufferPool>;
+using RenderBufferPoolVector = std::vector<RenderBufferPoolPtr>;
+
+enum class DataAccess
+{
+ READ_ONLY,
+ WRITE_ONLY,
+ READ_WRITE
+};
+
+enum class DataAlignment
+{
+ DATA_UNALIGNED,
+ DATA_ALIGNED,
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerUtils.cpp b/xbmc/cores/RetroPlayer/RetroPlayerUtils.cpp
new file mode 100644
index 0000000..42d6e47
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerUtils.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerUtils.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+const char* CRetroPlayerUtils::StretchModeToIdentifier(STRETCHMODE stretchMode)
+{
+ switch (stretchMode)
+ {
+ case STRETCHMODE::Normal:
+ return STRETCHMODE_NORMAL_ID;
+ case STRETCHMODE::Stretch4x3:
+ return STRETCHMODE_STRETCH_4_3_ID;
+ case STRETCHMODE::Fullscreen:
+ return STRETCHMODE_FULLSCREEN_ID;
+ case STRETCHMODE::Original:
+ return STRETCHMODE_ORIGINAL_ID;
+ default:
+ break;
+ }
+
+ return "";
+}
+
+STRETCHMODE CRetroPlayerUtils::IdentifierToStretchMode(const std::string& stretchMode)
+{
+ if (stretchMode == STRETCHMODE_NORMAL_ID)
+ return STRETCHMODE::Normal;
+ else if (stretchMode == STRETCHMODE_STRETCH_4_3_ID)
+ return STRETCHMODE::Stretch4x3;
+ else if (stretchMode == STRETCHMODE_FULLSCREEN_ID)
+ return STRETCHMODE::Fullscreen;
+ else if (stretchMode == STRETCHMODE_ORIGINAL_ID)
+ return STRETCHMODE::Original;
+
+ return STRETCHMODE::Normal;
+}
diff --git a/xbmc/cores/RetroPlayer/RetroPlayerUtils.h b/xbmc/cores/RetroPlayer/RetroPlayerUtils.h
new file mode 100644
index 0000000..27fee5a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/RetroPlayerUtils.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRetroPlayerUtils
+{
+public:
+ /*!
+ * \brief Convert a stretch mode enum to a short string identifier
+ *
+ * \param stretchMode The stretch mode
+ *
+ * \return A short string identifier specified by GameSettings.h, or an
+ * empty string if the stretch mode is invalid
+ */
+ static const char* StretchModeToIdentifier(STRETCHMODE stretchMode);
+
+ /*!
+ * \brief Convert a stretch mode identifier to an enum
+ *
+ * \param stretchMode The short string identifier, from GameSettings.h,
+ * representing the stretch mode
+ *
+ * \return The stretch mode enum, or STRETCHMODE::Normal if the identifier
+ * is invalid
+ */
+ static STRETCHMODE IdentifierToStretchMode(const std::string& stretchMode);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp b/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp
new file mode 100644
index 0000000..91f37ec
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AudioTranslator.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+AEDataFormat CAudioTranslator::TranslatePCMFormat(PCMFormat format)
+{
+ switch (format)
+ {
+ case PCMFormat::FMT_S16NE:
+ return AE_FMT_S16NE;
+ default:
+ break;
+ }
+ return AE_FMT_INVALID;
+}
+
+AEChannel CAudioTranslator::TranslateAudioChannel(AudioChannel channel)
+{
+ switch (channel)
+ {
+ case AudioChannel::CH_FL:
+ return AE_CH_FL;
+ case AudioChannel::CH_FR:
+ return AE_CH_FR;
+ case AudioChannel::CH_FC:
+ return AE_CH_FC;
+ case AudioChannel::CH_LFE:
+ return AE_CH_LFE;
+ case AudioChannel::CH_BL:
+ return AE_CH_BL;
+ case AudioChannel::CH_BR:
+ return AE_CH_BR;
+ case AudioChannel::CH_FLOC:
+ return AE_CH_FLOC;
+ case AudioChannel::CH_FROC:
+ return AE_CH_FROC;
+ case AudioChannel::CH_BC:
+ return AE_CH_BC;
+ case AudioChannel::CH_SL:
+ return AE_CH_SL;
+ case AudioChannel::CH_SR:
+ return AE_CH_SR;
+ case AudioChannel::CH_TFL:
+ return AE_CH_TFL;
+ case AudioChannel::CH_TFR:
+ return AE_CH_TFR;
+ case AudioChannel::CH_TFC:
+ return AE_CH_TFC;
+ case AudioChannel::CH_TC:
+ return AE_CH_TC;
+ case AudioChannel::CH_TBL:
+ return AE_CH_TBL;
+ case AudioChannel::CH_TBR:
+ return AE_CH_TBR;
+ case AudioChannel::CH_TBC:
+ return AE_CH_TBC;
+ case AudioChannel::CH_BLOC:
+ return AE_CH_BLOC;
+ case AudioChannel::CH_BROC:
+ return AE_CH_BROC;
+ default:
+ break;
+ }
+ return AE_CH_NULL;
+}
diff --git a/xbmc/cores/RetroPlayer/audio/AudioTranslator.h b/xbmc/cores/RetroPlayer/audio/AudioTranslator.h
new file mode 100644
index 0000000..f8935d8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/audio/AudioTranslator.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEChannelData.h"
+#include "cores/RetroPlayer/streams/RetroPlayerStreamTypes.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CAudioTranslator
+{
+public:
+ /*!
+ * \brief Translate audio PCM format (Game API to AudioEngine).
+ * \param format The audio PCM format to translate.
+ * \return Translated audio PCM format.
+ */
+ static AEDataFormat TranslatePCMFormat(PCMFormat format);
+
+ /*!
+ * \brief Translate audio channels (Game API to AudioEngine).
+ * \param format The audio channels to translate.
+ * \return Translated audio channels.
+ */
+ static AEChannel TranslateAudioChannel(AudioChannel channel);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/audio/CMakeLists.txt b/xbmc/cores/RetroPlayer/audio/CMakeLists.txt
new file mode 100644
index 0000000..1cbb113
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/audio/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES AudioTranslator.cpp
+)
+
+set(HEADERS AudioTranslator.h
+)
+
+core_add_library(rp_audio)
diff --git a/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.cpp b/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.cpp
new file mode 100644
index 0000000..2aec349
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BaseRenderBuffer.h"
+
+#include "IRenderBufferPool.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CBaseRenderBuffer::CBaseRenderBuffer() : m_refCount(0)
+{
+}
+
+void CBaseRenderBuffer::Acquire()
+{
+ m_refCount++;
+}
+
+void CBaseRenderBuffer::Acquire(std::shared_ptr<IRenderBufferPool> pool)
+{
+ m_refCount++;
+ m_pool = pool;
+}
+
+void CBaseRenderBuffer::Release()
+{
+ if (--m_refCount <= 0 && m_pool)
+ {
+ std::shared_ptr<IRenderBufferPool> pool = m_pool->GetPtr();
+ m_pool.reset();
+ pool->Return(this);
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.h b/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.h
new file mode 100644
index 0000000..a88cd0c
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/BaseRenderBuffer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IRenderBuffer.h"
+
+#include <atomic>
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CBaseRenderBuffer : public IRenderBuffer
+{
+public:
+ CBaseRenderBuffer();
+ ~CBaseRenderBuffer() override = default;
+
+ // Partial implementation of IRenderBuffer
+ void Acquire() override;
+ void Acquire(std::shared_ptr<IRenderBufferPool> pool) override;
+ void Release() override;
+ IRenderBufferPool* GetPool() override { return m_pool.get(); }
+
+protected:
+ // Reference counting
+ std::atomic_int m_refCount;
+
+ // Pool callback
+ std::shared_ptr<IRenderBufferPool> m_pool;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.cpp b/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.cpp
new file mode 100644
index 0000000..5ea42b6
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BaseRenderBufferPool.h"
+
+#include "IRenderBuffer.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+CBaseRenderBufferPool::~CBaseRenderBufferPool()
+{
+ Flush();
+}
+
+void CBaseRenderBufferPool::RegisterRenderer(CRPBaseRenderer* renderer)
+{
+ std::unique_lock<CCriticalSection> lock(m_rendererMutex);
+
+ m_renderers.push_back(renderer);
+}
+
+void CBaseRenderBufferPool::UnregisterRenderer(CRPBaseRenderer* renderer)
+{
+ std::unique_lock<CCriticalSection> lock(m_rendererMutex);
+
+ m_renderers.erase(std::remove(m_renderers.begin(), m_renderers.end(), renderer),
+ m_renderers.end());
+}
+
+bool CBaseRenderBufferPool::HasVisibleRenderer() const
+{
+ std::unique_lock<CCriticalSection> lock(m_rendererMutex);
+
+ for (auto renderer : m_renderers)
+ {
+ if (renderer->IsVisible())
+ return true;
+ }
+
+ return false;
+}
+
+bool CBaseRenderBufferPool::Configure(AVPixelFormat format)
+{
+ m_format = format;
+
+ if (ConfigureInternal())
+ m_bConfigured = true;
+
+ return m_bConfigured;
+}
+
+IRenderBuffer* CBaseRenderBufferPool::GetBuffer(unsigned int width, unsigned int height)
+{
+ if (!m_bConfigured)
+ return nullptr;
+
+ IRenderBuffer* renderBuffer = nullptr;
+
+ void* header = nullptr;
+
+ if (GetHeaderWithTimeout(header))
+ {
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ for (auto it = m_free.begin(); it != m_free.end(); ++it)
+ {
+ std::unique_ptr<IRenderBuffer>& buffer = *it;
+
+ // Only return buffers of the same dimensions
+ const unsigned int bufferWidth = buffer->GetWidth();
+ const unsigned int bufferHeight = buffer->GetHeight();
+
+ if (bufferWidth == width && bufferHeight == height)
+ {
+ renderBuffer = buffer.release();
+ renderBuffer->SetHeader(header);
+ m_free.erase(it);
+ break;
+ }
+ }
+
+ if (renderBuffer == nullptr)
+ {
+ CLog::Log(LOGDEBUG,
+ "RetroPlayer[RENDER]: Creating render buffer of size {}x{} for buffer pool", width,
+ height);
+
+ std::unique_ptr<IRenderBuffer> renderBufferPtr(CreateRenderBuffer(header));
+ if (renderBufferPtr->Allocate(m_format, width, height))
+ renderBuffer = renderBufferPtr.release();
+ else
+ CLog::Log(LOGERROR, "RetroPlayer[RENDER]: Failed to allocate render buffer");
+ }
+
+ if (renderBuffer != nullptr)
+ {
+ renderBuffer->Acquire(GetPtr());
+ renderBuffer->Update();
+ }
+ }
+
+ return renderBuffer;
+}
+
+void CBaseRenderBufferPool::Return(IRenderBuffer* buffer)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ buffer->SetLoaded(false);
+ buffer->SetRendered(false);
+
+ std::unique_ptr<IRenderBuffer> bufferPtr(buffer);
+ m_free.emplace_back(std::move(bufferPtr));
+}
+
+void CBaseRenderBufferPool::Prime(unsigned int width, unsigned int height)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Allocate two buffers for double buffering
+ unsigned int bufferCount = 2;
+
+ std::vector<IRenderBuffer*> buffers;
+
+ for (unsigned int i = 0; i < bufferCount; i++)
+ {
+ IRenderBuffer* buffer = GetBuffer(width, height);
+ if (buffer == nullptr)
+ break;
+
+ if (!SendBuffer(buffer))
+ buffers.emplace_back(buffer);
+ }
+
+ for (auto buffer : buffers)
+ buffer->Release();
+}
+
+void CBaseRenderBufferPool::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ m_free.clear();
+ m_bConfigured = false;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.h b/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.h
new file mode 100644
index 0000000..cda1c7c
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/BaseRenderBufferPool.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IRenderBufferPool.h"
+#include "threads/CriticalSection.h"
+
+#include <deque>
+#include <memory>
+#include <vector>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CBaseRenderBufferPool : public IRenderBufferPool
+{
+public:
+ CBaseRenderBufferPool() = default;
+ ~CBaseRenderBufferPool() override;
+
+ // Partial implementation of IRenderBufferPool
+ void RegisterRenderer(CRPBaseRenderer* renderer) override;
+ void UnregisterRenderer(CRPBaseRenderer* renderer) override;
+ bool HasVisibleRenderer() const override;
+ bool Configure(AVPixelFormat format) override;
+ bool IsConfigured() const override { return m_bConfigured; }
+ IRenderBuffer* GetBuffer(unsigned int width, unsigned int height) override;
+ void Return(IRenderBuffer* buffer) override;
+ void Prime(unsigned int width, unsigned int height) override;
+ void Flush() override;
+
+ // Buffer properties
+ AVPixelFormat Format() const { return m_format; }
+
+protected:
+ virtual IRenderBuffer* CreateRenderBuffer(void* header = nullptr) = 0;
+ virtual bool ConfigureInternal() { return true; }
+ virtual void* GetHeader(unsigned int timeoutMs = 0) { return nullptr; }
+ virtual bool GetHeaderWithTimeout(void*& header)
+ {
+ header = nullptr;
+ return true;
+ }
+ virtual bool SendBuffer(IRenderBuffer* buffer) { return false; }
+
+ // Configuration parameters
+ bool m_bConfigured = false;
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+
+private:
+ // Buffer properties
+ std::deque<std::unique_ptr<IRenderBuffer>> m_free;
+
+ std::vector<CRPBaseRenderer*> m_renderers;
+ mutable CCriticalSection m_rendererMutex;
+ CCriticalSection m_bufferMutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt
new file mode 100644
index 0000000..1dc954a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt
@@ -0,0 +1,34 @@
+set(SOURCES BaseRenderBuffer.cpp
+ BaseRenderBufferPool.cpp
+ RenderBufferManager.cpp
+)
+
+set(HEADERS BaseRenderBuffer.h
+ BaseRenderBufferPool.h
+ IRenderBuffer.h
+ IRenderBufferPool.h
+ RenderBufferManager.h
+)
+
+if(OPENGL_FOUND OR OPENGLES_FOUND)
+ list(APPEND SOURCES RenderBufferOpenGLES.cpp
+ RenderBufferPoolOpenGLES.cpp)
+ list(APPEND HEADERS RenderBufferOpenGLES.h
+ RenderBufferPoolOpenGLES.h)
+endif()
+
+if(OPENGL_FOUND)
+ list(APPEND SOURCES RenderBufferOpenGL.cpp
+ RenderBufferPoolOpenGL.cpp)
+ list(APPEND HEADERS RenderBufferOpenGL.cpp
+ RenderBufferPoolOpenGL.cpp)
+endif()
+
+if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND EGL_FOUND)
+ list(APPEND SOURCES RenderBufferDMA.cpp
+ RenderBufferPoolDMA.cpp)
+ list(APPEND HEADERS RenderBufferDMA.h
+ RenderBufferPoolDMA.h)
+endif()
+
+core_add_library(rp-buffers)
diff --git a/xbmc/cores/RetroPlayer/buffers/IRenderBuffer.h b/xbmc/cores/RetroPlayer/buffers/IRenderBuffer.h
new file mode 100644
index 0000000..579a3e8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/IRenderBuffer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/RetroPlayerTypes.h"
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+#include <memory>
+#include <stdint.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IRenderBufferPool;
+
+class IRenderBuffer
+{
+public:
+ virtual ~IRenderBuffer() = default;
+
+ // Pool functions
+ virtual void Acquire() = 0;
+ virtual void Acquire(std::shared_ptr<IRenderBufferPool> pool) = 0;
+ virtual void Release() = 0;
+ virtual IRenderBufferPool* GetPool() = 0;
+
+ // Buffer functions
+ virtual bool Allocate(AVPixelFormat format, unsigned int width, unsigned int height) = 0;
+ virtual void Update() {} //! @todo Remove me
+ virtual size_t GetFrameSize() const = 0;
+ virtual uint8_t* GetMemory() = 0;
+ virtual DataAccess GetMemoryAccess() const = 0;
+ virtual DataAlignment GetMemoryAlignment() const { return DataAlignment::DATA_UNALIGNED; }
+ virtual void ReleaseMemory() {}
+ virtual bool UploadTexture() = 0;
+ virtual void BindToUnit(unsigned int unit) {}
+ virtual void SetHeader(void* header) {}
+
+ // Buffer properties
+ AVPixelFormat GetFormat() const { return m_format; }
+ unsigned int GetWidth() const { return m_width; }
+ unsigned int GetHeight() const { return m_height; }
+ bool IsLoaded() const { return m_bLoaded; }
+ void SetLoaded(bool bLoaded) { m_bLoaded = bLoaded; }
+ bool IsRendered() const { return m_bRendered; }
+ void SetRendered(bool bRendered) { m_bRendered = bRendered; }
+ unsigned int GetRotation() const { return m_rotationDegCCW; }
+ void SetRotation(unsigned int rotationDegCCW) { m_rotationDegCCW = rotationDegCCW; }
+
+protected:
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+ unsigned int m_width = 0;
+ unsigned int m_height = 0;
+ bool m_bLoaded = false;
+ bool m_bRendered = false;
+ unsigned int m_rotationDegCCW = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/IRenderBufferPool.h b/xbmc/cores/RetroPlayer/buffers/IRenderBufferPool.h
new file mode 100644
index 0000000..f02257d
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/IRenderBufferPool.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+#include <memory>
+#include <stdint.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderBufferManager;
+class CRenderVideoSettings;
+class CRPBaseRenderer;
+class IRenderBuffer;
+
+class IRenderBufferPool : public std::enable_shared_from_this<IRenderBufferPool>
+{
+public:
+ virtual ~IRenderBufferPool() = default;
+
+ virtual void RegisterRenderer(CRPBaseRenderer* renderer) = 0;
+ virtual void UnregisterRenderer(CRPBaseRenderer* renderer) = 0;
+ virtual bool HasVisibleRenderer() const = 0;
+
+ virtual bool Configure(AVPixelFormat format) = 0;
+
+ virtual bool IsConfigured() const = 0;
+
+ virtual bool IsCompatible(const CRenderVideoSettings& renderSettings) const = 0;
+
+ /*!
+ * \brief Get a free buffer from the pool, sets ref count to 1
+ *
+ * \param width The horizontal pixel count of the buffer
+ * \param height The vertical pixel could of the buffer
+ *
+ * \return The allocated buffer, or nullptr on failure
+ */
+ virtual IRenderBuffer* GetBuffer(unsigned int width, unsigned int height) = 0;
+
+ /*!
+ * \brief Called by buffer when ref count goes to zero
+ *
+ * \param buffer A fully dereferenced buffer
+ */
+ virtual void Return(IRenderBuffer* buffer) = 0;
+
+ virtual void Prime(unsigned int width, unsigned int height) = 0;
+
+ virtual void Flush() = 0;
+
+ /*!
+ * \brief Call in GetBuffer() before returning buffer to caller
+ */
+ virtual std::shared_ptr<IRenderBufferPool> GetPtr() { return shared_from_this(); }
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.cpp
new file mode 100644
index 0000000..6277d62
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferDMA.h"
+
+#include "ServiceBroker.h"
+#include "utils/BufferObject.h"
+#include "utils/EGLImage.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+#include "windowing/linux/WinSystemEGL.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferDMA::CRenderBufferDMA(CRenderContext& context, int fourcc)
+ : m_context(context), m_fourcc(fourcc), m_bo(CBufferObject::GetBufferObject(false))
+{
+ auto winSystemEGL =
+ dynamic_cast<KODI::WINDOWING::LINUX::CWinSystemEGL*>(CServiceBroker::GetWinSystem());
+
+ if (winSystemEGL == nullptr)
+ throw std::runtime_error("dynamic_cast failed to cast to CWinSystemEGL. This is likely due to "
+ "a build misconfiguration as DMA can only be used with EGL and "
+ "specifically platforms that implement CWinSystemEGL");
+
+ m_egl = std::make_unique<CEGLImage>(winSystemEGL->GetEGLDisplay());
+
+ CLog::Log(LOGDEBUG, "CRenderBufferDMA: using BufferObject type: {}", m_bo->GetName());
+}
+
+CRenderBufferDMA::~CRenderBufferDMA()
+{
+ DeleteTexture();
+}
+
+bool CRenderBufferDMA::Allocate(AVPixelFormat format, unsigned int width, unsigned int height)
+{
+ // Initialize IRenderBuffer
+ m_format = format;
+ m_width = width;
+ m_height = height;
+
+ m_bo->CreateBufferObject(m_fourcc, m_width, m_height);
+
+ return true;
+}
+
+size_t CRenderBufferDMA::GetFrameSize() const
+{
+ return m_bo->GetStride() * m_height;
+}
+
+uint8_t* CRenderBufferDMA::GetMemory()
+{
+ m_bo->SyncStart();
+ return m_bo->GetMemory();
+}
+
+void CRenderBufferDMA::ReleaseMemory()
+{
+ m_bo->ReleaseMemory();
+ m_bo->SyncEnd();
+}
+
+void CRenderBufferDMA::CreateTexture()
+{
+ glGenTextures(1, &m_textureId);
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+}
+
+bool CRenderBufferDMA::UploadTexture()
+{
+ if (m_bo->GetFd() < 0)
+ return false;
+
+ if (!glIsTexture(m_textureId))
+ CreateTexture();
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ std::array<CEGLImage::EglPlane, CEGLImage::MAX_NUM_PLANES> planes;
+
+ planes[0].fd = m_bo->GetFd();
+ planes[0].offset = 0;
+ planes[0].pitch = m_bo->GetStride();
+ planes[0].modifier = m_bo->GetModifier();
+
+ CEGLImage::EglAttrs attribs;
+
+ attribs.width = m_width;
+ attribs.height = m_height;
+ attribs.format = m_fourcc;
+ attribs.planes = planes;
+
+ if (m_egl->CreateImage(attribs))
+ m_egl->UploadImage(m_textureTarget);
+
+ m_egl->DestroyImage();
+
+ glBindTexture(m_textureTarget, 0);
+
+ return true;
+}
+
+void CRenderBufferDMA::DeleteTexture()
+{
+ if (glIsTexture(m_textureId))
+ glDeleteTextures(1, &m_textureId);
+
+ m_textureId = 0;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.h
new file mode 100644
index 0000000..a007c17
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferDMA.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/buffers/BaseRenderBuffer.h"
+
+#include <memory>
+
+#include "system_gl.h"
+
+class CEGLImage;
+class IBufferObject;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+
+/**
+ * @brief Special IRenderBuffer implementation for use with CBufferObject.
+ * This buffer type uses Direct Memory Access (DMA) sharing via file
+ * descriptors (fds). The file descriptor is then used to create an
+ * EGL image.
+ *
+ */
+class CRenderBufferDMA : public CBaseRenderBuffer
+{
+public:
+ CRenderBufferDMA(CRenderContext& context, int fourcc);
+ ~CRenderBufferDMA() override;
+
+ // implementation of IRenderBuffer via CRenderBufferSysMem
+ bool Allocate(AVPixelFormat format, unsigned int width, unsigned int height) override;
+ size_t GetFrameSize() const override;
+ uint8_t* GetMemory() override;
+ DataAccess GetMemoryAccess() const override { return DataAccess::READ_WRITE; }
+ void ReleaseMemory() override;
+
+ // implementation of IRenderBuffer
+ bool UploadTexture() override;
+
+ GLuint TextureID() const { return m_textureId; }
+
+protected:
+ // Construction parameters
+ CRenderContext& m_context;
+ const int m_fourcc = 0;
+
+ const GLenum m_textureTarget = GL_TEXTURE_2D;
+ GLuint m_textureId = 0;
+
+private:
+ void CreateTexture();
+ void DeleteTexture();
+
+ std::unique_ptr<CEGLImage> m_egl;
+ std::unique_ptr<IBufferObject> m_bo;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.cpp
new file mode 100644
index 0000000..2fcc5bd
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferManager.h"
+
+#include "IRenderBufferPool.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferManager::~CRenderBufferManager()
+{
+ FlushPools();
+}
+
+void CRenderBufferManager::RegisterPools(IRendererFactory* factory, RenderBufferPoolVector pools)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_pools.emplace_back(RenderBufferPools{factory, std::move(pools)});
+}
+
+RenderBufferPoolVector CRenderBufferManager::GetPools(IRendererFactory* factory)
+{
+ RenderBufferPoolVector bufferPools;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ auto it = std::find_if(m_pools.begin(), m_pools.end(), [factory](const RenderBufferPools& pools) {
+ return pools.factory == factory;
+ });
+
+ if (it != m_pools.end())
+ bufferPools = it->pools;
+
+ return bufferPools;
+}
+
+std::vector<IRenderBufferPool*> CRenderBufferManager::GetBufferPools()
+{
+ std::vector<IRenderBufferPool*> bufferPools;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& pools : m_pools)
+ {
+ for (const auto& pool : pools.pools)
+ bufferPools.emplace_back(pool.get());
+ }
+
+ return bufferPools;
+}
+
+void CRenderBufferManager::FlushPools()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& pools : m_pools)
+ {
+ for (const auto& pool : pools.pools)
+ pool->Flush();
+ }
+}
+
+std::string CRenderBufferManager::GetRenderSystemName(IRenderBufferPool* renderBufferPool) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& pools : m_pools)
+ {
+ for (const auto& pool : pools.pools)
+ {
+ if (pool.get() == renderBufferPool)
+ return pools.factory->RenderSystemName();
+ }
+ }
+
+ return "";
+}
+
+bool CRenderBufferManager::HasScalingMethod(SCALINGMETHOD scalingMethod) const
+{
+ CRenderVideoSettings videoSettings;
+ videoSettings.SetScalingMethod(scalingMethod);
+
+ for (const auto& pools : m_pools)
+ {
+ for (const auto& pool : pools.pools)
+ if (pool->IsCompatible(videoSettings))
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.h
new file mode 100644
index 0000000..de69cad
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferManager.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/RetroPlayerTypes.h"
+#include "threads/CriticalSection.h"
+
+#include <memory>
+#include <vector>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IRendererFactory;
+class IRenderBufferPools;
+
+class CRenderBufferManager
+{
+public:
+ CRenderBufferManager() = default;
+ ~CRenderBufferManager();
+
+ void RegisterPools(IRendererFactory* factory, RenderBufferPoolVector pools);
+ RenderBufferPoolVector GetPools(IRendererFactory* factory);
+ std::vector<IRenderBufferPool*> GetBufferPools();
+ void FlushPools();
+
+ std::string GetRenderSystemName(IRenderBufferPool* renderBufferPool) const;
+
+ bool HasScalingMethod(SCALINGMETHOD scalingMethod) const;
+
+protected:
+ struct RenderBufferPools
+ {
+ IRendererFactory* factory;
+ RenderBufferPoolVector pools;
+ };
+
+ std::vector<RenderBufferPools> m_pools;
+ mutable CCriticalSection m_critSection;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.cpp
new file mode 100644
index 0000000..d852523
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferOpenGL.h"
+
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferOpenGL::CRenderBufferOpenGL(GLuint pixeltype,
+ GLuint internalformat,
+ GLuint pixelformat,
+ GLuint bpp)
+ : m_pixeltype(pixeltype), m_internalformat(internalformat), m_pixelformat(pixelformat), m_bpp(bpp)
+{
+}
+
+CRenderBufferOpenGL::~CRenderBufferOpenGL()
+{
+ DeleteTexture();
+}
+
+void CRenderBufferOpenGL::CreateTexture()
+{
+ glGenTextures(1, &m_textureId);
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ glTexImage2D(m_textureTarget, 0, m_internalformat, m_width, m_height, 0, m_pixelformat,
+ m_pixeltype, NULL);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+}
+
+bool CRenderBufferOpenGL::UploadTexture()
+{
+ if (!glIsTexture(m_textureId))
+ CreateTexture();
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ const int stride = GetFrameSize() / m_height;
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, m_bpp);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / m_bpp);
+
+ //! @todo This is subject to change:
+ //! We want to use PBO's instead of glTexSubImage2D!
+ //! This code has been borrowed from OpenGL ES in order
+ //! to remove GL dependencies on GLES.
+ glTexSubImage2D(m_textureTarget, 0, 0, 0, m_width, m_height, m_pixelformat, m_pixeltype,
+ m_data.data());
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ return true;
+}
+
+void CRenderBufferOpenGL::DeleteTexture()
+{
+ if (glIsTexture(m_textureId))
+ glDeleteTextures(1, &m_textureId);
+
+ m_textureId = 0;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.h
new file mode 100644
index 0000000..9fcb9e2
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGL.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/buffers/video/RenderBufferSysMem.h"
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+
+class CRenderBufferOpenGL : public CRenderBufferSysMem
+{
+public:
+ CRenderBufferOpenGL(GLuint pixeltype, GLuint internalformat, GLuint pixelformat, GLuint bpp);
+ ~CRenderBufferOpenGL() override;
+
+ bool UploadTexture() override;
+ GLuint TextureID() const { return m_textureId; }
+
+private:
+ // Construction parameters
+ const GLuint m_pixeltype;
+ const GLuint m_internalformat;
+ const GLuint m_pixelformat;
+ const GLuint m_bpp;
+
+ const GLenum m_textureTarget = GL_TEXTURE_2D; //! @todo
+ GLuint m_textureId = 0;
+
+ void CreateTexture();
+ void DeleteTexture();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.cpp
new file mode 100644
index 0000000..8ed366e
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferOpenGLES.h"
+
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferOpenGLES::CRenderBufferOpenGLES(CRenderContext& context,
+ GLuint pixeltype,
+ GLuint internalformat,
+ GLuint pixelformat,
+ GLuint bpp)
+ : m_context(context),
+ m_pixeltype(pixeltype),
+ m_internalformat(internalformat),
+ m_pixelformat(pixelformat),
+ m_bpp(bpp)
+{
+}
+
+CRenderBufferOpenGLES::~CRenderBufferOpenGLES()
+{
+ DeleteTexture();
+}
+
+void CRenderBufferOpenGLES::CreateTexture()
+{
+ glGenTextures(1, &m_textureId);
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ glTexImage2D(m_textureTarget, 0, m_internalformat, m_width, m_height, 0, m_pixelformat,
+ m_pixeltype, NULL);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+}
+
+bool CRenderBufferOpenGLES::UploadTexture()
+{
+ if (!glIsTexture(m_textureId))
+ CreateTexture();
+
+ glBindTexture(m_textureTarget, m_textureId);
+
+ const int stride = GetFrameSize() / m_height;
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, m_bpp);
+
+ if (m_bpp == 4 && m_pixelformat == GL_RGBA)
+ {
+ // XOR Swap RGBA -> BGRA
+ // GLES 2.0 doesn't support strided textures (unless GL_UNPACK_ROW_LENGTH_EXT is supported)
+ uint8_t* pixels = const_cast<uint8_t*>(m_data.data());
+ for (unsigned int y = 0; y < m_height; ++y, pixels += stride)
+ {
+ for (int x = 0; x < stride; x += 4)
+ std::swap(pixels[x], pixels[x + 2]);
+ glTexSubImage2D(m_textureTarget, 0, 0, y, m_width, 1, m_pixelformat, m_pixeltype, pixels);
+ }
+ }
+ else if (m_context.IsExtSupported("GL_EXT_unpack_subimage"))
+ {
+#ifdef GL_UNPACK_ROW_LENGTH_EXT
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / m_bpp);
+ glTexSubImage2D(m_textureTarget, 0, 0, 0, m_width, m_height, m_pixelformat, m_pixeltype,
+ m_data.data());
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+#endif
+ }
+ else
+ {
+ uint8_t* pixels = const_cast<uint8_t*>(m_data.data());
+ for (unsigned int y = 0; y < m_height; ++y, pixels += stride)
+ glTexSubImage2D(m_textureTarget, 0, 0, y, m_width, 1, m_pixelformat, m_pixeltype, pixels);
+ }
+
+ glBindTexture(m_textureTarget, 0);
+
+ return true;
+}
+
+void CRenderBufferOpenGLES::DeleteTexture()
+{
+ if (glIsTexture(m_textureId))
+ glDeleteTextures(1, &m_textureId);
+
+ m_textureId = 0;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.h
new file mode 100644
index 0000000..58e5d47
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferOpenGLES.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/buffers/video/RenderBufferSysMem.h"
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+
+class CRenderBufferOpenGLES : public CRenderBufferSysMem
+{
+public:
+ CRenderBufferOpenGLES(CRenderContext& context,
+ GLuint pixeltype,
+ GLuint internalformat,
+ GLuint pixelformat,
+ GLuint bpp);
+ ~CRenderBufferOpenGLES() override;
+
+ // implementation of IRenderBuffer via CRenderBufferSysMem
+ bool UploadTexture() override;
+
+ GLuint TextureID() const { return m_textureId; }
+
+private:
+ // Construction parameters
+ CRenderContext& m_context;
+ const GLuint m_pixeltype;
+ const GLuint m_internalformat;
+ const GLuint m_pixelformat;
+ const GLuint m_bpp;
+
+ const GLenum m_textureTarget = GL_TEXTURE_2D; //! @todo
+ GLuint m_textureId = 0;
+
+ void CreateTexture();
+ void DeleteTexture();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.cpp
new file mode 100644
index 0000000..42c47a3
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferPoolDMA.h"
+
+#include "RenderBufferDMA.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h"
+
+#include <drm_fourcc.h>
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferPoolDMA::CRenderBufferPoolDMA(CRenderContext& context) : m_context(context)
+{
+}
+
+bool CRenderBufferPoolDMA::IsCompatible(const CRenderVideoSettings& renderSettings) const
+{
+ if (!CRPRendererDMA::SupportsScalingMethod(renderSettings.GetScalingMethod()))
+ return false;
+
+ return true;
+}
+
+IRenderBuffer* CRenderBufferPoolDMA::CreateRenderBuffer(void* header /* = nullptr */)
+{
+ return new CRenderBufferDMA(m_context, m_fourcc);
+}
+
+bool CRenderBufferPoolDMA::ConfigureInternal()
+{
+ switch (m_format)
+ {
+ case AV_PIX_FMT_0RGB32:
+ {
+ m_fourcc = DRM_FORMAT_ARGB8888;
+ return true;
+ }
+ case AV_PIX_FMT_RGB555:
+ {
+ m_fourcc = DRM_FORMAT_ARGB1555;
+ return true;
+ }
+ case AV_PIX_FMT_RGB565:
+ {
+ m_fourcc = DRM_FORMAT_RGB565;
+ return true;
+ }
+ default:
+ break; // we shouldn't even get this far if we are given an unsupported pixel format
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.h
new file mode 100644
index 0000000..81645bb
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolDMA.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/buffers/BaseRenderBufferPool.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+
+/**
+ * @brief Special IRenderBufferPool implementation that converts
+ * AVPixelFormat to DRM_FORMAT_* for use with CRenderBufferDMA.
+ *
+ */
+class CRenderBufferPoolDMA : public CBaseRenderBufferPool
+{
+public:
+ CRenderBufferPoolDMA(CRenderContext& context);
+ ~CRenderBufferPoolDMA() override = default;
+
+ // implementation of IRenderBufferPool via CBaseRenderBufferPool
+ bool IsCompatible(const CRenderVideoSettings& renderSettings) const override;
+
+protected:
+ // implementation of CBaseRenderBufferPool
+ IRenderBuffer* CreateRenderBuffer(void* header = nullptr) override;
+ bool ConfigureInternal() override;
+
+ // Construction parameters
+ CRenderContext& m_context;
+
+ // Configuration parameters
+ int m_fourcc = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.cpp
new file mode 100644
index 0000000..6d30346
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferPoolOpenGL.h"
+
+#include "RenderBufferOpenGL.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "utils/GLUtils.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+bool CRenderBufferPoolOpenGL::IsCompatible(const CRenderVideoSettings& renderSettings) const
+{
+ return CRPRendererOpenGL::SupportsScalingMethod(renderSettings.GetScalingMethod());
+}
+
+IRenderBuffer* CRenderBufferPoolOpenGL::CreateRenderBuffer(void* header /* = nullptr */)
+{
+ return new CRenderBufferOpenGL(m_pixeltype, m_internalformat, m_pixelformat, m_bpp);
+}
+
+bool CRenderBufferPoolOpenGL::ConfigureInternal()
+{
+ // Configure CRenderBufferPoolOpenGLES
+ switch (m_format)
+ {
+ case AV_PIX_FMT_0RGB32:
+ {
+ m_pixeltype = GL_UNSIGNED_BYTE;
+ m_internalformat = GL_RGBA;
+ m_pixelformat = GL_BGRA;
+ m_bpp = sizeof(uint32_t);
+ return true;
+ }
+ case AV_PIX_FMT_RGB555:
+ {
+ m_pixeltype = GL_UNSIGNED_SHORT_5_5_5_1;
+ m_internalformat = GL_RGB;
+ m_pixelformat = GL_RGB;
+ m_bpp = sizeof(uint16_t);
+ return true;
+ }
+ case AV_PIX_FMT_RGB565:
+ {
+ m_pixeltype = GL_UNSIGNED_SHORT_5_6_5;
+ m_internalformat = GL_RGB;
+ m_pixelformat = GL_RGB;
+ m_bpp = sizeof(uint16_t);
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h
new file mode 100644
index 0000000..e8689b9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BaseRenderBufferPool.h"
+#include "IRenderBuffer.h"
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+class CRenderVideoSettings;
+
+class CRenderBufferPoolOpenGL : public CBaseRenderBufferPool
+{
+public:
+ CRenderBufferPoolOpenGL() = default;
+ ~CRenderBufferPoolOpenGL() override = default;
+
+ // implementation of IRenderBufferPool via CBaseRenderBufferPool
+ bool IsCompatible(const CRenderVideoSettings& renderSettings) const override;
+
+protected:
+ // implementation of CBaseRenderBufferPool
+ IRenderBuffer* CreateRenderBuffer(void* header = nullptr) override;
+ bool ConfigureInternal() override;
+
+private:
+ // Configuration parameters
+ GLuint m_pixeltype = 0;
+ GLuint m_internalformat = 0;
+ GLuint m_pixelformat = 0;
+ GLuint m_bpp = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.cpp b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.cpp
new file mode 100644
index 0000000..c7aa8f6
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferPoolOpenGLES.h"
+
+#include "RenderBufferOpenGLES.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "utils/GLUtils.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferPoolOpenGLES::CRenderBufferPoolOpenGLES(CRenderContext& context) : m_context(context)
+{
+}
+
+bool CRenderBufferPoolOpenGLES::IsCompatible(const CRenderVideoSettings& renderSettings) const
+{
+ return CRPRendererOpenGLES::SupportsScalingMethod(renderSettings.GetScalingMethod());
+}
+
+IRenderBuffer* CRenderBufferPoolOpenGLES::CreateRenderBuffer(void* header /* = nullptr */)
+{
+ return new CRenderBufferOpenGLES(m_context, m_pixeltype, m_internalformat, m_pixelformat, m_bpp);
+}
+
+bool CRenderBufferPoolOpenGLES::ConfigureInternal()
+{
+ switch (m_format)
+ {
+ case AV_PIX_FMT_0RGB32:
+ {
+ m_pixeltype = GL_UNSIGNED_BYTE;
+ if (m_context.IsExtSupported("GL_EXT_texture_format_BGRA8888") ||
+ m_context.IsExtSupported("GL_IMG_texture_format_BGRA8888"))
+ {
+ m_internalformat = GL_BGRA_EXT;
+ m_pixelformat = GL_BGRA_EXT;
+ }
+ else if (m_context.IsExtSupported("GL_APPLE_texture_format_BGRA8888"))
+ {
+ // Apple's implementation does not conform to spec. Instead, they require
+ // differing format/internalformat, more like GL.
+ m_internalformat = GL_RGBA;
+ m_pixelformat = GL_BGRA_EXT;
+ }
+ else
+ {
+ m_internalformat = GL_RGBA;
+ m_pixelformat = GL_RGBA;
+ }
+ m_bpp = sizeof(uint32_t);
+ return true;
+ }
+ case AV_PIX_FMT_RGB555:
+ case AV_PIX_FMT_RGB565:
+ {
+ m_pixeltype = GL_UNSIGNED_SHORT_5_6_5;
+ m_internalformat = GL_RGB;
+ m_pixelformat = GL_RGB;
+ m_bpp = sizeof(uint16_t);
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.h b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.h
new file mode 100644
index 0000000..b511cdb
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BaseRenderBufferPool.h"
+#include "IRenderBuffer.h"
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+class CRenderVideoSettings;
+
+class CRenderBufferPoolOpenGLES : public CBaseRenderBufferPool
+{
+public:
+ CRenderBufferPoolOpenGLES(CRenderContext& context);
+ ~CRenderBufferPoolOpenGLES() override = default;
+
+ // implementation of IRenderBufferPool via CBaseRenderBufferPool
+ bool IsCompatible(const CRenderVideoSettings& renderSettings) const override;
+
+private:
+ // implementation of CBaseRenderBufferPool
+ IRenderBuffer* CreateRenderBuffer(void* header = nullptr) override;
+ bool ConfigureInternal() override;
+
+ // Construction parameters
+ CRenderContext& m_context;
+
+ // Configuration parameters
+ GLuint m_pixeltype = 0;
+ GLuint m_internalformat = 0;
+ GLuint m_pixelformat = 0;
+ GLuint m_bpp = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/video/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/video/CMakeLists.txt
new file mode 100644
index 0000000..797ab90
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/video/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES RenderBufferGuiTexture.cpp
+ RenderBufferSysMem.cpp
+)
+
+set(HEADERS RenderBufferGuiTexture.h
+ RenderBufferSysMem.h
+)
+
+core_add_library(rp-buffers-video)
diff --git a/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.cpp b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.cpp
new file mode 100644
index 0000000..9f8e05b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferGuiTexture.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderBufferGuiTexture::CRenderBufferGuiTexture(SCALINGMETHOD scalingMethod)
+ : m_scalingMethod(scalingMethod)
+{
+ m_textureFormat = XB_FMT_A8R8G8B8;
+}
+
+bool CRenderBufferGuiTexture::Allocate(AVPixelFormat format,
+ unsigned int width,
+ unsigned int height)
+{
+ // Initialize IRenderBuffer
+ m_format = TranslateFormat(m_textureFormat);
+
+ if (m_format != AV_PIX_FMT_NONE)
+ {
+ m_texture = CTexture::CreateTexture(width, height, m_textureFormat);
+ m_texture->SetScalingMethod(TranslateScalingMethod(m_scalingMethod));
+ m_texture->SetCacheMemory(true);
+
+ // Update IRenderBuffer
+ m_width = m_texture->GetTextureWidth();
+ m_height = m_texture->GetTextureHeight();
+
+ return true;
+ }
+
+ return false;
+}
+
+size_t CRenderBufferGuiTexture::GetFrameSize() const
+{
+ if (m_texture)
+ return m_texture->GetPitch() * m_texture->GetRows();
+
+ return 0;
+}
+
+uint8_t* CRenderBufferGuiTexture::GetMemory()
+{
+ if (m_texture)
+ return m_texture->GetPixels();
+
+ return nullptr;
+}
+
+bool CRenderBufferGuiTexture::UploadTexture()
+{
+ bool bLoaded = false;
+
+ if (m_texture)
+ {
+ m_texture->LoadToGPU();
+ bLoaded = true;
+ }
+
+ return bLoaded;
+}
+
+void CRenderBufferGuiTexture::BindToUnit(unsigned int unit)
+{
+ if (m_texture)
+ m_texture->BindToUnit(unit);
+}
+
+AVPixelFormat CRenderBufferGuiTexture::TranslateFormat(unsigned int textureFormat)
+{
+ switch (textureFormat)
+ {
+ case XB_FMT_RGBA8:
+ case XB_FMT_A8R8G8B8:
+ return AV_PIX_FMT_BGRA;
+ default:
+ break;
+ }
+
+ return AV_PIX_FMT_NONE;
+}
+
+TEXTURE_SCALING CRenderBufferGuiTexture::TranslateScalingMethod(SCALINGMETHOD scalingMethod)
+{
+ switch (scalingMethod)
+ {
+ case SCALINGMETHOD::NEAREST:
+ return TEXTURE_SCALING::NEAREST;
+ case SCALINGMETHOD::LINEAR:
+ return TEXTURE_SCALING::LINEAR;
+ default:
+ break;
+ }
+
+ return TEXTURE_SCALING::NEAREST;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.h b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.h
new file mode 100644
index 0000000..b78d157
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/buffers/BaseRenderBuffer.h"
+#include "guilib/Texture.h"
+#include "guilib/TextureFormats.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderBufferGuiTexture : public CBaseRenderBuffer
+{
+public:
+ CRenderBufferGuiTexture(SCALINGMETHOD scalingMethod);
+ ~CRenderBufferGuiTexture() override = default;
+
+ // implementation of IRenderBuffer via CBaseRenderBuffer
+ bool Allocate(AVPixelFormat format, unsigned int width, unsigned int height) override;
+ size_t GetFrameSize() const override;
+ uint8_t* GetMemory() override;
+ DataAccess GetMemoryAccess() const override { return DataAccess::READ_WRITE; }
+ bool UploadTexture() override;
+ void BindToUnit(unsigned int unit) override;
+
+ // GUI texture interface
+ CTexture* GetTexture() { return m_texture.get(); }
+
+protected:
+ AVPixelFormat TranslateFormat(unsigned int textureFormat);
+ TEXTURE_SCALING TranslateScalingMethod(SCALINGMETHOD scalingMethod);
+
+ // Texture parameters
+ SCALINGMETHOD m_scalingMethod;
+ unsigned int m_textureFormat = XB_FMT_UNKNOWN;
+ std::unique_ptr<CTexture> m_texture;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.cpp b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.cpp
new file mode 100644
index 0000000..8fb4260
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderBufferSysMem.h"
+
+#include "cores/RetroPlayer/rendering/RenderTranslator.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+bool CRenderBufferSysMem::Allocate(AVPixelFormat format, unsigned int width, unsigned int height)
+{
+ // Initialize IRenderBuffer
+ m_format = format;
+ m_width = width;
+ m_height = height;
+
+ const size_t size = GetBufferSize(format, width, height);
+
+ if (m_format != AV_PIX_FMT_NONE && size > 0)
+ {
+ // Allocate memory
+ m_data.resize(size);
+ return true;
+ }
+
+ return false;
+}
+
+size_t CRenderBufferSysMem::GetFrameSize() const
+{
+ return m_data.size();
+}
+
+uint8_t* CRenderBufferSysMem::GetMemory()
+{
+ return m_data.data();
+}
+
+size_t CRenderBufferSysMem::GetBufferSize(AVPixelFormat format,
+ unsigned int width,
+ unsigned int height)
+{
+ const size_t bufferStride = CRenderTranslator::TranslateWidthToBytes(width, format);
+ const size_t bufferSize = bufferStride * height;
+
+ return bufferSize;
+}
diff --git a/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.h b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.h
new file mode 100644
index 0000000..c88ce94
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/buffers/video/RenderBufferSysMem.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/buffers/BaseRenderBuffer.h"
+
+#include <stdint.h>
+#include <vector>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderBufferSysMem : public CBaseRenderBuffer
+{
+public:
+ CRenderBufferSysMem() = default;
+ ~CRenderBufferSysMem() override = default;
+
+ // implementation of IRenderBuffer
+ bool Allocate(AVPixelFormat format, unsigned int width, unsigned int height) override;
+ size_t GetFrameSize() const override;
+ uint8_t* GetMemory() override;
+ DataAccess GetMemoryAccess() const override { return DataAccess::READ_WRITE; }
+
+ // Utility functions
+ static size_t GetBufferSize(AVPixelFormat format, unsigned int width, unsigned int height);
+
+protected:
+ std::vector<uint8_t> m_data;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt b/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt
new file mode 100644
index 0000000..0e1bf23
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES Cheevos.cpp)
+
+set(HEADERS Cheevos.h
+ RConsoleIDs.h)
+
+core_add_library(retroplayer_cheevos)
diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp
new file mode 100644
index 0000000..fe82245
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Cheevos.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/CurlFile.h"
+#include "filesystem/File.h"
+#include "games/addons/GameClient.h"
+#include "games/addons/cheevos/GameClientCheevos.h"
+#include "games/tags/GameInfoTag.h"
+#include "messaging/ApplicationMessenger.h"
+#include "utils/JSONVariantParser.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "vector"
+
+using namespace KODI;
+using namespace RETRO;
+
+namespace
+{
+// API JSON Field names
+constexpr auto SUCCESS = "Success";
+constexpr auto GAME_ID = "GameID";
+constexpr auto PATCH_DATA = "PatchData";
+constexpr auto RICH_PRESENCE = "RichPresencePatch";
+constexpr auto GAME_TITLE = "Title";
+constexpr auto PUBLISHER = "Publisher";
+constexpr auto DEVELOPER = "Developer";
+constexpr auto GENRE = "Genre";
+constexpr auto CONSOLE_NAME = "ConsoleName";
+
+constexpr int RESPONSE_SIZE = 64;
+} // namespace
+
+CCheevos::CCheevos(GAME::CGameClient* gameClient,
+ const std::string& userName,
+ const std::string& loginToken)
+ : m_gameClient(gameClient), m_userName(userName), m_loginToken(loginToken)
+{
+}
+
+void CCheevos::ResetRuntime()
+{
+ m_gameClient->Cheevos().RCResetRuntime();
+}
+
+bool CCheevos::LoadData()
+{
+ if (m_userName.empty() || m_loginToken.empty())
+ return false;
+
+ if (m_romHash.empty())
+ {
+ m_consoleID = ConsoleID();
+ if (m_consoleID == RConsoleID::RC_INVALID_ID)
+ return false;
+
+ std::string hash;
+ if (!m_gameClient->Cheevos().RCGenerateHashFromFile(hash, m_consoleID,
+ m_gameClient->GetGamePath().c_str()))
+ {
+ return false;
+ }
+
+ m_romHash = hash;
+ }
+
+ std::string requestURL;
+
+ if (!m_gameClient->Cheevos().RCGetGameIDUrl(requestURL, m_romHash))
+ return false;
+
+ XFILE::CFile response;
+ response.CURLCreate(requestURL);
+ response.CURLOpen(0);
+
+ char responseStr[RESPONSE_SIZE];
+ response.ReadString(responseStr, RESPONSE_SIZE);
+
+ response.Close();
+
+ CVariant data(CVariant::VariantTypeObject);
+ CJSONVariantParser::Parse(responseStr, data);
+
+ if (!data[SUCCESS].asBoolean())
+ return false;
+
+ m_gameID = data[GAME_ID].asUnsignedInteger32();
+
+ // For some reason RetroAchievements returns Success = true when the hash isn't found
+ if (m_gameID == 0)
+ return false;
+
+ if (!m_gameClient->Cheevos().RCGetPatchFileUrl(requestURL, m_userName, m_loginToken, m_gameID))
+ return false;
+
+ CURL curl(requestURL);
+ std::vector<uint8_t> patchData;
+ response.LoadFile(curl, patchData);
+
+ std::string strResponse(patchData.begin(), patchData.end());
+ CJSONVariantParser::Parse(strResponse, data);
+
+ if (!data[SUCCESS].asBoolean())
+ return false;
+
+ m_richPresenceScript = data[PATCH_DATA][RICH_PRESENCE].asString();
+ m_richPresenceLoaded = true;
+
+ std::unique_ptr<CFileItem> file{std::make_unique<CFileItem>()};
+
+ GAME::CGameInfoTag& tag = *file->GetGameInfoTag();
+ tag.SetTitle(data[PATCH_DATA][GAME_TITLE].asString());
+ tag.SetPublisher(data[PATCH_DATA][PUBLISHER].asString());
+ tag.SetDeveloper(data[PATCH_DATA][DEVELOPER].asString());
+ tag.SetGenres({data[PATCH_DATA][GENRE].asString()});
+ tag.SetPlatform(data[PATCH_DATA][CONSOLE_NAME].asString());
+
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_UPDATE_PLAYER_ITEM, -1, -1,
+ static_cast<void*>(file.release()));
+
+ return true;
+}
+
+void CCheevos::EnableRichPresence()
+{
+ if (!m_richPresenceLoaded)
+ {
+ if (!LoadData())
+ {
+ CLog::Log(LOGERROR, "Cheevos: Couldn't load patch file");
+ return;
+ }
+ }
+
+ m_gameClient->Cheevos().RCEnableRichPresence(m_richPresenceScript);
+ m_richPresenceScript.clear();
+}
+
+std::string CCheevos::GetRichPresenceEvaluation()
+{
+ if (!m_richPresenceLoaded)
+ {
+ CLog::Log(LOGERROR, "Cheevos: Rich Presence script was not found");
+ return "";
+ }
+
+ std::string evaluation;
+ m_gameClient->Cheevos().RCGetRichPresenceEvaluation(evaluation, m_consoleID);
+
+ std::string url;
+ std::string postData;
+ if (m_gameClient->Cheevos().RCPostRichPresenceUrl(url, postData, m_userName, m_loginToken,
+ m_gameID, evaluation))
+ {
+ XFILE::CCurlFile curl;
+ std::string res;
+ curl.Post(url, postData, res);
+ }
+
+ return evaluation;
+}
+
+RConsoleID CCheevos::ConsoleID()
+{
+ const std::string extension = URIUtils::GetExtension(m_gameClient->GetGamePath());
+ auto it = m_extensionToConsole.find(extension);
+
+ if (it == m_extensionToConsole.end())
+ return RConsoleID::RC_INVALID_ID;
+
+ return it->second;
+}
diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.h b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h
new file mode 100644
index 0000000..70ffe41
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RConsoleIDs.h"
+
+#include <cstdint>
+#include <map>
+#include <string>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClient;
+}
+
+namespace RETRO
+{
+class CCheevos
+{
+public:
+ CCheevos(GAME::CGameClient* gameClient,
+ const std::string& userName,
+ const std::string& loginToken);
+ void ResetRuntime();
+ void EnableRichPresence();
+ std::string GetRichPresenceEvaluation();
+
+private:
+ bool LoadData();
+ RConsoleID ConsoleID();
+
+ GAME::CGameClient* const m_gameClient;
+ std::string m_userName;
+ std::string m_loginToken;
+ std::string m_romHash;
+ std::string m_richPresenceScript;
+ uint32_t m_gameID{};
+ RConsoleID m_consoleID = RConsoleID::RC_INVALID_ID;
+ bool m_richPresenceLoaded{};
+
+ const std::map<std::string, RConsoleID> m_extensionToConsole = {
+ {".a26", RConsoleID::RC_CONSOLE_ATARI_2600},
+ {".a78", RConsoleID::RC_CONSOLE_ATARI_7800},
+ {".agb", RConsoleID::RC_CONSOLE_GAMEBOY_ADVANCE},
+ {".cdi", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".cdt", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".cgb", RConsoleID::RC_CONSOLE_GAMEBOY_COLOR},
+ {".chd", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".cpr", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".d64", RConsoleID::RC_CONSOLE_COMMODORE_64},
+ {".gb", RConsoleID::RC_CONSOLE_GAMEBOY},
+ {".gba", RConsoleID::RC_CONSOLE_GAMEBOY_ADVANCE},
+ {".gbc", RConsoleID::RC_CONSOLE_GAMEBOY_COLOR},
+ {".gdi", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".j64", RConsoleID::RC_CONSOLE_ATARI_JAGUAR},
+ {".jag", RConsoleID::RC_CONSOLE_ATARI_JAGUAR},
+ {".lnx", RConsoleID::RC_CONSOLE_ATARI_LYNX},
+ {".mds", RConsoleID::RC_CONSOLE_SATURN},
+ {".min", RConsoleID::RC_CONSOLE_POKEMON_MINI},
+ {".mx1", RConsoleID::RC_CONSOLE_MSX},
+ {".mx2", RConsoleID::RC_CONSOLE_MSX},
+ {".n64", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".ndd", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".nds", RConsoleID::RC_CONSOLE_NINTENDO_DS},
+ {".nes", RConsoleID::RC_CONSOLE_NINTENDO},
+ {".o", RConsoleID::RC_CONSOLE_ATARI_LYNX},
+ {".pce", RConsoleID::RC_CONSOLE_PC_ENGINE},
+ {".sfc", RConsoleID::RC_CONSOLE_SUPER_NINTENDO},
+ {".sgx", RConsoleID::RC_CONSOLE_PC_ENGINE},
+ {".smc", RConsoleID::RC_CONSOLE_SUPER_NINTENDO},
+ {".sna", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".tap", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".u1", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".v64", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".vb", RConsoleID::RC_CONSOLE_VIRTUAL_BOY},
+ {".vboy", RConsoleID::RC_CONSOLE_VIRTUAL_BOY},
+ {".vec", RConsoleID::RC_CONSOLE_VECTREX},
+ {".voc", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".z64", RConsoleID::RC_CONSOLE_NINTENDO_64}};
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h b/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h
new file mode 100644
index 0000000..c6c9d5a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+enum class RConsoleID
+{
+ RC_INVALID_ID = -1,
+ RC_CONSOLE_MEGA_DRIVE = 1,
+ RC_CONSOLE_NINTENDO_64 = 2,
+ RC_CONSOLE_SUPER_NINTENDO = 3,
+ RC_CONSOLE_GAMEBOY = 4,
+ RC_CONSOLE_GAMEBOY_ADVANCE = 5,
+ RC_CONSOLE_GAMEBOY_COLOR = 6,
+ RC_CONSOLE_NINTENDO = 7,
+ RC_CONSOLE_PC_ENGINE = 8,
+ RC_CONSOLE_SEGA_CD = 9,
+ RC_CONSOLE_SEGA_32X = 10,
+ RC_CONSOLE_MASTER_SYSTEM = 11,
+ RC_CONSOLE_PLAYSTATION = 12,
+ RC_CONSOLE_ATARI_LYNX = 13,
+ RC_CONSOLE_NEOGEO_POCKET = 14,
+ RC_CONSOLE_GAME_GEAR = 15,
+ RC_CONSOLE_GAMECUBE = 16,
+ RC_CONSOLE_ATARI_JAGUAR = 17,
+ RC_CONSOLE_NINTENDO_DS = 18,
+ RC_CONSOLE_WII = 19,
+ RC_CONSOLE_WII_U = 20,
+ RC_CONSOLE_PLAYSTATION_2 = 21,
+ RC_CONSOLE_XBOX = 22,
+ RC_CONSOLE_MAGNAVOX_ODYSSEY = 23,
+ RC_CONSOLE_POKEMON_MINI = 24,
+ RC_CONSOLE_ATARI_2600 = 25,
+ RC_CONSOLE_MS_DOS = 26,
+ RC_CONSOLE_ARCADE = 27,
+ RC_CONSOLE_VIRTUAL_BOY = 28,
+ RC_CONSOLE_MSX = 29,
+ RC_CONSOLE_COMMODORE_64 = 30,
+ RC_CONSOLE_ZX81 = 31,
+ RC_CONSOLE_ORIC = 32,
+ RC_CONSOLE_SG1000 = 33,
+ RC_CONSOLE_VIC20 = 34,
+ RC_CONSOLE_AMIGA = 35,
+ RC_CONSOLE_AMIGA_ST = 36,
+ RC_CONSOLE_AMSTRAD_PC = 37,
+ RC_CONSOLE_APPLE_II = 38,
+ RC_CONSOLE_SATURN = 39,
+ RC_CONSOLE_DREAMCAST = 40,
+ RC_CONSOLE_PSP = 41,
+ RC_CONSOLE_CDI = 42,
+ RC_CONSOLE_3DO = 43,
+ RC_CONSOLE_COLECOVISION = 44,
+ RC_CONSOLE_INTELLIVISION = 45,
+ RC_CONSOLE_VECTREX = 46,
+ RC_CONSOLE_PC8800 = 47,
+ RC_CONSOLE_PC9800 = 48,
+ RC_CONSOLE_PCFX = 49,
+ RC_CONSOLE_ATARI_5200 = 50,
+ RC_CONSOLE_ATARI_7800 = 51,
+ RC_CONSOLE_X68K = 52,
+ RC_CONSOLE_WONDERSWAN = 53,
+ RC_CONSOLE_CASSETTEVISION = 54,
+ RC_CONSOLE_SUPER_CASSETTEVISION = 55,
+ RC_CONSOLE_NEO_GEO_CD = 56,
+ RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57,
+ RC_CONSOLE_FM_TOWNS = 58,
+ RC_CONSOLE_ZX_SPECTRUM = 59,
+ RC_CONSOLE_GAME_AND_WATCH = 60,
+ RC_CONSOLE_NOKIA_NGAGE = 61,
+ RC_CONSOLE_NINTENDO_3DS = 62,
+
+ RC_CONSOLE_HUBS = 100,
+ RC_CONSOLE_EVENTS = 101
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/CMakeLists.txt b/xbmc/cores/RetroPlayer/guibridge/CMakeLists.txt
new file mode 100644
index 0000000..5ba7579
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(SOURCES GUIGameMessenger.cpp
+ GUIGameRenderManager.cpp
+ GUIGameSettings.cpp
+ GUIGameSettingsHandle.cpp
+ GUIGameVideoHandle.cpp
+ GUIRenderHandle.cpp
+ GUIRenderTarget.cpp
+ GUIRenderTargetFactory.cpp
+)
+
+set(HEADERS GUIGameMessenger.h
+ GUIGameRenderManager.h
+ GUIGameSettings.h
+ GUIGameSettingsHandle.h
+ GUIGameVideoHandle.h
+ GUIRenderHandle.h
+ GUIRenderTarget.h
+ GUIRenderTargetFactory.h
+ IGameCallback.h
+ IGUIRenderSettings.h
+ IRenderCallback.h
+)
+
+core_add_library(retroplayer_guibridge)
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.cpp
new file mode 100644
index 0000000..ff7efa3
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameMessenger.h"
+
+#include "FileItem.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameMessenger::CGUIGameMessenger(CRPProcessInfo& processInfo)
+ : m_guiComponent(processInfo.GetRenderContext().GUI())
+{
+}
+
+void CGUIGameMessenger::RefreshSavestates(const std::string& savestatePath /* = "" */,
+ ISavestate* savestate /* = nullptr */)
+{
+ if (m_guiComponent != nullptr)
+ {
+ CGUIMessage message(GUI_MSG_REFRESH_THUMBS, 0, WINDOW_DIALOG_IN_GAME_SAVES);
+
+ // Add path, if given
+ if (!savestatePath.empty())
+ message.SetStringParam(savestatePath);
+
+ // Add savestate info, if given
+ if (savestate != nullptr)
+ {
+ CFileItemPtr item = std::make_shared<CFileItem>();
+ CSavestateDatabase::GetSavestateItem(*savestate, savestatePath, *item);
+ message.SetItem(std::static_pointer_cast<CGUIListItem>(item));
+ }
+
+ // Notify the in-game savestate dialog
+ m_guiComponent->GetWindowManager().SendThreadMessage(message, WINDOW_DIALOG_IN_GAME_SAVES);
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.h b/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.h
new file mode 100644
index 0000000..7384f9a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameMessenger.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class CGUIComponent;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfo;
+class ISavestate;
+
+/*!
+ * \brief Class to send messages to the GUI, if a GUI is present
+ */
+class CGUIGameMessenger
+{
+public:
+ CGUIGameMessenger(CRPProcessInfo& processInfo);
+
+ /*!
+ * \brief Refresh savestate GUI elements being displayed
+ *
+ * \param savestatePath The savestate to refresh, or empty to refresh all savestates
+ * \param savestate Optional savestate info to send with the message
+ */
+ void RefreshSavestates(const std::string& savestatePath = "", ISavestate* savestate = nullptr);
+
+private:
+ CGUIComponent* const m_guiComponent;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.cpp
new file mode 100644
index 0000000..4071d10
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameRenderManager.h"
+
+#include "GUIGameSettingsHandle.h"
+#include "GUIGameVideoHandle.h"
+#include "GUIRenderHandle.h"
+#include "GUIRenderTarget.h"
+#include "GUIRenderTargetFactory.h"
+#include "IGameCallback.h"
+#include "IRenderCallback.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameRenderManager::~CGUIGameRenderManager() = default;
+
+void CGUIGameRenderManager::RegisterPlayer(CGUIRenderTargetFactory* factory,
+ IRenderCallback* callback,
+ IGameCallback* gameCallback)
+{
+ // Set factory
+ {
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+ m_factory = factory;
+ UpdateRenderTargets();
+ }
+
+ // Set callback
+ {
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+ m_callback = callback;
+ }
+
+ // Set game callback
+ {
+ std::unique_lock<CCriticalSection> lock(m_gameCallbackMutex);
+ m_gameCallback = gameCallback;
+ }
+}
+
+void CGUIGameRenderManager::UnregisterPlayer()
+{
+ // Reset game callback
+ {
+ std::unique_lock<CCriticalSection> lock(m_gameCallbackMutex);
+ m_gameCallback = nullptr;
+ }
+
+ // Reset callback
+ {
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+ m_callback = nullptr;
+ }
+
+ // Reset factory
+ {
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+ m_factory = nullptr;
+ UpdateRenderTargets();
+ }
+}
+
+std::shared_ptr<CGUIRenderHandle> CGUIGameRenderManager::RegisterControl(CGUIGameControl& control)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ // Create handle for game control
+ std::shared_ptr<CGUIRenderHandle> renderHandle(new CGUIRenderControlHandle(*this, control));
+
+ std::shared_ptr<CGUIRenderTarget> renderTarget;
+ if (m_factory != nullptr)
+ renderTarget.reset(m_factory->CreateRenderControl(control));
+
+ m_renderTargets.insert(std::make_pair(renderHandle.get(), std::move(renderTarget)));
+
+ return renderHandle;
+}
+
+std::shared_ptr<CGUIRenderHandle> CGUIGameRenderManager::RegisterWindow(
+ CGameWindowFullScreen& window)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ // Create handle for game window
+ std::shared_ptr<CGUIRenderHandle> renderHandle(new CGUIRenderFullScreenHandle(*this, window));
+
+ std::shared_ptr<CGUIRenderTarget> renderTarget;
+ if (m_factory != nullptr)
+ renderTarget.reset(m_factory->CreateRenderFullScreen(window));
+
+ m_renderTargets.insert(std::make_pair(renderHandle.get(), std::move(renderTarget)));
+
+ return renderHandle;
+}
+
+std::shared_ptr<CGUIGameVideoHandle> CGUIGameRenderManager::RegisterDialog(
+ GAME::CDialogGameVideoSelect& dialog)
+{
+ return std::make_shared<CGUIGameVideoHandle>(*this);
+}
+
+std::shared_ptr<CGUIGameSettingsHandle> CGUIGameRenderManager::RegisterGameSettingsDialog()
+{
+ return std::make_shared<CGUIGameSettingsHandle>(*this);
+}
+
+void CGUIGameRenderManager::UnregisterHandle(CGUIRenderHandle* handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ m_renderTargets.erase(handle);
+}
+
+void CGUIGameRenderManager::Render(CGUIRenderHandle* handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ auto it = m_renderTargets.find(handle);
+ if (it != m_renderTargets.end())
+ {
+ const std::shared_ptr<CGUIRenderTarget>& renderTarget = it->second;
+ if (renderTarget)
+ renderTarget->Render();
+ }
+}
+
+void CGUIGameRenderManager::RenderEx(CGUIRenderHandle* handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ auto it = m_renderTargets.find(handle);
+ if (it != m_renderTargets.end())
+ {
+ const std::shared_ptr<CGUIRenderTarget>& renderTarget = it->second;
+ if (renderTarget)
+ renderTarget->RenderEx();
+ }
+}
+
+void CGUIGameRenderManager::ClearBackground(CGUIRenderHandle* handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ auto it = m_renderTargets.find(handle);
+ if (it != m_renderTargets.end())
+ {
+ const std::shared_ptr<CGUIRenderTarget>& renderTarget = it->second;
+ if (renderTarget)
+ renderTarget->ClearBackground();
+ }
+}
+
+bool CGUIGameRenderManager::IsDirty(CGUIRenderHandle* handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_targetMutex);
+
+ auto it = m_renderTargets.find(handle);
+ if (it != m_renderTargets.end())
+ {
+ const std::shared_ptr<CGUIRenderTarget>& renderTarget = it->second;
+ if (renderTarget)
+ return renderTarget->IsDirty();
+ }
+
+ return false;
+}
+
+bool CGUIGameRenderManager::IsPlayingGame()
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ return m_callback != nullptr;
+}
+
+bool CGUIGameRenderManager::SupportsRenderFeature(RENDERFEATURE feature)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_callback != nullptr)
+ return m_callback->SupportsRenderFeature(feature);
+
+ return false;
+}
+
+bool CGUIGameRenderManager::SupportsScalingMethod(SCALINGMETHOD method)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_callback != nullptr)
+ return m_callback->SupportsScalingMethod(method);
+
+ return false;
+}
+
+std::string CGUIGameRenderManager::GameClientID()
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ return m_gameCallback->GameClientID();
+
+ return "";
+}
+
+std::string CGUIGameRenderManager::GetPlayingGame()
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ return m_gameCallback->GetPlayingGame();
+
+ return "";
+}
+
+std::string CGUIGameRenderManager::CreateSavestate(bool autosave)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ return m_gameCallback->CreateSavestate(autosave);
+
+ return "";
+}
+
+bool CGUIGameRenderManager::UpdateSavestate(const std::string& savestatePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ return m_gameCallback->UpdateSavestate(savestatePath);
+
+ return false;
+}
+
+bool CGUIGameRenderManager::LoadSavestate(const std::string& savestatePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ return m_gameCallback->LoadSavestate(savestatePath);
+
+ return false;
+}
+
+void CGUIGameRenderManager::FreeSavestateResources(const std::string& savestatePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ m_gameCallback->FreeSavestateResources(savestatePath);
+}
+
+void CGUIGameRenderManager::CloseOSD()
+{
+ std::unique_lock<CCriticalSection> lock(m_callbackMutex);
+
+ if (m_gameCallback != nullptr)
+ m_gameCallback->CloseOSDCallback();
+}
+
+void CGUIGameRenderManager::UpdateRenderTargets()
+{
+ if (m_factory != nullptr)
+ {
+ for (auto& it : m_renderTargets)
+ {
+ CGUIRenderHandle* handle = it.first;
+ std::shared_ptr<CGUIRenderTarget>& renderTarget = it.second;
+
+ if (!renderTarget)
+ renderTarget.reset(CreateRenderTarget(handle));
+ }
+ }
+ else
+ {
+ for (auto& it : m_renderTargets)
+ it.second.reset();
+ }
+}
+
+CGUIRenderTarget* CGUIGameRenderManager::CreateRenderTarget(CGUIRenderHandle* handle)
+{
+ switch (handle->Type())
+ {
+ case RENDER_HANDLE::CONTROL:
+ {
+ CGUIRenderControlHandle* controlHandle = static_cast<CGUIRenderControlHandle*>(handle);
+ return m_factory->CreateRenderControl(controlHandle->GetControl());
+ }
+ case RENDER_HANDLE::WINDOW:
+ {
+ CGUIRenderFullScreenHandle* fullScreenHandle =
+ static_cast<CGUIRenderFullScreenHandle*>(handle);
+ return m_factory->CreateRenderFullScreen(fullScreenHandle->GetWindow());
+ }
+ default:
+ break;
+ }
+
+ return nullptr;
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.h b/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.h
new file mode 100644
index 0000000..0a974b7
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameRenderManager.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameAdvancedSettings;
+class CDialogGameVideoSelect;
+} // namespace GAME
+
+namespace RETRO
+{
+class CGameWindowFullScreen;
+class CGUIGameControl;
+class CGUIGameSettingsHandle;
+class CGUIGameVideoHandle;
+class CGUIRenderTargetFactory;
+class CGUIRenderHandle;
+class CGUIRenderTarget;
+class IGameCallback;
+class IRenderCallback;
+class IPlayback;
+
+/*!
+ * \brief Class to safely route commands between the GUI and RetroPlayer
+ *
+ * This class is brought up before the GUI and player core factory. It
+ * provides the GUI with safe access to a registered player.
+ *
+ * Access to the player is done through handles. When a handle is no
+ * longer needed, it should be destroyed.
+ *
+ * Three kinds of handles are provided:
+ *
+ * - CGUIRenderHandle
+ * Allows the holder to invoke render events
+ *
+ * - CGUIGameVideoHandle
+ * Allows the holder to query video properties, such as the filter
+ * or view mode.
+ *
+ * - CGUIGameSettingsHandle
+ * Allows the holder to query game properties, such as the ID of the
+ * game client or the game's filename.
+ *
+ * Each manager fulfills the following design requirements:
+ *
+ * 1. No assumption of player lifetimes
+ *
+ * 2. No assumption of GUI element lifetimes, as long as handles are
+ * destroyed before this class is destructed
+ *
+ * 3. No limit on the number of handles
+ */
+class CGUIGameRenderManager
+{
+ // Classes that call into the protected interface
+ friend class CGUIGameSettingsHandle;
+ friend class CGUIGameVideoHandle;
+ friend class CGUIRenderHandle;
+
+public:
+ CGUIGameRenderManager() = default;
+ ~CGUIGameRenderManager();
+
+ /*!
+ * \brief Register a RetroPlayer instance
+ *
+ * \param factory The interface for creating render targets exposed to the GUI
+ * \param callback The interface for querying video properties
+ * \param gameCallback The interface for querying game properties
+ */
+ void RegisterPlayer(CGUIRenderTargetFactory* factory,
+ IRenderCallback* callback,
+ IGameCallback* gameCallback);
+
+ /*!
+ * \brief Unregister a RetroPlayer instance
+ */
+ void UnregisterPlayer();
+
+ /*!
+ * \brief Register a GUI game control ("gamewindow" skin control)
+ *
+ * \param control The game control
+ *
+ * \return A handle to invoke render events
+ */
+ std::shared_ptr<CGUIRenderHandle> RegisterControl(CGUIGameControl& control);
+
+ /*!
+ * \brief Register a fullscreen game window ("FullscreenGame" window)
+ *
+ * \param window The game window
+ *
+ * \return A handle to invoke render events
+ */
+ std::shared_ptr<CGUIRenderHandle> RegisterWindow(CGameWindowFullScreen& window);
+
+ /*!
+ * \brief Register a video select dialog (for selecting video filters,
+ * view modes, etc.)
+ *
+ * \param dialog The video select dialog
+ *
+ * \return A handle to query game and video properties
+ */
+ std::shared_ptr<CGUIGameVideoHandle> RegisterDialog(GAME::CDialogGameVideoSelect& dialog);
+
+ /*!
+ * \brief Register a game settings dialog
+ *
+ * \return A handle to query game properties
+ */
+ std::shared_ptr<CGUIGameSettingsHandle> RegisterGameSettingsDialog();
+
+protected:
+ // Functions exposed to friend class CGUIRenderHandle
+ void UnregisterHandle(CGUIRenderHandle* handle);
+ void Render(CGUIRenderHandle* handle);
+ void RenderEx(CGUIRenderHandle* handle);
+ void ClearBackground(CGUIRenderHandle* handle);
+ bool IsDirty(CGUIRenderHandle* handle);
+
+ // Functions exposed to friend class CGUIGameVideoHandle
+ void UnregisterHandle(CGUIGameVideoHandle* handle) {}
+ bool IsPlayingGame();
+ bool SupportsRenderFeature(RENDERFEATURE feature);
+ bool SupportsScalingMethod(SCALINGMETHOD method);
+
+ // Functions exposed to CGUIGameSettingsHandle
+ void UnregisterHandle(CGUIGameSettingsHandle* handle) {}
+ std::string GameClientID();
+ std::string GetPlayingGame();
+ std::string CreateSavestate(bool autosave);
+ bool UpdateSavestate(const std::string& savestatePath);
+ bool LoadSavestate(const std::string& savestatePath);
+ void FreeSavestateResources(const std::string& savestatePath);
+ void CloseOSD();
+
+private:
+ /*!
+ * \brief Helper function to create or destroy render targets when a
+ * factory is registered/unregistered
+ */
+ void UpdateRenderTargets();
+
+ /*!
+ * \brief Helper function to create a render target
+ *
+ * \param handle The handle given to the registered GUI element
+ *
+ * \return A target to receive rendering commands
+ */
+ CGUIRenderTarget* CreateRenderTarget(CGUIRenderHandle* handle);
+
+ // Render events
+ CGUIRenderTargetFactory* m_factory = nullptr;
+ std::map<CGUIRenderHandle*, std::shared_ptr<CGUIRenderTarget>> m_renderTargets;
+ CCriticalSection m_targetMutex;
+
+ // Video properties
+ IRenderCallback* m_callback = nullptr;
+ CCriticalSection m_callbackMutex;
+
+ // Game properties
+ IGameCallback* m_gameCallback = nullptr;
+ CCriticalSection m_gameCallbackMutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.cpp
new file mode 100644
index 0000000..4b030d8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameSettings.h"
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "settings/GameSettings.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameSettings::CGUIGameSettings(CRPProcessInfo& processInfo)
+ : m_processInfo(processInfo), m_guiSettings(processInfo.GetRenderContext().GetGameSettings())
+{
+ // Reset game settings
+ m_guiSettings = m_processInfo.GetRenderContext().GetDefaultGameSettings();
+
+ UpdateSettings();
+
+ m_guiSettings.RegisterObserver(this);
+}
+
+CGUIGameSettings::~CGUIGameSettings()
+{
+ m_guiSettings.UnregisterObserver(this);
+}
+
+CRenderSettings CGUIGameSettings::GetSettings() const
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_renderSettings;
+}
+
+void CGUIGameSettings::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ switch (msg)
+ {
+ case ObservableMessageSettingsChanged:
+ {
+ UpdateSettings();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CGUIGameSettings::UpdateSettings()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ // Get settings from GUI
+ std::string videoFilter = m_guiSettings.VideoFilter();
+ STRETCHMODE stretchMode = m_guiSettings.StretchMode();
+ unsigned int rotationDegCCW = m_guiSettings.RotationDegCCW();
+
+ // Save settings for renderer
+ m_renderSettings.VideoSettings().SetVideoFilter(videoFilter);
+ m_renderSettings.VideoSettings().SetRenderStretchMode(stretchMode);
+ m_renderSettings.VideoSettings().SetRenderRotation(rotationDegCCW);
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.h b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.h
new file mode 100644
index 0000000..6400b8d
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettings.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IGUIRenderSettings.h"
+#include "cores/RetroPlayer/rendering/RenderSettings.h"
+#include "threads/CriticalSection.h"
+#include "utils/Observer.h"
+
+#include <memory>
+
+class CGameSettings;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfo;
+
+class CGUIGameSettings : public IGUIRenderSettings, public Observer
+{
+public:
+ CGUIGameSettings(CRPProcessInfo& processInfo);
+ ~CGUIGameSettings() override;
+
+ // implementation of IGUIRenderSettings
+ CRenderSettings GetSettings() const override;
+
+ // implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
+private:
+ void UpdateSettings();
+
+ // Construction parameters
+ CRPProcessInfo& m_processInfo;
+
+ // GUI parameters
+ CGameSettings& m_guiSettings;
+
+ // Render parameters
+ CRenderSettings m_renderSettings;
+
+ // Synchronization parameters
+ mutable CCriticalSection m_mutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.cpp
new file mode 100644
index 0000000..9edba41
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameSettingsHandle.h"
+
+#include "GUIGameRenderManager.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameSettingsHandle::CGUIGameSettingsHandle(CGUIGameRenderManager& renderManager)
+ : m_renderManager(renderManager)
+{
+}
+
+CGUIGameSettingsHandle::~CGUIGameSettingsHandle()
+{
+ m_renderManager.UnregisterHandle(this);
+}
+
+std::string CGUIGameSettingsHandle::GameClientID()
+{
+ return m_renderManager.GameClientID();
+}
+
+std::string CGUIGameSettingsHandle::GetPlayingGame()
+{
+ return m_renderManager.GetPlayingGame();
+}
+
+std::string CGUIGameSettingsHandle::CreateSavestate(bool autosave)
+{
+ return m_renderManager.CreateSavestate(autosave);
+}
+
+bool CGUIGameSettingsHandle::UpdateSavestate(const std::string& savestatePath)
+{
+ return m_renderManager.UpdateSavestate(savestatePath);
+}
+
+bool CGUIGameSettingsHandle::LoadSavestate(const std::string& savestatePath)
+{
+ return m_renderManager.LoadSavestate(savestatePath);
+}
+
+void CGUIGameSettingsHandle::FreeSavestateResources(const std::string& savestatePath)
+{
+ return m_renderManager.FreeSavestateResources(savestatePath);
+}
+
+void CGUIGameSettingsHandle::CloseOSD()
+{
+ m_renderManager.CloseOSD();
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h
new file mode 100644
index 0000000..beddb90
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIGameRenderManager;
+class IPlayback;
+
+class CGUIGameSettingsHandle
+{
+public:
+ CGUIGameSettingsHandle(CGUIGameRenderManager& renderManager);
+ virtual ~CGUIGameSettingsHandle();
+
+ /*!
+ * \brief Get the ID of the active game client
+ *
+ * \return The ID of the active game client, or empty string if a game is
+ * not playing
+ */
+ std::string GameClientID();
+
+ /*!
+ * \brief Get the full path of the game being played
+ *
+ * \return The game's path, or empty string if a game is not playing
+ */
+ std::string GetPlayingGame();
+
+ /*!
+ * \brief Create a savestate of the current game being played
+ *
+ * \param autosave True if the save was invoked automatically, or false if
+ * the save was invoked by a player
+ *
+ * \return The path to the created savestate file, or empty string on
+ * failure or if a game is not playing
+ */
+ std::string CreateSavestate(bool autosave);
+
+ /*!
+ * \brief Update a savestate for the current game being played
+ *
+ * \param savestatePath The path to the created savestate file returned by
+ * CreateSavestate()
+ *
+ * \return True if the savestate was updated successfully, false otherwise
+ */
+ bool UpdateSavestate(const std::string& savestatePath);
+
+ /*!
+ * \brief Load a savestate for the current game being played
+ *
+ * \param savestatePath The path to the created savestate file returned by
+ * CreateSavestate()
+ *
+ * \return True if the savestate was loaded successfully, false otherwise
+ */
+ bool LoadSavestate(const std::string& savestatePath);
+
+ /*!
+ * \brief Clear the video frame stored for the given statestate
+ *
+ * Useful to reclaim memory if a savestate has been deleted.
+ *
+ * \param savestatePath The path to the savestate file
+ */
+ void FreeSavestateResources(const std::string& savestatePath);
+
+ /*!
+ * \brief Close the in-game OSD
+ */
+ void CloseOSD();
+
+private:
+ // Construction parameters
+ CGUIGameRenderManager& m_renderManager;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.cpp
new file mode 100644
index 0000000..521cf4b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameVideoHandle.h"
+
+#include "GUIGameRenderManager.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameVideoHandle::CGUIGameVideoHandle(CGUIGameRenderManager& renderManager)
+ : m_renderManager(renderManager)
+{
+}
+
+CGUIGameVideoHandle::~CGUIGameVideoHandle()
+{
+ m_renderManager.UnregisterHandle(this);
+}
+
+bool CGUIGameVideoHandle::IsPlayingGame()
+{
+ return m_renderManager.IsPlayingGame();
+}
+
+bool CGUIGameVideoHandle::SupportsRenderFeature(RENDERFEATURE feature)
+{
+ return m_renderManager.SupportsRenderFeature(feature);
+}
+
+bool CGUIGameVideoHandle::SupportsScalingMethod(SCALINGMETHOD method)
+{
+ return m_renderManager.SupportsScalingMethod(method);
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.h b/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.h
new file mode 100644
index 0000000..4d26fe4
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIGameVideoHandle.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIGameRenderManager;
+
+class CGUIGameVideoHandle
+{
+public:
+ CGUIGameVideoHandle(CGUIGameRenderManager& renderManager);
+ virtual ~CGUIGameVideoHandle();
+
+ bool IsPlayingGame();
+ bool SupportsRenderFeature(RENDERFEATURE feature);
+ bool SupportsScalingMethod(SCALINGMETHOD method);
+
+private:
+ // Construction parameters
+ CGUIGameRenderManager& m_renderManager;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.cpp
new file mode 100644
index 0000000..a4b6403
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRenderHandle.h"
+
+#include "GUIGameRenderManager.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CGUIRenderHandle --------------------------------------------------------
+
+CGUIRenderHandle::CGUIRenderHandle(CGUIGameRenderManager& renderManager, RENDER_HANDLE type)
+ : m_renderManager(renderManager), m_type(type)
+{
+}
+
+CGUIRenderHandle::~CGUIRenderHandle()
+{
+ m_renderManager.UnregisterHandle(this);
+}
+
+void CGUIRenderHandle::Render()
+{
+ m_renderManager.Render(this);
+}
+
+void CGUIRenderHandle::RenderEx()
+{
+ m_renderManager.RenderEx(this);
+}
+
+bool CGUIRenderHandle::IsDirty()
+{
+ return m_renderManager.IsDirty(this);
+}
+
+void CGUIRenderHandle::ClearBackground()
+{
+ m_renderManager.ClearBackground(this);
+}
+
+// --- CGUIRenderControlHandle -------------------------------------------------
+
+CGUIRenderControlHandle::CGUIRenderControlHandle(CGUIGameRenderManager& renderManager,
+ CGUIGameControl& control)
+ : CGUIRenderHandle(renderManager, RENDER_HANDLE::CONTROL), m_control(control)
+{
+}
+
+// --- CGUIRenderFullScreenHandle ----------------------------------------------
+
+CGUIRenderFullScreenHandle::CGUIRenderFullScreenHandle(CGUIGameRenderManager& renderManager,
+ CGameWindowFullScreen& window)
+ : CGUIRenderHandle(renderManager, RENDER_HANDLE::WINDOW), m_window(window)
+{
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.h b/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.h
new file mode 100644
index 0000000..f5b8d5b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderHandle.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGameWindowFullScreen;
+class CGUIGameControl;
+class CGUIGameRenderManager;
+
+enum class RENDER_HANDLE
+{
+ CONTROL,
+ WINDOW,
+};
+
+// --- CGUIRenderHandle ------------------------------------------------------
+
+class CGUIRenderHandle
+{
+public:
+ CGUIRenderHandle(CGUIGameRenderManager& renderManager, RENDER_HANDLE type);
+ virtual ~CGUIRenderHandle();
+
+ RENDER_HANDLE Type() const { return m_type; }
+
+ void Render();
+ void RenderEx();
+ bool IsDirty();
+ void ClearBackground();
+
+private:
+ // Construction parameters
+ CGUIGameRenderManager& m_renderManager;
+ const RENDER_HANDLE m_type;
+};
+
+// --- CGUIRenderControlHandle -----------------------------------------------
+
+class CGUIRenderControlHandle : public CGUIRenderHandle
+{
+public:
+ CGUIRenderControlHandle(CGUIGameRenderManager& renderManager, CGUIGameControl& control);
+ ~CGUIRenderControlHandle() override = default;
+
+ CGUIGameControl& GetControl() { return m_control; }
+
+private:
+ // Construction parameters
+ CGUIGameControl& m_control;
+};
+
+// --- CGUIRenderFullScreenHandle --------------------------------------------
+
+class CGUIRenderFullScreenHandle : public CGUIRenderHandle
+{
+public:
+ CGUIRenderFullScreenHandle(CGUIGameRenderManager& renderManager, CGameWindowFullScreen& window);
+ ~CGUIRenderFullScreenHandle() override = default;
+
+ CGameWindowFullScreen& GetWindow() { return m_window; }
+
+private:
+ // Construction parameters
+ CGameWindowFullScreen& m_window;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.cpp
new file mode 100644
index 0000000..15993fc
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRenderTarget.h"
+
+#include "cores/RetroPlayer/guicontrols/GUIGameControl.h"
+#include "cores/RetroPlayer/guiwindows/GameWindowFullScreen.h"
+#include "cores/RetroPlayer/rendering/IRenderManager.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CGUIRenderTarget --------------------------------------------------------
+
+CGUIRenderTarget::CGUIRenderTarget(IRenderManager* renderManager) : m_renderManager(renderManager)
+{
+}
+
+// --- CGUIRenderControl -------------------------------------------------------
+
+CGUIRenderControl::CGUIRenderControl(IRenderManager* renderManager, CGUIGameControl& gameControl)
+ : CGUIRenderTarget(renderManager), m_gameControl(gameControl)
+{
+}
+
+void CGUIRenderControl::Render()
+{
+ m_renderManager->RenderControl(true, true, m_gameControl.GetRenderRegion(),
+ m_gameControl.GetRenderSettings());
+}
+
+void CGUIRenderControl::RenderEx()
+{
+ //! @todo
+ // m_renderManager->RenderControl(false, false, m_gameControl.GetRenderRegion(),
+ // m_gameControl.GetRenderSettings());
+}
+
+// --- CGUIRenderFullScreen ----------------------------------------------------
+
+CGUIRenderFullScreen::CGUIRenderFullScreen(IRenderManager* renderManager,
+ CGameWindowFullScreen& window)
+ : CGUIRenderTarget(renderManager), m_window(window)
+{
+}
+
+void CGUIRenderFullScreen::Render()
+{
+ m_renderManager->RenderWindow(true, m_window.GetCoordsRes());
+}
+
+void CGUIRenderFullScreen::RenderEx()
+{
+ //! @todo
+ // m_renderManager->RenderWindow(false, m_window.GetCoordsRes());
+}
+
+void CGUIRenderFullScreen::ClearBackground()
+{
+ m_renderManager->ClearBackground();
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.h b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.h
new file mode 100644
index 0000000..574a13f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTarget.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGameWindowFullScreen;
+class CGUIGameControl;
+class IRenderManager;
+
+// --- CGUIRenderTarget ------------------------------------------------------
+
+/*!
+ * \brief A target of rendering commands
+ *
+ * This class abstracts the destination of rendering commands. As a result,
+ * controls and windows are given a unified API.
+ */
+class CGUIRenderTarget
+{
+public:
+ CGUIRenderTarget(IRenderManager* renderManager);
+
+ virtual ~CGUIRenderTarget() = default;
+
+ /*!
+ * \brief Draw the frame to the rendering area
+ */
+ virtual void Render() = 0;
+
+ /*!
+ * \brief Draw the frame to the rendering area differently somehow
+ */
+ virtual void RenderEx() = 0;
+
+ /*!
+ * \brief Clear the background of the rendering area
+ */
+ virtual void ClearBackground() {} //! @todo
+
+ /*!
+ * \brief Check of the rendering area is dirty
+ */
+ virtual bool IsDirty() { return true; } //! @todo
+
+protected:
+ // Construction parameters
+ IRenderManager* const m_renderManager;
+};
+
+// --- CGUIRenderControl -----------------------------------------------------
+
+class CGUIRenderControl : public CGUIRenderTarget
+{
+public:
+ CGUIRenderControl(IRenderManager* renderManager, CGUIGameControl& gameControl);
+ ~CGUIRenderControl() override = default;
+
+ // implementation of CGUIRenderTarget
+ void Render() override;
+ void RenderEx() override;
+
+private:
+ // Construction parameters
+ CGUIGameControl& m_gameControl;
+};
+
+// --- CGUIRenderFullScreen --------------------------------------------------
+
+class CGUIRenderFullScreen : public CGUIRenderTarget
+{
+public:
+ CGUIRenderFullScreen(IRenderManager* renderManager, CGameWindowFullScreen& window);
+ ~CGUIRenderFullScreen() override = default;
+
+ // implementation of CGUIRenderTarget
+ void Render() override;
+ void RenderEx() override;
+ void ClearBackground() override;
+
+private:
+ // Construction parameters
+ CGameWindowFullScreen& m_window;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.cpp b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.cpp
new file mode 100644
index 0000000..05675c3
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRenderTargetFactory.h"
+
+#include "GUIRenderTarget.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIRenderTargetFactory::CGUIRenderTargetFactory(IRenderManager* renderManager)
+ : m_renderManager(renderManager)
+{
+}
+
+CGUIRenderTarget* CGUIRenderTargetFactory::CreateRenderFullScreen(CGameWindowFullScreen& window)
+{
+ return new CGUIRenderFullScreen(m_renderManager, window);
+}
+
+CGUIRenderTarget* CGUIRenderTargetFactory::CreateRenderControl(CGUIGameControl& gameControl)
+{
+ return new CGUIRenderControl(m_renderManager, gameControl);
+}
diff --git a/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.h b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.h
new file mode 100644
index 0000000..8066854
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/GUIRenderTargetFactory.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGameWindowFullScreen;
+class CGUIGameControl;
+class IRenderManager;
+class CGUIRenderTarget;
+
+class CGUIRenderTargetFactory
+{
+public:
+ CGUIRenderTargetFactory(IRenderManager* renderManager);
+
+ /*!
+ * \brief Create a render target for the fullscreen window
+ */
+ CGUIRenderTarget* CreateRenderFullScreen(CGameWindowFullScreen& window);
+
+ /*!
+ * \brief Create a render target for a game control
+ */
+ CGUIRenderTarget* CreateRenderControl(CGUIGameControl& gameControl);
+
+private:
+ // Construction parameters
+ IRenderManager* m_renderManager;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/IGUIRenderSettings.h b/xbmc/cores/RetroPlayer/guibridge/IGUIRenderSettings.h
new file mode 100644
index 0000000..8fb0fd1
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/IGUIRenderSettings.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/rendering/RenderSettings.h"
+#include "utils/Geometry.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Interface to pass render settings from the GUI to the renderer
+ */
+class IGUIRenderSettings
+{
+public:
+ virtual ~IGUIRenderSettings() = default;
+
+ /*!
+ * \brief Returns true if this render target has a video filter set
+ */
+ virtual bool HasVideoFilter() const { return true; }
+
+ /*!
+ * \brief Returns true if this render target has a stretch mode set
+ */
+ virtual bool HasStretchMode() const { return true; }
+
+ /*!
+ * \brief Returns true if this render target has a video rotation set
+ */
+ virtual bool HasRotation() const { return true; }
+
+ /*!
+ * \brief Returns true if this render target has a path to a savestate for
+ * showing pixel data
+ */
+ virtual bool HasPixels() const { return true; }
+
+ /*!
+ * \brief Get the settings used to render this target
+ *
+ * \return The render settings
+ */
+ virtual CRenderSettings GetSettings() const = 0;
+
+ /*!
+ * \brief Get the dimensions of this target
+ *
+ * Dimensions are ignored for fullscreen windows.
+ *
+ * \return The destination dimensions, or unused for fullscreen window
+ */
+ virtual CRect GetDimensions() const { return CRect{}; }
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/IGameCallback.h b/xbmc/cores/RetroPlayer/guibridge/IGameCallback.h
new file mode 100644
index 0000000..057dd04
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/IGameCallback.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IPlayback;
+
+class IGameCallback
+{
+public:
+ virtual ~IGameCallback() = default;
+
+ /*!
+ * \brief Get the game client being used to play the game
+ *
+ * \return The game client's ID, or empty if no game is being played
+ */
+ virtual std::string GameClientID() const = 0;
+
+ /*!
+ * \brief Get the game that is being played
+ *
+ * \return The path to the game, or empty if no game is being played
+ */
+ virtual std::string GetPlayingGame() const = 0;
+
+ /*!
+ * \brief Creates a savestate
+ *
+ * \param autosave Whether the save type is auto
+ *
+ * \return The path to the created savestate, or empty on error
+ */
+ virtual std::string CreateSavestate(bool autosave) = 0;
+
+ /*!
+ * \brief Updates a savestate with the current game being played
+ *
+ * \param savestatePath The path to the savestate
+ *
+ * \return True if the savestate was updated, false on error
+ */
+ virtual bool UpdateSavestate(const std::string& savestatePath) = 0;
+
+ /*!
+ * \brief Loads a savestate
+ *
+ * \param savestatePath The path to the savestate
+ *
+ * \return True if the savestate was loaded, false on error
+ */
+ virtual bool LoadSavestate(const std::string& savestatePath) = 0;
+
+ /*!
+ * \brief Frees resources allocated to the savestate, such as its video thumbnail
+ *
+ * \param savestatePath The path to the savestate
+ */
+ virtual void FreeSavestateResources(const std::string& savestatePath) = 0;
+
+ /*!
+ * \brief Closes the OSD
+ */
+ virtual void CloseOSDCallback() = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guibridge/IRenderCallback.h b/xbmc/cores/RetroPlayer/guibridge/IRenderCallback.h
new file mode 100644
index 0000000..948c6cd
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guibridge/IRenderCallback.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class IRenderCallback
+{
+public:
+ virtual ~IRenderCallback() = default;
+
+ virtual bool SupportsRenderFeature(RENDERFEATURE feature) const = 0;
+ virtual bool SupportsScalingMethod(SCALINGMETHOD method) const = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guicontrols/CMakeLists.txt b/xbmc/cores/RetroPlayer/guicontrols/CMakeLists.txt
new file mode 100644
index 0000000..86aba95
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES GUIGameControl.cpp
+ GUIRenderSettings.cpp
+)
+
+set(HEADERS GUIGameControl.h
+ GUIRenderSettings.h
+)
+
+core_add_library(retroplayer_guicontrols)
diff --git a/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.cpp b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.cpp
new file mode 100644
index 0000000..bf2ceb9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIGameControl.h"
+
+#include "GUIRenderSettings.h"
+#include "ServiceBroker.h"
+#include "cores/RetroPlayer/RetroPlayerUtils.h"
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guibridge/GUIRenderHandle.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/Geometry.h"
+#include "utils/StringUtils.h"
+
+#include <sstream>
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIGameControl::CGUIGameControl(
+ int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_renderSettings(new CGUIRenderSettings(*this))
+{
+ // Initialize CGUIControl
+ ControlType = GUICONTROL_GAME;
+
+ m_renderSettings->SetDimensions(CRect(CPoint(posX, posY), CSize(width, height)));
+
+ RegisterControl();
+}
+
+CGUIGameControl::CGUIGameControl(const CGUIGameControl& other)
+ : CGUIControl(other),
+ m_videoFilterInfo(other.m_videoFilterInfo),
+ m_stretchModeInfo(other.m_stretchModeInfo),
+ m_rotationInfo(other.m_rotationInfo),
+ m_pixelInfo(other.m_pixelInfo),
+ m_bHasVideoFilter(other.m_bHasVideoFilter),
+ m_bHasStretchMode(other.m_bHasStretchMode),
+ m_bHasRotation(other.m_bHasRotation),
+ m_bHasPixels(other.m_bHasPixels),
+ m_renderSettings(new CGUIRenderSettings(*this))
+{
+ m_renderSettings->SetSettings(other.m_renderSettings->GetSettings());
+ m_renderSettings->SetDimensions(CRect(CPoint(m_posX, m_posY), CSize(m_width, m_height)));
+
+ RegisterControl();
+}
+
+CGUIGameControl::~CGUIGameControl()
+{
+ UnregisterControl();
+}
+
+void CGUIGameControl::SetVideoFilter(const GUILIB::GUIINFO::CGUIInfoLabel& videoFilter)
+{
+ m_videoFilterInfo = videoFilter;
+}
+
+void CGUIGameControl::SetStretchMode(const GUILIB::GUIINFO::CGUIInfoLabel& stretchMode)
+{
+ m_stretchModeInfo = stretchMode;
+}
+
+void CGUIGameControl::SetRotation(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& rotation)
+{
+ m_rotationInfo = rotation;
+}
+
+void CGUIGameControl::SetPixels(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& pixels)
+{
+ m_pixelInfo = pixels;
+}
+
+IGUIRenderSettings* CGUIGameControl::GetRenderSettings() const
+{
+ return m_renderSettings.get();
+}
+
+void CGUIGameControl::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ //! @todo Proper processing which marks when its actually changed
+ if (m_renderHandle->IsDirty())
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIGameControl::Render()
+{
+ m_renderHandle->Render();
+
+ CGUIControl::Render();
+}
+
+void CGUIGameControl::RenderEx()
+{
+ m_renderHandle->RenderEx();
+
+ CGUIControl::RenderEx();
+}
+
+bool CGUIGameControl::CanFocus() const
+{
+ // Unfocusable
+ return false;
+}
+
+void CGUIGameControl::SetPosition(float posX, float posY)
+{
+ CGUIControl::SetPosition(posX, posY);
+ m_renderSettings->SetDimensions(CRect(CPoint(posX, posY), CSize(m_width, m_height)));
+}
+
+void CGUIGameControl::SetWidth(float width)
+{
+ CGUIControl::SetWidth(width);
+ m_renderSettings->SetDimensions(CRect(CPoint(m_posX, m_posY), CSize(width, m_height)));
+}
+
+void CGUIGameControl::SetHeight(float height)
+{
+ CGUIControl::SetHeight(height);
+ m_renderSettings->SetDimensions(CRect(CPoint(m_posX, m_posY), CSize(m_width, height)));
+}
+
+void CGUIGameControl::UpdateInfo(const CGUIListItem* item /* = nullptr */)
+{
+ if (item)
+ {
+ Reset();
+
+ std::string strVideoFilter = m_videoFilterInfo.GetItemLabel(item);
+ if (!strVideoFilter.empty())
+ {
+ m_renderSettings->SetVideoFilter(strVideoFilter);
+ m_bHasVideoFilter = true;
+ }
+
+ std::string strStretchMode = m_stretchModeInfo.GetItemLabel(item);
+ if (!strStretchMode.empty())
+ {
+ STRETCHMODE stretchMode = CRetroPlayerUtils::IdentifierToStretchMode(strStretchMode);
+ m_renderSettings->SetStretchMode(stretchMode);
+ m_bHasStretchMode = true;
+ }
+
+ std::string strRotation = m_rotationInfo.GetItemLabel(item);
+ if (StringUtils::IsNaturalNumber(strRotation))
+ {
+ unsigned int rotation;
+ std::istringstream(strRotation) >> rotation;
+ m_renderSettings->SetRotationDegCCW(rotation);
+ m_bHasRotation = true;
+ }
+
+ std::string strPixels = m_pixelInfo.GetItemLabel(item);
+ if (!strPixels.empty())
+ {
+ m_renderSettings->SetPixels(strPixels);
+ m_bHasPixels = true;
+ }
+ }
+}
+
+void CGUIGameControl::Reset()
+{
+ m_bHasVideoFilter = false;
+ m_bHasStretchMode = false;
+ m_bHasRotation = false;
+ m_bHasPixels = false;
+ m_renderSettings->Reset();
+}
+
+void CGUIGameControl::RegisterControl()
+{
+ m_renderHandle = CServiceBroker::GetGameRenderManager().RegisterControl(*this);
+}
+
+void CGUIGameControl::UnregisterControl()
+{
+ m_renderHandle.reset();
+}
diff --git a/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.dox b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.dox
new file mode 100644
index 0000000..6350110
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.dox
@@ -0,0 +1,45 @@
+/*!
+
+\page Game_Control Game Control
+\brief **Used to display the currently playing game, with optional effects,
+whilst in the GUI.**
+
+\tableofcontents
+
+The gamewindow control is used for displaying the currently playing game
+elsewhere in the Kodi GUI. You can choose the position, and size of the game
+displayed, as well as various effects. Note that the control is only rendered if
+game is being played.
+
+
+--------------------------------------------------------------------------------
+\section Game_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="gamewindow" id="2">
+ <description>My first game control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Game_Control_sect2 Available tags
+
+The [default control](http://kodi.wiki/view/Default_Control_Tags) tags are
+applicable to this control.
+
+
+--------------------------------------------------------------------------------
+\section Game_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.h b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.h
new file mode 100644
index 0000000..a386952
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/GUIGameControl.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/GUIControl.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIRenderSettings;
+class CGUIRenderHandle;
+class IGUIRenderSettings;
+
+// Label to use when disabling rendering via the <pixels> property. A path
+// to pixel data is expected, but instead this constant can be provided to
+// skip a file existence check in the renderer.
+constexpr const char* NO_PIXEL_DATA = "-";
+
+class CGUIGameControl : public CGUIControl
+{
+public:
+ CGUIGameControl(int parentID, int controlID, float posX, float posY, float width, float height);
+ CGUIGameControl(const CGUIGameControl& other);
+ ~CGUIGameControl() override;
+
+ // GUI functions
+ void SetVideoFilter(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& videoFilter);
+ void SetStretchMode(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& stretchMode);
+ void SetRotation(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& rotation);
+ void SetPixels(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& pixels);
+
+ // Rendering functions
+ bool HasVideoFilter() const { return m_bHasVideoFilter; }
+ bool HasStretchMode() const { return m_bHasStretchMode; }
+ bool HasRotation() const { return m_bHasRotation; }
+ bool HasPixels() const { return m_bHasPixels; }
+ IGUIRenderSettings* GetRenderSettings() const;
+
+ // implementation of CGUIControl
+ CGUIGameControl* Clone() const override { return new CGUIGameControl(*this); }
+ void Process(unsigned int currentTime, CDirtyRegionList& dirtyregions) override;
+ void Render() override;
+ void RenderEx() override;
+ bool CanFocus() const override;
+ void SetPosition(float posX, float posY) override;
+ void SetWidth(float width) override;
+ void SetHeight(float height) override;
+ void UpdateInfo(const CGUIListItem* item = nullptr) override;
+
+private:
+ void Reset();
+
+ void RegisterControl();
+ void UnregisterControl();
+
+ // GUI properties
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_videoFilterInfo;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_stretchModeInfo;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_rotationInfo;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_pixelInfo;
+
+ // Rendering properties
+ bool m_bHasVideoFilter = false;
+ bool m_bHasStretchMode = false;
+ bool m_bHasRotation = false;
+ bool m_bHasPixels = false;
+ std::unique_ptr<CGUIRenderSettings> m_renderSettings;
+ std::shared_ptr<CGUIRenderHandle> m_renderHandle;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.cpp b/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.cpp
new file mode 100644
index 0000000..66cb4ea
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRenderSettings.h"
+
+#include "GUIGameControl.h"
+
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIRenderSettings::CGUIRenderSettings(CGUIGameControl& guiControl) : m_guiControl(guiControl)
+{
+}
+
+bool CGUIRenderSettings::HasVideoFilter() const
+{
+ return m_guiControl.HasVideoFilter();
+}
+
+bool CGUIRenderSettings::HasStretchMode() const
+{
+ return m_guiControl.HasStretchMode();
+}
+
+bool CGUIRenderSettings::HasRotation() const
+{
+ return m_guiControl.HasRotation();
+}
+
+bool CGUIRenderSettings::HasPixels() const
+{
+ return m_guiControl.HasPixels();
+}
+
+CRenderSettings CGUIRenderSettings::GetSettings() const
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_renderSettings;
+}
+
+CRect CGUIRenderSettings::GetDimensions() const
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_renderDimensions;
+}
+
+void CGUIRenderSettings::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ return m_renderSettings.Reset();
+}
+
+void CGUIRenderSettings::SetSettings(CRenderSettings settings)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderSettings = settings;
+}
+
+void CGUIRenderSettings::SetDimensions(const CRect& dimensions)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderDimensions = dimensions;
+}
+
+void CGUIRenderSettings::SetVideoFilter(const std::string& videoFilter)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderSettings.VideoSettings().SetVideoFilter(videoFilter);
+}
+
+void CGUIRenderSettings::SetStretchMode(STRETCHMODE stretchMode)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderSettings.VideoSettings().SetRenderStretchMode(stretchMode);
+}
+
+void CGUIRenderSettings::SetRotationDegCCW(unsigned int rotationDegCCW)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderSettings.VideoSettings().SetRenderRotation(rotationDegCCW);
+}
+
+void CGUIRenderSettings::SetPixels(const std::string& pixelPath)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ m_renderSettings.VideoSettings().SetPixels(pixelPath);
+}
diff --git a/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.h b/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.h
new file mode 100644
index 0000000..80ba115
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guicontrols/GUIRenderSettings.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/guibridge/IGUIRenderSettings.h"
+#include "cores/RetroPlayer/rendering/RenderSettings.h"
+#include "threads/CriticalSection.h"
+#include "utils/Geometry.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIGameControl;
+
+class CGUIRenderSettings : public IGUIRenderSettings
+{
+public:
+ CGUIRenderSettings(CGUIGameControl& guiControl);
+ ~CGUIRenderSettings() override = default;
+
+ // implementation of IGUIRenderSettings
+ bool HasVideoFilter() const override;
+ bool HasStretchMode() const override;
+ bool HasRotation() const override;
+ bool HasPixels() const override;
+ CRenderSettings GetSettings() const override;
+ CRect GetDimensions() const override;
+
+ // Render functions
+ void Reset();
+ void SetSettings(CRenderSettings settings);
+ void SetDimensions(const CRect& dimensions);
+ void SetVideoFilter(const std::string& videoFilter);
+ void SetStretchMode(STRETCHMODE stretchMode);
+ void SetRotationDegCCW(unsigned int rotationDegCCW);
+ void SetPixels(const std::string& pixelPath);
+
+private:
+ // Construction parameters
+ CGUIGameControl& m_guiControl;
+
+ // Render parameters
+ CRenderSettings m_renderSettings;
+ CRect m_renderDimensions;
+
+ // Synchronization parameters
+ mutable CCriticalSection m_mutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guiplayback/CMakeLists.txt b/xbmc/cores/RetroPlayer/guiplayback/CMakeLists.txt
new file mode 100644
index 0000000..4750111
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiplayback/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES GUIPlaybackControl.cpp
+)
+
+set(HEADERS GUIPlaybackControl.h
+)
+
+core_add_library(retroplayer_guiplayback)
diff --git a/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.cpp b/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.cpp
new file mode 100644
index 0000000..054454f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIPlaybackControl.h"
+
+#include "ServiceBroker.h"
+#include "games/dialogs/osd/DialogGameOSD.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGUIPlaybackControl::CGUIPlaybackControl(IPlaybackCallback& callback) : m_callback(callback)
+{
+}
+
+CGUIPlaybackControl::~CGUIPlaybackControl() = default;
+
+void CGUIPlaybackControl::FrameMove()
+{
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui == nullptr)
+ return;
+
+ const int windowId = gui->GetWindowManager().GetActiveWindow();
+ const int dialogId = gui->GetWindowManager().GetActiveWindowOrDialog();
+
+ // Check if game has entered fullscreen yet
+ const bool bFullscreen = (windowId == WINDOW_FULLSCREEN_GAME);
+
+ // Check if game is in the OSD dialog
+ const bool bInMenu = (dialogId != WINDOW_FULLSCREEN_GAME);
+
+ // Check if game should play in the background of dialog
+ const bool bInBackground = GAME::CDialogGameOSD::PlayInBackground(dialogId);
+
+ GuiState nextState = NextState(bFullscreen, bInMenu, bInBackground);
+ if (nextState != m_state)
+ {
+ m_state = nextState;
+
+ double targetSpeed = GetTargetSpeed(m_state);
+ if (m_previousSpeed != targetSpeed)
+ {
+ m_previousSpeed = targetSpeed;
+ m_callback.SetPlaybackSpeed(targetSpeed);
+ }
+
+ m_callback.EnableInput(AcceptsInput(m_state));
+ }
+}
+
+CGUIPlaybackControl::GuiState CGUIPlaybackControl::NextState(bool bFullscreen,
+ bool bInMenu,
+ bool bInBackground)
+{
+ GuiState newState = m_state;
+
+ switch (m_state)
+ {
+ case GuiState::UNKNOWN:
+ {
+ // Wait for game to enter fullscreen
+ if (bFullscreen)
+ newState = GuiState::FULLSCREEN;
+ break;
+ }
+ case GuiState::FULLSCREEN:
+ {
+ if (bInMenu)
+ {
+ if (bInBackground)
+ newState = GuiState::MENU_PLAYING;
+ else
+ newState = GuiState::MENU_PAUSED;
+ }
+ break;
+ }
+ case GuiState::MENU_PAUSED:
+ {
+ if (!bInMenu)
+ newState = GuiState::FULLSCREEN;
+ else if (bInBackground)
+ newState = GuiState::MENU_PLAYING;
+ break;
+ }
+ case GuiState::MENU_PLAYING:
+ {
+ if (!bInBackground)
+ {
+ if (!bInMenu)
+ newState = GuiState::FULLSCREEN;
+ else
+ newState = GuiState::MENU_PAUSED;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return newState;
+}
+
+double CGUIPlaybackControl::GetTargetSpeed(GuiState state)
+{
+ double targetSpeed = 0.0;
+
+ switch (state)
+ {
+ case GuiState::FULLSCREEN:
+ {
+ targetSpeed = 1.0;
+ break;
+ }
+ case GuiState::MENU_PAUSED:
+ {
+ targetSpeed = 0.0;
+ break;
+ }
+ case GuiState::MENU_PLAYING:
+ {
+ targetSpeed = 1.0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return targetSpeed;
+}
+
+bool CGUIPlaybackControl::AcceptsInput(GuiState state)
+{
+ bool bEnableInput = false;
+
+ switch (state)
+ {
+ case GuiState::FULLSCREEN:
+ {
+ bEnableInput = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return bEnableInput;
+}
diff --git a/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.h b/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.h
new file mode 100644
index 0000000..48baced
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiplayback/GUIPlaybackControl.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/playback/IPlaybackControl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Class to control playback by monitoring OSD status
+ */
+class CGUIPlaybackControl : public IPlaybackControl
+{
+public:
+ CGUIPlaybackControl(IPlaybackCallback& callback);
+
+ ~CGUIPlaybackControl() override;
+
+ // Implementation of IPlaybackControl
+ void FrameMove() override;
+
+private:
+ enum class GuiState
+ {
+ UNKNOWN,
+ FULLSCREEN,
+ MENU_PAUSED,
+ MENU_PLAYING,
+ };
+
+ // Helper functions
+ GuiState NextState(bool bFullscreen, bool bInMenu, bool bInBackground);
+ static double GetTargetSpeed(GuiState state);
+ static bool AcceptsInput(GuiState state);
+
+ // Construction parameters
+ IPlaybackCallback& m_callback;
+
+ // State parameters
+ GuiState m_state = GuiState::UNKNOWN;
+ double m_previousSpeed = 0.0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guiwindows/CMakeLists.txt b/xbmc/cores/RetroPlayer/guiwindows/CMakeLists.txt
new file mode 100644
index 0000000..aaa05a2
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiwindows/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES GameWindowFullScreen.cpp
+ GameWindowFullScreenText.cpp
+)
+
+set(HEADERS GameWindowFullScreen.h
+ GameWindowFullScreenText.h
+)
+
+core_add_library(retroplayer_guiwindows)
diff --git a/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.cpp b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.cpp
new file mode 100644
index 0000000..f50ed7d
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GameWindowFullScreen.h"
+
+#include "GUIInfoManager.h" //! @todo Remove me
+#include "GameWindowFullScreenText.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h" //! @todo Remove me
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guibridge/GUIRenderHandle.h"
+#include "games/GameServices.h"
+#include "games/GameSettings.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIControl.h"
+#include "guilib/GUIDialog.h"
+#include "guilib/GUIWindowManager.h" //! @todo Remove me
+#include "guilib/WindowIDs.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "windowing/GraphicContext.h" //! @todo Remove me
+
+using namespace KODI;
+using namespace KODI::GUILIB;
+using namespace RETRO;
+
+CGameWindowFullScreen::CGameWindowFullScreen(void)
+ : CGUIWindow(WINDOW_FULLSCREEN_GAME, "VideoFullScreen.xml"),
+ m_fullscreenText(new CGameWindowFullScreenText(*this))
+{
+ // initialize CGUIControl
+ m_controlStats = new GUICONTROLSTATS;
+
+ // initialize CGUIWindow
+ m_loadType = KEEP_IN_MEMORY;
+
+ RegisterWindow();
+}
+
+CGameWindowFullScreen::~CGameWindowFullScreen()
+{
+ UnregisterWindow();
+
+ delete m_controlStats;
+}
+
+void CGameWindowFullScreen::Process(unsigned int currentTime, CDirtyRegionList& dirtyregion)
+{
+ if (m_renderHandle->IsDirty())
+ MarkDirtyRegion();
+
+ m_controlStats->Reset();
+
+ CGUIWindow::Process(currentTime, dirtyregion);
+
+ //! @todo This isn't quite optimal - ideally we'd only be dirtying up the actual video render rect
+ //! which is probably the job of the renderer as it can more easily track resizing etc.
+ m_renderRegion.SetRect(
+ 0, 0, static_cast<float>(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()),
+ static_cast<float>(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()));
+}
+
+void CGameWindowFullScreen::Render()
+{
+ m_renderHandle->Render();
+
+ CGUIWindow::Render();
+}
+
+void CGameWindowFullScreen::RenderEx()
+{
+ CGUIWindow::RenderEx();
+
+ m_renderHandle->RenderEx();
+}
+
+bool CGameWindowFullScreen::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_SHOW_OSD:
+ case ACTION_TRIGGER_OSD:
+ {
+ TriggerOSD();
+ return true;
+ }
+ case ACTION_MOUSE_MOVE:
+ {
+ if (action.GetAmount(2) || action.GetAmount(3))
+ {
+ TriggerOSD();
+ return true;
+ }
+ break;
+ }
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ TriggerOSD();
+ return true;
+ }
+ case ACTION_SHOW_GUI:
+ {
+ // Switch back to the menu
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+ return true;
+ }
+ case ACTION_ASPECT_RATIO:
+ {
+ // Toggle the aspect ratio mode (only if the info is onscreen)
+ // g_application.GetAppPlayer().SetRenderViewMode(CViewModeSettings::GetNextQuickCycleViewMode(CMediaSettings::GetInstance().GetCurrentVideoSettings().m_ViewMode));
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return CGUIWindow::OnAction(action);
+}
+
+bool CGameWindowFullScreen::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_SETFOCUS:
+ case GUI_MSG_LOSTFOCUS:
+ {
+ if (message.GetSenderId() != WINDOW_FULLSCREEN_GAME)
+ return true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return CGUIWindow::OnMessage(message);
+}
+
+void CGameWindowFullScreen::FrameMove()
+{
+ m_fullscreenText->FrameMove();
+
+ CGUIWindow::FrameMove();
+}
+
+void CGameWindowFullScreen::ClearBackground()
+{
+ m_renderHandle->ClearBackground();
+
+ CGUIWindow::ClearBackground();
+}
+
+bool CGameWindowFullScreen::HasVisibleControls()
+{
+ return m_controlStats->nCountVisible > 0;
+}
+
+void CGameWindowFullScreen::OnWindowLoaded()
+{
+ CGUIWindow::OnWindowLoaded();
+
+ // Override the clear colour - we must never clear fullscreen
+ m_clearBackground = 0;
+
+ m_fullscreenText->OnWindowLoaded();
+}
+
+void CGameWindowFullScreen::OnInitWindow()
+{
+ GUIINFO::CPlayerGUIInfo& guiInfo =
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider();
+ guiInfo.SetShowInfo(false);
+
+ // Switch resolution
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(true); //! @todo
+
+ CGUIWindow::OnInitWindow();
+
+ // Show OSD help
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ if (gameSettings.ShowOSDHelp())
+ TriggerOSD();
+ else
+ {
+ //! @todo We need to route this check through the GUI bridge. By adding the
+ // dependency to the application player here, we are prevented from
+ // having multiple players.
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer->HasGameAgent())
+ {
+ gameSettings.SetShowOSDHelp(true);
+ TriggerOSD();
+ }
+ }
+}
+
+void CGameWindowFullScreen::OnDeinitWindow(int nextWindowID)
+{
+ // Close all active modal dialogs
+ CServiceBroker::GetGUI()->GetWindowManager().CloseInternalModalDialogs(true);
+
+ CGUIWindow::OnDeinitWindow(nextWindowID);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(false); //! @todo
+}
+
+void CGameWindowFullScreen::TriggerOSD()
+{
+ CGUIDialog* pOSD = GetOSD();
+ if (pOSD != nullptr)
+ {
+ if (!pOSD->IsDialogRunning())
+ pOSD->Open();
+ }
+}
+
+CGUIDialog* CGameWindowFullScreen::GetOSD()
+{
+ return CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_GAME_OSD);
+}
+
+void CGameWindowFullScreen::RegisterWindow()
+{
+ m_renderHandle = CServiceBroker::GetGameRenderManager().RegisterWindow(*this);
+}
+
+void CGameWindowFullScreen::UnregisterWindow()
+{
+ m_renderHandle.reset();
+}
diff --git a/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.h b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.h
new file mode 100644
index 0000000..a925c8b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreen.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/GUIWindow.h"
+
+class CGUIDialog;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGameWindowFullScreenText;
+class CGUIRenderHandle;
+
+class CGameWindowFullScreen : public CGUIWindow
+{
+public:
+ CGameWindowFullScreen();
+ ~CGameWindowFullScreen() override;
+
+ // implementation of CGUIControl via CGUIWindow
+ void Process(unsigned int currentTime, CDirtyRegionList& dirtyregion) override;
+ void Render() override;
+ void RenderEx() override;
+ bool OnAction(const CAction& action) override;
+ bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of CGUIWindow
+ void FrameMove() override;
+ void ClearBackground() override;
+ bool HasVisibleControls() override;
+ void OnWindowLoaded() override;
+ void OnDeinitWindow(int nextWindowID) override;
+
+protected:
+ // implementation of CGUIWindow
+ void OnInitWindow() override;
+
+private:
+ void TriggerOSD();
+ CGUIDialog* GetOSD();
+
+ void RegisterWindow();
+ void UnregisterWindow();
+
+ // GUI parameters
+ std::unique_ptr<CGameWindowFullScreenText> m_fullscreenText;
+
+ // Rendering parameters
+ std::shared_ptr<CGUIRenderHandle> m_renderHandle;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.cpp b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.cpp
new file mode 100644
index 0000000..e0f722a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GameWindowFullScreenText.h"
+
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindow.h"
+#include "video/windows/GUIWindowFullScreenDefines.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CGameWindowFullScreenText::CGameWindowFullScreenText(CGUIWindow& fullscreenWindow)
+ : m_fullscreenWindow(fullscreenWindow)
+{
+}
+
+void CGameWindowFullScreenText::OnWindowLoaded()
+{
+ m_bShowText = false;
+ m_bTextChanged = true;
+ m_bTextVisibilityChanged = true;
+ m_lines.clear();
+}
+
+void CGameWindowFullScreenText::FrameMove()
+{
+ if (m_bTextChanged)
+ {
+ m_bTextChanged = false;
+ UploadText();
+ }
+
+ if (m_bTextVisibilityChanged)
+ {
+ m_bTextVisibilityChanged = false;
+
+ if (m_bShowText)
+ Show();
+ else
+ Hide();
+ }
+}
+
+const std::string& CGameWindowFullScreenText::GetText(unsigned int lineIndex) const
+{
+ if (lineIndex < m_lines.size())
+ return m_lines[lineIndex];
+
+ static const std::string empty;
+ return empty;
+}
+
+void CGameWindowFullScreenText::SetText(unsigned int lineIndex, std::string line)
+{
+ if (lineIndex >= m_lines.size())
+ m_lines.resize(lineIndex + 1);
+
+ m_lines[lineIndex] = std::move(line);
+}
+
+const std::vector<std::string>& CGameWindowFullScreenText::GetText() const
+{
+ return m_lines;
+}
+
+void CGameWindowFullScreenText::SetText(std::vector<std::string> text)
+{
+ m_lines = std::move(text);
+}
+
+void CGameWindowFullScreenText::UploadText()
+{
+ for (unsigned int i = 0; i < m_lines.size(); i++)
+ {
+ int rowControl = GetControlID(i);
+ if (rowControl > 0)
+ SET_CONTROL_LABEL(rowControl, m_lines[i]);
+ }
+}
+
+void CGameWindowFullScreenText::Show()
+{
+ SET_CONTROL_VISIBLE(LABEL_ROW1);
+ SET_CONTROL_VISIBLE(LABEL_ROW2);
+ SET_CONTROL_VISIBLE(LABEL_ROW3);
+ SET_CONTROL_VISIBLE(BLUE_BAR);
+}
+
+void CGameWindowFullScreenText::Hide()
+{
+ SET_CONTROL_HIDDEN(LABEL_ROW1);
+ SET_CONTROL_HIDDEN(LABEL_ROW2);
+ SET_CONTROL_HIDDEN(LABEL_ROW3);
+ SET_CONTROL_HIDDEN(BLUE_BAR);
+}
+
+int CGameWindowFullScreenText::GetID() const
+{
+ return m_fullscreenWindow.GetID();
+}
+
+bool CGameWindowFullScreenText::OnMessage(CGUIMessage& message)
+{
+ return m_fullscreenWindow.OnMessage(message);
+}
+
+int CGameWindowFullScreenText::GetControlID(unsigned int lineIndex)
+{
+ switch (lineIndex)
+ {
+ case 0:
+ return LABEL_ROW1;
+ case 1:
+ return LABEL_ROW2;
+ case 2:
+ return LABEL_ROW3;
+ default:
+ break;
+ }
+
+ return -1;
+}
diff --git a/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.h b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.h
new file mode 100644
index 0000000..a454a03
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/guiwindows/GameWindowFullScreenText.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+class CGUIDialog;
+class CGUIMessage;
+class CGUIWindow;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGameWindowFullScreenText
+{
+public:
+ CGameWindowFullScreenText(CGUIWindow& fullscreenWindow);
+ ~CGameWindowFullScreenText() = default;
+
+ // Window functions
+ void OnWindowLoaded();
+ void FrameMove();
+
+ /*!
+ * \brief Get a line of text
+ */
+ const std::string& GetText(unsigned int lineIndex) const;
+
+ /*!
+ * \brief Set a line of text
+ */
+ void SetText(unsigned int lineIndex, std::string line);
+
+ /*!
+ * \brief Get entire text
+ */
+ const std::vector<std::string>& GetText() const;
+
+ /*!
+ * \brief Set entire text
+ */
+ void SetText(std::vector<std::string> text);
+
+private:
+ // Window functions
+ void UploadText();
+ void Show();
+ void Hide();
+
+ /*!
+ * \brief Translate line index to the control ID in the skin
+ *
+ * \param lineIndex The line in the string vector
+ *
+ * \return The ID of the line's label control in the skin
+ */
+ static int GetControlID(unsigned int lineIndex);
+
+ // Window functions required by GUIMessage macros
+ //! @todo Change macros into functions
+ int GetID() const;
+ bool OnMessage(CGUIMessage& message);
+
+ // Construction parameters
+ CGUIWindow& m_fullscreenWindow;
+
+ // Window state
+ bool m_bShowText = false;
+ bool m_bTextChanged = true;
+ bool m_bTextVisibilityChanged = true;
+
+ // Text
+ std::vector<std::string> m_lines;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/messages/CMakeLists.txt b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt
new file mode 100644
index 0000000..e2e5fa1
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt
@@ -0,0 +1,25 @@
+set(MESSAGES savestate.fbs
+ video.fbs
+)
+
+foreach(_file ${MESSAGES})
+ get_filename_component(FLATC_OUTPUT ${_file} NAME_WE)
+ set(FLATC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FLATC_OUTPUT}_generated.h)
+ list(APPEND FLATC_OUTPUTS ${FLATC_OUTPUT})
+
+ add_custom_command(OUTPUT ${FLATC_OUTPUT}
+ COMMAND ${FLATBUFFERS_FLATC_EXECUTABLE}
+ ARGS -c -o "${FLATBUFFERS_MESSAGES_INCLUDE_DIR}/" ${_file}
+ DEPENDS ${_file}
+ COMMENT "Building C++ header for ${_file}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endforeach()
+
+add_custom_target(retroplayer_messages DEPENDS ${FLATC_OUTPUTS})
+set_target_properties(retroplayer_messages PROPERTIES FOLDER "Generated Messages"
+ INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}
+ SOURCES "${FLATC_OUTPUTS}")
+
+if(TARGET flatbuffers::flatbuffers)
+ add_dependencies(retroplayer_messages flatbuffers::flatbuffers)
+endif()
diff --git a/xbmc/cores/RetroPlayer/messages/savestate.fbs b/xbmc/cores/RetroPlayer/messages/savestate.fbs
new file mode 100644
index 0000000..7f8d805
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/messages/savestate.fbs
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2018 Team Kodi
+// This file is part of Kodi - https://kodi.tv
+//
+// SPDX-License-Identifier: MIT
+// See LICENSES/README.md for more information.
+//
+
+include "video.fbs";
+
+namespace KODI.RETRO;
+
+// Savestate schema
+// Version 3
+
+file_identifier "SAV_";
+
+enum SaveType : uint8 {
+ Unknown,
+ Auto,
+ Manual
+}
+
+table Savestate {
+ // Schema version
+ version:uint8 (id: 0);
+
+ // Savestate properties
+ type:SaveType (id: 1);
+ slot:uint8 (id: 2);
+ label:string (id: 3);
+ caption:string (id: 11);
+ created:string (id: 4); // W3C date time [ISO 8601 : 1988 (E)] with timezone info
+
+ // Game properties
+ game_file_name:string (id: 5);
+
+ // Environment properties
+ timestamp_frames:uint64 (id: 6);
+ timestamp_wall_clock_ns:uint64 (id: 7);
+
+ // Emulator properties
+ emulator_addon_id:string (id: 8);
+ emulator_version:string (id: 9); // Semantic version
+
+ // Video stream properties
+ pixel_format:PixelFormat (id: 12);
+ nominal_width:uint16 (id: 13);
+ nominal_height:uint16 (id: 14);
+ max_width:uint16 (id: 15);
+ max_height:uint16 (id: 16);
+ pixel_aspect_ratio:float (id: 17);
+
+ // Video frame properties
+ video_data:[uint8] (id: 18);
+ video_width:uint16 (id: 19);
+ video_height:uint16 (id: 20);
+ rotation_ccw:VideoRotation (id: 21);
+
+ // Memory properties
+ memory_data:[uint8] (id: 10);
+}
+
+root_type Savestate;
diff --git a/xbmc/cores/RetroPlayer/messages/video.fbs b/xbmc/cores/RetroPlayer/messages/video.fbs
new file mode 100644
index 0000000..f83ba5f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/messages/video.fbs
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2023 Team Kodi
+// This file is part of Kodi - https://kodi.tv
+//
+// SPDX-License-Identifier: MIT
+// See LICENSES/README.md for more information.
+//
+
+namespace KODI.RETRO;
+
+enum PixelFormat : uint8 {
+ /// @brief Value for unknown pixel formats
+ Unknown,
+
+ /// @brief packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
+ /// This is the preferred format for web compatibility
+ RGBA_8888,
+
+ /// @brief packed RGB 8:8:8, 32bpp, XRGBXRGB... X=unused/undefined
+ XRGB_8888,
+
+ /// @brief packed BGR 8:8:8, 32bpp, BGRXBGRX... X=unused/undefined
+ BGRX_8888,
+
+ /// @brief packed RGB 5:6:5, 16bpp, (msb), 5R 6G 5B(lsb), big-endian
+ RGB_565_BE,
+
+ /// @brief packed RGB 5:6:5, 16bpp, (msb), 5R 6G 5B(lsb), little-endian
+ RGB_565_LE,
+
+ /// @brief packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian, X=unused/undefined
+ RGB_555_BE,
+
+ /// @brief packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined
+ RGB_555_LE,
+}
+
+enum VideoRotation : uint8 {
+ CCW_0,
+ CCW_90,
+ CCW_180,
+ CCW_270,
+}
diff --git a/xbmc/cores/RetroPlayer/playback/CMakeLists.txt b/xbmc/cores/RetroPlayer/playback/CMakeLists.txt
new file mode 100644
index 0000000..058522f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES GameLoop.cpp
+ ReversiblePlayback.cpp)
+
+set(HEADERS GameLoop.h
+ IPlayback.h
+ IPlaybackControl.h
+ RealtimePlayback.h
+ ReversiblePlayback.h)
+
+core_add_library(retroplayer_playback)
diff --git a/xbmc/cores/RetroPlayer/playback/GameLoop.cpp b/xbmc/cores/RetroPlayer/playback/GameLoop.cpp
new file mode 100644
index 0000000..c8862d2
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/GameLoop.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GameLoop.h"
+
+#include <chrono>
+#include <cmath>
+
+using namespace KODI;
+using namespace RETRO;
+using namespace std::chrono_literals;
+
+#define DEFAULT_FPS 60 // In case fps is 0 (shouldn't happen)
+#define FOREVER_MS (7 * 24 * 60 * 60 * 1000) // 1 week is large enough
+
+CGameLoop::CGameLoop(IGameLoopCallback* callback, double fps)
+ : CThread("GameLoop"),
+ m_callback(callback),
+ m_fps(fps ? fps : DEFAULT_FPS),
+ m_speedFactor(0.0),
+ m_lastFrameMs(0.0),
+ m_adjustTime(0.0)
+{
+}
+
+CGameLoop::~CGameLoop()
+{
+ Stop();
+}
+
+void CGameLoop::Start()
+{
+ Create();
+}
+
+void CGameLoop::Stop()
+{
+ StopThread(false);
+ m_sleepEvent.Set();
+ StopThread(true);
+}
+
+void CGameLoop::SetSpeed(double speedFactor)
+{
+ m_speedFactor = speedFactor;
+
+ m_sleepEvent.Set();
+}
+
+void CGameLoop::PauseAsync()
+{
+ SetSpeed(0.0);
+}
+
+void CGameLoop::Process(void)
+{
+ while (!m_bStop)
+ {
+ if (m_speedFactor == 0.0)
+ {
+ m_lastFrameMs = 0.0;
+ m_sleepEvent.Wait(5000ms);
+ }
+ else
+ {
+ if (m_speedFactor > 0.0)
+ m_callback->FrameEvent();
+ else if (m_speedFactor < 0.0)
+ m_callback->RewindEvent();
+
+ if (m_lastFrameMs > 0.0)
+ {
+ m_lastFrameMs += FrameTimeMs();
+ m_adjustTime = m_lastFrameMs - NowMs();
+ }
+ else
+ {
+ m_lastFrameMs = NowMs();
+ m_adjustTime = 0.0;
+ }
+
+ // Calculate sleep time
+ double sleepTimeMs = SleepTimeMs();
+
+ // Sleep at least 1 ms to avoid sleeping forever
+ while (sleepTimeMs > 1.0)
+ {
+ m_sleepEvent.Wait(std::chrono::milliseconds(static_cast<unsigned int>(sleepTimeMs)));
+
+ if (m_bStop)
+ break;
+
+ // Speed may have changed, update sleep time
+ sleepTimeMs = SleepTimeMs();
+ }
+ }
+ }
+}
+
+double CGameLoop::FrameTimeMs() const
+{
+ if (m_speedFactor != 0.0)
+ return 1000.0 / m_fps / std::abs(m_speedFactor);
+ else
+ return 1000.0 / m_fps / 1.0;
+}
+
+double CGameLoop::SleepTimeMs() const
+{
+ // Calculate next frame time
+ const double nextFrameMs = m_lastFrameMs + FrameTimeMs();
+
+ // Calculate sleep time
+ double sleepTimeMs = (nextFrameMs - NowMs()) + m_adjustTime;
+
+ // Reset adjust time
+ m_adjustTime = 0.0;
+
+ // Positive or zero
+ sleepTimeMs = (sleepTimeMs >= 0.0 ? sleepTimeMs : 0.0);
+
+ return sleepTimeMs;
+}
+
+double CGameLoop::NowMs() const
+{
+ return std::chrono::duration<double, std::milli>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
diff --git a/xbmc/cores/RetroPlayer/playback/GameLoop.h b/xbmc/cores/RetroPlayer/playback/GameLoop.h
new file mode 100644
index 0000000..e4fbcff
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/GameLoop.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Event.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IGameLoopCallback
+{
+public:
+ virtual ~IGameLoopCallback() = default;
+
+ /*!
+ * \brief The next frame is being shown
+ */
+ virtual void FrameEvent() = 0;
+
+ /*!
+ * \brief The prior frame is being shown
+ */
+ virtual void RewindEvent() = 0;
+};
+
+class CGameLoop : protected CThread
+{
+public:
+ CGameLoop(IGameLoopCallback* callback, double fps);
+
+ ~CGameLoop() override;
+
+ void Start();
+ void Stop();
+
+ double FPS() const { return m_fps; }
+
+ double GetSpeed() const { return m_speedFactor; }
+ void SetSpeed(double speedFactor);
+ void PauseAsync();
+
+protected:
+ // implementation of CThread
+ void Process() override;
+
+private:
+ double FrameTimeMs() const;
+ double SleepTimeMs() const;
+ double NowMs() const;
+
+ IGameLoopCallback* const m_callback;
+ const double m_fps;
+ std::atomic<double> m_speedFactor;
+ double m_lastFrameMs;
+ mutable double m_adjustTime;
+ CEvent m_sleepEvent;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/playback/IPlayback.h b/xbmc/cores/RetroPlayer/playback/IPlayback.h
new file mode 100644
index 0000000..9871889
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/IPlayback.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IPlayback
+{
+public:
+ virtual ~IPlayback() = default;
+
+ // Lifetime management
+ virtual void Initialize() = 0;
+ virtual void Deinitialize() = 0;
+
+ // Playback capabilities
+ virtual bool CanPause() const = 0;
+ virtual bool CanSeek() const = 0;
+
+ // Control playback
+ virtual unsigned int GetTimeMs() const = 0;
+ virtual unsigned int GetTotalTimeMs() const = 0;
+ virtual unsigned int GetCacheTimeMs() const = 0;
+ virtual void SeekTimeMs(unsigned int timeMs) = 0;
+ virtual double GetSpeed() const = 0;
+ virtual void SetSpeed(double speedFactor) = 0;
+ virtual void PauseAsync() = 0; // Pauses after the following frame
+
+ // Savestates
+ virtual std::string CreateSavestate(
+ bool autosave,
+ const std::string& savestatePath = "") = 0; // Returns the path of savestate on success
+ virtual bool LoadSavestate(const std::string& savestatePath) = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/playback/IPlaybackControl.h b/xbmc/cores/RetroPlayer/playback/IPlaybackControl.h
new file mode 100644
index 0000000..6637cae
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/IPlaybackControl.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief The playback client being controlled
+ */
+class IPlaybackCallback
+{
+public:
+ virtual ~IPlaybackCallback() = default;
+
+ /*!
+ * \brief Set the playback speed
+ *
+ * \param speed The new speed
+ */
+ virtual void SetPlaybackSpeed(double speed) = 0;
+
+ /*!
+ * \brief Enable/disable game input
+ *
+ * \param bEnable True to enable input, false to disable input
+ */
+ virtual void EnableInput(bool bEnable) = 0;
+};
+
+/*!
+ * \brief Class that can control playback and input
+ */
+class IPlaybackControl
+{
+public:
+ virtual ~IPlaybackControl() = default;
+
+ /*!
+ * \brief Called every frame
+ */
+ virtual void FrameMove() = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/playback/RealtimePlayback.h b/xbmc/cores/RetroPlayer/playback/RealtimePlayback.h
new file mode 100644
index 0000000..15e3bf6
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/RealtimePlayback.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IPlayback.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRealtimePlayback : public IPlayback
+{
+public:
+ ~CRealtimePlayback() override = default;
+
+ // implementation of IPlayback
+ void Initialize() override {}
+ void Deinitialize() override {}
+ bool CanPause() const override { return false; }
+ bool CanSeek() const override { return false; }
+ unsigned int GetTimeMs() const override { return 0; }
+ unsigned int GetTotalTimeMs() const override { return 0; }
+ unsigned int GetCacheTimeMs() const override { return 0; }
+ void SeekTimeMs(unsigned int timeMs) override {}
+ double GetSpeed() const override { return 1.0; }
+ void SetSpeed(double speedFactor) override {}
+ void PauseAsync() override {}
+ std::string CreateSavestate(bool autosave, const std::string& savestatePath = "") override
+ {
+ return "";
+ }
+ bool LoadSavestate(const std::string& savestatePath) override { return false; }
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp
new file mode 100644
index 0000000..653b209
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ReversiblePlayback.h"
+
+#include "ServiceBroker.h"
+#include "XBDateTime.h"
+#include "addons/AddonVersion.h"
+#include "cores/RetroPlayer/cheevos/Cheevos.h"
+#include "cores/RetroPlayer/guibridge/GUIGameMessenger.h"
+#include "cores/RetroPlayer/rendering/RPRenderManager.h"
+#include "cores/RetroPlayer/savestates/ISavestate.h"
+#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h"
+#include "filesystem/File.h"
+#include "games/GameServices.h"
+#include "games/GameSettings.h"
+#include "games/addons/GameClient.h"
+#include "utils/MathUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace KODI;
+using namespace RETRO;
+
+#define REWIND_FACTOR 0.25 // Rewind at 25% of gameplay speed
+
+CReversiblePlayback::CReversiblePlayback(GAME::CGameClient* gameClient,
+ CRPRenderManager& renderManager,
+ CCheevos* cheevos,
+ CGUIGameMessenger& guiMessenger,
+ double fps,
+ size_t serializeSize)
+ : m_gameClient(gameClient),
+ m_renderManager(renderManager),
+ m_cheevos(cheevos),
+ m_guiMessenger(guiMessenger),
+ m_gameLoop(this, fps),
+ m_savestateDatabase(new CSavestateDatabase),
+ m_totalFrameCount(0),
+ m_pastFrameCount(0),
+ m_futureFrameCount(0),
+ m_playTimeMs(0),
+ m_totalTimeMs(0),
+ m_cacheTimeMs(0)
+{
+ UpdateMemoryStream();
+
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ gameSettings.RegisterObserver(this);
+}
+
+CReversiblePlayback::~CReversiblePlayback()
+{
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ gameSettings.UnregisterObserver(this);
+
+ Deinitialize();
+}
+
+void CReversiblePlayback::Initialize()
+{
+ m_gameLoop.Start();
+}
+
+void CReversiblePlayback::Deinitialize()
+{
+ // Wait for autosave tasks
+ for (std::future<void>& task : m_savestateThreads)
+ task.wait();
+ m_savestateThreads.clear();
+
+ m_gameLoop.Stop();
+}
+
+void CReversiblePlayback::SeekTimeMs(unsigned int timeMs)
+{
+ const int offsetTimeMs = timeMs - GetTimeMs();
+ const int offsetFrames = MathUtils::round_int(offsetTimeMs / 1000.0 * m_gameLoop.FPS());
+
+ if (offsetFrames > 0)
+ {
+ const uint64_t frames = std::min(static_cast<uint64_t>(offsetFrames), m_futureFrameCount);
+ if (frames > 0)
+ {
+ m_gameLoop.SetSpeed(0.0);
+ AdvanceFrames(frames);
+ m_gameLoop.SetSpeed(1.0);
+ }
+ }
+ else if (offsetFrames < 0)
+ {
+ const uint64_t frames = std::min(static_cast<uint64_t>(-offsetFrames), m_pastFrameCount);
+ if (frames > 0)
+ {
+ m_gameLoop.SetSpeed(0.0);
+ RewindFrames(frames);
+ m_gameLoop.SetSpeed(1.0);
+ }
+ }
+}
+
+double CReversiblePlayback::GetSpeed() const
+{
+ return m_gameLoop.GetSpeed();
+}
+
+void CReversiblePlayback::SetSpeed(double speedFactor)
+{
+ if (speedFactor >= 0.0)
+ m_gameLoop.SetSpeed(speedFactor);
+ else
+ m_gameLoop.SetSpeed(speedFactor * REWIND_FACTOR);
+}
+
+void CReversiblePlayback::PauseAsync()
+{
+ m_gameLoop.PauseAsync();
+}
+
+std::string CReversiblePlayback::CreateSavestate(bool autosave,
+ const std::string& savestatePath /* = "" */)
+{
+ const size_t memorySize = m_gameClient->SerializeSize();
+
+ // Game client must support serialization
+ if (memorySize == 0)
+ return "";
+
+ //! @todo Handle savestates for standalone game clients
+ if (m_gameClient->GetGamePath().empty())
+ {
+ return "";
+ }
+
+ // Take a timestamp of the system clock
+ const CDateTime nowUTC = CDateTime::GetUTCDateTime();
+
+ // Record the frame count
+ const uint64_t timestampFrames = m_totalFrameCount;
+
+ // Get the savestate path
+ std::string savePath(savestatePath);
+ {
+ std::unique_lock<CCriticalSection> lock(m_savestateMutex);
+
+ if (autosave && savePath.empty())
+ savePath = m_autosavePath;
+
+ // Clear autosave path so the next autosave is created in a new slot and
+ // does not overwrite the newly-created manual save
+ if (!autosave && savePath == m_autosavePath)
+ m_autosavePath.clear();
+
+ // If path is still unknown, calculate it now
+ if (savePath.empty())
+ savePath = CSavestateDatabase::MakeSavestatePath(m_gameClient->GetGamePath(), nowUTC);
+
+ // Update autosave path
+ if (autosave)
+ m_autosavePath = savePath;
+ }
+
+ // Capture the current video frame
+ m_renderManager.CacheVideoFrame(savePath);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_savestateMutex);
+
+ // Prune any finished autosave threads
+ m_savestateThreads.erase(std::remove_if(m_savestateThreads.begin(), m_savestateThreads.end(),
+ [](std::future<void>& task) {
+ return task.wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready;
+ }),
+ m_savestateThreads.end());
+
+ // Save async to not block game loop
+ std::future<void> task =
+ std::async(std::launch::async, [this, autosave, savePath, nowUTC, timestampFrames]() {
+ CommitSavestate(autosave, savePath, nowUTC, timestampFrames);
+ });
+
+ m_savestateThreads.emplace_back(std::move(task));
+ }
+
+ return savePath;
+}
+
+void CReversiblePlayback::CommitSavestate(bool autosave,
+ const std::string& savePath,
+ const CDateTime& nowUTC,
+ uint64_t timestampFrames)
+{
+ std::unique_ptr<ISavestate> savestate = CSavestateDatabase::AllocateSavestate();
+ std::unique_ptr<ISavestate> loadedSavestate;
+
+ const size_t memorySize = m_gameClient->SerializeSize();
+ uint8_t* const memoryData = savestate->GetMemoryBuffer(memorySize);
+
+ // Copy the savestate memory
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ if (m_memoryStream && m_memoryStream->CurrentFrame() != nullptr)
+ {
+ std::memcpy(memoryData, m_memoryStream->CurrentFrame(), memorySize);
+ }
+ else
+ {
+ lock.unlock();
+ if (!m_gameClient->Serialize(memoryData, memorySize))
+ return;
+ }
+ }
+
+ // Attempt to get existing properties
+ {
+ std::unique_lock<CCriticalSection> lock(m_savestateMutex);
+ if (!savePath.empty() && XFILE::CFile::Exists(savePath))
+ {
+ loadedSavestate = CSavestateDatabase::AllocateSavestate();
+ if (!m_savestateDatabase->GetSavestate(savePath, *loadedSavestate))
+ loadedSavestate.reset();
+ }
+ }
+
+ const std::string caption = m_cheevos->GetRichPresenceEvaluation();
+ const std::string gameFileName = URIUtils::GetFileName(m_gameClient->GetGamePath());
+ const double timestampWallClock =
+ (timestampFrames /
+ m_gameClient->GetFrameRate()); //! @todo Accumulate playtime instead of deriving it
+ const std::string gameClientId = m_gameClient->ID();
+ const std::string gameClientVersion = m_gameClient->Version().asString();
+
+ savestate->SetType(autosave ? SAVE_TYPE::AUTO : SAVE_TYPE::MANUAL);
+ savestate->SetLabel(loadedSavestate ? loadedSavestate->Label() : "");
+ savestate->SetCaption(caption);
+ savestate->SetCreated(nowUTC);
+ savestate->SetGameFileName(gameFileName);
+ savestate->SetTimestampFrames(timestampFrames);
+ savestate->SetTimestampWallClock(timestampWallClock);
+ savestate->SetGameClientID(gameClientId);
+ savestate->SetGameClientVersion(gameClientVersion);
+
+ m_renderManager.SaveVideoFrame(savePath, *savestate);
+
+ savestate->Finalize();
+
+ bool success;
+ {
+ std::unique_lock<CCriticalSection> lock(m_savestateMutex);
+ success = m_savestateDatabase->AddSavestate(savePath, m_gameClient->GetGamePath(), *savestate);
+ }
+
+ if (success)
+ {
+ std::string thumbnailPath = CSavestateDatabase::MakeThumbnailPath(savePath);
+ m_renderManager.SaveThumbnail(thumbnailPath);
+ }
+
+ // Notify the GUI that the metadata for this savestate should be refreshed
+ m_guiMessenger.RefreshSavestates(savePath, savestate.get());
+}
+
+bool CReversiblePlayback::LoadSavestate(const std::string& savestatePath)
+{
+ const size_t memorySize = m_gameClient->SerializeSize();
+
+ // Game client must support serialization
+ if (memorySize == 0)
+ return false;
+
+ bool bSuccess = false;
+
+ std::unique_ptr<ISavestate> savestate = CSavestateDatabase::AllocateSavestate();
+ if (m_savestateDatabase->GetSavestate(savestatePath, *savestate))
+ {
+ if (savestate->GetMemorySize() != memorySize)
+ {
+ CLog::Log(LOGERROR, "Invalid memory size, got {}, expected {}", memorySize,
+ savestate->GetMemorySize());
+ }
+ else
+ {
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ if (m_memoryStream)
+ {
+ m_memoryStream->SetFrameCounter(savestate->TimestampFrames());
+ std::memcpy(m_memoryStream->BeginFrame(), savestate->GetMemoryData(), memorySize);
+ m_memoryStream->SubmitFrame();
+ }
+ }
+
+ if (m_gameClient->Deserialize(savestate->GetMemoryData(), memorySize))
+ {
+ m_totalFrameCount = savestate->TimestampFrames();
+ bSuccess = true;
+ if (savestate->Type() == SAVE_TYPE::AUTO)
+ m_autosavePath = savestatePath;
+ }
+ }
+ }
+
+ m_cheevos->ResetRuntime();
+
+ return bSuccess;
+}
+
+void CReversiblePlayback::FrameEvent()
+{
+ m_gameClient->RunFrame();
+
+ AddFrame();
+}
+
+void CReversiblePlayback::RewindEvent()
+{
+ RewindFrames(1);
+
+ m_gameClient->RunFrame();
+}
+
+void CReversiblePlayback::AddFrame()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (m_memoryStream)
+ {
+ if (m_gameClient->Serialize(m_memoryStream->BeginFrame(), m_memoryStream->FrameSize()))
+ {
+ m_memoryStream->SubmitFrame();
+ UpdatePlaybackStats();
+ }
+ }
+
+ m_totalFrameCount++;
+}
+
+void CReversiblePlayback::RewindFrames(uint64_t frames)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (m_memoryStream)
+ {
+ m_memoryStream->RewindFrames(frames);
+ m_gameClient->Deserialize(m_memoryStream->CurrentFrame(), m_memoryStream->FrameSize());
+ UpdatePlaybackStats();
+ }
+
+ m_totalFrameCount -= std::min(m_totalFrameCount, frames);
+}
+
+void CReversiblePlayback::AdvanceFrames(uint64_t frames)
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (m_memoryStream)
+ {
+ m_memoryStream->AdvanceFrames(frames);
+ m_gameClient->Deserialize(m_memoryStream->CurrentFrame(), m_memoryStream->FrameSize());
+ UpdatePlaybackStats();
+ }
+
+ m_totalFrameCount += frames;
+}
+
+void CReversiblePlayback::UpdatePlaybackStats()
+{
+ m_pastFrameCount = m_memoryStream->PastFramesAvailable();
+ m_futureFrameCount = m_memoryStream->FutureFramesAvailable();
+
+ const uint64_t played = m_pastFrameCount + (m_memoryStream->CurrentFrame() ? 1 : 0);
+ const uint64_t total = m_memoryStream->MaxFrameCount();
+ const uint64_t cached = m_futureFrameCount;
+
+ m_playTimeMs = MathUtils::round_int(1000.0 * played / m_gameLoop.FPS());
+ m_totalTimeMs = MathUtils::round_int(1000.0 * total / m_gameLoop.FPS());
+ m_cacheTimeMs = MathUtils::round_int(1000.0 * cached / m_gameLoop.FPS());
+}
+
+void CReversiblePlayback::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ switch (msg)
+ {
+ case ObservableMessageSettingsChanged:
+ UpdateMemoryStream();
+ break;
+ default:
+ break;
+ }
+}
+
+void CReversiblePlayback::UpdateMemoryStream()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ bool bRewindEnabled = false;
+
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+
+ if (m_gameClient->SerializeSize() > 0)
+ bRewindEnabled = gameSettings.RewindEnabled();
+
+ if (bRewindEnabled)
+ {
+ unsigned int rewindBufferSec = gameSettings.MaxRewindTimeSec();
+ if (rewindBufferSec < 10)
+ rewindBufferSec = 10; // Sanity check
+
+ unsigned int frameCount = MathUtils::round_int(rewindBufferSec * m_gameLoop.FPS());
+
+ if (!m_memoryStream)
+ {
+ m_memoryStream.reset(new CDeltaPairMemoryStream);
+ m_memoryStream->Init(m_gameClient->SerializeSize(), frameCount);
+ }
+
+ if (m_memoryStream->MaxFrameCount() != frameCount)
+ {
+ m_memoryStream->SetMaxFrameCount(frameCount);
+ }
+ }
+ else
+ {
+ m_memoryStream.reset();
+
+ // Reset playback stats
+ m_pastFrameCount = 0;
+ m_futureFrameCount = 0;
+ m_playTimeMs = 0;
+ m_totalTimeMs = 0;
+ m_cacheTimeMs = 0;
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h
new file mode 100644
index 0000000..bd2fc37
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GameLoop.h"
+#include "IPlayback.h"
+#include "threads/CriticalSection.h"
+#include "utils/Observer.h"
+
+#include <future>
+#include <memory>
+#include <stddef.h>
+#include <stdint.h>
+
+class CDateTime;
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClient;
+}
+
+namespace RETRO
+{
+class CCheevos;
+class CGUIGameMessenger;
+class CRPRenderManager;
+class CSavestateDatabase;
+class IMemoryStream;
+
+class CReversiblePlayback : public IPlayback, public IGameLoopCallback, public Observer
+{
+public:
+ CReversiblePlayback(GAME::CGameClient* gameClient,
+ CRPRenderManager& renderManager,
+ CCheevos* cheevos,
+ CGUIGameMessenger& guiMessenger,
+ double fps,
+ size_t serializeSize);
+
+ ~CReversiblePlayback() override;
+
+ // implementation of IPlayback
+ void Initialize() override;
+ void Deinitialize() override;
+ bool CanPause() const override { return true; }
+ bool CanSeek() const override { return true; }
+ unsigned int GetTimeMs() const override { return m_playTimeMs; }
+ unsigned int GetTotalTimeMs() const override { return m_totalTimeMs; }
+ unsigned int GetCacheTimeMs() const override { return m_cacheTimeMs; }
+ void SeekTimeMs(unsigned int timeMs) override;
+ double GetSpeed() const override;
+ void SetSpeed(double speedFactor) override;
+ void PauseAsync() override;
+ std::string CreateSavestate(bool autosave, const std::string& savestatePath = "") override;
+ bool LoadSavestate(const std::string& savestatePath) override;
+
+ // implementation of IGameLoopCallback
+ void FrameEvent() override;
+ void RewindEvent() override;
+
+ // implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
+private:
+ void AddFrame();
+ void RewindFrames(uint64_t frames);
+ void AdvanceFrames(uint64_t frames);
+ void UpdatePlaybackStats();
+ void UpdateMemoryStream();
+ void CommitSavestate(bool autosave,
+ const std::string& savePath,
+ const CDateTime& nowUTC,
+ uint64_t timestampFrames);
+
+ // Construction parameter
+ GAME::CGameClient* const m_gameClient;
+ CRPRenderManager& m_renderManager;
+ CCheevos* const m_cheevos;
+ CGUIGameMessenger& m_guiMessenger;
+
+ // Gameplay functionality
+ CGameLoop m_gameLoop;
+ std::unique_ptr<IMemoryStream> m_memoryStream;
+ CCriticalSection m_mutex;
+
+ // Savestate functionality
+ std::unique_ptr<CSavestateDatabase> m_savestateDatabase;
+ std::string m_autosavePath{};
+ std::vector<std::future<void>> m_savestateThreads;
+ CCriticalSection m_savestateMutex;
+
+ // Playback stats
+ uint64_t m_totalFrameCount;
+ uint64_t m_pastFrameCount;
+ uint64_t m_futureFrameCount;
+ unsigned int m_playTimeMs;
+ unsigned int m_totalTimeMs;
+ unsigned int m_cacheTimeMs;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/CMakeLists.txt
new file mode 100644
index 0000000..f8b7fc1
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES RPProcessInfo.cpp
+)
+
+set(HEADERS RPProcessInfo.h
+)
+
+core_add_library(rp-process)
diff --git a/xbmc/cores/RetroPlayer/process/RPProcessInfo.cpp b/xbmc/cores/RetroPlayer/process/RPProcessInfo.cpp
new file mode 100644
index 0000000..93fec60
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/RPProcessInfo.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfo.h"
+
+#include "ServiceBroker.h"
+#include "cores/DataCacheCore.h"
+#include "cores/RetroPlayer/buffers/RenderBufferManager.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "settings/DisplaySettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+extern "C"
+{
+#include <libavutil/pixdesc.h>
+}
+
+#include <utility>
+
+using namespace KODI;
+using namespace RETRO;
+
+CreateRPProcessControl CRPProcessInfo::m_processControl = nullptr;
+std::vector<std::unique_ptr<IRendererFactory>> CRPProcessInfo::m_rendererFactories;
+CCriticalSection CRPProcessInfo::m_createSection;
+
+CRPProcessInfo::CRPProcessInfo(std::string platformName)
+ : m_platformName(std::move(platformName)),
+ m_renderBufferManager(new CRenderBufferManager),
+ m_renderContext(new CRenderContext(CServiceBroker::GetRenderSystem(),
+ CServiceBroker::GetWinSystem(),
+ CServiceBroker::GetWinSystem()->GetGfxContext(),
+ CDisplaySettings::GetInstance(),
+ CMediaSettings::GetInstance(),
+ CServiceBroker::GetGameServices(),
+ CServiceBroker::GetGUI()))
+{
+ for (auto& rendererFactory : m_rendererFactories)
+ {
+ RenderBufferPoolVector bufferPools = rendererFactory->CreateBufferPools(*m_renderContext);
+ if (!bufferPools.empty())
+ m_renderBufferManager->RegisterPools(rendererFactory.get(), std::move(bufferPools));
+ }
+
+ // Initialize default scaling method
+ for (auto scalingMethod : GetScalingMethods())
+ {
+ if (HasScalingMethod(scalingMethod))
+ {
+ m_defaultScalingMethod = scalingMethod;
+ break;
+ }
+ }
+}
+
+CRPProcessInfo::~CRPProcessInfo() = default;
+
+CRPProcessInfo* CRPProcessInfo::CreateInstance()
+{
+ CRPProcessInfo* processInfo = nullptr;
+
+ std::unique_lock<CCriticalSection> lock(m_createSection);
+
+ if (m_processControl != nullptr)
+ {
+ processInfo = m_processControl();
+
+ if (processInfo != nullptr)
+ CLog::Log(LOGINFO, "RetroPlayer[PROCESS]: Created process info for {}",
+ processInfo->GetPlatformName());
+ else
+ CLog::Log(LOGERROR, "RetroPlayer[PROCESS]: Failed to create process info");
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[PROCESS]: No process control registered");
+ }
+
+ return processInfo;
+}
+
+void CRPProcessInfo::RegisterProcessControl(CreateRPProcessControl createFunc)
+{
+ m_processControl = createFunc;
+}
+
+void CRPProcessInfo::RegisterRendererFactory(IRendererFactory* factory)
+{
+ std::unique_lock<CCriticalSection> lock(m_createSection);
+
+ CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Registering renderer factory for {}",
+ factory->RenderSystemName());
+
+ m_rendererFactories.emplace_back(factory);
+}
+
+std::string CRPProcessInfo::GetRenderSystemName(IRenderBufferPool* renderBufferPool) const
+{
+ return m_renderBufferManager->GetRenderSystemName(renderBufferPool);
+}
+
+CRPBaseRenderer* CRPProcessInfo::CreateRenderer(IRenderBufferPool* renderBufferPool,
+ const CRenderSettings& renderSettings)
+{
+ std::unique_lock<CCriticalSection> lock(m_createSection);
+
+ for (auto& rendererFactory : m_rendererFactories)
+ {
+ RenderBufferPoolVector bufferPools = m_renderBufferManager->GetPools(rendererFactory.get());
+ for (auto& bufferPool : bufferPools)
+ {
+ if (bufferPool.get() == renderBufferPool)
+ return rendererFactory->CreateRenderer(renderSettings, *m_renderContext,
+ std::move(bufferPool));
+ }
+ }
+
+ CLog::Log(LOGERROR, "RetroPlayer[RENDER]: Failed to find a suitable renderer factory");
+
+ return nullptr;
+}
+
+void CRPProcessInfo::SetDataCache(CDataCacheCore* cache)
+{
+ m_dataCache = cache;
+ ;
+}
+
+void CRPProcessInfo::ResetInfo()
+{
+ if (m_dataCache != nullptr)
+ {
+ m_dataCache->SetVideoDecoderName("", false);
+ m_dataCache->SetVideoDeintMethod("");
+ m_dataCache->SetVideoPixelFormat("");
+ m_dataCache->SetVideoDimensions(0, 0);
+ m_dataCache->SetVideoFps(0.0f);
+ m_dataCache->SetVideoDAR(1.0f);
+ m_dataCache->SetAudioDecoderName("");
+ m_dataCache->SetAudioChannels("");
+ m_dataCache->SetAudioSampleRate(0);
+ m_dataCache->SetAudioBitsPerSample(0);
+ m_dataCache->SetRenderClockSync(false);
+ m_dataCache->SetStateSeeking(false);
+ m_dataCache->SetSpeed(1.0f, 1.0f);
+ m_dataCache->SetGuiRender(true); //! @todo
+ m_dataCache->SetVideoRender(false); //! @todo
+ m_dataCache->SetPlayTimes(0, 0, 0, 0);
+ }
+}
+
+bool CRPProcessInfo::HasScalingMethod(SCALINGMETHOD scalingMethod) const
+{
+ return m_renderBufferManager->HasScalingMethod(scalingMethod);
+}
+
+std::vector<SCALINGMETHOD> CRPProcessInfo::GetScalingMethods()
+{
+ return {
+ SCALINGMETHOD::NEAREST,
+ SCALINGMETHOD::LINEAR,
+ };
+}
+
+//******************************************************************************
+// video codec
+//******************************************************************************
+void CRPProcessInfo::SetVideoPixelFormat(AVPixelFormat pixFormat)
+{
+ const char* videoPixelFormat = av_get_pix_fmt_name(pixFormat);
+
+ if (m_dataCache != nullptr)
+ m_dataCache->SetVideoPixelFormat(videoPixelFormat != nullptr ? videoPixelFormat : "");
+}
+
+void CRPProcessInfo::SetVideoDimensions(int width, int height)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetVideoDimensions(width, height);
+}
+
+void CRPProcessInfo::SetVideoFps(float fps)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetVideoFps(fps);
+}
+
+//******************************************************************************
+// player audio info
+//******************************************************************************
+void CRPProcessInfo::SetAudioChannels(const std::string& channels)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetAudioChannels(channels);
+}
+
+void CRPProcessInfo::SetAudioSampleRate(int sampleRate)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetAudioSampleRate(sampleRate);
+}
+
+void CRPProcessInfo::SetAudioBitsPerSample(int bitsPerSample)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetAudioBitsPerSample(bitsPerSample);
+}
+
+//******************************************************************************
+// player states
+//******************************************************************************
+void CRPProcessInfo::SetSpeed(float speed)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetSpeed(1.0f, speed);
+}
+
+void CRPProcessInfo::SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max)
+{
+ if (m_dataCache != nullptr)
+ m_dataCache->SetPlayTimes(start, current, min, max);
+}
diff --git a/xbmc/cores/RetroPlayer/process/RPProcessInfo.h b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h
new file mode 100644
index 0000000..bc5a5db
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/RetroPlayerTypes.h"
+#include "threads/CriticalSection.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <libavutil/pixfmt.h>
+
+class CDataCacheCore;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderBufferManager;
+class CRenderContext;
+class CRenderSettings;
+class CRPBaseRenderer;
+class CRPProcessInfo;
+class IRenderBufferPool;
+
+/*!
+ * \brief Process info factory
+ */
+using CreateRPProcessControl = CRPProcessInfo* (*)();
+
+/*!
+ * \brief Rendering factory
+ */
+class IRendererFactory
+{
+public:
+ virtual ~IRendererFactory() = default;
+
+ /*!
+ * \brief Get a description name of the rendering system
+ */
+ virtual std::string RenderSystemName() const = 0;
+
+ /*!
+ * \brief Create a renderer
+ *
+ * \param settings The renderer's initial settings
+ * \param context The rendering context
+ * \param bufferPool The buffer pool to which buffers are returned
+ */
+ virtual CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) = 0;
+
+ /*!
+ * \brief Create buffer pools to manager buffers
+ *
+ * \param context The rendering context shared with the buffer pools
+ *
+ * \return The buffer pools supported by the rendering system
+ */
+ virtual RenderBufferPoolVector CreateBufferPools(CRenderContext& context) = 0;
+};
+
+/*!
+ * \brief Player process info
+ */
+class CRPProcessInfo
+{
+public:
+ static CRPProcessInfo* CreateInstance();
+ static void RegisterProcessControl(CreateRPProcessControl createFunc);
+ static void RegisterRendererFactory(IRendererFactory* factory);
+
+ virtual ~CRPProcessInfo();
+
+ /*!
+ * \brief Get the descriptive name of the platform
+ *
+ * \return The name of the platform as set by windowing
+ */
+ const std::string& GetPlatformName() const { return m_platformName; }
+
+ /*!
+ * \brief Get the descriptive name of the rendering system
+ *
+ * \param renderBufferPool A pool belonging to the rendering system
+ *
+ * \return The name of the rendering system as set by windowing
+ */
+ std::string GetRenderSystemName(IRenderBufferPool* renderBufferPool) const;
+
+ /*!
+ * \brief Create a renderer
+ *
+ * \param renderBufferPool The buffer pool used to return render buffers
+ * \param renderSettings The settings for this renderer
+ *
+ * \return The renderer, or nullptr on failure
+ */
+ CRPBaseRenderer* CreateRenderer(IRenderBufferPool* renderBufferPool,
+ const CRenderSettings& renderSettings);
+
+ /*!
+ * \brief Set data cache
+ */
+ void SetDataCache(CDataCacheCore* cache);
+
+ /*!
+ * \brief Reset data cache info
+ */
+ void ResetInfo();
+
+ /// @name Rendering functions
+ ///{
+
+ /*!
+ * \brief Get the context shared by the rendering system
+ */
+ CRenderContext& GetRenderContext() { return *m_renderContext; }
+
+ /*!
+ * \brief Get the buffer manager that owns the buffer pools
+ */
+ CRenderBufferManager& GetBufferManager() { return *m_renderBufferManager; }
+
+ /*!
+ * \brief Check if a buffer pool supports the given scaling method
+ */
+ bool HasScalingMethod(SCALINGMETHOD scalingMethod) const;
+
+ /*!
+ * \brief Get the default scaling method for this rendering system
+ */
+ SCALINGMETHOD GetDefaultScalingMethod() const { return m_defaultScalingMethod; }
+
+ ///}
+
+ /// @name Player video info
+ ///{
+ void SetVideoPixelFormat(AVPixelFormat pixFormat);
+ void SetVideoDimensions(int width, int height);
+ void SetVideoFps(float fps);
+ ///}
+
+ /// @name Player audio info
+ ///{
+ void SetAudioChannels(const std::string& channels);
+ void SetAudioSampleRate(int sampleRate);
+ void SetAudioBitsPerSample(int bitsPerSample);
+ ///}
+
+ /// @name Player states
+ ///{
+ void SetSpeed(float speed);
+ void SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max);
+ ///}
+
+protected:
+ /*!
+ * \brief Constructor
+ *
+ * \param platformName A descriptive name of the platform
+ */
+ CRPProcessInfo(std::string platformName);
+
+ /*!
+ * \brief Get all scaling methods available to the rendering system
+ */
+ static std::vector<SCALINGMETHOD> GetScalingMethods();
+
+ // Static factories
+ static CreateRPProcessControl m_processControl;
+ static std::vector<std::unique_ptr<IRendererFactory>> m_rendererFactories;
+ static CCriticalSection m_createSection;
+
+ // Construction parameters
+ const std::string m_platformName;
+
+ // Info parameters
+ CDataCacheCore* m_dataCache = nullptr;
+
+ // Rendering parameters
+ std::unique_ptr<CRenderBufferManager> m_renderBufferManager;
+
+private:
+ // Rendering parameters
+ std::unique_ptr<CRenderContext> m_renderContext;
+ SCALINGMETHOD m_defaultScalingMethod = SCALINGMETHOD::AUTO;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/X11/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/X11/CMakeLists.txt
new file mode 100644
index 0000000..93e2c0f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/X11/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES RPProcessInfoX11.cpp)
+
+set(HEADERS RPProcessInfoX11.h)
+
+core_add_library(rp-process-x11)
diff --git a/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.cpp b/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.cpp
new file mode 100644
index 0000000..05cc819
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoX11.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoX11::CRPProcessInfoX11() : CRPProcessInfo("X11")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoX11::Create()
+{
+ return new CRPProcessInfoX11();
+}
+
+void CRPProcessInfoX11::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoX11::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.h b/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.h
new file mode 100644
index 0000000..9236e6f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/X11/RPProcessInfoX11.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoX11 : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoX11();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/android/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/android/CMakeLists.txt
new file mode 100644
index 0000000..08ed293
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/android/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES RPProcessInfoAndroid.cpp)
+set(HEADERS RPProcessInfoAndroid.h)
+
+core_add_library(rp-process-android)
diff --git a/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.cpp b/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.cpp
new file mode 100644
index 0000000..52498de
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoAndroid.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoAndroid::CRPProcessInfoAndroid() : CRPProcessInfo("Android")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoAndroid::Create()
+{
+ return new CRPProcessInfoAndroid();
+}
+
+void CRPProcessInfoAndroid::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoAndroid::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.h b/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.h
new file mode 100644
index 0000000..ef00acf
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/android/RPProcessInfoAndroid.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoAndroid : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoAndroid();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/gbm/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/gbm/CMakeLists.txt
new file mode 100644
index 0000000..c41253f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/gbm/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES RPProcessInfoGbm.cpp)
+set(HEADERS RPProcessInfoGbm.h)
+
+core_add_library(rp-process-gbm)
diff --git a/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.cpp b/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.cpp
new file mode 100644
index 0000000..f17ad3e
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoGbm.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoGbm::CRPProcessInfoGbm() : CRPProcessInfo("GBM")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoGbm::Create()
+{
+ return new CRPProcessInfoGbm();
+}
+
+void CRPProcessInfoGbm::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoGbm::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h b/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h
new file mode 100644
index 0000000..c1453d7
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoGbm : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoGbm();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/ios/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/ios/CMakeLists.txt
new file mode 100644
index 0000000..1e20f65
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/ios/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES RPProcessInfoIOS.cpp)
+
+set(HEADERS RPProcessInfoIOS.h)
+
+core_add_library(rp-process-ios)
diff --git a/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.cpp b/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.cpp
new file mode 100644
index 0000000..c849e76
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoIOS.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoIOS::CRPProcessInfoIOS() : CRPProcessInfo("iOS")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoIOS::Create()
+{
+ return new CRPProcessInfoIOS();
+}
+
+void CRPProcessInfoIOS::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoIOS::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.h b/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.h
new file mode 100644
index 0000000..8caa5b1
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/ios/RPProcessInfoIOS.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoIOS : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoIOS();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/osx/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/osx/CMakeLists.txt
new file mode 100644
index 0000000..b8f5a18
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/osx/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES RPProcessInfoOSX.cpp)
+
+set(HEADERS RPProcessInfoOSX.h)
+
+core_add_library(rp-process-osx)
diff --git a/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.cpp b/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.cpp
new file mode 100644
index 0000000..c9192b9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoOSX.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoOSX::CRPProcessInfoOSX() : CRPProcessInfo("macOS")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoOSX::Create()
+{
+ return new CRPProcessInfoOSX();
+}
+
+void CRPProcessInfoOSX::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoOSX::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.h b/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.h
new file mode 100644
index 0000000..646f3fa
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/osx/RPProcessInfoOSX.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoOSX : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoOSX();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/wayland/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/wayland/CMakeLists.txt
new file mode 100644
index 0000000..1cc13fa
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/wayland/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES RPProcessInfoWayland.cpp)
+
+set(HEADERS RPProcessInfoWayland.h)
+
+core_add_library(rp-process-wayland)
diff --git a/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.cpp b/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.cpp
new file mode 100644
index 0000000..2de0fa9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoWayland.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoWayland::CRPProcessInfoWayland() : CRPProcessInfo("Wayland")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoWayland::Create()
+{
+ return new CRPProcessInfoWayland();
+}
+
+void CRPProcessInfoWayland::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoWayland::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h b/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h
new file mode 100644
index 0000000..244ee20
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoWayland : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoWayland();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/process/windows/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/windows/CMakeLists.txt
new file mode 100644
index 0000000..644ce8e
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/windows/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES RPProcessInfoWin.cpp)
+set(HEADERS RPProcessInfoWin.h)
+
+core_add_library(rp-processwin)
diff --git a/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.cpp b/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.cpp
new file mode 100644
index 0000000..3601551
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPProcessInfoWin.h"
+
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPProcessInfoWin::CRPProcessInfoWin() : CRPProcessInfo("Windows")
+{
+}
+
+CRPProcessInfo* CRPProcessInfoWin::Create()
+{
+ return new CRPProcessInfoWin();
+}
+
+void CRPProcessInfoWin::Register()
+{
+ CRPProcessInfo::RegisterProcessControl(CRPProcessInfoWin::Create);
+}
diff --git a/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.h b/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.h
new file mode 100644
index 0000000..8596446
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/process/windows/RPProcessInfoWin.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfoWin : public CRPProcessInfo
+{
+public:
+ CRPProcessInfoWin();
+
+ static CRPProcessInfo* Create();
+ static void Register();
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/CMakeLists.txt
new file mode 100644
index 0000000..5e02a7a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(SOURCES RenderContext.cpp
+ RenderSettings.cpp
+ RenderTranslator.cpp
+ RenderUtils.cpp
+ RenderVideoSettings.cpp
+ RPRenderManager.cpp
+)
+
+set(HEADERS IRenderManager.h
+ RenderContext.h
+ RenderSettings.h
+ RenderTranslator.h
+ RenderUtils.h
+ RenderVideoSettings.h
+ RPRenderManager.h
+)
+
+core_add_library(rp-rendering)
diff --git a/xbmc/cores/RetroPlayer/rendering/IRenderManager.h b/xbmc/cores/RetroPlayer/rendering/IRenderManager.h
new file mode 100644
index 0000000..32bb08c
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/IRenderManager.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Geometry.h"
+
+struct RESOLUTION_INFO;
+
+namespace KODI
+{
+namespace RETRO
+{
+class IGUIRenderSettings;
+
+/*!
+ * \brief Interface to expose rendering functions to GUI components
+ */
+class IRenderManager
+{
+public:
+ virtual ~IRenderManager() = default;
+
+ /*!
+ * \brief Render a fullscreen window
+ *
+ * \param bClear Whether the render area should be cleared
+ * \param coordsRes Resolution that the window coordinates are in
+ */
+ virtual void RenderWindow(bool bClear, const RESOLUTION_INFO& coordsRes) = 0;
+
+ /*!
+ * \brief Render a game control
+ *
+ * \param bClear Whether the render area should be cleared
+ * \param bUseAlpha Whether the graphics context's alpha should be used
+ * \param renderRegion The region of the control being rendered
+ * \param renderSettings The settings used to render the control
+ */
+ virtual void RenderControl(bool bClear,
+ bool bUseAlpha,
+ const CRect& renderRegion,
+ const IGUIRenderSettings* renderSettings) = 0;
+
+ /*!
+ * \brief Clear the background of a fullscreen window
+ */
+ virtual void ClearBackground() = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp
new file mode 100644
index 0000000..7d76d7e
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp
@@ -0,0 +1,1066 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPRenderManager.h"
+
+#include "RenderContext.h"
+#include "RenderSettings.h"
+#include "RenderTranslator.h"
+#include "URL.h"
+#include "cores/RetroPlayer/buffers/IRenderBuffer.h"
+#include "cores/RetroPlayer/buffers/IRenderBufferPool.h"
+#include "cores/RetroPlayer/buffers/RenderBufferManager.h"
+#include "cores/RetroPlayer/guibridge/GUIGameSettings.h"
+#include "cores/RetroPlayer/guibridge/GUIRenderTargetFactory.h"
+#include "cores/RetroPlayer/guibridge/IGUIRenderSettings.h"
+#include "cores/RetroPlayer/guicontrols/GUIGameControl.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h"
+#include "cores/RetroPlayer/savestates/ISavestate.h"
+#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "cores/RetroPlayer/streams/RetroPlayerVideo.h"
+#include "filesystem/File.h"
+#include "pictures/Picture.h"
+#include "threads/SingleLock.h"
+#include "utils/ColorUtils.h"
+#include "utils/TransformMatrix.h"
+#include "utils/log.h"
+
+#include <cstring>
+#include <mutex>
+
+extern "C"
+{
+#include <libswscale/swscale.h>
+}
+
+#include <algorithm>
+#include <cstring>
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPRenderManager::CRPRenderManager(CRPProcessInfo& processInfo)
+ : m_processInfo(processInfo),
+ m_renderContext(processInfo.GetRenderContext()),
+ m_renderSettings(new CGUIGameSettings(processInfo)),
+ m_renderControlFactory(new CGUIRenderTargetFactory(this))
+{
+}
+
+void CRPRenderManager::Initialize()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Initializing render manager");
+}
+
+void CRPRenderManager::Deinitialize()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Deinitializing render manager");
+
+ // Wait for savestate tasks
+ for (std::future<void>& task : m_savestateThreads)
+ task.wait();
+ m_savestateThreads.clear();
+
+ for (auto& pixelScalerMap : m_scalers)
+ {
+ for (auto& pixelScaler : pixelScalerMap.second)
+ {
+ if (pixelScaler.second != nullptr)
+ sws_freeContext(pixelScaler.second);
+ }
+ }
+ m_scalers.clear();
+
+ for (auto renderBuffer : m_renderBuffers)
+ renderBuffer->Release();
+ m_renderBuffers.clear();
+
+ for (auto buffer : m_pendingBuffers)
+ buffer->Release();
+ m_pendingBuffers.clear();
+
+ for (auto& [savestatePath, renderBuffers] : m_savestateBuffers)
+ {
+ for (auto renderBuffer : renderBuffers)
+ renderBuffer->Release();
+ }
+ m_savestateBuffers.clear();
+
+ m_renderers.clear();
+
+ m_state = RENDER_STATE::UNCONFIGURED;
+}
+
+bool CRPRenderManager::Configure(AVPixelFormat format,
+ unsigned int nominalWidth,
+ unsigned int nominalHeight,
+ unsigned int maxWidth,
+ unsigned int maxHeight,
+ float pixelAspectRatio)
+{
+ CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Configuring format {}, nominal {}x{}, max {}x{}",
+ CRenderTranslator::TranslatePixelFormat(format), nominalWidth, nominalHeight, maxWidth,
+ maxHeight);
+
+ // Immutable parameters
+ m_format = format;
+ m_nominalWidth = nominalWidth;
+ m_nominalHeight = nominalHeight;
+ m_maxWidth = maxWidth;
+ m_maxHeight = maxHeight;
+ m_pixelAspectRatio = pixelAspectRatio;
+
+ std::unique_lock<CCriticalSection> lock(m_stateMutex);
+
+ m_state = RENDER_STATE::CONFIGURING;
+
+ return true;
+}
+
+bool CRPRenderManager::GetVideoBuffer(unsigned int width,
+ unsigned int height,
+ VideoStreamBuffer& buffer)
+{
+ // Clear any previous pending buffers
+ for (IRenderBuffer* buffer : m_pendingBuffers)
+ buffer->Release();
+ m_pendingBuffers.clear();
+
+ if (m_bFlush || m_state != RENDER_STATE::CONFIGURED)
+ return false;
+
+ // We should do our best to get a valid render buffer. If we return false,
+ // the game add-on will likely allocate its own memory.
+ IRenderBuffer* renderBuffer = nullptr;
+
+ auto bufferPools = m_processInfo.GetBufferManager().GetBufferPools();
+
+ std::sort(bufferPools.begin(), bufferPools.end(),
+ [](const IRenderBufferPool* lhs, const IRenderBufferPool* rhs) {
+ // Prefer buffer pools with a visible renderer
+ if (lhs->HasVisibleRenderer() && !rhs->HasVisibleRenderer())
+ return true;
+ if (!lhs->HasVisibleRenderer() && rhs->HasVisibleRenderer())
+ return false;
+
+ //! @todo De-prioritize buffer pools with write-only or unaligned memory
+
+ return false;
+ });
+
+ for (IRenderBufferPool* bufferPool : bufferPools)
+ {
+ renderBuffer = bufferPool->GetBuffer(width, height);
+ if (renderBuffer != nullptr)
+ break;
+ else
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Unable to get video buffer for frame");
+ }
+
+ if (renderBuffer == nullptr)
+ return false;
+
+ buffer = VideoStreamBuffer{renderBuffer->GetFormat(), renderBuffer->GetMemory(),
+ renderBuffer->GetFrameSize(), renderBuffer->GetMemoryAccess(),
+ renderBuffer->GetMemoryAlignment()};
+
+ m_pendingBuffers.emplace_back(std::move(renderBuffer));
+
+ return true;
+}
+
+void CRPRenderManager::AddFrame(const uint8_t* data,
+ size_t size,
+ unsigned int width,
+ unsigned int height,
+ unsigned int orientationDegCCW)
+{
+ if (m_bFlush || m_state != RENDER_STATE::CONFIGURED)
+ return;
+
+ // Validate parameters
+ if (data == nullptr || size == 0 || width == 0 || height == 0)
+ return;
+
+ // Get render buffers to copy the frame into
+ std::vector<IRenderBuffer*> renderBuffers;
+
+ // Check pending buffers
+ for (IRenderBuffer* buffer : m_pendingBuffers)
+ {
+ if (buffer->GetMemory() == data)
+ {
+ buffer->Acquire();
+ renderBuffers.emplace_back(buffer);
+ }
+ }
+
+ // If we aren't submitting a zero-copy frame, copy into render buffer now
+ if (renderBuffers.empty())
+ {
+ // Copy frame to buffers with visible renderers
+ for (IRenderBufferPool* bufferPool : m_processInfo.GetBufferManager().GetBufferPools())
+ {
+ if (!bufferPool->HasVisibleRenderer())
+ continue;
+
+ IRenderBuffer* renderBuffer = bufferPool->GetBuffer(width, height);
+ if (renderBuffer != nullptr)
+ {
+ CopyFrame(renderBuffer, m_format, data, size, width, height);
+ renderBuffers.emplace_back(renderBuffer);
+ }
+ else
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Unable to get render buffer for frame");
+ }
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Set render buffers
+ for (auto renderBuffer : m_renderBuffers)
+ renderBuffer->Release();
+ m_renderBuffers = std::move(renderBuffers);
+
+ // Apply rotation to render buffers
+ for (auto renderBuffer : m_renderBuffers)
+ renderBuffer->SetRotation(orientationDegCCW);
+
+ // Cache frame if it arrived after being paused
+ if (m_speed == 0.0)
+ {
+ std::vector<uint8_t> cachedFrame = std::move(m_cachedFrame);
+
+ if (!m_bHasCachedFrame)
+ {
+ // In this case, cachedFrame is definitely empty (see invariant for
+ // m_bHasCachedFrame). Otherwise, cachedFrame may be empty if the frame
+ // is being copied in the rendering thread. In that case, we would want
+ // to leave cached frame empty to avoid caching another frame.
+
+ cachedFrame.resize(size);
+ m_bHasCachedFrame = true;
+ }
+
+ if (!cachedFrame.empty())
+ {
+ {
+ CSingleExit exit(m_bufferMutex);
+ std::memcpy(cachedFrame.data(), data, size);
+ }
+ m_cachedFrame = std::move(cachedFrame);
+ m_cachedWidth = width;
+ m_cachedHeight = height;
+ m_cachedRotationCCW = orientationDegCCW;
+ }
+ }
+ }
+}
+
+void CRPRenderManager::SetSpeed(double speed)
+{
+ m_speed = speed;
+}
+
+void CRPRenderManager::FrameMove()
+{
+ CheckFlush();
+
+ bool bIsConfigured = false;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_stateMutex);
+
+ if (m_state == RENDER_STATE::CONFIGURING)
+ {
+ m_state = RENDER_STATE::CONFIGURED;
+
+ CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Renderer configured on first frame");
+ }
+
+ if (m_state == RENDER_STATE::CONFIGURED)
+ bIsConfigured = true;
+ }
+
+ if (bIsConfigured)
+ {
+ for (auto& renderer : m_renderers)
+ renderer->FrameMove();
+ }
+}
+
+void CRPRenderManager::CheckFlush()
+{
+ if (m_bFlush)
+ {
+ {
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+ for (auto renderBuffer : m_renderBuffers)
+ renderBuffer->Release();
+ m_renderBuffers.clear();
+
+ m_cachedFrame.clear();
+ m_cachedWidth = 0;
+ m_cachedHeight = 0;
+
+ m_bHasCachedFrame = false;
+ }
+
+ for (const auto& renderer : m_renderers)
+ renderer->Flush();
+
+ m_processInfo.GetBufferManager().FlushPools();
+
+ m_bFlush = false;
+ }
+}
+
+void CRPRenderManager::Flush()
+{
+ m_bFlush = true;
+}
+
+void CRPRenderManager::RenderWindow(bool bClear, const RESOLUTION_INFO& coordsRes)
+{
+ // Get a renderer for the fullscreen window
+ std::shared_ptr<CRPBaseRenderer> renderer = GetRendererForSettings(nullptr);
+ if (!renderer)
+ return;
+
+ // Get a render buffer for the renderer
+ IRenderBuffer* renderBuffer = GetRenderBuffer(renderer->GetBufferPool());
+
+ m_renderContext.SetRenderingResolution(m_renderContext.GetVideoResolution(), false);
+
+ if (!m_bDisplayScaleSet && m_renderContext.DisplayHardwareScalingEnabled())
+ {
+ // If the renderer has a render buffer, get the dimensions
+ const unsigned int sourceWidth = (renderBuffer != nullptr ? renderBuffer->GetWidth() : 0);
+ const unsigned int sourceHeight = (renderBuffer != nullptr ? renderBuffer->GetHeight() : 0);
+
+ // Get render video settings for the fullscreen window
+ CRenderVideoSettings renderVideoSettings = GetEffectiveSettings(nullptr);
+
+ // Get the scaling mode of the render video settings
+ const SCALINGMETHOD scaleMode = renderVideoSettings.GetScalingMethod();
+ const STRETCHMODE stretchMode = renderVideoSettings.GetRenderStretchMode();
+
+ // Update display with video dimensions for integer scaling
+ if (scaleMode == SCALINGMETHOD::NEAREST && stretchMode == STRETCHMODE::Original &&
+ sourceWidth > 0 && sourceHeight > 0)
+ {
+ RESOLUTION_INFO gameRes = m_renderContext.GetResInfo();
+ gameRes.Overscan.left = 0;
+ gameRes.Overscan.top = 0;
+ gameRes.Overscan.right = sourceWidth;
+ gameRes.Overscan.bottom = sourceHeight;
+ gameRes.iWidth = sourceWidth;
+ gameRes.iHeight = sourceHeight;
+ gameRes.iScreenWidth = sourceWidth;
+ gameRes.iScreenHeight = sourceHeight;
+
+ m_renderContext.UpdateDisplayHardwareScaling(gameRes);
+ m_bDisplayScaleSet = true;
+ }
+ }
+
+ RenderInternal(renderer, renderBuffer, bClear, 255);
+
+ m_renderContext.SetRenderingResolution(coordsRes, false);
+}
+
+void CRPRenderManager::RenderControl(bool bClear,
+ bool bUseAlpha,
+ const CRect& renderRegion,
+ const IGUIRenderSettings* renderSettings)
+{
+ // Get a renderer for the control
+ std::shared_ptr<CRPBaseRenderer> renderer = GetRendererForSettings(renderSettings);
+ if (!renderer)
+ return;
+
+ IRenderBuffer* renderBuffer = nullptr;
+
+ // Get render buffer for external pixels, if requested
+ const std::string& pixelPath = renderer->GetRenderSettings().VideoSettings().GetPixels();
+ if (!pixelPath.empty())
+ {
+ renderBuffer = GetRenderBufferForSavestate(pixelPath, renderer->GetBufferPool());
+ }
+ else
+ {
+ // Get a render buffer for the renderer
+ renderBuffer = GetRenderBuffer(renderer->GetBufferPool());
+ }
+
+ if (renderBuffer == nullptr)
+ return;
+
+ // Set fullscreen
+ const bool bWasFullscreen = m_renderContext.IsFullScreenVideo();
+ if (bWasFullscreen)
+ m_renderContext.SetFullScreenVideo(false);
+
+ // Set coordinates
+ CRect coords = renderSettings->GetDimensions();
+ m_renderContext.SetViewWindow(coords.x1, coords.y1, coords.x2, coords.y2);
+ TransformMatrix mat;
+ m_renderContext.SetTransform(mat, 1.0, 1.0);
+
+ // Clear render area
+ if (bClear)
+ {
+ CRect old = m_renderContext.GetScissors();
+ CRect region = renderRegion;
+ region.Intersect(old);
+ m_renderContext.SetScissors(region);
+ m_renderContext.Clear(UTILS::COLOR::BLACK);
+ m_renderContext.SetScissors(old);
+ }
+
+ // Calculate alpha
+ UTILS::COLOR::Color alpha = 255;
+ if (bUseAlpha)
+ alpha = m_renderContext.MergeAlpha(UTILS::COLOR::BLACK) >> 24;
+
+ RenderInternal(renderer, renderBuffer, false, alpha);
+
+ // Restore coordinates
+ m_renderContext.RemoveTransform();
+
+ // Restore fullscreen
+ if (bWasFullscreen)
+ m_renderContext.SetFullScreenVideo(true);
+}
+
+void CRPRenderManager::ClearBackground()
+{
+ m_renderContext.Clear(0);
+}
+
+bool CRPRenderManager::SupportsRenderFeature(RENDERFEATURE feature) const
+{
+ //! @todo Move to ProcessInfo
+ for (const auto& renderer : m_renderers)
+ {
+ if (renderer->Supports(feature))
+ return true;
+ }
+
+ return false;
+}
+
+bool CRPRenderManager::SupportsScalingMethod(SCALINGMETHOD method) const
+{
+ //! @todo Move to ProcessInfo
+ for (IRenderBufferPool* bufferPool : m_processInfo.GetBufferManager().GetBufferPools())
+ {
+ CRenderVideoSettings renderSettings;
+ renderSettings.SetScalingMethod(method);
+ if (bufferPool->IsCompatible(renderSettings))
+ return true;
+ }
+
+ return false;
+}
+
+void CRPRenderManager::RenderInternal(const std::shared_ptr<CRPBaseRenderer>& renderer,
+ IRenderBuffer* renderBuffer,
+ bool bClear,
+ uint32_t alpha)
+{
+ renderer->PreRender(bClear);
+
+ CSingleExit exitLock(m_renderContext.GraphicsMutex());
+
+ if (renderBuffer != nullptr)
+ {
+ bool bUploaded = true;
+
+ if (!renderBuffer->IsLoaded())
+ {
+ bUploaded = renderBuffer->UploadTexture();
+ renderBuffer->SetLoaded(true);
+ }
+
+ if (bUploaded)
+ renderer->SetBuffer(renderBuffer);
+
+ renderBuffer->Release();
+ }
+
+ renderer->RenderFrame(bClear, alpha);
+}
+
+std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRendererForSettings(
+ const IGUIRenderSettings* renderSettings)
+{
+ std::shared_ptr<CRPBaseRenderer> renderer;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_stateMutex);
+ if (m_state == RENDER_STATE::UNCONFIGURED)
+ return renderer;
+ }
+
+ CRenderSettings effectiveRenderSettings;
+ effectiveRenderSettings.VideoSettings() = GetEffectiveSettings(renderSettings);
+
+ // Check renderers in order of buffer pools
+ for (IRenderBufferPool* bufferPool : m_processInfo.GetBufferManager().GetBufferPools())
+ {
+ renderer = GetRendererForPool(bufferPool, effectiveRenderSettings);
+ if (renderer)
+ break;
+ }
+
+ if (renderer)
+ {
+ renderer->SetScalingMethod(effectiveRenderSettings.VideoSettings().GetScalingMethod());
+ renderer->SetStretchMode(effectiveRenderSettings.VideoSettings().GetRenderStretchMode());
+ renderer->SetRenderRotation(effectiveRenderSettings.VideoSettings().GetRenderRotation());
+ renderer->SetPixels(effectiveRenderSettings.VideoSettings().GetPixels());
+ }
+
+ return renderer;
+}
+
+std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRendererForPool(
+ IRenderBufferPool* bufferPool, const CRenderSettings& renderSettings)
+{
+ std::shared_ptr<CRPBaseRenderer> renderer;
+
+ if (!bufferPool->IsCompatible(renderSettings.VideoSettings()))
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: buffer pool is not compatible with renderer");
+ return renderer;
+ }
+
+ // Get compatible renderer for this buffer pool
+ for (const auto& it : m_renderers)
+ {
+ if (it->GetBufferPool() != bufferPool)
+ continue;
+
+ if (!it->IsCompatible(renderSettings.VideoSettings()))
+ continue;
+
+ renderer = it;
+ break;
+ }
+
+ // If buffer pool has no compatible renderers, create one now
+ if (!renderer)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Creating renderer for {}",
+ m_processInfo.GetRenderSystemName(bufferPool));
+
+ renderer.reset(m_processInfo.CreateRenderer(bufferPool, renderSettings));
+ if (renderer && renderer->Configure(m_format))
+ {
+ // Ensure we have a render buffer for this renderer
+ CreateRenderBuffer(renderer->GetBufferPool());
+
+ m_renderers.insert(renderer);
+ }
+ else
+ renderer.reset();
+ }
+
+ return renderer;
+}
+
+bool CRPRenderManager::HasRenderBuffer(IRenderBufferPool* bufferPool)
+{
+ bool bHasRenderBuffer = false;
+
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ auto it = std::find_if(
+ m_renderBuffers.begin(), m_renderBuffers.end(),
+ [bufferPool](IRenderBuffer* renderBuffer) { return renderBuffer->GetPool() == bufferPool; });
+
+ if (it != m_renderBuffers.end())
+ bHasRenderBuffer = true;
+
+ return bHasRenderBuffer;
+}
+
+IRenderBuffer* CRPRenderManager::GetRenderBuffer(IRenderBufferPool* bufferPool)
+{
+ if (m_bFlush || m_state != RENDER_STATE::CONFIGURED)
+ return nullptr;
+
+ IRenderBuffer* renderBuffer = nullptr;
+
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ auto getRenderBuffer = [bufferPool](IRenderBuffer* renderBuffer) {
+ return renderBuffer->GetPool() == bufferPool;
+ };
+
+ auto it = std::find_if(m_renderBuffers.begin(), m_renderBuffers.end(), getRenderBuffer);
+
+ // If our renderer has no buffer, try to create one from paused frame now
+ if (it == m_renderBuffers.end())
+ {
+ CreateRenderBuffer(bufferPool);
+ it = std::find_if(m_renderBuffers.begin(), m_renderBuffers.end(), getRenderBuffer);
+ }
+
+ if (it != m_renderBuffers.end())
+ {
+ renderBuffer = *it;
+ renderBuffer->Acquire();
+ }
+
+ return renderBuffer;
+}
+
+IRenderBuffer* CRPRenderManager::GetRenderBufferForSavestate(const std::string& savestatePath,
+ const IRenderBufferPool* bufferPool)
+{
+ IRenderBuffer* renderBuffer = nullptr;
+
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Check to see if we have a buffers for the specified path
+ auto it = m_savestateBuffers.find(savestatePath);
+ if (it != m_savestateBuffers.end())
+ {
+ // Get a render buffer belonging to the specified pool
+ const std::vector<IRenderBuffer*>& renderBuffers = it->second;
+
+ auto it = std::find_if(
+ renderBuffers.begin(), renderBuffers.end(),
+ [bufferPool](IRenderBuffer* buffer) { return buffer->GetPool() == bufferPool; });
+
+ if (it != renderBuffers.end())
+ {
+ renderBuffer = *it;
+ renderBuffer->Acquire();
+ }
+ }
+ else
+ {
+ // The path isn't loaded, mark it as seen and load asynchronously
+ m_savestateBuffers[savestatePath] = {};
+ LoadVideoFrameAsync(savestatePath);
+ }
+
+ return renderBuffer;
+}
+
+void CRPRenderManager::CreateRenderBuffer(IRenderBufferPool* bufferPool)
+{
+ if (m_bFlush || m_state != RENDER_STATE::CONFIGURED)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ if (!HasRenderBuffer(bufferPool) && m_bHasCachedFrame)
+ {
+ IRenderBuffer* renderBuffer =
+ CreateFromCache(m_cachedFrame, m_cachedWidth, m_cachedHeight, bufferPool, m_bufferMutex);
+ if (renderBuffer != nullptr)
+ m_renderBuffers.emplace_back(renderBuffer);
+ }
+}
+
+IRenderBuffer* CRPRenderManager::CreateFromCache(std::vector<uint8_t>& cachedFrame,
+ unsigned int width,
+ unsigned int height,
+ IRenderBufferPool* bufferPool,
+ CCriticalSection& mutex)
+{
+ // Take ownership of cached frame
+ std::vector<uint8_t> ownedFrame = std::move(cachedFrame);
+
+ if (!ownedFrame.empty())
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[RENDER]: Creating render buffer for renderer from cache");
+
+ IRenderBuffer* renderBuffer = bufferPool->GetBuffer(width, height);
+ if (renderBuffer != nullptr)
+ {
+ CSingleExit exit(mutex);
+ CopyFrame(renderBuffer, m_format, ownedFrame.data(), ownedFrame.size(), width, height);
+ }
+
+ // Return ownership of cached frame
+ cachedFrame = std::move(ownedFrame);
+
+ return renderBuffer;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[RENDER]: Failed to create render buffer, no cached frame");
+ }
+
+ return nullptr;
+}
+
+void CRPRenderManager::CopyFrame(IRenderBuffer* renderBuffer,
+ AVPixelFormat format,
+ const uint8_t* data,
+ size_t size,
+ unsigned int width,
+ unsigned int height)
+{
+ const uint8_t* source = data;
+ uint8_t* target = renderBuffer->GetMemory();
+
+ if (target != nullptr)
+ {
+ const unsigned int sourceStride = static_cast<unsigned int>(size / height);
+ const unsigned int targetStride =
+ static_cast<unsigned int>(renderBuffer->GetFrameSize() / renderBuffer->GetHeight());
+
+ if (format == renderBuffer->GetFormat())
+ {
+ if (sourceStride == targetStride)
+ std::memcpy(target, source, size);
+ else
+ {
+ const unsigned int widthBytes = CRenderTranslator::TranslateWidthToBytes(width, format);
+ if (widthBytes > 0)
+ {
+ for (unsigned int i = 0; i < height; i++)
+ std::memcpy(target + targetStride * i, source + sourceStride * i, widthBytes);
+ }
+ }
+ }
+ else
+ {
+ SwsContext*& scalerContext = m_scalers[format][renderBuffer->GetFormat()];
+ scalerContext = sws_getCachedContext(
+ scalerContext, width, height, format, renderBuffer->GetWidth(), renderBuffer->GetHeight(),
+ renderBuffer->GetFormat(), SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
+
+ if (scalerContext != nullptr)
+ {
+ uint8_t* src[] = {const_cast<uint8_t*>(source), nullptr, nullptr, nullptr};
+ int srcStride[] = {static_cast<int>(sourceStride), 0, 0, 0};
+ uint8_t* dst[] = {target, nullptr, nullptr, nullptr};
+ int dstStride[] = {static_cast<int>(targetStride), 0, 0, 0};
+
+ sws_scale(scalerContext, src, srcStride, 0, height, dst, dstStride);
+ }
+ }
+ }
+
+ renderBuffer->ReleaseMemory();
+}
+
+CRenderVideoSettings CRPRenderManager::GetEffectiveSettings(
+ const IGUIRenderSettings* settings) const
+{
+ CRenderVideoSettings effectiveSettings = m_renderSettings->GetSettings().VideoSettings();
+
+ if (settings != nullptr)
+ {
+ if (settings->HasVideoFilter())
+ effectiveSettings.SetVideoFilter(settings->GetSettings().VideoSettings().GetVideoFilter());
+ if (settings->HasStretchMode())
+ effectiveSettings.SetRenderStretchMode(
+ settings->GetSettings().VideoSettings().GetRenderStretchMode());
+ if (settings->HasRotation())
+ effectiveSettings.SetRenderRotation(
+ settings->GetSettings().VideoSettings().GetRenderRotation());
+ if (settings->HasPixels())
+ effectiveSettings.SetPixels(settings->GetSettings().VideoSettings().GetPixels());
+ }
+
+ // Sanitize settings
+ if (!m_processInfo.HasScalingMethod(effectiveSettings.GetScalingMethod()))
+ {
+ effectiveSettings.SetScalingMethod(m_processInfo.GetDefaultScalingMethod());
+ }
+
+ return effectiveSettings;
+}
+
+void CRPRenderManager::SaveThumbnail(const std::string& thumbnailPath)
+{
+ // Get a suitable render buffer for capturing the video data, or use the
+ // cached frame if a readable buffer can't be found
+ IRenderBuffer* renderBuffer = nullptr;
+ std::vector<uint8_t> cachedFrame;
+
+ GetVideoFrame(renderBuffer, cachedFrame);
+
+ // Video frame properties
+ AVPixelFormat sourceFormat = AV_PIX_FMT_NONE;
+ const uint8_t* sourceData = nullptr;
+ size_t sourceSize = 0;
+ unsigned int width = 0;
+ unsigned int height = 0;
+ unsigned int rotationCCW = 0;
+
+ if (renderBuffer != nullptr)
+ {
+ sourceFormat = renderBuffer->GetFormat();
+ sourceData = renderBuffer->GetMemory();
+ sourceSize = renderBuffer->GetFrameSize();
+ width = renderBuffer->GetWidth();
+ height = renderBuffer->GetHeight();
+ rotationCCW = renderBuffer->GetRotation();
+ }
+ else if (!cachedFrame.empty())
+ {
+ sourceFormat = m_format;
+ sourceData = m_cachedFrame.data();
+ sourceSize = m_cachedFrame.size();
+ width = m_cachedWidth;
+ height = m_cachedHeight;
+ rotationCCW = m_cachedRotationCCW;
+ }
+
+ if (sourceFormat == AV_PIX_FMT_NONE)
+ {
+ CLog::Log(LOGERROR, "Failed to get a video frame for savestate thumbnail");
+ return;
+ }
+
+ std::vector<uint8_t> copiedData(sourceData, sourceData + sourceSize);
+
+ const int stride = CRenderTranslator::TranslateWidthToBytes(width, sourceFormat);
+
+ unsigned int scaleWidth = 400;
+ unsigned int scaleHeight = 220;
+ CPicture::GetScale(width, height, scaleWidth, scaleHeight);
+
+ const int bytesPerPixel = 4;
+ std::vector<uint8_t> scaledImage(scaleWidth * scaleHeight * bytesPerPixel);
+
+ const AVPixelFormat outFormat = AV_PIX_FMT_BGR0;
+ const int scaleStride = CRenderTranslator::TranslateWidthToBytes(scaleWidth, outFormat);
+
+ if (CPicture::ScaleImage(copiedData.data(), width, height, stride, sourceFormat,
+ scaledImage.data(), scaleWidth, scaleHeight, scaleStride, outFormat))
+ {
+ //! @todo rotate image by rotationCCW
+ (void)rotationCCW;
+
+ CPicture::CreateThumbnailFromSurface(scaledImage.data(), scaleWidth, scaleHeight, scaleStride,
+ thumbnailPath);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "Failed to scale image from size {}x{} to size {}x{}", width, height,
+ scaleWidth, scaleHeight);
+ }
+
+ FreeVideoFrame(renderBuffer, std::move(cachedFrame));
+}
+
+void CRPRenderManager::CacheVideoFrame(const std::string& savestatePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Get the render buffers for this savestate path
+ std::vector<IRenderBuffer*>& savestateBuffers = m_savestateBuffers[savestatePath];
+
+ // Release old buffers
+ for (IRenderBuffer* renderBuffer : savestateBuffers)
+ renderBuffer->Release();
+
+ // Save buffers
+ savestateBuffers = m_renderBuffers;
+
+ // Acquire new buffers
+ for (IRenderBuffer* renderBuffer : savestateBuffers)
+ renderBuffer->Acquire();
+}
+
+void CRPRenderManager::SaveVideoFrame(const std::string& savestatePath, ISavestate& savestate)
+{
+ // Get a suitable render buffer for capturing the video data, or use the
+ // cached frame if a readable buffer can't be found
+ IRenderBuffer* readableBuffer = nullptr;
+ std::vector<uint8_t> cachedFrame;
+
+ GetVideoFrame(readableBuffer, cachedFrame);
+
+ // Video frame properties
+ AVPixelFormat targetFormat = AV_PIX_FMT_NONE;
+ unsigned int width = 0;
+ unsigned int height = 0;
+ unsigned int rotationCCW = 0;
+ size_t sourceSize = 0;
+ const uint8_t* sourceData = nullptr;
+
+ if (readableBuffer != nullptr)
+ {
+ targetFormat = readableBuffer->GetFormat();
+ width = readableBuffer->GetWidth();
+ height = readableBuffer->GetHeight();
+ rotationCCW = readableBuffer->GetRotation();
+ sourceSize = readableBuffer->GetFrameSize();
+ sourceData = readableBuffer->GetMemory();
+ }
+ else if (!cachedFrame.empty())
+ {
+ targetFormat = m_format;
+ width = m_cachedWidth;
+ height = m_cachedHeight;
+ rotationCCW = m_cachedRotationCCW;
+ sourceSize = m_cachedFrame.size();
+ sourceData = m_cachedFrame.data();
+ }
+
+ if (targetFormat == AV_PIX_FMT_NONE)
+ {
+ CLog::Log(LOGERROR, "Failed to get a video frame for savestate video frame");
+ }
+ else
+ {
+ // Serialize video stream properties
+ savestate.SetPixelFormat(targetFormat);
+ savestate.SetNominalWidth(m_nominalWidth);
+ savestate.SetNominalHeight(m_nominalHeight);
+ savestate.SetMaxWidth(m_maxWidth);
+ savestate.SetMaxHeight(m_maxHeight);
+ savestate.SetPixelAspectRatio(m_pixelAspectRatio);
+
+ // Serialize video frame properties
+ savestate.SetVideoWidth(width);
+ savestate.SetVideoHeight(height);
+ savestate.SetRotationDegCCW(rotationCCW);
+ uint8_t* const targetData = savestate.GetVideoBuffer(sourceSize);
+ std::memcpy(targetData, sourceData, sourceSize);
+ }
+
+ FreeVideoFrame(readableBuffer, std::move(cachedFrame));
+}
+
+void CRPRenderManager::ClearVideoFrame(const std::string& savestatePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ auto it = m_savestateBuffers.find(savestatePath);
+ if (it != m_savestateBuffers.end())
+ {
+ for (auto renderBuffer : it->second)
+ renderBuffer->Release();
+ m_savestateBuffers.erase(it);
+ }
+}
+
+void CRPRenderManager::GetVideoFrame(IRenderBuffer*& readableBuffer,
+ std::vector<uint8_t>& cachedFrame)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Get a readable render buffer
+ auto it = std::find_if(m_renderBuffers.begin(), m_renderBuffers.end(),
+ [](const IRenderBuffer* renderBuffer) {
+ return renderBuffer->GetMemoryAccess() != DataAccess::WRITE_ONLY;
+ });
+
+ // Aquire buffer if one was found
+ if (it != m_renderBuffers.end())
+ {
+ readableBuffer = *it;
+ readableBuffer->Acquire();
+ }
+ else
+ {
+ // If no buffers were readable, check the cached frame
+ if (m_speed == 0.0 && m_bHasCachedFrame && !m_cachedFrame.empty())
+ cachedFrame = std::move(m_cachedFrame);
+ }
+}
+
+void CRPRenderManager::FreeVideoFrame(IRenderBuffer* readableBuffer,
+ std::vector<uint8_t> cachedFrame)
+{
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+
+ // Free resources
+ if (readableBuffer != nullptr)
+ {
+ readableBuffer->ReleaseMemory();
+ readableBuffer->Release();
+ }
+ if (!cachedFrame.empty())
+ {
+ m_cachedFrame = std::move(cachedFrame);
+ }
+}
+
+void CRPRenderManager::LoadVideoFrameAsync(const std::string& savestatePath)
+{
+ // Prune any finished loader threads
+ m_savestateThreads.erase(std::remove_if(m_savestateThreads.begin(), m_savestateThreads.end(),
+ [](std::future<void>& task) {
+ return task.wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready;
+ }),
+ m_savestateThreads.end());
+
+ // Load the video data from the savestate asynchronously
+ std::future<void> task = std::async(
+ std::launch::async, [this, savestatePath]() { LoadVideoFrameSync(savestatePath); });
+
+ m_savestateThreads.emplace_back(std::move(task));
+}
+
+void CRPRenderManager::LoadVideoFrameSync(const std::string& savestatePath)
+{
+ if (!XFILE::CFile::Exists(savestatePath))
+ {
+ CLog::Log(LOGERROR, "Failed to load savestate: doesn't exist at path {}",
+ CURL::GetRedacted(savestatePath));
+ return;
+ }
+
+ std::unique_ptr<ISavestate> savestate = CSavestateDatabase::AllocateSavestate();
+ CSavestateDatabase db;
+ if (!db.GetSavestate(savestatePath, *savestate))
+ return;
+
+ // Load video data
+ const AVPixelFormat format = savestate->GetPixelFormat();
+ const uint8_t* data = savestate->GetVideoData();
+ const size_t size = savestate->GetVideoSize();
+ const unsigned int width = savestate->GetVideoWidth();
+ const unsigned int height = savestate->GetVideoHeight();
+ const unsigned int rotationCCW = savestate->GetRotationDegCCW();
+
+ // Validate parameters
+ if (format == AV_PIX_FMT_NONE || data == nullptr || size == 0 || width == 0 || height == 0)
+ {
+ CLog::Log(LOGERROR, "Invalid video data: format {}, data {}, size {}, width {}, height {}",
+ static_cast<long int>(format), static_cast<const void*>(data), size, width, height);
+ return;
+ }
+
+ // Copy frame to render buffers
+ std::vector<IRenderBuffer*> renderBuffers;
+ for (auto* bufferPool : m_processInfo.GetBufferManager().GetBufferPools())
+ {
+ IRenderBuffer* newBuffer = bufferPool->GetBuffer(width, height);
+ if (newBuffer == nullptr)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Unable to get render buffer for savestate");
+ continue;
+ }
+
+ CopyFrame(newBuffer, format, data, size, width, height);
+ newBuffer->SetRotation(rotationCCW);
+
+ renderBuffers.emplace_back(newBuffer);
+ }
+
+ // Save render buffers
+ std::unique_lock<CCriticalSection> lock(m_bufferMutex);
+ m_savestateBuffers[savestatePath] = std::move(renderBuffers);
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h
new file mode 100644
index 0000000..bd1ebb5
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IRenderManager.h"
+#include "RenderVideoSettings.h"
+#include "cores/RetroPlayer/guibridge/IRenderCallback.h"
+#include "threads/CriticalSection.h"
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+#include <atomic>
+#include <future>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+struct SwsContext;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIRenderTargetFactory;
+class CRenderContext;
+class CRenderSettings;
+class CRPBaseRenderer;
+class CRPProcessInfo;
+class IGUIRenderSettings;
+class IRenderBuffer;
+class IRenderBufferPool;
+class ISavestate;
+struct VideoStreamBuffer;
+
+/*!
+ * \brief Renders video frames provided by the game loop
+ *
+ * Generally, buffer pools are registered by the windowing subsystem. A buffer
+ * pool provides a software or hardware buffer to store the added frame. When
+ * RenderManager is created, it instantiates all registered buffer pools.
+ *
+ * When a frame arrives, it is copied into a buffer from each buffer pool with
+ * a visible renderer. For example, if a GLES and MMAL renderer are both
+ * visible in the GUI, then the frame will be copied into two buffers.
+ *
+ * When it is time to render the frame, the GUI control or window calls into
+ * this class through the IRenderManager interface. RenderManager selects an
+ * appropriate renderer to use to render the frame. The renderer is then
+ * given the buffer that came from its buffer pool.
+ *
+ * Special behavior is needed when the game is paused. As no new frames are
+ * delivered, a newly created renderer will stay black. For this scenario,
+ * when we detect a pause event, the frame is preemptively cached so that a
+ * newly created renderer will have something to display.
+ */
+class CRPRenderManager : public IRenderManager, public IRenderCallback
+{
+public:
+ CRPRenderManager(CRPProcessInfo& processInfo);
+ ~CRPRenderManager() override = default;
+
+ void Initialize();
+ void Deinitialize();
+
+ /*!
+ * \brief Access the factory for creating GUI render targets
+ */
+ CGUIRenderTargetFactory* GetGUIRenderTargetFactory() { return m_renderControlFactory.get(); }
+
+ // Stream properties, set upon configuration
+ AVPixelFormat GetPixelFormat() const { return m_format; }
+ unsigned int GetNominalWidth() const { return m_nominalWidth; }
+ unsigned int GetNominalHeight() const { return m_nominalHeight; }
+ unsigned int GetMaxWidth() const { return m_maxWidth; }
+ unsigned int GetMaxHeight() const { return m_maxHeight; }
+ float GetPixelAspectRatio() const { return m_pixelAspectRatio; }
+
+ // Functions called from game loop
+ bool Configure(AVPixelFormat format,
+ unsigned int nominalWidth,
+ unsigned int nominalHeight,
+ unsigned int maxWidth,
+ unsigned int maxHeight,
+ float pixelAspectRatio);
+ bool GetVideoBuffer(unsigned int width, unsigned int height, VideoStreamBuffer& buffer);
+ void AddFrame(const uint8_t* data,
+ size_t size,
+ unsigned int width,
+ unsigned int height,
+ unsigned int orientationDegCW);
+ void Flush();
+
+ // Functions called from the player
+ void SetSpeed(double speed);
+
+ // Functions called from render thread
+ void FrameMove();
+
+ // Implementation of IRenderManager
+ void RenderWindow(bool bClear, const RESOLUTION_INFO& coordsRes) override;
+ void RenderControl(bool bClear,
+ bool bUseAlpha,
+ const CRect& renderRegion,
+ const IGUIRenderSettings* renderSettings) override;
+ void ClearBackground() override;
+
+ // Implementation of IRenderCallback
+ bool SupportsRenderFeature(RENDERFEATURE feature) const override;
+ bool SupportsScalingMethod(SCALINGMETHOD method) const override;
+
+ // Savestate functions
+ void SaveThumbnail(const std::string& thumbnailPath);
+
+ // Savestate functions
+ void CacheVideoFrame(const std::string& savestatePath);
+ void SaveVideoFrame(const std::string& savestatePath, ISavestate& savestate);
+ void ClearVideoFrame(const std::string& savestatePath);
+
+private:
+ /*!
+ * \brief Get or create a renderer compatible with the given render settings
+ */
+ std::shared_ptr<CRPBaseRenderer> GetRendererForSettings(const IGUIRenderSettings* renderSettings);
+
+ /*!
+ * \brief Get or create a renderer for the given buffer pool and render settings
+ */
+ std::shared_ptr<CRPBaseRenderer> GetRendererForPool(IRenderBufferPool* bufferPool,
+ const CRenderSettings& renderSettings);
+
+ /*!
+ * \brief Render a frame using the given renderer
+ */
+ void RenderInternal(const std::shared_ptr<CRPBaseRenderer>& renderer,
+ IRenderBuffer* renderBuffer,
+ bool bClear,
+ uint32_t alpha);
+
+ /*!
+ * \brief Return true if we have a render buffer belonging to the specified pool
+ */
+ bool HasRenderBuffer(IRenderBufferPool* bufferPool);
+
+ /*!
+ * \brief Get a render buffer belonging to the specified pool
+ */
+ IRenderBuffer* GetRenderBuffer(IRenderBufferPool* bufferPool);
+
+ /*!
+ * \brief Get a render buffer containing pixels from the specified savestate
+ */
+ IRenderBuffer* GetRenderBufferForSavestate(const std::string& savestatePath,
+ const IRenderBufferPool* bufferPool);
+
+ /*!
+ * \brief Create a render buffer for the specified pool from a cached frame
+ */
+ void CreateRenderBuffer(IRenderBufferPool* bufferPool);
+
+ /*!
+ * \brief Create a render buffer and copy the cached data into it
+ *
+ * The cached frame is accessed by both the game and rendering threads,
+ * and therefore requires synchronization.
+ *
+ * However, assuming the memory copy is expensive, we must avoid holding
+ * the mutex during the copy.
+ *
+ * To allow for this, the function is permitted to invalidate its
+ * cachedFrame parameter, as long as it is restored upon exit. While the
+ * mutex is exited inside this function, cachedFrame is guaranteed to be
+ * empty.
+ *
+ * \param cachedFrame The cached frame
+ * \param width The width of the cached frame
+ * \param height The height of the cached frame
+ * \param bufferPool The buffer pool used to create the render buffer
+ * \param mutex The locked mutex, to be unlocked during memory copy
+ *
+ * \return The render buffer if one was created from the cached frame,
+ * otherwise nullptr
+ */
+ IRenderBuffer* CreateFromCache(std::vector<uint8_t>& cachedFrame,
+ unsigned int width,
+ unsigned int height,
+ IRenderBufferPool* bufferPool,
+ CCriticalSection& mutex);
+
+ /*!
+ * \brief Utility function to copy a frame and rescale pixels if necessary
+ */
+ void CopyFrame(IRenderBuffer* renderBuffer,
+ AVPixelFormat format,
+ const uint8_t* data,
+ size_t size,
+ unsigned int width,
+ unsigned int height);
+
+ CRenderVideoSettings GetEffectiveSettings(const IGUIRenderSettings* settings) const;
+
+ void CheckFlush();
+
+ void GetVideoFrame(IRenderBuffer*& readableBuffer, std::vector<uint8_t>& cachedFrame);
+ void FreeVideoFrame(IRenderBuffer* readableBuffer, std::vector<uint8_t> cachedFrame);
+ void LoadVideoFrameAsync(const std::string& savestatePath);
+ void LoadVideoFrameSync(const std::string& savestatePath);
+
+ // Construction parameters
+ CRPProcessInfo& m_processInfo;
+ CRenderContext& m_renderContext;
+
+ // Subsystems
+ std::shared_ptr<IGUIRenderSettings> m_renderSettings;
+ std::shared_ptr<CGUIRenderTargetFactory> m_renderControlFactory;
+
+ // Stream properties
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+ unsigned int m_nominalWidth{0};
+ unsigned int m_nominalHeight{0};
+ unsigned int m_maxWidth = 0;
+ unsigned int m_maxHeight = 0;
+ float m_pixelAspectRatio{1.0f};
+
+ // Render resources
+ std::set<std::shared_ptr<CRPBaseRenderer>> m_renderers;
+ std::vector<IRenderBuffer*> m_pendingBuffers; // Only access from game thread
+ std::vector<IRenderBuffer*> m_renderBuffers;
+ std::map<AVPixelFormat, std::map<AVPixelFormat, SwsContext*>> m_scalers; // From -> to -> context
+ std::vector<uint8_t> m_cachedFrame;
+ unsigned int m_cachedWidth = 0;
+ unsigned int m_cachedHeight = 0;
+ unsigned int m_cachedRotationCCW{0};
+ std::map<std::string, std::vector<IRenderBuffer*>>
+ m_savestateBuffers; // Render buffers for savestates
+ std::vector<std::future<void>> m_savestateThreads;
+
+ // State parameters
+ enum class RENDER_STATE
+ {
+ UNCONFIGURED,
+ CONFIGURING,
+ CONFIGURED,
+ };
+ RENDER_STATE m_state = RENDER_STATE::UNCONFIGURED;
+ bool m_bHasCachedFrame = false; // Invariant: m_cachedFrame is empty if false
+ std::set<std::string> m_failedShaderPresets;
+ std::atomic<bool> m_bFlush = {false};
+
+ // Windowing state
+ bool m_bDisplayScaleSet = false;
+
+ // Playback parameters
+ std::atomic<double> m_speed = {1.0};
+
+ // Synchronization parameters
+ CCriticalSection m_stateMutex;
+ CCriticalSection m_bufferMutex;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderContext.cpp b/xbmc/cores/RetroPlayer/rendering/RenderContext.cpp
new file mode 100644
index 0000000..9be9c74
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderContext.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderContext.h"
+
+#include "games/GameServices.h"
+#include "games/agents/GameAgentManager.h"
+#include "rendering/RenderSystem.h"
+#include "settings/DisplaySettings.h"
+#include "settings/MediaSettings.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include "system_gl.h"
+
+#if defined(HAS_GL)
+#include "rendering/gl/RenderSystemGL.h"
+#elif HAS_GLES >= 2
+#include "rendering/gles/RenderSystemGLES.h"
+#elif defined(TARGET_WINDOWS)
+#include "rendering/dx/RenderSystemDX.h"
+#endif
+
+using namespace KODI;
+using namespace RETRO;
+
+CRenderContext::CRenderContext(CRenderSystemBase* rendering,
+ CWinSystemBase* windowing,
+ CGraphicContext& graphicsContext,
+ CDisplaySettings& displaySettings,
+ CMediaSettings& mediaSettings,
+ GAME::CGameServices& gameServices,
+ CGUIComponent* guiComponent)
+ : m_rendering(rendering),
+ m_windowing(windowing),
+ m_graphicsContext(graphicsContext),
+ m_displaySettings(displaySettings),
+ m_mediaSettings(mediaSettings),
+ m_gameServices(gameServices),
+ m_guiComponent(guiComponent)
+{
+}
+
+void CRenderContext::SetViewPort(const CRect& viewPort)
+{
+ m_rendering->SetViewPort(viewPort);
+}
+
+void CRenderContext::GetViewPort(CRect& viewPort)
+{
+ m_rendering->GetViewPort(viewPort);
+}
+
+void CRenderContext::SetScissors(const CRect& rect)
+{
+ m_rendering->SetScissors(rect);
+}
+
+void CRenderContext::ApplyStateBlock()
+{
+ m_rendering->ApplyStateBlock();
+}
+
+bool CRenderContext::IsExtSupported(const char* extension)
+{
+ return m_rendering->IsExtSupported(extension);
+}
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+namespace
+{
+
+#ifdef HAS_GL
+static ShaderMethodGL TranslateShaderMethodGL(GL_SHADER_METHOD method)
+{
+ switch (method)
+ {
+ case GL_SHADER_METHOD::DEFAULT:
+ return ShaderMethodGL::SM_DEFAULT;
+ case GL_SHADER_METHOD::TEXTURE:
+ return ShaderMethodGL::SM_TEXTURE;
+ default:
+ break;
+ }
+
+ return ShaderMethodGL::SM_DEFAULT;
+}
+#endif
+#ifdef HAS_GLES
+static ShaderMethodGLES TranslateShaderMethodGLES(GL_SHADER_METHOD method)
+{
+ switch (method)
+ {
+ case GL_SHADER_METHOD::DEFAULT:
+ return ShaderMethodGLES::SM_DEFAULT;
+ case GL_SHADER_METHOD::TEXTURE:
+ return ShaderMethodGLES::SM_TEXTURE;
+ case GL_SHADER_METHOD::TEXTURE_NOALPHA:
+ return ShaderMethodGLES::SM_TEXTURE_NOALPHA;
+ default:
+ break;
+ }
+
+ return ShaderMethodGLES::SM_DEFAULT;
+}
+#endif
+
+} // namespace
+#endif
+
+void CRenderContext::EnableGUIShader(GL_SHADER_METHOD method)
+{
+#if defined(HAS_GL)
+ CRenderSystemGL* rendering = dynamic_cast<CRenderSystemGL*>(m_rendering);
+ if (rendering != nullptr)
+ rendering->EnableShader(TranslateShaderMethodGL(method));
+#elif HAS_GLES >= 2
+ CRenderSystemGLES* renderingGLES = dynamic_cast<CRenderSystemGLES*>(m_rendering);
+ if (renderingGLES != nullptr)
+ renderingGLES->EnableGUIShader(TranslateShaderMethodGLES(method));
+#endif
+}
+
+void CRenderContext::DisableGUIShader()
+{
+#if defined(HAS_GL)
+ CRenderSystemGL* renderingGL = dynamic_cast<CRenderSystemGL*>(m_rendering);
+ if (renderingGL != nullptr)
+ renderingGL->DisableShader();
+#elif HAS_GLES >= 2
+ CRenderSystemGLES* renderingGLES = dynamic_cast<CRenderSystemGLES*>(m_rendering);
+ if (renderingGLES != nullptr)
+ renderingGLES->DisableGUIShader();
+#endif
+}
+
+int CRenderContext::GUIShaderGetPos()
+{
+#if defined(HAS_GL)
+ CRenderSystemGL* renderingGL = dynamic_cast<CRenderSystemGL*>(m_rendering);
+ if (renderingGL != nullptr)
+ return static_cast<int>(renderingGL->ShaderGetPos());
+#elif HAS_GLES >= 2
+ CRenderSystemGLES* renderingGLES = dynamic_cast<CRenderSystemGLES*>(m_rendering);
+ if (renderingGLES != nullptr)
+ return static_cast<int>(renderingGLES->GUIShaderGetPos());
+#endif
+
+ return -1;
+}
+
+int CRenderContext::GUIShaderGetCoord0()
+{
+#if defined(HAS_GL)
+ CRenderSystemGL* renderingGL = dynamic_cast<CRenderSystemGL*>(m_rendering);
+ if (renderingGL != nullptr)
+ return static_cast<int>(renderingGL->ShaderGetCoord0());
+#elif HAS_GLES >= 2
+ CRenderSystemGLES* renderingGLES = dynamic_cast<CRenderSystemGLES*>(m_rendering);
+ if (renderingGLES != nullptr)
+ return static_cast<int>(renderingGLES->GUIShaderGetCoord0());
+#endif
+
+ return -1;
+}
+
+int CRenderContext::GUIShaderGetUniCol()
+{
+#if defined(HAS_GL)
+ CRenderSystemGL* renderingGL = dynamic_cast<CRenderSystemGL*>(m_rendering);
+ if (renderingGL != nullptr)
+ return static_cast<int>(renderingGL->ShaderGetUniCol());
+#elif HAS_GLES >= 2
+ CRenderSystemGLES* renderingGLES = dynamic_cast<CRenderSystemGLES*>(m_rendering);
+ if (renderingGLES != nullptr)
+ return static_cast<int>(renderingGLES->GUIShaderGetUniCol());
+#endif
+
+ return -1;
+}
+
+CGUIShaderDX* CRenderContext::GetGUIShader()
+{
+#if defined(HAS_DX)
+ CRenderSystemDX* renderingDX = dynamic_cast<CRenderSystemDX*>(m_rendering);
+ if (renderingDX != nullptr)
+ return renderingDX->GetGUIShader();
+#endif
+
+ return nullptr;
+}
+
+bool CRenderContext::UseLimitedColor()
+{
+ return m_windowing->UseLimitedColor();
+}
+
+bool CRenderContext::DisplayHardwareScalingEnabled()
+{
+ return m_windowing->DisplayHardwareScalingEnabled();
+}
+
+void CRenderContext::UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo)
+{
+ return m_windowing->UpdateDisplayHardwareScaling(resInfo);
+}
+
+int CRenderContext::GetScreenWidth()
+{
+ return m_graphicsContext.GetWidth();
+}
+
+int CRenderContext::GetScreenHeight()
+{
+ return m_graphicsContext.GetHeight();
+}
+
+const CRect& CRenderContext::GetScissors()
+{
+ return m_graphicsContext.GetScissors();
+}
+
+CRect CRenderContext::GetViewWindow()
+{
+ return m_graphicsContext.GetViewWindow();
+}
+
+void CRenderContext::SetViewWindow(float left, float top, float right, float bottom)
+{
+ m_graphicsContext.SetViewWindow(left, top, right, bottom);
+}
+
+void CRenderContext::SetFullScreenVideo(bool bOnOff)
+{
+ m_graphicsContext.SetFullScreenVideo(bOnOff);
+}
+
+bool CRenderContext::IsFullScreenVideo()
+{
+ return m_graphicsContext.IsFullScreenVideo();
+}
+
+bool CRenderContext::IsCalibrating()
+{
+ return m_graphicsContext.IsCalibrating();
+}
+
+RESOLUTION CRenderContext::GetVideoResolution()
+{
+ return m_graphicsContext.GetVideoResolution();
+}
+
+void CRenderContext::Clear(UTILS::COLOR::Color color)
+{
+ m_graphicsContext.Clear(color);
+}
+
+RESOLUTION_INFO CRenderContext::GetResInfo()
+{
+ return m_graphicsContext.GetResInfo();
+}
+
+void CRenderContext::SetRenderingResolution(const RESOLUTION_INFO& res, bool needsScaling)
+{
+ m_graphicsContext.SetRenderingResolution(res, needsScaling);
+}
+
+UTILS::COLOR::Color CRenderContext::MergeAlpha(UTILS::COLOR::Color color)
+{
+ return m_graphicsContext.MergeAlpha(color);
+}
+
+void CRenderContext::SetTransform(const TransformMatrix& matrix, float scaleX, float scaleY)
+{
+ m_graphicsContext.SetTransform(matrix, scaleX, scaleY);
+}
+
+void CRenderContext::RemoveTransform()
+{
+ m_graphicsContext.RemoveTransform();
+}
+
+CRect CRenderContext::StereoCorrection(const CRect& rect)
+{
+ return m_graphicsContext.StereoCorrection(rect);
+}
+
+CCriticalSection& CRenderContext::GraphicsMutex()
+{
+ return m_graphicsContext;
+}
+
+RESOLUTION_INFO& CRenderContext::GetResolutionInfo(RESOLUTION resolution)
+{
+ return m_displaySettings.GetResolutionInfo(resolution);
+}
+
+::CGameSettings& CRenderContext::GetGameSettings()
+{
+ return m_mediaSettings.GetCurrentGameSettings();
+}
+
+::CGameSettings& CRenderContext::GetDefaultGameSettings()
+{
+ return m_mediaSettings.GetDefaultGameSettings();
+}
+
+void CRenderContext::StartAgentManager(GAME::GameClientPtr gameClient)
+{
+ m_gameServices.GameAgentManager().Start(std::move(gameClient));
+}
+
+void CRenderContext::StopAgentManager()
+{
+ m_gameServices.GameAgentManager().Stop();
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderContext.h b/xbmc/cores/RetroPlayer/rendering/RenderContext.h
new file mode 100644
index 0000000..de663a4
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderContext.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "games/GameTypes.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h"
+#include "windowing/Resolution.h"
+
+class CCriticalSection;
+class CDisplaySettings;
+class CGameSettings;
+class CGraphicContext;
+class CGUIComponent;
+class CGUIShaderDX;
+class CMediaSettings;
+class CRenderSystemBase;
+class CWinSystemBase;
+class TransformMatrix;
+
+enum class GL_SHADER_METHOD
+{
+ DEFAULT,
+ TEXTURE,
+ TEXTURE_NOALPHA,
+};
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameServices;
+}
+
+namespace RETRO
+{
+class CRenderContext
+{
+public:
+ CRenderContext(CRenderSystemBase* rendering,
+ CWinSystemBase* windowing,
+ CGraphicContext& graphicsContext,
+ CDisplaySettings& displaySettings,
+ CMediaSettings& mediaSettings,
+ GAME::CGameServices& gameServices,
+ CGUIComponent* guiComponent);
+
+ CRenderSystemBase* Rendering() { return m_rendering; }
+ CWinSystemBase* Windowing() { return m_windowing; }
+ CGraphicContext& GraphicsContext() { return m_graphicsContext; }
+ CGUIComponent* GUI() { return m_guiComponent; }
+
+ // Rendering functions
+ void SetViewPort(const CRect& viewPort);
+ void GetViewPort(CRect& viewPort);
+ void SetScissors(const CRect& rect);
+ void ApplyStateBlock();
+ bool IsExtSupported(const char* extension);
+
+ // OpenGL(ES) rendering functions
+ void EnableGUIShader(GL_SHADER_METHOD method);
+ void DisableGUIShader();
+ int GUIShaderGetPos();
+ int GUIShaderGetCoord0();
+ int GUIShaderGetUniCol();
+
+ // DirectX rendering functions
+ CGUIShaderDX* GetGUIShader();
+
+ // Windowing functions
+ bool UseLimitedColor();
+ bool DisplayHardwareScalingEnabled();
+ void UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo);
+
+ // Graphics functions
+ int GetScreenWidth();
+ int GetScreenHeight();
+ const CRect& GetScissors();
+ CRect GetViewWindow();
+ void SetViewWindow(float left, float top, float right, float bottom);
+ void SetFullScreenVideo(bool bOnOff);
+ bool IsFullScreenVideo();
+ bool IsCalibrating();
+ RESOLUTION GetVideoResolution();
+ void Clear(UTILS::COLOR::Color color);
+ RESOLUTION_INFO GetResInfo();
+ void SetRenderingResolution(const RESOLUTION_INFO& res, bool needsScaling);
+ UTILS::COLOR::Color MergeAlpha(UTILS::COLOR::Color color);
+ void SetTransform(const TransformMatrix& matrix, float scaleX, float scaleY);
+ void RemoveTransform();
+ CRect StereoCorrection(const CRect& rect);
+ CCriticalSection& GraphicsMutex();
+
+ // Display settings
+ RESOLUTION_INFO& GetResolutionInfo(RESOLUTION resolution);
+
+ // Media settings
+ ::CGameSettings& GetGameSettings();
+ ::CGameSettings& GetDefaultGameSettings();
+
+ // Agent functions
+ void StartAgentManager(GAME::GameClientPtr gameClient);
+ void StopAgentManager();
+
+private:
+ // Construction parameters
+ CRenderSystemBase* const m_rendering;
+ CWinSystemBase* const m_windowing;
+ CGraphicContext& m_graphicsContext;
+ CDisplaySettings& m_displaySettings;
+ CMediaSettings& m_mediaSettings;
+ GAME::CGameServices& m_gameServices;
+ CGUIComponent* const m_guiComponent;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderSettings.cpp b/xbmc/cores/RetroPlayer/rendering/RenderSettings.cpp
new file mode 100644
index 0000000..3db43d2
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderSettings.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderSettings.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+void CRenderSettings::Reset()
+{
+ m_videoSettings.Reset();
+}
+
+bool CRenderSettings::operator==(const CRenderSettings& rhs) const
+{
+ return m_videoSettings == rhs.m_videoSettings;
+}
+
+bool CRenderSettings::operator<(const CRenderSettings& rhs) const
+{
+ if (m_videoSettings < rhs.m_videoSettings)
+ return true;
+ if (m_videoSettings > rhs.m_videoSettings)
+ return false;
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderSettings.h b/xbmc/cores/RetroPlayer/rendering/RenderSettings.h
new file mode 100644
index 0000000..d40cb42
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderSettings.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RenderVideoSettings.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderSettings
+{
+public:
+ CRenderSettings() { Reset(); }
+
+ void Reset();
+
+ bool operator==(const CRenderSettings& rhs) const;
+ bool operator<(const CRenderSettings& rhs) const;
+
+ CRenderVideoSettings& VideoSettings() { return m_videoSettings; }
+ const CRenderVideoSettings& VideoSettings() const { return m_videoSettings; }
+
+private:
+ CRenderVideoSettings m_videoSettings;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderTranslator.cpp b/xbmc/cores/RetroPlayer/rendering/RenderTranslator.cpp
new file mode 100644
index 0000000..1256af7
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderTranslator.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderTranslator.h"
+
+#include <stdint.h>
+
+using namespace KODI;
+using namespace RETRO;
+
+const char* CRenderTranslator::TranslatePixelFormat(AVPixelFormat format)
+{
+ switch (format)
+ {
+ case AV_PIX_FMT_0RGB32:
+ return "0RGB32";
+ case AV_PIX_FMT_RGBA:
+ return "RGBA32";
+ case AV_PIX_FMT_RGB565:
+ return "RGB565";
+ case AV_PIX_FMT_RGB555:
+ return "RGB555";
+ default:
+ break;
+ }
+
+ return "unknown";
+}
+
+const char* CRenderTranslator::TranslateScalingMethod(SCALINGMETHOD scalingMethod)
+{
+ switch (scalingMethod)
+ {
+ case SCALINGMETHOD::NEAREST:
+ return "nearest";
+ case SCALINGMETHOD::LINEAR:
+ return "linear";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+unsigned int CRenderTranslator::TranslateWidthToBytes(unsigned int width, AVPixelFormat format)
+{
+ unsigned int bpp = 0;
+
+ switch (format)
+ {
+ case AV_PIX_FMT_0RGB32:
+ case AV_PIX_FMT_RGBA:
+ {
+ bpp = sizeof(uint32_t);
+ break;
+ }
+ case AV_PIX_FMT_RGB555:
+ {
+ bpp = sizeof(uint16_t);
+ break;
+ }
+ case AV_PIX_FMT_RGB565:
+ {
+ bpp = sizeof(uint16_t);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return width * bpp;
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderTranslator.h b/xbmc/cores/RetroPlayer/rendering/RenderTranslator.h
new file mode 100644
index 0000000..575ad81
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderTranslator.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+
+#include <libavutil/pixfmt.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderTranslator
+{
+public:
+ /*!
+ * \brief Translate a pixel format to a string suitable for logging
+ */
+ static const char* TranslatePixelFormat(AVPixelFormat format);
+
+ /*!
+ * \brief Translate a scaling method to a string suitable for logging
+ */
+ static const char* TranslateScalingMethod(SCALINGMETHOD scalingMethod);
+
+ /*!
+ * \brief Translate a width in pixels to a width in bytes
+ *
+ * \param width The width in pixels
+ * \param format The pixel format
+ *
+ * \return The width in bytes, or 0 if unknown
+ */
+ static unsigned int TranslateWidthToBytes(unsigned int width, AVPixelFormat format);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderUtils.cpp b/xbmc/cores/RetroPlayer/rendering/RenderUtils.cpp
new file mode 100644
index 0000000..f8769dd
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderUtils.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderUtils.h"
+
+#include "utils/MathUtils.h"
+
+#include <cmath>
+
+using namespace KODI;
+using namespace RETRO;
+
+void CRenderUtils::CalculateStretchMode(STRETCHMODE stretchMode,
+ unsigned int rotationDegCCW,
+ unsigned int sourceWidth,
+ unsigned int sourceHeight,
+ float screenWidth,
+ float screenHeight,
+ float& pixelRatio,
+ float& zoomAmount)
+{
+ const float sourceFrameRatio = static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight);
+
+ switch (stretchMode)
+ {
+ case STRETCHMODE::Normal:
+ {
+ switch (rotationDegCCW)
+ {
+ case 90:
+ case 270:
+ {
+ pixelRatio = 1.0f / (sourceFrameRatio * sourceFrameRatio);
+ break;
+ }
+ default:
+ pixelRatio = 1.0f;
+ break;
+ }
+ zoomAmount = 1.0f;
+
+ break;
+ }
+ case STRETCHMODE::Stretch4x3:
+ {
+ // Stretch to 4:3 ratio
+ pixelRatio = (4.0f / 3.0f) / sourceFrameRatio;
+ zoomAmount = 1.0f;
+
+ break;
+ }
+ case STRETCHMODE::Fullscreen:
+ {
+ // Stretch to the limits of the screen
+ pixelRatio = (screenWidth / screenHeight) / sourceFrameRatio;
+ zoomAmount = 1.0f;
+
+ break;
+ }
+ case STRETCHMODE::Original:
+ {
+ switch (rotationDegCCW)
+ {
+ case 90:
+ case 270:
+ {
+ pixelRatio = 1.0f / (sourceFrameRatio * sourceFrameRatio);
+ break;
+ }
+ default:
+ pixelRatio = 1.0f;
+ break;
+ }
+
+ // Calculate the correct zoom amount
+ // First zoom to full width
+ float newHeight = screenWidth / pixelRatio;
+ if (newHeight > screenHeight)
+ {
+ // Zoom to full height
+ newHeight = screenHeight;
+ }
+
+ // Now work out the zoom amount so that no zoom is done
+ zoomAmount = sourceHeight / newHeight;
+
+ switch (rotationDegCCW)
+ {
+ case 90:
+ case 270:
+ {
+ zoomAmount *= sourceFrameRatio;
+ break;
+ }
+ default:
+ break;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CRenderUtils::CalcNormalRenderRect(const CRect& viewRect,
+ float outputFrameRatio,
+ float zoomAmount,
+ CRect& destRect)
+{
+ const float offsetX = viewRect.x1;
+ const float offsetY = viewRect.y1;
+ const float width = viewRect.Width();
+ const float height = viewRect.Height();
+
+ // If view window is empty, set empty destination
+ if (height == 0 || width == 0)
+ {
+ destRect.SetRect(0.0f, 0.0f, 0.0f, 0.0f);
+ return;
+ }
+
+ // Maximize the game width
+ float newWidth = width;
+ float newHeight = newWidth / outputFrameRatio;
+
+ if (newHeight > height)
+ {
+ newHeight = height;
+ newWidth = newHeight * outputFrameRatio;
+ }
+
+ // Scale the game up by set zoom amount
+ newWidth *= zoomAmount;
+ newHeight *= zoomAmount;
+
+ // If we are less than one pixel off use the complete screen instead
+ if (std::fabs(newWidth - width) < 1.0f)
+ newWidth = width;
+ if (std::fabs(newHeight - height) < 1.0f)
+ newHeight = height;
+
+ // Center the game
+ float posY = (height - newHeight) / 2;
+ float posX = (width - newWidth) / 2;
+
+ destRect.x1 = static_cast<float>(MathUtils::round_int(static_cast<double>(posX + offsetX)));
+ destRect.x2 = destRect.x1 + MathUtils::round_int(static_cast<double>(newWidth));
+ destRect.y1 = static_cast<float>(MathUtils::round_int(static_cast<double>(posY + offsetY)));
+ destRect.y2 = destRect.y1 + MathUtils::round_int(static_cast<double>(newHeight));
+}
+
+void CRenderUtils::ClipRect(const CRect& viewRect, CRect& sourceRect, CRect& destRect)
+{
+ const float offsetX = viewRect.x1;
+ const float offsetY = viewRect.y1;
+ const float width = viewRect.Width();
+ const float height = viewRect.Height();
+
+ CRect original(destRect);
+ destRect.Intersect(CRect(offsetX, offsetY, offsetX + width, offsetY + height));
+ if (destRect != original)
+ {
+ float scaleX = sourceRect.Width() / original.Width();
+ float scaleY = sourceRect.Height() / original.Height();
+ sourceRect.x1 += (destRect.x1 - original.x1) * scaleX;
+ sourceRect.y1 += (destRect.y1 - original.y1) * scaleY;
+ sourceRect.x2 += (destRect.x2 - original.x2) * scaleX;
+ sourceRect.y2 += (destRect.y2 - original.y2) * scaleY;
+ }
+}
+
+std::array<CPoint, 4> CRenderUtils::ReorderDrawPoints(const CRect& destRect,
+ unsigned int orientationDegCCW)
+{
+ std::array<CPoint, 4> rotatedDestCoords{};
+
+ switch (orientationDegCCW)
+ {
+ case 0:
+ {
+ rotatedDestCoords[0] = CPoint{destRect.x1, destRect.y1}; // Top left
+ rotatedDestCoords[1] = CPoint{destRect.x2, destRect.y1}; // Top right
+ rotatedDestCoords[2] = CPoint{destRect.x2, destRect.y2}; // Bottom right
+ rotatedDestCoords[3] = CPoint{destRect.x1, destRect.y2}; // Bottom left
+ break;
+ }
+ case 90:
+ {
+ rotatedDestCoords[0] = CPoint{destRect.x1, destRect.y2}; // Bottom left
+ rotatedDestCoords[1] = CPoint{destRect.x1, destRect.y1}; // Top left
+ rotatedDestCoords[2] = CPoint{destRect.x2, destRect.y1}; // Top right
+ rotatedDestCoords[3] = CPoint{destRect.x2, destRect.y2}; // Bottom right
+ break;
+ }
+ case 180:
+ {
+ rotatedDestCoords[0] = CPoint{destRect.x2, destRect.y2}; // Bottom right
+ rotatedDestCoords[1] = CPoint{destRect.x1, destRect.y2}; // Bottom left
+ rotatedDestCoords[2] = CPoint{destRect.x1, destRect.y1}; // Top left
+ rotatedDestCoords[3] = CPoint{destRect.x2, destRect.y1}; // Top right
+ break;
+ }
+ case 270:
+ {
+ rotatedDestCoords[0] = CPoint{destRect.x2, destRect.y1}; // Top right
+ rotatedDestCoords[1] = CPoint{destRect.x2, destRect.y2}; // Bottom right
+ rotatedDestCoords[2] = CPoint{destRect.x1, destRect.y2}; // Bottom left
+ rotatedDestCoords[3] = CPoint{destRect.x1, destRect.y1}; // Top left
+ break;
+ }
+ default:
+ break;
+ }
+
+ return rotatedDestCoords;
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderUtils.h b/xbmc/cores/RetroPlayer/rendering/RenderUtils.h
new file mode 100644
index 0000000..0976c3e
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderUtils.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "utils/Geometry.h"
+
+#include <array>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderUtils
+{
+public:
+ static void CalculateStretchMode(STRETCHMODE stretchMode,
+ unsigned int rotationDegCCW,
+ unsigned int sourceWidth,
+ unsigned int sourceHeight,
+ float screenWidth,
+ float screenHeight,
+ float& pixelRatio,
+ float& zoomAmount);
+
+ static void CalcNormalRenderRect(const CRect& viewRect,
+ float outputFrameRatio,
+ float zoomAmount,
+ CRect& destRect);
+
+ static void ClipRect(const CRect& viewRect, CRect& sourceRect, CRect& destRect);
+
+ static std::array<CPoint, 4> ReorderDrawPoints(const CRect& destRect,
+ unsigned int orientationDegCCW);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.cpp b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.cpp
new file mode 100644
index 0000000..53ee3f0
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderVideoSettings.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+#define VIDEO_FILTER_NEAREST "nearest"
+#define VIDEO_FILTER_LINEAR "linear"
+
+void CRenderVideoSettings::Reset()
+{
+ m_scalingMethod = SCALINGMETHOD::AUTO;
+ m_stretchMode = STRETCHMODE::Normal;
+ m_rotationDegCCW = 0;
+ m_pixelPath.clear();
+}
+
+bool CRenderVideoSettings::operator==(const CRenderVideoSettings& rhs) const
+{
+ return m_scalingMethod == rhs.m_scalingMethod && m_stretchMode == rhs.m_stretchMode &&
+ m_rotationDegCCW == rhs.m_rotationDegCCW && m_pixelPath == rhs.m_pixelPath;
+}
+
+bool CRenderVideoSettings::operator<(const CRenderVideoSettings& rhs) const
+{
+ if (m_scalingMethod < rhs.m_scalingMethod)
+ return true;
+ if (m_scalingMethod > rhs.m_scalingMethod)
+ return false;
+
+ if (m_stretchMode < rhs.m_stretchMode)
+ return true;
+ if (m_stretchMode > rhs.m_stretchMode)
+ return false;
+
+ if (m_rotationDegCCW < rhs.m_rotationDegCCW)
+ return true;
+ if (m_rotationDegCCW > rhs.m_rotationDegCCW)
+ return false;
+
+ if (m_pixelPath < rhs.m_pixelPath)
+ return true;
+ if (m_pixelPath > rhs.m_pixelPath)
+ return false;
+
+ return false;
+}
+
+std::string CRenderVideoSettings::GetVideoFilter() const
+{
+ switch (m_scalingMethod)
+ {
+ case SCALINGMETHOD::NEAREST:
+ return VIDEO_FILTER_NEAREST;
+ case SCALINGMETHOD::LINEAR:
+ return VIDEO_FILTER_LINEAR;
+ default:
+ break;
+ }
+
+ return "";
+}
+
+void CRenderVideoSettings::SetVideoFilter(const std::string& videoFilter)
+{
+ if (videoFilter == VIDEO_FILTER_NEAREST)
+ {
+ m_scalingMethod = SCALINGMETHOD::NEAREST;
+ }
+ else if (videoFilter == VIDEO_FILTER_LINEAR)
+ {
+ m_scalingMethod = SCALINGMETHOD::LINEAR;
+ }
+ else
+ {
+ m_scalingMethod = SCALINGMETHOD::AUTO;
+ }
+}
+void CRenderVideoSettings::ResetPixels()
+{
+ m_pixelPath.clear();
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h
new file mode 100644
index 0000000..1d14270
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Video settings provided by the rendering system
+ */
+class CRenderVideoSettings
+{
+public:
+ CRenderVideoSettings() { Reset(); }
+
+ void Reset();
+
+ bool operator==(const CRenderVideoSettings& rhs) const;
+ bool operator!=(const CRenderVideoSettings& rhs) const { return !(*this == rhs); }
+ bool operator<(const CRenderVideoSettings& rhs) const;
+ bool operator>(const CRenderVideoSettings& rhs) const { return !(*this == rhs || *this < rhs); }
+
+ /*!
+ * \brief Get a string representation of the video filter parameters
+ */
+ std::string GetVideoFilter() const;
+ void SetVideoFilter(const std::string& videoFilter);
+
+ SCALINGMETHOD GetScalingMethod() const { return m_scalingMethod; }
+ void SetScalingMethod(SCALINGMETHOD method) { m_scalingMethod = method; }
+
+ STRETCHMODE GetRenderStretchMode() const { return m_stretchMode; }
+ void SetRenderStretchMode(STRETCHMODE mode) { m_stretchMode = mode; }
+
+ unsigned int GetRenderRotation() const { return m_rotationDegCCW; }
+ void SetRenderRotation(unsigned int rotationDegCCW) { m_rotationDegCCW = rotationDegCCW; }
+
+ std::string GetPixels() const { return m_pixelPath; }
+ void SetPixels(const std::string& pixelPath) { m_pixelPath = pixelPath; }
+ void ResetPixels();
+
+private:
+ SCALINGMETHOD m_scalingMethod;
+ STRETCHMODE m_stretchMode;
+ unsigned int m_rotationDegCCW;
+ std::string m_pixelPath;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt
new file mode 100644
index 0000000..6f9d908
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt
@@ -0,0 +1,26 @@
+set(SOURCES RPBaseRenderer.cpp
+ RPRendererGuiTexture.cpp)
+set(HEADERS RPBaseRenderer.h
+ RPRendererGuiTexture.h)
+
+if(CORE_SYSTEM_NAME STREQUAL windows)
+ list(APPEND SOURCES RPWinRenderer.cpp)
+ list(APPEND HEADERS RPWinRenderer.h)
+endif()
+
+if(OPENGL_FOUND OR OPENGLES_FOUND)
+ list(APPEND SOURCES RPRendererOpenGLES.cpp)
+ list(APPEND HEADERS RPRendererOpenGLES.h)
+endif()
+
+if(OPENGL_FOUND)
+ list(APPEND SOURCES RPRendererOpenGL.cpp)
+ list(APPEND HEADERS RPRendererOpenGL.h)
+endif()
+
+if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND EGL_FOUND)
+ list(APPEND SOURCES RPRendererDMA.cpp)
+ list(APPEND HEADERS RPRendererDMA.h)
+endif()
+
+core_add_library(rp-videorenderers)
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp
new file mode 100644
index 0000000..9823475
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPBaseRenderer.h"
+
+#include "cores/RetroPlayer/buffers/IRenderBuffer.h"
+#include "cores/RetroPlayer/buffers/IRenderBufferPool.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderUtils.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+// Consider renderer visible until this many frames have passed without rendering
+#define VISIBLE_DURATION_FRAME_COUNT 1
+
+CRPBaseRenderer::CRPBaseRenderer(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : m_context(context), m_bufferPool(std::move(bufferPool)), m_renderSettings(renderSettings)
+{
+ m_bufferPool->RegisterRenderer(this);
+}
+
+CRPBaseRenderer::~CRPBaseRenderer()
+{
+ SetBuffer(nullptr);
+
+ m_bufferPool->UnregisterRenderer(this);
+}
+
+bool CRPBaseRenderer::IsCompatible(const CRenderVideoSettings& settings) const
+{
+ return m_bufferPool->IsCompatible(settings);
+}
+
+bool CRPBaseRenderer::Configure(AVPixelFormat format)
+{
+ m_format = format;
+
+ if (!m_bufferPool->IsConfigured())
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Configuring buffer pool");
+
+ if (!m_bufferPool->Configure(format))
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[RENDER]: Failed to configure buffer pool");
+ return false;
+ }
+ }
+
+ if (ConfigureInternal())
+ m_bConfigured = true;
+
+ return m_bConfigured;
+}
+
+void CRPBaseRenderer::FrameMove()
+{
+ m_renderFrameCount++;
+}
+
+bool CRPBaseRenderer::IsVisible() const
+{
+ return m_renderFrameCount <= m_lastRender + VISIBLE_DURATION_FRAME_COUNT;
+}
+
+IRenderBuffer* CRPBaseRenderer::GetRenderBuffer() const
+{
+ if (m_renderBuffer != nullptr)
+ {
+ m_renderBuffer->Acquire();
+ return m_renderBuffer;
+ }
+
+ return nullptr;
+}
+
+void CRPBaseRenderer::SetBuffer(IRenderBuffer* buffer)
+{
+ if (m_renderBuffer != buffer)
+ {
+ if (m_renderBuffer != nullptr)
+ m_renderBuffer->Release();
+
+ m_renderBuffer = buffer;
+
+ if (m_renderBuffer != nullptr)
+ m_renderBuffer->Acquire();
+ }
+}
+
+void CRPBaseRenderer::RenderFrame(bool clear, uint8_t alpha)
+{
+ m_lastRender = m_renderFrameCount;
+
+ if (!m_bConfigured || m_renderBuffer == nullptr)
+ return;
+
+ ManageRenderArea(*m_renderBuffer);
+
+ RenderInternal(clear, alpha);
+ PostRender();
+
+ m_renderBuffer->SetRendered(true);
+}
+
+void CRPBaseRenderer::Flush()
+{
+ SetBuffer(nullptr);
+ FlushInternal();
+}
+
+void CRPBaseRenderer::SetScalingMethod(SCALINGMETHOD method)
+{
+ m_renderSettings.VideoSettings().SetScalingMethod(method);
+}
+
+void CRPBaseRenderer::SetStretchMode(STRETCHMODE stretchMode)
+{
+ m_renderSettings.VideoSettings().SetRenderStretchMode(stretchMode);
+}
+
+void CRPBaseRenderer::SetRenderRotation(unsigned int rotationDegCCW)
+{
+ m_renderSettings.VideoSettings().SetRenderRotation(rotationDegCCW);
+}
+
+void CRPBaseRenderer::SetPixels(const std::string& pixelPath)
+{
+ m_renderSettings.VideoSettings().SetPixels(pixelPath);
+}
+
+void CRPBaseRenderer::ManageRenderArea(const IRenderBuffer& renderBuffer)
+{
+ // Get texture parameters
+ const unsigned int sourceWidth = renderBuffer.GetWidth();
+ const unsigned int sourceHeight = renderBuffer.GetHeight();
+ const unsigned int sourceRotationDegCCW = renderBuffer.GetRotation();
+ const float sourceAspectRatio =
+ static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight);
+
+ const SCALINGMETHOD scaleMode = m_renderSettings.VideoSettings().GetScalingMethod();
+ const STRETCHMODE stretchMode = m_renderSettings.VideoSettings().GetRenderStretchMode();
+ const unsigned int rotationDegCCW =
+ (sourceRotationDegCCW + m_renderSettings.VideoSettings().GetRenderRotation()) % 360;
+
+ // Get screen parameters
+ float screenWidth;
+ float screenHeight;
+ //! @Todo screenPixelRatio unused - Possibly due to display integer scaling according to Garbear
+ float screenPixelRatio;
+
+ if (scaleMode == SCALINGMETHOD::NEAREST && stretchMode == STRETCHMODE::Original &&
+ m_context.DisplayHardwareScalingEnabled())
+ {
+ screenWidth = sourceWidth;
+ screenHeight = sourceHeight;
+ screenPixelRatio = 1.0;
+ }
+ else
+ {
+ GetScreenDimensions(screenWidth, screenHeight, screenPixelRatio);
+ }
+
+ // Entire target rendering area for the video (including black bars)
+ const CRect viewRect = m_context.GetViewWindow();
+
+ // Calculate pixel ratio and zoom amount
+ float pixelRatio = 1.0f;
+ float zoomAmount = 1.0f;
+ CRenderUtils::CalculateStretchMode(stretchMode, rotationDegCCW, sourceWidth, sourceHeight,
+ screenWidth, screenHeight, pixelRatio, zoomAmount);
+
+ // Calculate destination dimensions
+ CRect destRect;
+ CRenderUtils::CalcNormalRenderRect(viewRect, sourceAspectRatio * pixelRatio, zoomAmount,
+ destRect);
+
+ m_sourceRect.x1 = 0.0f;
+ m_sourceRect.y1 = 0.0f;
+ m_sourceRect.x2 = static_cast<float>(sourceWidth);
+ m_sourceRect.y2 = static_cast<float>(sourceHeight);
+
+ // Clip as needed
+ if (!(m_context.IsFullScreenVideo() || m_context.IsCalibrating()))
+ CRenderUtils::ClipRect(viewRect, m_sourceRect, destRect);
+
+ // Adapt the drawing rect points if we have to rotate
+ m_rotatedDestCoords = CRenderUtils::ReorderDrawPoints(destRect, rotationDegCCW);
+}
+
+void CRPBaseRenderer::MarkDirty()
+{
+ // CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(m_dimensions); //! @todo
+}
+
+void CRPBaseRenderer::PreRender(bool clear)
+{
+ if (!m_bConfigured)
+ return;
+
+ // Clear screen
+ if (clear)
+ m_context.Clear(m_context.UseLimitedColor() ? UTILS::COLOR::LIMITED_BLACK
+ : UTILS::COLOR::BLACK);
+}
+
+void CRPBaseRenderer::PostRender()
+{
+ m_context.ApplyStateBlock();
+}
+
+void CRPBaseRenderer::GetScreenDimensions(float& screenWidth,
+ float& screenHeight,
+ float& screenPixelRatio)
+{
+ // Get our calibrated full screen resolution
+ RESOLUTION_INFO info = m_context.GetResInfo();
+
+ screenWidth = static_cast<float>(info.Overscan.right - info.Overscan.left);
+ screenHeight = static_cast<float>(info.Overscan.bottom - info.Overscan.top);
+
+ // Splitres scaling factor
+ float xscale = static_cast<float>(info.iScreenWidth) / static_cast<float>(info.iWidth);
+ float yscale = static_cast<float>(info.iScreenHeight) / static_cast<float>(info.iHeight);
+
+ screenWidth *= xscale;
+ screenHeight *= yscale;
+
+ screenPixelRatio = info.fPixelRatio;
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h
new file mode 100644
index 0000000..6e4d030
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/rendering/RenderSettings.h"
+#include "utils/Geometry.h"
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+#include <array>
+#include <memory>
+#include <stdint.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+class IRenderBuffer;
+class IRenderBufferPool;
+
+class CRPBaseRenderer
+{
+public:
+ CRPBaseRenderer(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ virtual ~CRPBaseRenderer();
+
+ /*!
+ * \brief Get the buffer pool used by this renderer
+ */
+ IRenderBufferPool* GetBufferPool() { return m_bufferPool.get(); }
+
+ // Player functions
+ bool Configure(AVPixelFormat format);
+ void FrameMove();
+ /*!
+ * \brief Performs whatever necessary before rendering the frame
+ */
+ void PreRender(bool clear);
+ void SetBuffer(IRenderBuffer* buffer);
+ void RenderFrame(bool clear, uint8_t alpha);
+
+ // Feature support
+ virtual bool Supports(RENDERFEATURE feature) const = 0;
+ bool IsCompatible(const CRenderVideoSettings& settings) const;
+ virtual SCALINGMETHOD GetDefaultScalingMethod() const = 0;
+
+ // Public renderer interface
+ virtual void Flush();
+
+ // Get render settings
+ const CRenderSettings& GetRenderSettings() const { return m_renderSettings; }
+
+ // Set render settings
+ void SetScalingMethod(SCALINGMETHOD method);
+ void SetStretchMode(STRETCHMODE stretchMode);
+ void SetRenderRotation(unsigned int rotationDegCCW);
+ void SetPixels(const std::string& pixelPath);
+
+ // Rendering properties
+ bool IsVisible() const;
+ IRenderBuffer* GetRenderBuffer() const;
+
+protected:
+ // Protected renderer interface
+ virtual bool ConfigureInternal() { return true; }
+ virtual void RenderInternal(bool clear, uint8_t alpha) = 0;
+ virtual void FlushInternal() {}
+
+ // Construction parameters
+ CRenderContext& m_context;
+ std::shared_ptr<IRenderBufferPool> m_bufferPool;
+
+ // Stream properties
+ bool m_bConfigured = false;
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+
+ // Rendering properties
+ CRenderSettings m_renderSettings;
+ IRenderBuffer* m_renderBuffer = nullptr;
+
+ // Geometry properties
+ CRect m_sourceRect;
+ std::array<CPoint, 4> m_rotatedDestCoords{};
+
+private:
+ /*!
+ * \brief Calculate driven dimensions
+ */
+ virtual void ManageRenderArea(const IRenderBuffer& renderBuffer);
+
+ /*!
+ * \brief Performs whatever necessary after a frame has been rendered
+ */
+ void PostRender();
+
+ void MarkDirty();
+
+ // Utility functions
+ void GetScreenDimensions(float& screenWidth, float& screenHeight, float& screenPixelRatio);
+
+ uint64_t m_renderFrameCount = 0;
+ uint64_t m_lastRender = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.cpp
new file mode 100644
index 0000000..97a9eaa
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPRendererDMA.h"
+
+#include "cores/RetroPlayer/buffers/RenderBufferDMA.h"
+#include "cores/RetroPlayer/buffers/RenderBufferPoolDMA.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "utils/BufferObjectFactory.h"
+#include "utils/GLUtils.h"
+
+#include <cassert>
+#include <cstddef>
+
+using namespace KODI;
+using namespace RETRO;
+
+std::string CRendererFactoryDMA::RenderSystemName() const
+{
+ return "DMA";
+}
+
+CRPBaseRenderer* CRendererFactoryDMA::CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+{
+ return new CRPRendererDMA(settings, context, std::move(bufferPool));
+}
+
+RenderBufferPoolVector CRendererFactoryDMA::CreateBufferPools(CRenderContext& context)
+{
+ if (!CBufferObjectFactory::CreateBufferObject(false))
+ return {};
+
+ return {std::make_shared<CRenderBufferPoolDMA>(context)};
+}
+
+CRPRendererDMA::CRPRendererDMA(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : CRPRendererOpenGLES(renderSettings, context, std::move(bufferPool))
+{
+}
+
+void CRPRendererDMA::Render(uint8_t alpha)
+{
+ auto renderBuffer = static_cast<CRenderBufferDMA*>(m_renderBuffer);
+ assert(renderBuffer != nullptr);
+
+ CRect rect = m_sourceRect;
+
+ rect.x1 /= renderBuffer->GetWidth();
+ rect.x2 /= renderBuffer->GetWidth();
+ rect.y1 /= renderBuffer->GetHeight();
+ rect.y2 /= renderBuffer->GetHeight();
+
+ const uint32_t color = (alpha << 24) | 0xFFFFFF;
+
+ glBindTexture(m_textureTarget, renderBuffer->TextureID());
+
+ GLint filter = GL_NEAREST;
+ if (GetRenderSettings().VideoSettings().GetScalingMethod() == SCALINGMETHOD::LINEAR)
+ filter = GL_LINEAR;
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE);
+
+ GLubyte colour[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ GLint vertLoc = m_context.GUIShaderGetPos();
+ GLint loc = m_context.GUIShaderGetCoord0();
+ GLint uniColLoc = m_context.GUIShaderGetUniCol();
+
+ // Setup color values
+ colour[0] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::R, color);
+ colour[1] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::G, color);
+ colour[2] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::B, color);
+ colour[3] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::A, color);
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ // Setup vertex position values
+ vertex[i].x = m_rotatedDestCoords[i].x;
+ vertex[i].y = m_rotatedDestCoords[i].y;
+ vertex[i].z = 0.0f;
+ }
+
+ // Setup texture coordinates
+ vertex[0].u1 = vertex[3].u1 = rect.x1;
+ vertex[0].v1 = vertex[1].v1 = rect.y1;
+ vertex[1].u1 = vertex[2].u1 = rect.x2;
+ vertex[2].v1 = vertex[3].v1 = rect.y2;
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLuint*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLuint*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mainIndexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW);
+
+ glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f),
+ (colour[3] / 255.0f));
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ m_context.DisableGUIShader();
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h
new file mode 100644
index 0000000..38d7bae
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RPRendererOpenGLES.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRendererFactoryDMA : public IRendererFactory
+{
+public:
+ ~CRendererFactoryDMA() override = default;
+
+ // implementation of IRendererFactory
+ std::string RenderSystemName() const override;
+ CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) override;
+ RenderBufferPoolVector CreateBufferPools(CRenderContext& context) override;
+};
+
+/**
+ * @brief Special CRPBaseRenderer implementation to handle Direct Memory
+ * Access (DMA) buffer types. For specific use with
+ * CRenderBufferPoolDMA and CRenderBufferDMA. A windowing system
+ * must register use of this renderer and register at least one
+ * CBufferObject types.
+ *
+ */
+class CRPRendererDMA : public CRPRendererOpenGLES
+{
+public:
+ CRPRendererDMA(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ ~CRPRendererDMA() override = default;
+
+protected:
+ // implementation of CRPRendererOpenGLES
+ void Render(uint8_t alpha) override;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.cpp
new file mode 100644
index 0000000..f38e240
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPRendererGuiTexture.h"
+
+#include "cores/RetroPlayer/buffers/video/RenderBufferGuiTexture.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+
+#if defined(HAS_DX)
+#include "guilib/GUIShaderDX.h"
+#include "guilib/TextureDX.h"
+
+#include <DirectXMath.h>
+using namespace DirectX;
+#endif
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+#include "utils/GLUtils.h"
+
+#include "system_gl.h"
+#endif
+
+#include <cstddef>
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CRendererFactoryGuiTexture ----------------------------------------------
+
+std::string CRendererFactoryGuiTexture::RenderSystemName() const
+{
+ return "GUITexture";
+}
+
+CRPBaseRenderer* CRendererFactoryGuiTexture::CreateRenderer(
+ const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+{
+ return new CRPRendererGuiTexture(settings, context, std::move(bufferPool));
+}
+
+RenderBufferPoolVector CRendererFactoryGuiTexture::CreateBufferPools(CRenderContext& context)
+{
+ return
+ {
+#if !defined(HAS_DX)
+ std::make_shared<CRenderBufferPoolGuiTexture>(SCALINGMETHOD::NEAREST),
+#endif
+ std::make_shared<CRenderBufferPoolGuiTexture>(SCALINGMETHOD::LINEAR),
+ };
+}
+
+// --- CRenderBufferPoolGuiTexture -----------------------------------------------
+
+CRenderBufferPoolGuiTexture::CRenderBufferPoolGuiTexture(SCALINGMETHOD scalingMethod)
+ : m_scalingMethod(scalingMethod)
+{
+}
+
+bool CRenderBufferPoolGuiTexture::IsCompatible(const CRenderVideoSettings& renderSettings) const
+{
+ return renderSettings.GetScalingMethod() == m_scalingMethod;
+}
+
+IRenderBuffer* CRenderBufferPoolGuiTexture::CreateRenderBuffer(void* header /* = nullptr */)
+{
+ return new CRenderBufferGuiTexture(m_scalingMethod);
+}
+
+// --- CRPRendererGuiTexture -----------------------------------------------------
+
+CRPRendererGuiTexture::CRPRendererGuiTexture(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : CRPBaseRenderer(renderSettings, context, std::move(bufferPool))
+{
+}
+
+bool CRPRendererGuiTexture::Supports(RENDERFEATURE feature) const
+{
+ return feature == RENDERFEATURE::STRETCH || feature == RENDERFEATURE::ZOOM ||
+ feature == RENDERFEATURE::PIXEL_RATIO || feature == RENDERFEATURE::ROTATION;
+}
+
+void CRPRendererGuiTexture::RenderInternal(bool clear, uint8_t alpha)
+{
+ CRenderBufferGuiTexture* renderBuffer = static_cast<CRenderBufferGuiTexture*>(m_renderBuffer);
+
+ CRect rect = m_sourceRect;
+
+ rect.x1 /= renderBuffer->GetWidth();
+ rect.x2 /= renderBuffer->GetWidth();
+ rect.y1 /= renderBuffer->GetHeight();
+ rect.y2 /= renderBuffer->GetHeight();
+
+ float u1 = rect.x1;
+ float u2 = rect.x2;
+ float v1 = rect.y1;
+ float v2 = rect.y2;
+
+ const uint32_t color = (alpha << 24) | 0xFFFFFF;
+
+#if defined(HAS_DX)
+
+ Vertex vertex[5];
+ for (int i = 0; i < 4; i++)
+ {
+ vertex[i].pos = XMFLOAT3(m_rotatedDestCoords[i].x, m_rotatedDestCoords[i].y, 0);
+ CD3DHelper::XMStoreColor(&vertex[i].color, color);
+ vertex[i].texCoord = XMFLOAT2(0.0f, 0.0f);
+ vertex[i].texCoord2 = XMFLOAT2(0.0f, 0.0f);
+ }
+
+ (void)u1;
+ (void)v1;
+ vertex[1].texCoord.x = vertex[2].texCoord.x = u2;
+ vertex[2].texCoord.y = vertex[3].texCoord.y = v2;
+
+ vertex[4] = vertex[0]; // Not used when renderBuffer != nullptr
+
+ CGUIShaderDX* pGUIShader = m_context.GetGUIShader();
+ if (pGUIShader != nullptr)
+ {
+ pGUIShader->Begin(SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ // Set state to render the image
+ auto dxTexture = static_cast<CDXTexture*>(renderBuffer->GetTexture());
+ ID3D11ShaderResourceView* shaderRes = dxTexture->GetShaderResource();
+ pGUIShader->SetShaderViews(1, &shaderRes);
+ pGUIShader->DrawQuad(vertex[0], vertex[1], vertex[2], vertex[3]);
+ }
+
+#elif defined(HAS_GL)
+
+ renderBuffer->BindToUnit(0);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE);
+
+ GLubyte colour[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ // Setup vertex position values
+ vertex[0].x = m_rotatedDestCoords[0].x;
+ vertex[0].y = m_rotatedDestCoords[0].y;
+ vertex[0].z = 0;
+ vertex[0].u1 = u1;
+ vertex[0].v1 = v1;
+
+ vertex[1].x = m_rotatedDestCoords[1].x;
+ vertex[1].y = m_rotatedDestCoords[1].y;
+ vertex[1].z = 0;
+ vertex[1].u1 = u2;
+ vertex[1].v1 = v1;
+
+ vertex[2].x = m_rotatedDestCoords[2].x;
+ vertex[2].y = m_rotatedDestCoords[2].y;
+ vertex[2].z = 0;
+ vertex[2].u1 = u2;
+ vertex[2].v1 = v2;
+
+ vertex[3].x = m_rotatedDestCoords[3].x;
+ vertex[3].y = m_rotatedDestCoords[3].y;
+ vertex[3].z = 0;
+ vertex[3].u1 = u1;
+ vertex[3].v1 = v2;
+
+ GLint posLoc = m_context.GUIShaderGetPos();
+ GLint tex0Loc = m_context.GUIShaderGetCoord0();
+ GLint uniColLoc = m_context.GUIShaderGetUniCol();
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ // Setup Colour values
+ colour[0] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::R, color);
+ colour[1] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::G, color);
+ colour[2] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::B, color);
+ colour[3] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::A, color);
+
+ if (m_context.UseLimitedColor())
+ {
+ colour[0] = (235 - 16) * colour[0] / 255 + 16;
+ colour[1] = (235 - 16) * colour[1] / 255 + 16;
+ colour[2] = (235 - 16) * colour[2] / 255 + 16;
+ }
+
+ glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f),
+ (colour[3] / 255.0f));
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ m_context.DisableGUIShader();
+
+#elif defined(HAS_GLES)
+
+ renderBuffer->BindToUnit(0);
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND); // Turn blending On
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE);
+
+ GLubyte col[4];
+ GLfloat ver[4][3];
+ GLfloat tex[4][2];
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip
+
+ GLint posLoc = m_context.GUIShaderGetPos();
+ GLint tex0Loc = m_context.GUIShaderGetCoord0();
+ GLint uniColLoc = m_context.GUIShaderGetUniCol();
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, 0, ver);
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ // Setup color values
+ col[0] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::R, color);
+ col[1] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::G, color);
+ col[2] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::B, color);
+ col[3] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::A, color);
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ // Setup vertex position values
+ ver[i][0] = m_rotatedDestCoords[i].x;
+ ver[i][1] = m_rotatedDestCoords[i].y;
+ ver[i][2] = 0.0f;
+ }
+
+ // Setup texture coordinates
+ tex[0][0] = tex[3][0] = u1;
+ tex[0][1] = tex[1][1] = v1;
+ tex[1][0] = tex[2][0] = u2;
+ tex[2][1] = tex[3][1] = v2;
+
+ glUniform4f(uniColLoc, (col[0] / 255.0f), (col[1] / 255.0f), (col[2] / 255.0f),
+ (col[3] / 255.0f));
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ m_context.DisableGUIShader();
+
+#endif
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.h
new file mode 100644
index 0000000..8441a67
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererGuiTexture.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RPBaseRenderer.h"
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/buffers/BaseRenderBufferPool.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRendererFactoryGuiTexture : public IRendererFactory
+{
+public:
+ ~CRendererFactoryGuiTexture() override = default;
+
+ // implementation of IRendererFactory
+ std::string RenderSystemName() const override;
+ CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) override;
+ RenderBufferPoolVector CreateBufferPools(CRenderContext& context) override;
+};
+
+class CRenderBufferPoolGuiTexture : public CBaseRenderBufferPool
+{
+public:
+ CRenderBufferPoolGuiTexture(SCALINGMETHOD scalingMethod);
+ ~CRenderBufferPoolGuiTexture() override = default;
+
+ // implementation of IRenderBufferPool via CBaseRenderBufferPool
+ bool IsCompatible(const CRenderVideoSettings& renderSettings) const override;
+
+ // implementation of CBaseRenderBufferPool
+ IRenderBuffer* CreateRenderBuffer(void* header = nullptr) override;
+
+private:
+ SCALINGMETHOD m_scalingMethod;
+};
+
+class CRPRendererGuiTexture : public CRPBaseRenderer
+{
+public:
+ CRPRendererGuiTexture(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ ~CRPRendererGuiTexture() override = default;
+
+ // public implementation of CRPBaseRenderer
+ bool Supports(RENDERFEATURE feature) const override;
+ SCALINGMETHOD GetDefaultScalingMethod() const override { return SCALINGMETHOD::NEAREST; }
+
+protected:
+ // protected implementation of CRPBaseRenderer
+ void RenderInternal(bool clear, uint8_t alpha) override;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp
new file mode 100644
index 0000000..49ff7f8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPRendererOpenGL.h"
+
+#include "cores/RetroPlayer/buffers/RenderBufferOpenGL.h"
+#include "cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <cstddef>
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CRendererFactoryOpenGL --------------------------------------------------
+
+std::string CRendererFactoryOpenGL::RenderSystemName() const
+{
+ return "OpenGL";
+}
+
+CRPBaseRenderer* CRendererFactoryOpenGL::CreateRenderer(
+ const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+{
+ return new CRPRendererOpenGL(settings, context, std::move(bufferPool));
+}
+
+RenderBufferPoolVector CRendererFactoryOpenGL::CreateBufferPools(CRenderContext& context)
+{
+ return {std::make_shared<CRenderBufferPoolOpenGL>()};
+}
+
+// --- CRPRendererOpenGL -------------------------------------------------------
+
+CRPRendererOpenGL::CRPRendererOpenGL(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : CRPBaseRenderer(renderSettings, context, std::move(bufferPool))
+{
+ // Initialize CRPRendererOpenGL
+ m_clearColour = m_context.UseLimitedColor() ? (16.0f / 0xff) : 0.0f;
+
+ // Set up main screen VAO/VBOs
+ glGenVertexArrays(1, &m_mainVAO);
+ glBindVertexArray(m_mainVAO);
+
+ glGenBuffers(1, &m_mainVertexVBO);
+ glGenBuffers(1, &m_mainIndexVBO);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE);
+ GLint vertLoc = m_context.GUIShaderGetPos();
+ GLint loc = m_context.GUIShaderGetCoord0();
+ m_context.DisableGUIShader();
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mainIndexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO);
+ glEnableVertexAttribArray(vertLoc);
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glEnableVertexAttribArray(loc);
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ // Set up black bars VAO/VBO
+ glGenVertexArrays(1, &m_blackbarsVAO);
+ glBindVertexArray(m_blackbarsVAO);
+
+ glGenBuffers(1, &m_blackbarsVertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, m_blackbarsVertexVBO);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::DEFAULT);
+ GLint posLoc = m_context.GUIShaderGetPos();
+ m_context.DisableGUIShader();
+
+ glEnableVertexAttribArray(posLoc);
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Svertex), 0);
+
+ // Unbind everything just to be safe
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+CRPRendererOpenGL::~CRPRendererOpenGL()
+{
+ glDeleteBuffers(1, &m_mainIndexVBO);
+ glDeleteBuffers(1, &m_mainVertexVBO);
+ glDeleteBuffers(1, &m_blackbarsVertexVBO);
+
+ glDeleteVertexArrays(1, &m_mainVAO);
+ glDeleteVertexArrays(1, &m_blackbarsVAO);
+}
+
+void CRPRendererOpenGL::RenderInternal(bool clear, uint8_t alpha)
+{
+ if (clear)
+ {
+ if (alpha == 255)
+ DrawBlackBars();
+ else
+ ClearBackBuffer();
+ }
+
+ if (alpha < 255)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+
+ Render(alpha);
+
+ glEnable(GL_BLEND);
+ glFlush();
+}
+
+void CRPRendererOpenGL::FlushInternal()
+{
+ if (!m_bConfigured)
+ return;
+
+ glFinish();
+}
+
+bool CRPRendererOpenGL::Supports(RENDERFEATURE feature) const
+{
+ return feature == RENDERFEATURE::STRETCH || feature == RENDERFEATURE::ZOOM ||
+ feature == RENDERFEATURE::PIXEL_RATIO || feature == RENDERFEATURE::ROTATION;
+}
+
+bool CRPRendererOpenGL::SupportsScalingMethod(SCALINGMETHOD method)
+{
+ return method == SCALINGMETHOD::NEAREST || method == SCALINGMETHOD::LINEAR;
+}
+
+void CRPRendererOpenGL::ClearBackBuffer()
+{
+ glClearColor(m_clearColour, m_clearColour, m_clearColour, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+void CRPRendererOpenGL::DrawBlackBars()
+{
+ glDisable(GL_BLEND);
+
+ Svertex vertices[24];
+ GLubyte count = 0;
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::DEFAULT);
+ GLint uniCol = m_context.GUIShaderGetUniCol();
+
+ glUniform4f(uniCol, m_clearColour / 255.0f, m_clearColour / 255.0f, m_clearColour / 255.0f, 1.0f);
+
+ // top quad
+ if (m_rotatedDestCoords[0].y > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = 0.0;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = 0;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // bottom quad
+ if (m_rotatedDestCoords[2].y < m_context.GetScreenHeight())
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_rotatedDestCoords[2].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_context.GetScreenHeight();
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_context.GetScreenHeight();
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // left quad
+ if (m_rotatedDestCoords[0].x > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_rotatedDestCoords[0].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_rotatedDestCoords[0].x;
+ vertices[quad + 1].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_rotatedDestCoords[3].x;
+ vertices[quad + 2].y = m_rotatedDestCoords[3].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_rotatedDestCoords[3].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // right quad
+ if (m_rotatedDestCoords[2].x < m_context.GetScreenWidth())
+ {
+ GLubyte quad = count;
+ vertices[quad].x = m_rotatedDestCoords[1].x;
+ vertices[quad].y = m_rotatedDestCoords[1].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = m_rotatedDestCoords[1].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = m_rotatedDestCoords[1].x;
+ vertices[quad + 4].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ glBindVertexArray(m_blackbarsVAO);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_blackbarsVertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Svertex) * count, &vertices[0], GL_STATIC_DRAW);
+
+ glDrawArrays(GL_TRIANGLES, 0, count);
+
+ // Unbind VAO/VBO just to be safe
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ m_context.DisableGUIShader();
+}
+
+void CRPRendererOpenGL::Render(uint8_t alpha)
+{
+ CRenderBufferOpenGL* renderBuffer = static_cast<CRenderBufferOpenGL*>(m_renderBuffer);
+
+ if (renderBuffer == nullptr)
+ return;
+
+ CRect rect = m_sourceRect;
+
+ rect.x1 /= renderBuffer->GetWidth();
+ rect.x2 /= renderBuffer->GetWidth();
+ rect.y1 /= renderBuffer->GetHeight();
+ rect.y2 /= renderBuffer->GetHeight();
+
+ const uint32_t color = (alpha << 24) | 0xFFFFFF;
+
+ glBindTexture(m_textureTarget, renderBuffer->TextureID());
+
+ GLint filter = GL_NEAREST;
+ if (GetRenderSettings().VideoSettings().GetScalingMethod() == SCALINGMETHOD::LINEAR)
+ filter = GL_LINEAR;
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE);
+
+ GLubyte colour[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip
+ PackedVertex vertex[4];
+
+ GLint uniColLoc = m_context.GUIShaderGetUniCol();
+
+ // Setup color values
+ colour[0] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::R, color);
+ colour[1] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::G, color);
+ colour[2] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::B, color);
+ colour[3] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::A, color);
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ // Setup vertex position values
+ vertex[i].x = m_rotatedDestCoords[i].x;
+ vertex[i].y = m_rotatedDestCoords[i].y;
+ vertex[i].z = 0.0f;
+ }
+
+ // Setup texture coordinates
+ vertex[0].u1 = vertex[3].u1 = rect.x1;
+ vertex[0].v1 = vertex[1].v1 = rect.y1;
+ vertex[1].u1 = vertex[2].u1 = rect.x2;
+ vertex[2].v1 = vertex[3].v1 = rect.y2;
+
+ glBindVertexArray(m_mainVAO);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW);
+
+ // No need to bind the index VBO, it's part of VAO state
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW);
+
+ glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f),
+ (colour[3] / 255.0f));
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ // Unbind VAO/VBO just to be safe
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ m_context.DisableGUIShader();
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h
new file mode 100644
index 0000000..be9e4f2
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RPBaseRenderer.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+
+class CRendererFactoryOpenGL : public IRendererFactory
+{
+public:
+ ~CRendererFactoryOpenGL() override = default;
+
+ // implementation of IRendererFactory
+ std::string RenderSystemName() const override;
+ CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) override;
+ RenderBufferPoolVector CreateBufferPools(CRenderContext& context) override;
+};
+
+class CRPRendererOpenGL : public CRPBaseRenderer
+{
+public:
+ CRPRendererOpenGL(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ ~CRPRendererOpenGL() override;
+
+ // implementation of CRPBaseRenderer
+ bool Supports(RENDERFEATURE feature) const override;
+ SCALINGMETHOD GetDefaultScalingMethod() const override { return SCALINGMETHOD::NEAREST; }
+
+ static bool SupportsScalingMethod(SCALINGMETHOD method);
+
+protected:
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ };
+ struct Svertex
+ {
+ float x;
+ float y;
+ float z;
+ };
+
+ // implementation of CRPBaseRenderer
+ void RenderInternal(bool clear, uint8_t alpha) override;
+ void FlushInternal() override;
+
+ /*!
+ * \brief Set the entire backbuffer to black
+ */
+ void ClearBackBuffer();
+
+ /*!
+ * \brief Draw black bars around the video quad
+ *
+ * This is more efficient than glClear() since it only sets pixels to
+ * black that aren't going to be overwritten by the game.
+ */
+ void DrawBlackBars();
+
+ virtual void Render(uint8_t alpha);
+
+ GLuint m_mainVAO;
+ GLuint m_mainVertexVBO;
+ GLuint m_mainIndexVBO;
+
+ GLuint m_blackbarsVAO;
+ GLuint m_blackbarsVertexVBO;
+
+ GLenum m_textureTarget = GL_TEXTURE_2D;
+ float m_clearColour = 0.0f;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.cpp
new file mode 100644
index 0000000..b8e4259
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPRendererOpenGLES.h"
+
+#include "cores/RetroPlayer/buffers/RenderBufferOpenGLES.h"
+#include "cores/RetroPlayer/buffers/RenderBufferPoolOpenGLES.h"
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <cstring>
+#include <stddef.h>
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CRendererFactoryOpenGLES ------------------------------------------------
+
+std::string CRendererFactoryOpenGLES::RenderSystemName() const
+{
+ return "OpenGLES";
+}
+
+CRPBaseRenderer* CRendererFactoryOpenGLES::CreateRenderer(
+ const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+{
+ return new CRPRendererOpenGLES(settings, context, std::move(bufferPool));
+}
+
+RenderBufferPoolVector CRendererFactoryOpenGLES::CreateBufferPools(CRenderContext& context)
+{
+ return {std::make_shared<CRenderBufferPoolOpenGLES>(context)};
+}
+
+// --- CRPRendererOpenGLES -----------------------------------------------------
+
+CRPRendererOpenGLES::CRPRendererOpenGLES(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : CRPBaseRenderer(renderSettings, context, std::move(bufferPool))
+{
+ glGenBuffers(1, &m_mainIndexVBO);
+ glGenBuffers(1, &m_mainVertexVBO);
+ glGenBuffers(1, &m_blackbarsVertexVBO);
+}
+
+CRPRendererOpenGLES::~CRPRendererOpenGLES()
+{
+ glDeleteBuffers(1, &m_mainIndexVBO);
+ glDeleteBuffers(1, &m_mainVertexVBO);
+ glDeleteBuffers(1, &m_blackbarsVertexVBO);
+}
+
+void CRPRendererOpenGLES::RenderInternal(bool clear, uint8_t alpha)
+{
+ if (clear)
+ {
+ if (alpha == 255)
+ DrawBlackBars();
+ else
+ ClearBackBuffer();
+ }
+
+ if (alpha < 255)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+
+ Render(alpha);
+
+ glEnable(GL_BLEND);
+ glFlush();
+}
+
+void CRPRendererOpenGLES::FlushInternal()
+{
+ if (!m_bConfigured)
+ return;
+
+ glFinish();
+}
+
+bool CRPRendererOpenGLES::Supports(RENDERFEATURE feature) const
+{
+ return feature == RENDERFEATURE::STRETCH || feature == RENDERFEATURE::ZOOM ||
+ feature == RENDERFEATURE::PIXEL_RATIO || feature == RENDERFEATURE::ROTATION;
+}
+
+bool CRPRendererOpenGLES::SupportsScalingMethod(SCALINGMETHOD method)
+{
+ return method == SCALINGMETHOD::NEAREST || method == SCALINGMETHOD::LINEAR;
+}
+
+void CRPRendererOpenGLES::ClearBackBuffer()
+{
+ glClearColor(m_clearColour, m_clearColour, m_clearColour, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+void CRPRendererOpenGLES::DrawBlackBars()
+{
+ glDisable(GL_BLEND);
+
+ struct Svertex
+ {
+ float x;
+ float y;
+ float z;
+ };
+ Svertex vertices[24];
+ GLubyte count = 0;
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::DEFAULT);
+ GLint posLoc = m_context.GUIShaderGetPos();
+ GLint uniCol = m_context.GUIShaderGetUniCol();
+
+ glUniform4f(uniCol, m_clearColour / 255.0f, m_clearColour / 255.0f, m_clearColour / 255.0f, 1.0f);
+
+ // top quad
+ if (m_rotatedDestCoords[0].y > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = 0.0;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = 0;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // bottom quad
+ if (m_rotatedDestCoords[2].y < m_context.GetScreenHeight())
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_rotatedDestCoords[2].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_context.GetScreenHeight();
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_context.GetScreenHeight();
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // left quad
+ if (m_rotatedDestCoords[0].x > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_rotatedDestCoords[0].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_rotatedDestCoords[0].x;
+ vertices[quad + 1].y = m_rotatedDestCoords[0].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_rotatedDestCoords[3].x;
+ vertices[quad + 2].y = m_rotatedDestCoords[3].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = 0;
+ vertices[quad + 4].y = m_rotatedDestCoords[3].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ // right quad
+ if (m_rotatedDestCoords[2].x < m_context.GetScreenWidth())
+ {
+ GLubyte quad = count;
+ vertices[quad].x = m_rotatedDestCoords[1].x;
+ vertices[quad].y = m_rotatedDestCoords[1].y;
+ vertices[quad].z = 0;
+ vertices[quad + 1].x = m_context.GetScreenWidth();
+ vertices[quad + 1].y = m_rotatedDestCoords[1].y;
+ vertices[quad + 1].z = 0;
+ vertices[quad + 2].x = m_context.GetScreenWidth();
+ vertices[quad + 2].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 2].z = 0;
+ vertices[quad + 3] = vertices[quad + 2];
+ vertices[quad + 4].x = m_rotatedDestCoords[1].x;
+ vertices[quad + 4].y = m_rotatedDestCoords[2].y;
+ vertices[quad + 4].z = 0;
+ vertices[quad + 5] = vertices[quad];
+ count += 6;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_blackbarsVertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Svertex) * count, &vertices[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Svertex), 0);
+ glEnableVertexAttribArray(posLoc);
+
+ glDrawArrays(GL_TRIANGLES, 0, count);
+
+ glDisableVertexAttribArray(posLoc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ m_context.DisableGUIShader();
+}
+
+void CRPRendererOpenGLES::Render(uint8_t alpha)
+{
+ CRenderBufferOpenGLES* renderBuffer = static_cast<CRenderBufferOpenGLES*>(m_renderBuffer);
+
+ if (renderBuffer == nullptr)
+ return;
+
+ CRect rect = m_sourceRect;
+
+ rect.x1 /= renderBuffer->GetWidth();
+ rect.x2 /= renderBuffer->GetWidth();
+ rect.y1 /= renderBuffer->GetHeight();
+ rect.y2 /= renderBuffer->GetHeight();
+
+ const uint32_t color = (alpha << 24) | 0xFFFFFF;
+
+ glBindTexture(m_textureTarget, renderBuffer->TextureID());
+
+ GLint filter = GL_NEAREST;
+ if (GetRenderSettings().VideoSettings().GetScalingMethod() == SCALINGMETHOD::LINEAR)
+ filter = GL_LINEAR;
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE_NOALPHA);
+
+ GLubyte colour[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ GLint vertLoc = m_context.GUIShaderGetPos();
+ GLint loc = m_context.GUIShaderGetCoord0();
+ GLint uniColLoc = m_context.GUIShaderGetUniCol();
+
+ // Setup color values
+ colour[0] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::R, color);
+ colour[1] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::G, color);
+ colour[2] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::B, color);
+ colour[3] = UTILS::GL::GetChannelFromARGB(UTILS::GL::ColorChannel::A, color);
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ // Setup vertex position values
+ vertex[i].x = m_rotatedDestCoords[i].x;
+ vertex[i].y = m_rotatedDestCoords[i].y;
+ vertex[i].z = 0.0f;
+ }
+
+ // Setup texture coordinates
+ vertex[0].u1 = vertex[3].u1 = rect.x1;
+ vertex[0].v1 = vertex[1].v1 = rect.y1;
+ vertex[1].u1 = vertex[2].u1 = rect.x2;
+ vertex[2].v1 = vertex[3].v1 = rect.y2;
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mainIndexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW);
+
+ glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f),
+ (colour[3] / 255.0f));
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ m_context.DisableGUIShader();
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h
new file mode 100644
index 0000000..ca1f583
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RPBaseRenderer.h"
+#include "cores/GameSettings.h"
+#include "cores/RetroPlayer/buffers/BaseRenderBufferPool.h"
+#include "cores/RetroPlayer/buffers/video/RenderBufferSysMem.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+#include <atomic>
+#include <stdint.h>
+#include <vector>
+
+#include "system_gl.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRendererFactoryOpenGLES : public IRendererFactory
+{
+public:
+ ~CRendererFactoryOpenGLES() override = default;
+
+ // implementation of IRendererFactory
+ std::string RenderSystemName() const override;
+ CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) override;
+ RenderBufferPoolVector CreateBufferPools(CRenderContext& context) override;
+};
+
+class CRPRendererOpenGLES : public CRPBaseRenderer
+{
+public:
+ CRPRendererOpenGLES(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ ~CRPRendererOpenGLES() override;
+
+ // implementation of CRPBaseRenderer
+ bool Supports(RENDERFEATURE feature) const override;
+ SCALINGMETHOD GetDefaultScalingMethod() const override { return SCALINGMETHOD::NEAREST; }
+
+ static bool SupportsScalingMethod(SCALINGMETHOD method);
+
+protected:
+ // implementation of CRPBaseRenderer
+ void RenderInternal(bool clear, uint8_t alpha) override;
+ void FlushInternal() override;
+
+ /*!
+ * \brief Set the entire backbuffer to black
+ */
+ void ClearBackBuffer();
+
+ /*!
+ * \brief Draw black bars around the video quad
+ *
+ * This is more efficient than glClear() since it only sets pixels to
+ * black that aren't going to be overwritten by the game.
+ */
+ void DrawBlackBars();
+
+ virtual void Render(uint8_t alpha);
+
+ GLuint m_mainIndexVBO;
+ GLuint m_mainVertexVBO;
+ GLuint m_blackbarsVertexVBO;
+ GLenum m_textureTarget = GL_TEXTURE_2D;
+ float m_clearColour = 0.0f;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.cpp
new file mode 100644
index 0000000..167f6a8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.cpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPWinRenderer.h"
+
+#include "cores/RetroPlayer/rendering/RenderContext.h"
+#include "cores/RetroPlayer/rendering/RenderTranslator.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h"
+#include "guilib/D3DResource.h"
+#include "rendering/dx/RenderSystemDX.h"
+#include "utils/log.h"
+
+extern "C"
+{
+#include <libswscale/swscale.h>
+}
+
+#include <cstring>
+
+using namespace KODI;
+using namespace RETRO;
+
+// --- CWinRendererFactory -----------------------------------------------------
+
+std::string CWinRendererFactory::RenderSystemName() const
+{
+ return "DirectX";
+}
+
+CRPBaseRenderer* CWinRendererFactory::CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+{
+ return new CRPWinRenderer(settings, context, std::move(bufferPool));
+}
+
+RenderBufferPoolVector CWinRendererFactory::CreateBufferPools(CRenderContext& context)
+{
+ return {std::make_shared<CWinRenderBufferPool>()};
+}
+
+// --- CWinRenderBuffer --------------------------------------------------------
+
+CWinRenderBuffer::CWinRenderBuffer(AVPixelFormat pixFormat, DXGI_FORMAT dxFormat)
+ : m_pixFormat(pixFormat), m_targetDxFormat(dxFormat), m_targetPixFormat(GetPixFormat())
+{
+}
+
+CWinRenderBuffer::~CWinRenderBuffer()
+{
+ if (m_swsContext != nullptr)
+ sws_freeContext(m_swsContext);
+}
+
+bool CWinRenderBuffer::CreateTexture()
+{
+ if (!m_intermediateTarget->Create(m_width, m_height, 1, D3D11_USAGE_DYNAMIC, m_targetDxFormat))
+ {
+ CLog::Log(LOGERROR, "WinRenderer: Intermediate render target creation failed");
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinRenderBuffer::GetTexture(uint8_t*& data, unsigned int& stride)
+{
+ // Scale and upload texture
+ D3D11_MAPPED_SUBRESOURCE destlr;
+ if (!m_intermediateTarget->LockRect(0, &destlr, D3D11_MAP_WRITE_DISCARD))
+ {
+ CLog::Log(LOGERROR, "WinRenderer: Failed to lock swtarget texture into memory");
+ return false;
+ }
+
+ data = static_cast<uint8_t*>(destlr.pData);
+ stride = destlr.RowPitch;
+
+ return true;
+}
+
+bool CWinRenderBuffer::ReleaseTexture()
+{
+ if (!m_intermediateTarget->UnlockRect(0))
+ {
+ CLog::Log(LOGERROR, "WinRenderer: Failed to unlock swtarget texture");
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinRenderBuffer::UploadTexture()
+{
+ if (m_targetDxFormat == DXGI_FORMAT_UNKNOWN)
+ {
+ CLog::Log(LOGERROR, "WinRenderer: Invalid DX texture format");
+ return false;
+ }
+
+ if (!CreateScalingContext())
+ return false;
+
+ // Create intermediate texture
+ if (!m_intermediateTarget)
+ {
+ m_intermediateTarget.reset(new CD3DTexture);
+ if (!CreateTexture())
+ {
+ m_intermediateTarget.reset();
+ return false;
+ }
+ }
+
+ uint8_t* destData = nullptr;
+ unsigned int destStride = 0;
+ if (!GetTexture(destData, destStride))
+ return false;
+
+ const unsigned int sourceStride = static_cast<unsigned int>(m_data.size() / m_height);
+ ScalePixels(m_data.data(), sourceStride, destData, destStride);
+
+ if (!ReleaseTexture())
+ return false;
+
+ return true;
+}
+
+bool CWinRenderBuffer::CreateScalingContext()
+{
+ if (m_swsContext == nullptr)
+ {
+ m_swsContext = sws_getContext(m_width, m_height, m_pixFormat, m_width, m_height,
+ m_targetPixFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL);
+
+ if (m_swsContext == nullptr)
+ {
+ CLog::Log(LOGERROR, "WinRenderer: Failed to create swscale context");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void CWinRenderBuffer::ScalePixels(const uint8_t* source,
+ unsigned int sourceStride,
+ uint8_t* target,
+ unsigned int targetStride)
+{
+ uint8_t* src[] = {const_cast<uint8_t*>(source), nullptr, nullptr, nullptr};
+ int srcStride[] = {static_cast<int>(sourceStride), 0, 0, 0};
+ uint8_t* dst[] = {target, nullptr, nullptr, nullptr};
+ int dstStride[] = {static_cast<int>(targetStride), 0, 0, 0};
+
+ sws_scale(m_swsContext, src, srcStride, 0, m_height, dst, dstStride);
+}
+
+AVPixelFormat CWinRenderBuffer::GetPixFormat()
+{
+ return AV_PIX_FMT_BGRA;
+}
+
+// --- CWinRenderBufferPool ----------------------------------------------------
+
+CWinRenderBufferPool::CWinRenderBufferPool()
+{
+ CompileOutputShaders();
+}
+
+bool CWinRenderBufferPool::IsCompatible(const CRenderVideoSettings& renderSettings) const
+{
+ return GetShader(renderSettings.GetScalingMethod()) != nullptr;
+}
+
+IRenderBuffer* CWinRenderBufferPool::CreateRenderBuffer(void* header /* = nullptr */)
+{
+ return new CWinRenderBuffer(m_format, m_targetDxFormat);
+}
+
+bool CWinRenderBufferPool::ConfigureDX()
+{
+ if (m_targetDxFormat != DXGI_FORMAT_UNKNOWN)
+ return false; // Already configured
+
+ // There are three pixel formats used by libretro: 0RGB32, RGB565 and
+ // RGB555. DirectX support for these varies, so always use BGRA32 as the
+ // intermediate format.
+ m_targetDxFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
+
+ return true;
+}
+
+CRPWinOutputShader* CWinRenderBufferPool::GetShader(SCALINGMETHOD scalingMethod) const
+{
+ auto it = m_outputShaders.find(scalingMethod);
+
+ if (it != m_outputShaders.end())
+ return it->second.get();
+
+ return nullptr;
+}
+
+const std::vector<SCALINGMETHOD>& CWinRenderBufferPool::GetScalingMethods()
+{
+ static std::vector<SCALINGMETHOD> scalingMethods = {
+ SCALINGMETHOD::NEAREST,
+ SCALINGMETHOD::LINEAR,
+ };
+
+ return scalingMethods;
+}
+
+void CWinRenderBufferPool::CompileOutputShaders()
+{
+ for (auto scalingMethod : GetScalingMethods())
+ {
+ std::unique_ptr<CRPWinOutputShader> outputShader(new CRPWinOutputShader);
+ if (outputShader->Create(scalingMethod))
+ m_outputShaders[scalingMethod] = std::move(outputShader);
+ else
+ CLog::Log(LOGERROR, "RPWinRenderer: Unable to create output shader ({})",
+ CRenderTranslator::TranslateScalingMethod(scalingMethod));
+ }
+}
+
+// --- CRPWinRenderer ----------------------------------------------------------
+
+CRPWinRenderer::CRPWinRenderer(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool)
+ : CRPBaseRenderer(renderSettings, context, std::move(bufferPool))
+{
+}
+
+bool CRPWinRenderer::ConfigureInternal()
+{
+ CRenderSystemDX* renderingDx = static_cast<CRenderSystemDX*>(m_context.Rendering());
+
+ static_cast<CWinRenderBufferPool*>(m_bufferPool.get())->ConfigureDX();
+
+ return true;
+}
+
+void CRPWinRenderer::RenderInternal(bool clear, uint8_t alpha)
+{
+ CRenderSystemDX* renderingDx = static_cast<CRenderSystemDX*>(m_context.Rendering());
+
+ // Set alpha blend state
+ renderingDx->SetAlphaBlendEnable(alpha < 0xFF);
+
+ Render(renderingDx->GetBackBuffer());
+}
+
+bool CRPWinRenderer::Supports(RENDERFEATURE feature) const
+{
+ if (feature == RENDERFEATURE::STRETCH || feature == RENDERFEATURE::ZOOM ||
+ feature == RENDERFEATURE::PIXEL_RATIO || feature == RENDERFEATURE::ROTATION)
+ return true;
+
+ return false;
+}
+
+bool CRPWinRenderer::SupportsScalingMethod(SCALINGMETHOD method)
+{
+ if (method == SCALINGMETHOD::LINEAR || method == SCALINGMETHOD::NEAREST)
+ return true;
+
+ return false;
+}
+
+void CRPWinRenderer::Render(CD3DTexture& target)
+{
+ const CPoint destPoints[4] = {m_rotatedDestCoords[0], m_rotatedDestCoords[1],
+ m_rotatedDestCoords[2], m_rotatedDestCoords[3]};
+
+ if (m_renderBuffer != nullptr)
+ {
+ CD3DTexture* intermediateTarget = static_cast<CWinRenderBuffer*>(m_renderBuffer)->GetTarget();
+ if (intermediateTarget != nullptr)
+ {
+ CRect viewPort;
+ m_context.GetViewPort(viewPort);
+
+ // Pick appropriate output shader depending on the scaling method of the renderer
+ SCALINGMETHOD scalingMethod = m_renderSettings.VideoSettings().GetScalingMethod();
+
+ CWinRenderBufferPool* bufferPool = static_cast<CWinRenderBufferPool*>(m_bufferPool.get());
+ CRPWinOutputShader* outputShader = bufferPool->GetShader(scalingMethod);
+
+ // Use the picked output shader to render to the target
+ if (outputShader != nullptr)
+ {
+ outputShader->Render(*intermediateTarget, m_sourceRect, destPoints, viewPort, &target,
+ m_context.UseLimitedColor() ? 1 : 0);
+ }
+ }
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h
new file mode 100644
index 0000000..20271f9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RPBaseRenderer.h"
+#include "cores/RetroPlayer/buffers/BaseRenderBufferPool.h"
+#include "cores/RetroPlayer/buffers/video/RenderBufferSysMem.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+
+#include <memory>
+#include <stdint.h>
+#include <vector>
+
+#include <dxgi.h>
+
+class CD3DTexture;
+struct SwsContext;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRenderContext;
+class CRPWinOutputShader;
+
+class CWinRendererFactory : public IRendererFactory
+{
+public:
+ virtual ~CWinRendererFactory() = default;
+
+ // implementation of IRendererFactory
+ std::string RenderSystemName() const override;
+ CRPBaseRenderer* CreateRenderer(const CRenderSettings& settings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool) override;
+ RenderBufferPoolVector CreateBufferPools(CRenderContext& context) override;
+};
+
+class CWinRenderBuffer : public CRenderBufferSysMem
+{
+public:
+ CWinRenderBuffer(AVPixelFormat pixFormat, DXGI_FORMAT dxFormat);
+ ~CWinRenderBuffer() override;
+
+ // implementation of IRenderBuffer via CRenderBufferSysMem
+ bool UploadTexture() override;
+
+ CD3DTexture* GetTarget() { return m_intermediateTarget.get(); }
+
+private:
+ bool CreateTexture();
+ bool GetTexture(uint8_t*& data, unsigned int& stride);
+ bool ReleaseTexture();
+
+ bool CreateScalingContext();
+ void ScalePixels(const uint8_t* source,
+ unsigned int sourceStride,
+ uint8_t* target,
+ unsigned int targetStride);
+
+ static AVPixelFormat GetPixFormat();
+
+ // Construction parameters
+ const AVPixelFormat m_pixFormat;
+ const DXGI_FORMAT m_targetDxFormat;
+
+ AVPixelFormat m_targetPixFormat;
+ std::unique_ptr<CD3DTexture> m_intermediateTarget;
+
+ SwsContext* m_swsContext = nullptr;
+};
+
+class CWinRenderBufferPool : public CBaseRenderBufferPool
+{
+public:
+ CWinRenderBufferPool();
+ ~CWinRenderBufferPool() override = default;
+
+ // implementation of IRenderBufferPool via CRenderBufferPoolSysMem
+ bool IsCompatible(const CRenderVideoSettings& renderSettings) const override;
+
+ // implementation of CBaseRenderBufferPool via CRenderBufferPoolSysMem
+ IRenderBuffer* CreateRenderBuffer(void* header = nullptr) override;
+
+ // DirectX interface
+ bool ConfigureDX();
+ CRPWinOutputShader* GetShader(SCALINGMETHOD scalingMethod) const;
+
+private:
+ static const std::vector<SCALINGMETHOD>& GetScalingMethods();
+
+ void CompileOutputShaders();
+
+ DXGI_FORMAT m_targetDxFormat = DXGI_FORMAT_UNKNOWN;
+ std::map<SCALINGMETHOD, std::unique_ptr<CRPWinOutputShader>> m_outputShaders;
+};
+
+class CRPWinRenderer : public CRPBaseRenderer
+{
+public:
+ CRPWinRenderer(const CRenderSettings& renderSettings,
+ CRenderContext& context,
+ std::shared_ptr<IRenderBufferPool> bufferPool);
+ ~CRPWinRenderer() override = default;
+
+ // implementation of CRPBaseRenderer
+ bool Supports(RENDERFEATURE feature) const override;
+ SCALINGMETHOD GetDefaultScalingMethod() const override { return DEFAULT_SCALING_METHOD; }
+
+ static bool SupportsScalingMethod(SCALINGMETHOD method);
+
+ /*!
+ * \brief The default scaling method of the renderer
+ */
+ static const SCALINGMETHOD DEFAULT_SCALING_METHOD = SCALINGMETHOD::NEAREST;
+
+protected:
+ // implementation of CRPBaseRenderer
+ bool ConfigureInternal() override;
+ void RenderInternal(bool clear, uint8_t alpha) override;
+
+private:
+ void Render(CD3DTexture& target);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoShaders/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoShaders/CMakeLists.txt
new file mode 100644
index 0000000..d886000
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoShaders/CMakeLists.txt
@@ -0,0 +1,3 @@
+if(NOT ENABLE_STATIC_LIBS)
+ core_add_library(rp-videoshaders)
+endif()
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/CMakeLists.txt
new file mode 100644
index 0000000..51fda41
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES RPWinOutputShader.cpp)
+
+set(HEADERS RPWinOutputShader.h)
+
+core_add_library(rp-videoshaders-windows)
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.cpp b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.cpp
new file mode 100644
index 0000000..efc827b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "RPWinOutputShader.h"
+
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+bool CRPWinOutputShader::Create(SCALINGMETHOD scalingMethod)
+{
+ CWinShader::CreateVertexBuffer(4, sizeof(CUSTOMVERTEX));
+
+ DefinesMap defines;
+ switch (scalingMethod)
+ {
+ case SCALINGMETHOD::NEAREST:
+ defines["SAMP_NEAREST"] = "";
+ break;
+ case SCALINGMETHOD::LINEAR:
+ default:
+ break;
+ }
+
+ std::string effectPath("special://xbmc/system/shaders/rp_output_d3d.fx");
+
+ if (!LoadEffect(effectPath, &defines))
+ {
+ CLog::LogF(LOGERROR, "Failed to load shader {}.", effectPath);
+ return false;
+ }
+
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] = {
+ {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
+ {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
+ };
+ return CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout));
+}
+
+void CRPWinOutputShader::Render(CD3DTexture& sourceTexture,
+ CRect sourceRect,
+ const CPoint points[4],
+ CRect& viewPort,
+ CD3DTexture* target,
+ unsigned range)
+{
+ PrepareParameters(sourceTexture.GetWidth(), sourceTexture.GetHeight(), sourceRect, points);
+ SetShaderParameters(sourceTexture, range, viewPort);
+ Execute({target}, 4);
+}
+
+void CRPWinOutputShader::PrepareParameters(unsigned sourceWidth,
+ unsigned sourceHeight,
+ CRect sourceRect,
+ const CPoint points[4])
+{
+ bool changed = false;
+ for (int i = 0; i < 4 && !changed; ++i)
+ changed = points[i] != m_destPoints[i];
+
+ if (m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight ||
+ m_sourceRect != sourceRect || changed)
+ {
+ m_sourceWidth = sourceWidth;
+ m_sourceHeight = sourceHeight;
+ m_sourceRect = sourceRect;
+
+ for (int i = 0; i < 4; ++i)
+ m_destPoints[i] = points[i];
+
+ CUSTOMVERTEX* v = nullptr;
+ CWinShader::LockVertexBuffer(static_cast<void**>(static_cast<void*>(&v)));
+
+ v[0].x = m_destPoints[0].x;
+ v[0].y = m_destPoints[0].y;
+ v[0].z = 0.0f;
+ v[0].tu = m_sourceRect.x1 / m_sourceWidth;
+ v[0].tv = m_sourceRect.y1 / m_sourceHeight;
+
+ v[1].x = m_destPoints[1].x;
+ v[1].y = m_destPoints[1].y;
+ v[1].z = 0.0f;
+ v[1].tu = m_sourceRect.x2 / m_sourceWidth;
+ v[1].tv = m_sourceRect.y1 / m_sourceHeight;
+
+ v[2].x = m_destPoints[2].x;
+ v[2].y = m_destPoints[2].y;
+ v[2].z = 0.0f;
+ v[2].tu = m_sourceRect.x2 / m_sourceWidth;
+ v[2].tv = m_sourceRect.y2 / m_sourceHeight;
+
+ v[3].x = m_destPoints[3].x;
+ v[3].y = m_destPoints[3].y;
+ v[3].z = 0.0f;
+ v[3].tu = m_sourceRect.x1 / m_sourceWidth;
+ v[3].tv = m_sourceRect.y2 / m_sourceHeight;
+
+ CWinShader::UnlockVertexBuffer();
+ }
+}
+
+void CRPWinOutputShader::SetShaderParameters(CD3DTexture& sourceTexture,
+ unsigned range,
+ CRect& viewPort)
+{
+ m_effect.SetTechnique("OUTPUT_T");
+ m_effect.SetResources("g_Texture", sourceTexture.GetAddressOfSRV(), 1);
+
+ float viewPortArray[2] = {viewPort.Width(), viewPort.Height()};
+ m_effect.SetFloatArray("g_viewPort", viewPortArray, 2);
+
+ float params[3] = {static_cast<float>(range)};
+ m_effect.SetFloatArray("m_params", params, 1);
+}
diff --git a/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h
new file mode 100644
index 0000000..ebeb164
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/GameSettings.h"
+#include "cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+
+class CRPWinOutputShader : public CWinShader
+{
+public:
+ ~CRPWinOutputShader() = default;
+
+ bool Create(SCALINGMETHOD scalingMethod);
+ void Render(CD3DTexture& sourceTexture,
+ CRect sourceRect,
+ const CPoint points[4],
+ CRect& viewPort,
+ CD3DTexture* target,
+ unsigned range = 0);
+
+private:
+ void PrepareParameters(unsigned sourceWidth,
+ unsigned sourceHeight,
+ CRect sourceRect,
+ const CPoint points[4]);
+ void SetShaderParameters(CD3DTexture& sourceTexture, unsigned range, CRect& viewPort);
+
+ unsigned m_sourceWidth{0};
+ unsigned m_sourceHeight{0};
+ CRect m_sourceRect{0.f, 0.f, 0.f, 0.f};
+ CPoint m_destPoints[4] = {
+ {0.f, 0.f},
+ {0.f, 0.f},
+ {0.f, 0.f},
+ {0.f, 0.f},
+ };
+
+ struct CUSTOMVERTEX
+ {
+ FLOAT x;
+ FLOAT y;
+ FLOAT z;
+
+ FLOAT tu;
+ FLOAT tv;
+ };
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt b/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt
new file mode 100644
index 0000000..956776d
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(SOURCES SavestateDatabase.cpp
+ SavestateFlatBuffer.cpp
+)
+
+set(HEADERS ISavestate.h
+ SavestateDatabase.h
+ SavestateFlatBuffer.h
+ SavestateTypes.h
+)
+
+core_add_library(retroplayer_savestates)
+
+set(DEPENDS retroplayer_messages)
+
+if(ENABLE_STATIC_LIBS)
+ add_dependencies(retroplayer_savestates ${DEPENDS})
+else()
+ add_dependencies(lib${APP_NAME_LC} ${DEPENDS})
+endif()
diff --git a/xbmc/cores/RetroPlayer/savestates/ISavestate.h b/xbmc/cores/RetroPlayer/savestates/ISavestate.h
new file mode 100644
index 0000000..c12d837
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/ISavestate.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "SavestateTypes.h"
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+class CDateTime;
+
+namespace KODI
+{
+namespace RETRO
+{
+class ISavestate
+{
+public:
+ virtual ~ISavestate() = default;
+
+ /*!
+ * \brief Reset to the initial state
+ */
+ virtual void Reset() = 0;
+
+ /*!
+ * Access the data representation of this savestate
+ */
+ virtual bool Serialize(const uint8_t*& data, size_t& size) const = 0;
+
+ /// @name Savestate properties
+ ///{
+ /*!
+ * \brief The type of save action that created this savestate, either
+ * manual or automatic
+ */
+ virtual SAVE_TYPE Type() const = 0;
+
+ /*!
+ * \brief The slot this savestate was saved into, or 0 for no slot
+ *
+ * This allows for keyboard access of saved games using the number keys 1-9.
+ */
+ virtual uint8_t Slot() const = 0;
+
+ /*!
+ * \brief The label shown in the GUI for this savestate
+ */
+ virtual std::string Label() const = 0;
+
+ /*!
+ * \brief A caption that describes the state of the game for this savestate
+ */
+ virtual std::string Caption() const = 0;
+
+ /*!
+ * \brief The timestamp of this savestate's creation
+ */
+ virtual CDateTime Created() const = 0;
+ ///}
+
+ /// @name Game properties
+ ///{
+ /*!
+ * \brief The name of the file belonging to this savestate's game
+ */
+ virtual std::string GameFileName() const = 0;
+ ///}
+
+ /// @name Environment properties
+ ///{
+ /*!
+ * \brief The number of frames in the entire gameplay history
+ */
+ virtual uint64_t TimestampFrames() const = 0;
+
+ /*!
+ * \brief The duration of the entire gameplay history as seen by a wall clock
+ */
+ virtual double TimestampWallClock() const = 0;
+ ///}
+
+ /// @name Game client properties
+ ///{
+ /*!
+ * \brief The game client add-on ID that created this savestate
+ */
+ virtual std::string GameClientID() const = 0;
+
+ /*!
+ * \brief The semantic version of the game client
+ */
+ virtual std::string GameClientVersion() const = 0;
+ ///}
+
+ /// @name Video stream properties
+ ///{
+ /*!
+ * \brief The pixel format of the video stream
+ */
+ virtual AVPixelFormat GetPixelFormat() const = 0;
+
+ /*!
+ * \brief The nominal width of the video stream, a good guess for subsequent frames
+ */
+ virtual unsigned int GetNominalWidth() const = 0;
+
+ /*!
+ * \brief The nominal height of the video stream, a good guess for subsequent frames
+ */
+ virtual unsigned int GetNominalHeight() const = 0;
+
+ /*!
+ * \brief The maximum width of the video stream, in pixels
+ */
+ virtual unsigned int GetMaxWidth() const = 0;
+
+ /*!
+ * \brief The maximum height of the video stream, in pixels
+ */
+ virtual unsigned int GetMaxHeight() const = 0;
+
+ /*!
+ * \brief The pixel aspect ratio of the video stream
+ */
+ virtual float GetPixelAspectRatio() const = 0;
+ ///}
+
+ /// @name Video frame properties
+ ///{
+ /*!
+ * \brief A pointer to the frame's video data (pixels)
+ */
+ virtual const uint8_t* GetVideoData() const = 0;
+
+ /*!
+ * \brief The size of the frame's video data, in bytes
+ */
+ virtual size_t GetVideoSize() const = 0;
+
+ /*!
+ * \brief The width of the video frame, in pixels
+ */
+ virtual unsigned int GetVideoWidth() const = 0;
+
+ /*!
+ * \brief The height of the video frame, in pixels
+ */
+ virtual unsigned int GetVideoHeight() const = 0;
+
+ /*!
+ * \brief The rotation of the video frame, in degrees counter-clockwise
+ */
+ virtual unsigned int GetRotationDegCCW() const = 0;
+ ///}
+
+ /// @name Memory properties
+ ///{
+ /*!
+ * \brief A pointer to the internal memory (SRAM) of the frame
+ */
+ virtual const uint8_t* GetMemoryData() const = 0;
+
+ /*!
+ * \brief The size of the memory region returned by GetMemoryData()
+ */
+ virtual size_t GetMemorySize() const = 0;
+ ///}
+
+ /// @name Builders for setting individual fields
+ ///{
+ virtual void SetType(SAVE_TYPE type) = 0;
+ virtual void SetSlot(uint8_t slot) = 0;
+ virtual void SetLabel(const std::string& label) = 0;
+ virtual void SetCaption(const std::string& caption) = 0;
+ virtual void SetCreated(const CDateTime& createdUTC) = 0;
+ virtual void SetGameFileName(const std::string& gameFileName) = 0;
+ virtual void SetTimestampFrames(uint64_t timestampFrames) = 0;
+ virtual void SetTimestampWallClock(double timestampWallClock) = 0;
+ virtual void SetGameClientID(const std::string& gameClient) = 0;
+ virtual void SetGameClientVersion(const std::string& gameClient) = 0;
+ virtual void SetPixelFormat(AVPixelFormat pixelFormat) = 0;
+ virtual void SetNominalWidth(unsigned int nominalWidth) = 0;
+ virtual void SetNominalHeight(unsigned int nominalHeight) = 0;
+ virtual void SetMaxWidth(unsigned int maxWidth) = 0;
+ virtual void SetMaxHeight(unsigned int maxHeight) = 0;
+ virtual void SetPixelAspectRatio(float pixelAspectRatio) = 0;
+ virtual uint8_t* GetVideoBuffer(size_t size) = 0;
+ virtual void SetVideoWidth(unsigned int videoWidth) = 0;
+ virtual void SetVideoHeight(unsigned int videoHeight) = 0;
+ virtual void SetRotationDegCCW(unsigned int rotationCCW) = 0;
+ virtual uint8_t* GetMemoryBuffer(size_t size) = 0;
+ virtual void Finalize() = 0;
+ ///}
+
+ /*!
+ * \brief Take ownership and initialize the flatbuffer with the given vector
+ */
+ virtual bool Deserialize(std::vector<uint8_t> data) = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp
new file mode 100644
index 0000000..2611f25
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SavestateDatabase.h"
+
+#include "FileItem.h"
+#include "SavestateFlatBuffer.h"
+#include "URL.h"
+#include "XBDateTime.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "filesystem/IFileTypes.h"
+#include "games/dialogs/DialogGameDefines.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+namespace
+{
+constexpr auto SAVESTATE_EXTENSION = ".sav";
+constexpr auto SAVESTATE_BASE_FOLDER = "special://home/saves/";
+} // namespace
+
+using namespace KODI;
+using namespace RETRO;
+
+CSavestateDatabase::CSavestateDatabase() = default;
+
+std::unique_ptr<ISavestate> CSavestateDatabase::AllocateSavestate()
+{
+ std::unique_ptr<ISavestate> savestate;
+
+ savestate.reset(new CSavestateFlatBuffer);
+
+ return savestate;
+}
+
+bool CSavestateDatabase::AddSavestate(const std::string& savestatePath,
+ const std::string& gamePath,
+ const ISavestate& save)
+{
+ bool bSuccess = false;
+ std::string path;
+
+ if (savestatePath.empty())
+ path = MakeSavestatePath(gamePath, save.Created());
+ else
+ path = savestatePath;
+
+ CLog::Log(LOGDEBUG, "Saving savestate to {}", CURL::GetRedacted(path));
+
+ const uint8_t* data = nullptr;
+ size_t size = 0;
+ if (save.Serialize(data, size))
+ {
+ XFILE::CFile file;
+ if (file.OpenForWrite(path, true))
+ {
+ const ssize_t written = file.Write(data, size);
+ if (written == static_cast<ssize_t>(size))
+ {
+ CLog::Log(LOGDEBUG, "Wrote savestate of {} bytes", size);
+ bSuccess = true;
+ }
+ }
+ else
+ CLog::Log(LOGERROR, "Failed to open savestate for writing");
+ }
+
+ return bSuccess;
+}
+
+bool CSavestateDatabase::GetSavestate(const std::string& savestatePath, ISavestate& save)
+{
+ bool bSuccess = false;
+
+ CLog::Log(LOGDEBUG, "Loading savestate from {}", CURL::GetRedacted(savestatePath));
+
+ std::vector<uint8_t> savestateData;
+
+ XFILE::CFile savestateFile;
+ if (savestateFile.Open(savestatePath, XFILE::READ_TRUNCATED))
+ {
+ int64_t size = savestateFile.GetLength();
+ if (size > 0)
+ {
+ savestateData.resize(static_cast<size_t>(size));
+
+ const ssize_t readLength = savestateFile.Read(savestateData.data(), savestateData.size());
+ if (readLength != static_cast<ssize_t>(savestateData.size()))
+ {
+ CLog::Log(LOGERROR, "Failed to read savestate {} of size {} bytes",
+ CURL::GetRedacted(savestatePath), size);
+ savestateData.clear();
+ }
+ }
+ else
+ CLog::Log(LOGERROR, "Failed to get savestate length: {}", CURL::GetRedacted(savestatePath));
+ }
+ else
+ CLog::Log(LOGERROR, "Failed to open savestate file {}", CURL::GetRedacted(savestatePath));
+
+ if (!savestateData.empty())
+ bSuccess = save.Deserialize(std::move(savestateData));
+
+ return bSuccess;
+}
+
+bool CSavestateDatabase::GetSavestatesNav(CFileItemList& items,
+ const std::string& gamePath,
+ const std::string& gameClient /* = "" */)
+{
+ const std::string savesFolder = MakePath(gamePath);
+
+ XFILE::CDirectory::CHints hints;
+ hints.mask = SAVESTATE_EXTENSION;
+
+ if (!XFILE::CDirectory::GetDirectory(savesFolder, items, hints))
+ return false;
+
+ if (!gameClient.empty())
+ {
+ for (int i = items.Size() - 1; i >= 0; i--)
+ {
+ std::unique_ptr<ISavestate> save = AllocateSavestate();
+ GetSavestate(items[i]->GetPath(), *save);
+ if (save->GameClientID() != gameClient)
+ items.Remove(i);
+ }
+ }
+
+ for (int i = 0; i < items.Size(); i++)
+ {
+ std::unique_ptr<ISavestate> savestate = AllocateSavestate();
+ GetSavestate(items[i]->GetPath(), *savestate);
+
+ GetSavestateItem(*savestate, items[i]->GetPath(), *items[i]);
+ }
+
+ return true;
+}
+
+void CSavestateDatabase::GetSavestateItem(const ISavestate& savestate,
+ const std::string& savestatePath,
+ CFileItem& item)
+{
+ CDateTime dateUTC = CDateTime::FromUTCDateTime(savestate.Created());
+
+ std::string label;
+ std::string label2;
+
+ // Date has the lowest priority of being shown
+ label = dateUTC.GetAsLocalizedDateTime(false, false);
+
+ // Label has the next priority
+ if (!savestate.Label().empty())
+ {
+ label2 = std::move(label);
+ label = savestate.Label();
+ }
+
+ // "Autosave" has the highest priority
+ if (savestate.Type() == SAVE_TYPE::AUTO)
+ {
+ label2 = std::move(label);
+ label = g_localizeStrings.Get(15316); // "Autosave"
+ }
+
+ item.SetLabel(label);
+ item.SetLabel2(label2);
+ item.SetPath(savestatePath);
+ item.SetArt("screenshot", MakeThumbnailPath(savestatePath));
+ item.SetProperty(SAVESTATE_LABEL, savestate.Label());
+ item.SetProperty(SAVESTATE_CAPTION, savestate.Caption());
+ item.SetProperty(SAVESTATE_GAME_CLIENT, savestate.GameClientID());
+ item.m_dateTime = dateUTC;
+}
+
+std::unique_ptr<ISavestate> CSavestateDatabase::RenameSavestate(const std::string& savestatePath,
+ const std::string& label)
+{
+ std::unique_ptr<ISavestate> savestate = AllocateSavestate();
+ if (!GetSavestate(savestatePath, *savestate))
+ return {};
+
+ std::unique_ptr<ISavestate> newSavestate = AllocateSavestate();
+
+ newSavestate->SetLabel(label);
+ newSavestate->SetCaption(savestate->Caption());
+ newSavestate->SetType(savestate->Type());
+ newSavestate->SetCreated(savestate->Created());
+ newSavestate->SetGameFileName(savestate->GameFileName());
+ newSavestate->SetTimestampFrames(savestate->TimestampFrames());
+ newSavestate->SetTimestampWallClock(savestate->TimestampWallClock());
+ newSavestate->SetGameClientID(savestate->GameClientID());
+ newSavestate->SetGameClientVersion(savestate->GameClientVersion());
+
+ size_t memorySize = savestate->GetMemorySize();
+ std::memcpy(newSavestate->GetMemoryBuffer(memorySize), savestate->GetMemoryData(), memorySize);
+
+ newSavestate->Finalize();
+
+ std::string path = savestatePath;
+ if (!AddSavestate(path, "", *newSavestate))
+ return {};
+
+ return newSavestate;
+}
+
+bool CSavestateDatabase::DeleteSavestate(const std::string& savestatePath)
+{
+ if (!XFILE::CFile::Delete(savestatePath))
+ {
+ CLog::Log(LOGERROR, "Failed to delete savestate file {}", CURL::GetRedacted(savestatePath));
+ return false;
+ }
+
+ XFILE::CFile::Delete(MakeThumbnailPath(savestatePath));
+ return true;
+}
+
+bool CSavestateDatabase::ClearSavestatesOfGame(const std::string& gamePath,
+ const std::string& gameClient /* = "" */)
+{
+ //! @todo
+ return false;
+}
+
+std::string CSavestateDatabase::MakeSavestatePath(const std::string& gamePath,
+ const CDateTime& creationTime)
+{
+ std::string path = MakePath(gamePath);
+ return URIUtils::AddFileToFolder(path, creationTime.GetAsSaveString() + SAVESTATE_EXTENSION);
+}
+
+std::string CSavestateDatabase::MakeThumbnailPath(const std::string& savestatePath)
+{
+ return URIUtils::ReplaceExtension(savestatePath, ".jpg");
+}
+
+std::string CSavestateDatabase::MakePath(const std::string& gamePath)
+{
+ if (!CreateFolderIfNotExists(SAVESTATE_BASE_FOLDER))
+ return "";
+
+ std::string gameName = URIUtils::GetFileName(gamePath);
+ std::string folderPath = URIUtils::AddFileToFolder(SAVESTATE_BASE_FOLDER, gameName);
+
+ if (!CreateFolderIfNotExists(folderPath))
+ return "";
+
+ return folderPath;
+}
+
+bool CSavestateDatabase::CreateFolderIfNotExists(const std::string& path)
+{
+ if (!XFILE::CDirectory::Exists(path))
+ {
+ if (!XFILE::CDirectory::Create(path))
+ {
+ CLog::Log(LOGERROR, "Failed to create folder: {}", path);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h
new file mode 100644
index 0000000..b084771
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+class CDateTime;
+class CFileItem;
+class CFileItemList;
+
+namespace KODI
+{
+namespace RETRO
+{
+class ISavestate;
+
+class CSavestateDatabase
+{
+public:
+ CSavestateDatabase();
+ virtual ~CSavestateDatabase() = default;
+
+ static std::unique_ptr<ISavestate> AllocateSavestate();
+
+ bool AddSavestate(const std::string& savestatePath,
+ const std::string& gamePath,
+ const ISavestate& save);
+
+ bool GetSavestate(const std::string& savestatePath, ISavestate& save);
+
+ bool GetSavestatesNav(CFileItemList& items,
+ const std::string& gamePath,
+ const std::string& gameClient = "");
+
+ static void GetSavestateItem(const ISavestate& savestate,
+ const std::string& savestatePath,
+ CFileItem& item);
+
+ std::unique_ptr<ISavestate> RenameSavestate(const std::string& savestatePath,
+ const std::string& label);
+
+ bool DeleteSavestate(const std::string& savestatePath);
+
+ bool ClearSavestatesOfGame(const std::string& gamePath, const std::string& gameClient = "");
+
+ static std::string MakeSavestatePath(const std::string& gamePath, const CDateTime& creationTime);
+ static std::string MakeThumbnailPath(const std::string& savestatePath);
+
+private:
+ static std::string MakePath(const std::string& gamePath);
+ static bool CreateFolderIfNotExists(const std::string& path);
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp
new file mode 100644
index 0000000..9b98bde
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SavestateFlatBuffer.h"
+
+#include "XBDateTime.h"
+#include "savestate_generated.h"
+#include "utils/log.h"
+#include "video_generated.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+namespace
+{
+const uint8_t SCHEMA_VERSION = 3;
+const uint8_t SCHEMA_MIN_VERSION = 1;
+
+/*!
+ * \brief The initial size of the FlatBuffer's memory buffer
+ *
+ * 1024 is the default size in the FlatBuffers header. We might as well use
+ * this until our size requirements are more known.
+ */
+const size_t INITIAL_FLATBUFFER_SIZE = 1024;
+
+/*!
+ * \brief Translate the save type (RetroPlayer to FlatBuffers)
+ */
+SaveType TranslateType(SAVE_TYPE type)
+{
+ switch (type)
+ {
+ case SAVE_TYPE::AUTO:
+ return SaveType_Auto;
+ case SAVE_TYPE::MANUAL:
+ return SaveType_Manual;
+ default:
+ break;
+ }
+
+ return SaveType_Unknown;
+}
+
+/*!
+ * \brief Translate the save type (FlatBuffers to RetroPlayer)
+ */
+SAVE_TYPE TranslateType(SaveType type)
+{
+ switch (type)
+ {
+ case SaveType_Auto:
+ return SAVE_TYPE::AUTO;
+ case SaveType_Manual:
+ return SAVE_TYPE::MANUAL;
+ default:
+ break;
+ }
+
+ return SAVE_TYPE::UNKNOWN;
+}
+
+/*!
+ * \brief Translate the video pixel format (RetroPlayer to FlatBuffers)
+ */
+PixelFormat TranslatePixelFormat(AVPixelFormat pixelFormat)
+{
+ switch (pixelFormat)
+ {
+ case AV_PIX_FMT_RGBA:
+ return PixelFormat_RGBA_8888;
+
+ case AV_PIX_FMT_0RGB32:
+#if defined(__BIG_ENDIAN__)
+ return PixelFormat_XRGB_8888;
+#else
+ return PixelFormat_BGRX_8888;
+#endif
+
+ case AV_PIX_FMT_RGB565:
+#if defined(__BIG_ENDIAN__)
+ return PixelFormat_RGB_565_BE;
+#else
+ return PixelFormat_RGB_565_LE;
+#endif
+
+ case AV_PIX_FMT_RGB555:
+#if defined(__BIG_ENDIAN__)
+ return PixelFormat_RGB_555_BE;
+#else
+ return PixelFormat_RGB_555_LE;
+#endif
+
+ default:
+ break;
+ }
+
+ return PixelFormat_Unknown;
+}
+
+/*!
+ * \brief Translate the video pixel format (FlatBuffers to RetroPlayer)
+ */
+AVPixelFormat TranslatePixelFormat(PixelFormat pixelFormat)
+{
+ switch (pixelFormat)
+ {
+ case PixelFormat_RGBA_8888:
+ return AV_PIX_FMT_RGBA;
+
+ case PixelFormat_XRGB_8888:
+ return AV_PIX_FMT_0RGB;
+
+ case PixelFormat_BGRX_8888:
+ return AV_PIX_FMT_BGR0;
+
+ case PixelFormat_RGB_565_BE:
+ return AV_PIX_FMT_RGB565BE;
+
+ case PixelFormat_RGB_565_LE:
+ return AV_PIX_FMT_RGB565LE;
+
+ case PixelFormat_RGB_555_BE:
+ return AV_PIX_FMT_RGB555BE;
+
+ case PixelFormat_RGB_555_LE:
+ return AV_PIX_FMT_RGB555LE;
+
+ default:
+ break;
+ }
+
+ return AV_PIX_FMT_NONE;
+}
+
+/*!
+ * \brief Translate the video rotation (RetroPlayer to FlatBuffers)
+ */
+VideoRotation TranslateRotation(unsigned int rotationCCW)
+{
+ switch (rotationCCW)
+ {
+ case 0:
+ return VideoRotation_CCW_0;
+ case 90:
+ return VideoRotation_CCW_90;
+ case 180:
+ return VideoRotation_CCW_180;
+ case 270:
+ return VideoRotation_CCW_270;
+ default:
+ break;
+ }
+
+ return VideoRotation_CCW_0;
+}
+
+/*!
+ * \brief Translate the video rotation (RetroPlayer to FlatBuffers)
+ */
+unsigned int TranslateRotation(VideoRotation rotationCCW)
+{
+ switch (rotationCCW)
+ {
+ case VideoRotation_CCW_0:
+ return 0;
+ case VideoRotation_CCW_90:
+ return 90;
+ case VideoRotation_CCW_180:
+ return 180;
+ case VideoRotation_CCW_270:
+ return 270;
+ default:
+ break;
+ }
+
+ return 0;
+}
+} // namespace
+
+CSavestateFlatBuffer::CSavestateFlatBuffer()
+{
+ Reset();
+}
+
+CSavestateFlatBuffer::~CSavestateFlatBuffer() = default;
+
+void CSavestateFlatBuffer::Reset()
+{
+ m_builder.reset(new flatbuffers::FlatBufferBuilder(INITIAL_FLATBUFFER_SIZE));
+ m_data.clear();
+ m_savestate = nullptr;
+}
+
+bool CSavestateFlatBuffer::Serialize(const uint8_t*& data, size_t& size) const
+{
+ // Check if savestate was deserialized from vector or built with FlatBuffers
+ if (!m_data.empty())
+ {
+ data = m_data.data();
+ size = m_data.size();
+ }
+ else
+ {
+ data = m_builder->GetBufferPointer();
+ size = m_builder->GetSize();
+ }
+
+ return true;
+}
+
+SAVE_TYPE CSavestateFlatBuffer::Type() const
+{
+ if (m_savestate != nullptr)
+ return TranslateType(m_savestate->type());
+
+ return SAVE_TYPE::UNKNOWN;
+}
+
+void CSavestateFlatBuffer::SetType(SAVE_TYPE type)
+{
+ m_type = type;
+}
+
+uint8_t CSavestateFlatBuffer::Slot() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->slot();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetSlot(uint8_t slot)
+{
+ m_slot = slot;
+}
+
+std::string CSavestateFlatBuffer::Label() const
+{
+ std::string label;
+
+ if (m_savestate != nullptr && m_savestate->label())
+ label = m_savestate->label()->c_str();
+
+ return label;
+}
+
+void CSavestateFlatBuffer::SetLabel(const std::string& label)
+{
+ m_labelOffset.reset(new StringOffset{m_builder->CreateString(label)});
+}
+
+std::string CSavestateFlatBuffer::Caption() const
+{
+ std::string caption;
+
+ if (m_savestate != nullptr && m_savestate->caption())
+ caption = m_savestate->caption()->str();
+
+ return caption;
+}
+
+void CSavestateFlatBuffer::SetCaption(const std::string& caption)
+{
+ m_captionOffset = std::make_unique<StringOffset>(m_builder->CreateString(caption));
+}
+
+CDateTime CSavestateFlatBuffer::Created() const
+{
+ CDateTime createdUTC;
+
+ if (m_savestate != nullptr && m_savestate->created())
+ createdUTC.SetFromW3CDateTime(m_savestate->created()->c_str(), false);
+
+ return createdUTC;
+}
+
+void CSavestateFlatBuffer::SetCreated(const CDateTime& createdUTC)
+{
+ m_createdOffset =
+ std::make_unique<StringOffset>(m_builder->CreateString(createdUTC.GetAsW3CDateTime(true)));
+}
+
+std::string CSavestateFlatBuffer::GameFileName() const
+{
+ std::string gameFileName;
+
+ if (m_savestate != nullptr && m_savestate->game_file_name())
+ gameFileName = m_savestate->game_file_name()->c_str();
+
+ return gameFileName;
+}
+
+void CSavestateFlatBuffer::SetGameFileName(const std::string& gameFileName)
+{
+ m_gameFileNameOffset.reset(new StringOffset{m_builder->CreateString(gameFileName)});
+}
+
+uint64_t CSavestateFlatBuffer::TimestampFrames() const
+{
+ return m_savestate->timestamp_frames();
+}
+
+void CSavestateFlatBuffer::SetTimestampFrames(uint64_t timestampFrames)
+{
+ m_timestampFrames = timestampFrames;
+}
+
+double CSavestateFlatBuffer::TimestampWallClock() const
+{
+ if (m_savestate != nullptr)
+ return static_cast<double>(m_savestate->timestamp_wall_clock_ns()) / 1000.0 / 1000.0 / 1000.0;
+
+ return 0.0;
+}
+
+void CSavestateFlatBuffer::SetTimestampWallClock(double timestampWallClock)
+{
+ m_timestampWallClock = timestampWallClock;
+}
+
+std::string CSavestateFlatBuffer::GameClientID() const
+{
+ std::string gameClientId;
+
+ if (m_savestate != nullptr && m_savestate->emulator_addon_id())
+ gameClientId = m_savestate->emulator_addon_id()->c_str();
+
+ return gameClientId;
+}
+
+void CSavestateFlatBuffer::SetGameClientID(const std::string& gameClientId)
+{
+ m_emulatorAddonIdOffset.reset(new StringOffset{m_builder->CreateString(gameClientId)});
+}
+
+std::string CSavestateFlatBuffer::GameClientVersion() const
+{
+ std::string gameClientVersion;
+
+ if (m_savestate != nullptr && m_savestate->emulator_version())
+ gameClientVersion = m_savestate->emulator_version()->c_str();
+
+ return gameClientVersion;
+}
+
+void CSavestateFlatBuffer::SetGameClientVersion(const std::string& gameClientVersion)
+{
+ m_emulatorVersionOffset.reset(new StringOffset{m_builder->CreateString(gameClientVersion)});
+}
+
+AVPixelFormat CSavestateFlatBuffer::GetPixelFormat() const
+{
+ if (m_savestate != nullptr)
+ return TranslatePixelFormat(m_savestate->pixel_format());
+
+ return AV_PIX_FMT_NONE;
+}
+
+void CSavestateFlatBuffer::SetPixelFormat(AVPixelFormat pixelFormat)
+{
+ m_pixelFormat = pixelFormat;
+}
+
+unsigned int CSavestateFlatBuffer::GetNominalWidth() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->nominal_width();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetNominalWidth(unsigned int nominalWidth)
+{
+ m_nominalWidth = nominalWidth;
+}
+
+unsigned int CSavestateFlatBuffer::GetNominalHeight() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->nominal_height();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetNominalHeight(unsigned int nominalHeight)
+{
+ m_nominalHeight = nominalHeight;
+}
+
+unsigned int CSavestateFlatBuffer::GetMaxWidth() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->max_width();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetMaxWidth(unsigned int maxWidth)
+{
+ m_maxWidth = maxWidth;
+}
+
+unsigned int CSavestateFlatBuffer::GetMaxHeight() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->max_height();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetMaxHeight(unsigned int maxHeight)
+{
+ m_maxHeight = maxHeight;
+}
+
+float CSavestateFlatBuffer::GetPixelAspectRatio() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->pixel_aspect_ratio();
+
+ return 0.0f;
+}
+
+void CSavestateFlatBuffer::SetPixelAspectRatio(float pixelAspectRatio)
+{
+ m_pixelAspectRatio = pixelAspectRatio;
+}
+
+const uint8_t* CSavestateFlatBuffer::GetVideoData() const
+{
+ if (m_savestate != nullptr && m_savestate->video_data())
+ return m_savestate->video_data()->data();
+
+ return nullptr;
+}
+
+size_t CSavestateFlatBuffer::GetVideoSize() const
+{
+ if (m_savestate != nullptr && m_savestate->video_data())
+ return m_savestate->video_data()->size();
+
+ return 0;
+}
+
+uint8_t* CSavestateFlatBuffer::GetVideoBuffer(size_t size)
+{
+ uint8_t* videoBuffer = nullptr;
+
+ m_videoDataOffset =
+ std::make_unique<VectorOffset>(m_builder->CreateUninitializedVector(size, &videoBuffer));
+
+ return videoBuffer;
+}
+
+unsigned int CSavestateFlatBuffer::GetVideoWidth() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->video_width();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetVideoWidth(unsigned int videoWidth)
+{
+ m_videoWidth = videoWidth;
+}
+
+unsigned int CSavestateFlatBuffer::GetVideoHeight() const
+{
+ if (m_savestate != nullptr)
+ return m_savestate->video_height();
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetVideoHeight(unsigned int videoHeight)
+{
+ m_videoHeight = videoHeight;
+}
+
+unsigned int CSavestateFlatBuffer::GetRotationDegCCW() const
+{
+ if (m_savestate != nullptr)
+ return TranslateRotation(m_savestate->rotation_ccw());
+
+ return 0;
+}
+
+void CSavestateFlatBuffer::SetRotationDegCCW(unsigned int rotationCCW)
+{
+ m_rotationCCW = rotationCCW;
+}
+
+const uint8_t* CSavestateFlatBuffer::GetMemoryData() const
+{
+ if (m_savestate != nullptr && m_savestate->memory_data())
+ return m_savestate->memory_data()->data();
+
+ return nullptr;
+}
+
+size_t CSavestateFlatBuffer::GetMemorySize() const
+{
+ if (m_savestate != nullptr && m_savestate->memory_data())
+ return m_savestate->memory_data()->size();
+
+ return 0;
+}
+
+uint8_t* CSavestateFlatBuffer::GetMemoryBuffer(size_t size)
+{
+ uint8_t* memoryBuffer = nullptr;
+
+ m_memoryDataOffset.reset(
+ new VectorOffset{m_builder->CreateUninitializedVector(size, &memoryBuffer)});
+
+ return memoryBuffer;
+}
+
+void CSavestateFlatBuffer::Finalize()
+{
+ // Helper class to build the nested Savestate table
+ SavestateBuilder savestateBuilder(*m_builder);
+
+ savestateBuilder.add_version(SCHEMA_VERSION);
+
+ savestateBuilder.add_type(TranslateType(m_type));
+
+ savestateBuilder.add_slot(m_slot);
+
+ if (m_labelOffset)
+ {
+ savestateBuilder.add_label(*m_labelOffset);
+ m_labelOffset.reset();
+ }
+
+ if (m_captionOffset)
+ {
+ savestateBuilder.add_caption(*m_captionOffset);
+ m_captionOffset.reset();
+ }
+
+ if (m_createdOffset)
+ {
+ savestateBuilder.add_created(*m_createdOffset);
+ m_createdOffset.reset();
+ }
+
+ if (m_gameFileNameOffset)
+ {
+ savestateBuilder.add_game_file_name(*m_gameFileNameOffset);
+ m_gameFileNameOffset.reset();
+ }
+
+ savestateBuilder.add_timestamp_frames(m_timestampFrames);
+
+ const uint64_t wallClockNs =
+ static_cast<uint64_t>(m_timestampWallClock * 1000.0 * 1000.0 * 1000.0);
+ savestateBuilder.add_timestamp_wall_clock_ns(wallClockNs);
+
+ if (m_emulatorAddonIdOffset)
+ {
+ savestateBuilder.add_emulator_addon_id(*m_emulatorAddonIdOffset);
+ m_emulatorAddonIdOffset.reset();
+ }
+
+ if (m_emulatorVersionOffset)
+ {
+ savestateBuilder.add_emulator_version(*m_emulatorVersionOffset);
+ m_emulatorVersionOffset.reset();
+ }
+
+ savestateBuilder.add_pixel_format(TranslatePixelFormat(m_pixelFormat));
+
+ savestateBuilder.add_nominal_width(m_nominalWidth);
+
+ savestateBuilder.add_nominal_height(m_nominalHeight);
+
+ savestateBuilder.add_max_width(m_maxWidth);
+
+ savestateBuilder.add_max_height(m_maxHeight);
+
+ savestateBuilder.add_pixel_aspect_ratio(m_pixelAspectRatio);
+
+ if (m_videoDataOffset)
+ {
+ savestateBuilder.add_video_data(*m_videoDataOffset);
+ m_videoDataOffset.reset();
+ }
+
+ savestateBuilder.add_video_width(m_videoWidth);
+
+ savestateBuilder.add_video_height(m_videoHeight);
+
+ savestateBuilder.add_rotation_ccw(TranslateRotation(m_rotationCCW));
+
+ if (m_memoryDataOffset)
+ {
+ savestateBuilder.add_memory_data(*m_memoryDataOffset);
+ m_memoryDataOffset.reset();
+ }
+
+ auto savestate = savestateBuilder.Finish();
+ FinishSavestateBuffer(*m_builder, savestate);
+
+ m_savestate = GetSavestate(m_builder->GetBufferPointer());
+}
+
+bool CSavestateFlatBuffer::Deserialize(std::vector<uint8_t> data)
+{
+ flatbuffers::Verifier verifier(data.data(), data.size());
+ if (VerifySavestateBuffer(verifier))
+ {
+ const Savestate* savestate = GetSavestate(data.data());
+
+ if (savestate->version() < SCHEMA_MIN_VERSION)
+ {
+ CLog::Log(LOGERROR,
+ "RetroPlayer[SAVE): Schema version {} not supported, must be at least version {}",
+ savestate->version(), SCHEMA_MIN_VERSION);
+ }
+ else
+ {
+ m_data = std::move(data);
+ m_savestate = GetSavestate(m_data.data());
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h
new file mode 100644
index 0000000..fa42a9b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ISavestate.h"
+
+#include <memory>
+
+#include <flatbuffers/flatbuffers.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+struct Savestate;
+struct SavestateBuilder;
+
+class CSavestateFlatBuffer : public ISavestate
+{
+public:
+ CSavestateFlatBuffer();
+ ~CSavestateFlatBuffer() override;
+
+ // Implementation of ISavestate
+ void Reset() override;
+ bool Serialize(const uint8_t*& data, size_t& size) const override;
+ SAVE_TYPE Type() const override;
+ uint8_t Slot() const override;
+ std::string Label() const override;
+ std::string Caption() const override;
+ CDateTime Created() const override;
+ std::string GameFileName() const override;
+ uint64_t TimestampFrames() const override;
+ double TimestampWallClock() const override;
+ std::string GameClientID() const override;
+ std::string GameClientVersion() const override;
+ AVPixelFormat GetPixelFormat() const override;
+ unsigned int GetNominalWidth() const override;
+ unsigned int GetNominalHeight() const override;
+ unsigned int GetMaxWidth() const override;
+ unsigned int GetMaxHeight() const override;
+ float GetPixelAspectRatio() const override;
+ const uint8_t* GetVideoData() const override;
+ size_t GetVideoSize() const override;
+ unsigned int GetVideoWidth() const override;
+ unsigned int GetVideoHeight() const override;
+ unsigned int GetRotationDegCCW() const override;
+ const uint8_t* GetMemoryData() const override;
+ size_t GetMemorySize() const override;
+ void SetType(SAVE_TYPE type) override;
+ void SetSlot(uint8_t slot) override;
+ void SetLabel(const std::string& label) override;
+ void SetCaption(const std::string& caption) override;
+ void SetCreated(const CDateTime& createdUTC) override;
+ void SetGameFileName(const std::string& gameFileName) override;
+ void SetTimestampFrames(uint64_t timestampFrames) override;
+ void SetTimestampWallClock(double timestampWallClock) override;
+ void SetGameClientID(const std::string& gameClient) override;
+ void SetGameClientVersion(const std::string& gameClient) override;
+ void SetPixelFormat(AVPixelFormat pixelFormat) override;
+ void SetNominalWidth(unsigned int nominalWidth) override;
+ void SetNominalHeight(unsigned int nominalHeight) override;
+ void SetMaxWidth(unsigned int maxWidth) override;
+ void SetMaxHeight(unsigned int maxHeight) override;
+ void SetPixelAspectRatio(float pixelAspectRatio) override;
+ uint8_t* GetVideoBuffer(size_t size) override;
+ void SetVideoWidth(unsigned int videoWidth) override;
+ void SetVideoHeight(unsigned int videoHeight) override;
+ void SetRotationDegCCW(unsigned int rotationCCW) override;
+ uint8_t* GetMemoryBuffer(size_t size) override;
+ void Finalize() override;
+ bool Deserialize(std::vector<uint8_t> data) override;
+
+private:
+ /*!
+ * \brief Helper class to hold data needed in creation of a FlatBuffer
+ *
+ * The builder is used when deserializing from individual fields.
+ */
+ std::unique_ptr<flatbuffers::FlatBufferBuilder> m_builder;
+
+ /*!
+ * \brief System memory storage (for deserializing savestates)
+ *
+ * This memory is used when deserializing from a vector.
+ */
+ std::vector<uint8_t> m_data;
+
+ /*!
+ * \brief FlatBuffer struct used for accessing data
+ */
+ const Savestate* m_savestate = nullptr;
+
+ using StringOffset = flatbuffers::Offset<flatbuffers::String>;
+ using VectorOffset = flatbuffers::Offset<flatbuffers::Vector<uint8_t>>;
+
+ // Temporary deserialization variables
+ SAVE_TYPE m_type = SAVE_TYPE::UNKNOWN;
+ uint8_t m_slot = 0;
+ std::unique_ptr<StringOffset> m_labelOffset;
+ std::unique_ptr<StringOffset> m_captionOffset;
+ std::unique_ptr<StringOffset> m_createdOffset;
+ std::unique_ptr<StringOffset> m_gameFileNameOffset;
+ uint64_t m_timestampFrames = 0;
+ double m_timestampWallClock = 0.0;
+ std::unique_ptr<StringOffset> m_emulatorAddonIdOffset;
+ std::unique_ptr<StringOffset> m_emulatorVersionOffset;
+ AVPixelFormat m_pixelFormat{AV_PIX_FMT_NONE};
+ unsigned int m_nominalWidth{0};
+ unsigned int m_nominalHeight{0};
+ unsigned int m_maxWidth{0};
+ unsigned int m_maxHeight{0};
+ float m_pixelAspectRatio{0.0f};
+ std::unique_ptr<VectorOffset> m_videoDataOffset;
+ unsigned int m_videoWidth{0};
+ unsigned int m_videoHeight{0};
+ unsigned int m_rotationCCW{0};
+ std::unique_ptr<VectorOffset> m_memoryDataOffset;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h b/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h
new file mode 100644
index 0000000..e4bbc9b
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Type of save action, either:
+ *
+ * - automatic (saving was not prompted by the user)
+ * - manual (user manually prompted the save)
+ */
+enum class SAVE_TYPE
+{
+ UNKNOWN,
+ AUTO,
+ MANUAL,
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/CMakeLists.txt b/xbmc/cores/RetroPlayer/streams/CMakeLists.txt
new file mode 100644
index 0000000..b5acbc9
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES RetroPlayerAudio.cpp
+ RetroPlayerStreamTypes.cpp
+ RetroPlayerVideo.cpp
+ RPStreamManager.cpp
+)
+
+set(HEADERS IRetroPlayerStream.h
+ IStreamManager.h
+ RetroPlayerAudio.h
+ RetroPlayerStreamTypes.h
+ RetroPlayerVideo.h
+ RPStreamManager.h
+)
+
+core_add_library(retroplayer_streams)
diff --git a/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h b/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h
new file mode 100644
index 0000000..dddf3ff
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RetroPlayerStreamTypes.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+
+struct StreamProperties
+{
+};
+
+struct StreamBuffer
+{
+};
+
+struct StreamPacket
+{
+};
+
+class IRetroPlayerStream
+{
+public:
+ virtual ~IRetroPlayerStream() = default;
+
+ /*!
+ * \brief Open a stream
+ *
+ * \return True if the stream was opened, false otherwise
+ */
+ virtual bool OpenStream(const StreamProperties& properties) = 0;
+
+ /*!
+ * \brief Get a buffer for zero-copy stream data
+ *
+ * \param width The framebuffer width, or 0 for no width specified
+ * \param height The framebuffer height, or 0 for no height specified
+ * \param[out] buffer The buffer, or unmodified if false is returned
+ *
+ * \return True if a buffer was returned, false otherwise
+ */
+ virtual bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) = 0;
+
+ /*!
+ * \brief Add a data packet to a stream
+ *
+ * \param packet The data packet
+ */
+ virtual void AddStreamData(const StreamPacket& packet) = 0;
+
+ /*!
+ * \brief Close the stream
+ */
+ virtual void CloseStream() = 0;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/IStreamManager.h b/xbmc/cores/RetroPlayer/streams/IStreamManager.h
new file mode 100644
index 0000000..b490642
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/IStreamManager.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RetroPlayerStreamTypes.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+
+class IStreamManager
+{
+public:
+ virtual ~IStreamManager() = default;
+
+ /*!
+ * \brief Create a stream for gameplay data
+ *
+ * \param streamType The stream type
+ *
+ * \return A stream handle, or empty on failure
+ */
+ virtual StreamPtr CreateStream(StreamType streamType) = 0;
+
+ /*!
+ * \brief Free the specified stream
+ *
+ * \param stream The stream to close
+ */
+ virtual void CloseStream(StreamPtr stream) = 0;
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp
new file mode 100644
index 0000000..2682d4d
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RPStreamManager.h"
+
+#include "IRetroPlayerStream.h"
+#include "RetroPlayerAudio.h"
+#include "RetroPlayerVideo.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRPStreamManager::CRPStreamManager(CRPRenderManager& renderManager, CRPProcessInfo& processInfo)
+ : m_renderManager(renderManager), m_processInfo(processInfo)
+{
+}
+
+void CRPStreamManager::EnableAudio(bool bEnable)
+{
+ if (m_audioStream != nullptr)
+ m_audioStream->Enable(bEnable);
+}
+
+StreamPtr CRPStreamManager::CreateStream(StreamType streamType)
+{
+ switch (streamType)
+ {
+ case StreamType::AUDIO:
+ {
+ // Save pointer to audio stream
+ m_audioStream = new CRetroPlayerAudio(m_processInfo);
+
+ return StreamPtr(m_audioStream);
+ }
+ case StreamType::VIDEO:
+ case StreamType::SW_BUFFER:
+ {
+ return StreamPtr(new CRetroPlayerVideo(m_renderManager, m_processInfo));
+ }
+ case StreamType::HW_BUFFER:
+ {
+ // return StreamPtr(new CRetroPlayerHardware(m_renderManager, m_processInfo)); //! @todo
+ }
+ default:
+ break;
+ }
+
+ return StreamPtr();
+}
+
+void CRPStreamManager::CloseStream(StreamPtr stream)
+{
+ if (stream)
+ {
+ if (stream.get() == m_audioStream)
+ m_audioStream = nullptr;
+
+ stream->CloseStream();
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/streams/RPStreamManager.h b/xbmc/cores/RetroPlayer/streams/RPStreamManager.h
new file mode 100644
index 0000000..3615329
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IStreamManager.h"
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRetroPlayerAudio;
+class CRPProcessInfo;
+class CRPRenderManager;
+
+class CRPStreamManager : public IStreamManager
+{
+public:
+ CRPStreamManager(CRPRenderManager& renderManager, CRPProcessInfo& processInfo);
+ ~CRPStreamManager() override = default;
+
+ void EnableAudio(bool bEnable);
+
+ // Implementation of IStreamManager
+ StreamPtr CreateStream(StreamType streamType) override;
+ void CloseStream(StreamPtr stream) override;
+
+private:
+ // Construction parameters
+ CRPRenderManager& m_renderManager;
+ CRPProcessInfo& m_processInfo;
+
+ // Stream parameters
+ CRetroPlayerAudio* m_audioStream = nullptr;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp
new file mode 100644
index 0000000..d4e5a05
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerAudio.h"
+
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "cores/AudioEngine/Utils/AEChannelInfo.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/RetroPlayer/audio/AudioTranslator.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "utils/log.h"
+
+#include <cmath>
+
+using namespace KODI;
+using namespace RETRO;
+
+const double MAX_DELAY = 0.3; // seconds
+
+CRetroPlayerAudio::CRetroPlayerAudio(CRPProcessInfo& processInfo)
+ : m_processInfo(processInfo), m_pAudioStream(nullptr), m_bAudioEnabled(true)
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Initializing audio");
+}
+
+CRetroPlayerAudio::~CRetroPlayerAudio()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Deinitializing audio");
+
+ CloseStream();
+}
+
+bool CRetroPlayerAudio::OpenStream(const StreamProperties& properties)
+{
+ const AudioStreamProperties& audioProperties =
+ static_cast<const AudioStreamProperties&>(properties);
+
+ const AEDataFormat pcmFormat = CAudioTranslator::TranslatePCMFormat(audioProperties.format);
+ if (pcmFormat == AE_FMT_INVALID)
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Unknown PCM format: {}",
+ static_cast<int>(audioProperties.format));
+ return false;
+ }
+
+ unsigned int iSampleRate = static_cast<unsigned int>(std::round(audioProperties.sampleRate));
+ if (iSampleRate == 0)
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Invalid samplerate: {:f}", audioProperties.sampleRate);
+ return false;
+ }
+
+ CAEChannelInfo channelLayout;
+ for (auto it = audioProperties.channelMap.begin(); it != audioProperties.channelMap.end(); ++it)
+ {
+ AEChannel channel = CAudioTranslator::TranslateAudioChannel(*it);
+ if (channel == AE_CH_NULL)
+ break;
+
+ channelLayout += channel;
+ }
+
+ if (!channelLayout.IsLayoutValid())
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Empty channel layout");
+ return false;
+ }
+
+ if (m_pAudioStream != nullptr)
+ CloseStream();
+
+ IAE* audioEngine = CServiceBroker::GetActiveAE();
+ if (audioEngine == nullptr)
+ return false;
+
+ CLog::Log(
+ LOGINFO,
+ "RetroPlayer[AUDIO]: Creating audio stream, format = {}, sample rate = {}, channels = {}",
+ CAEUtil::DataFormatToStr(pcmFormat), iSampleRate, channelLayout.Count());
+
+ AEAudioFormat audioFormat;
+ audioFormat.m_dataFormat = pcmFormat;
+ audioFormat.m_sampleRate = iSampleRate;
+ audioFormat.m_channelLayout = channelLayout;
+ m_pAudioStream = audioEngine->MakeStream(audioFormat);
+
+ if (m_pAudioStream == nullptr)
+ {
+ CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Failed to create audio stream");
+ return false;
+ }
+
+ m_processInfo.SetAudioChannels(audioFormat.m_channelLayout);
+ m_processInfo.SetAudioSampleRate(audioFormat.m_sampleRate);
+ m_processInfo.SetAudioBitsPerSample(CAEUtil::DataFormatToUsedBits(audioFormat.m_dataFormat));
+
+ return true;
+}
+
+void CRetroPlayerAudio::AddStreamData(const StreamPacket& packet)
+{
+ const AudioStreamPacket& audioPacket = static_cast<const AudioStreamPacket&>(packet);
+
+ if (m_bAudioEnabled)
+ {
+ if (m_pAudioStream)
+ {
+ const double delaySecs = m_pAudioStream->GetDelay();
+
+ const size_t frameSize = m_pAudioStream->GetChannelCount() *
+ (CAEUtil::DataFormatToBits(m_pAudioStream->GetDataFormat()) >> 3);
+
+ const unsigned int frameCount = static_cast<unsigned int>(audioPacket.size / frameSize);
+
+ if (delaySecs > MAX_DELAY)
+ {
+ m_pAudioStream->Flush();
+ CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Audio delay ({:0.2f} ms) is too high - flushing",
+ delaySecs * 1000);
+ }
+
+ m_pAudioStream->AddData(&audioPacket.data, 0, frameCount, nullptr);
+ }
+ }
+}
+
+void CRetroPlayerAudio::CloseStream()
+{
+ if (m_pAudioStream)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Closing audio stream");
+
+ m_pAudioStream.reset();
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h
new file mode 100644
index 0000000..184fd54
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IRetroPlayerStream.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+
+#include <memory>
+
+class IAEStream;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfo;
+
+struct AudioStreamProperties : public StreamProperties
+{
+ AudioStreamProperties(PCMFormat format, double sampleRate, AudioChannelMap channelMap)
+ : format(format), sampleRate(sampleRate), channelMap(channelMap)
+ {
+ }
+
+ PCMFormat format;
+ double sampleRate;
+ AudioChannelMap channelMap;
+};
+
+struct AudioStreamPacket : public StreamPacket
+{
+ AudioStreamPacket(const uint8_t* data, size_t size) : data(data), size(size) {}
+
+ const uint8_t* data;
+ size_t size;
+};
+
+class CRetroPlayerAudio : public IRetroPlayerStream
+{
+public:
+ explicit CRetroPlayerAudio(CRPProcessInfo& processInfo);
+ ~CRetroPlayerAudio() override;
+
+ void Enable(bool bEnabled) { m_bAudioEnabled = bEnabled; }
+
+ // implementation of IRetroPlayerStream
+ bool OpenStream(const StreamProperties& properties) override;
+ bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) override
+ {
+ return false;
+ }
+ void AddStreamData(const StreamPacket& packet) override;
+ void CloseStream() override;
+
+private:
+ CRPProcessInfo& m_processInfo;
+ IAE::StreamPtr m_pAudioStream;
+ bool m_bAudioEnabled;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp
new file mode 100644
index 0000000..7f7d0de
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerStreamTypes.h"
+
+#include "IRetroPlayerStream.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+void DeleteStream::operator()(IRetroPlayerStream* stream)
+{
+ delete stream;
+}
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h
new file mode 100644
index 0000000..aa5686a
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <array>
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class IRetroPlayerStream;
+
+struct DeleteStream
+{
+ void operator()(IRetroPlayerStream* stream);
+};
+
+using StreamPtr = std::unique_ptr<IRetroPlayerStream, DeleteStream>;
+
+enum class StreamType
+{
+ AUDIO,
+ VIDEO,
+ SW_BUFFER,
+ HW_BUFFER,
+};
+
+enum class PCMFormat
+{
+ FMT_UNKNOWN,
+ FMT_S16NE,
+};
+
+enum class AudioChannel
+{
+ CH_NULL, // Channel list terminator
+ CH_FL,
+ CH_FR,
+ CH_FC,
+ CH_LFE,
+ CH_BL,
+ CH_BR,
+ CH_FLOC,
+ CH_FROC,
+ CH_BC,
+ CH_SL,
+ CH_SR,
+ CH_TFL,
+ CH_TFR,
+ CH_TFC,
+ CH_TC,
+ CH_TBL,
+ CH_TBR,
+ CH_TBC,
+ CH_BLOC,
+ CH_BROC,
+ CH_COUNT
+};
+
+using AudioChannelMap = std::array<AudioChannel, static_cast<unsigned int>(AudioChannel::CH_COUNT)>;
+
+enum class PixelFormat
+{
+ FMT_UNKNOWN,
+ FMT_0RGB8888,
+ FMT_RGB565,
+ FMT_0RGB1555,
+};
+
+enum class VideoRotation
+{
+ ROTATION_0,
+ ROTATION_90_CCW,
+ ROTATION_180_CCW,
+ ROTATION_270_CCW,
+};
+
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp
new file mode 100644
index 0000000..14c66a8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RetroPlayerVideo.h"
+
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/RPRenderManager.h"
+#include "cores/RetroPlayer/rendering/RenderTranslator.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CRetroPlayerVideo::CRetroPlayerVideo(CRPRenderManager& renderManager, CRPProcessInfo& processInfo)
+ : m_renderManager(renderManager), m_processInfo(processInfo)
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Initializing video");
+
+ m_renderManager.Initialize();
+}
+
+CRetroPlayerVideo::~CRetroPlayerVideo()
+{
+ CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Deinitializing video");
+
+ CloseStream();
+ m_renderManager.Deinitialize();
+}
+
+bool CRetroPlayerVideo::OpenStream(const StreamProperties& properties)
+{
+ const VideoStreamProperties& videoProperties =
+ static_cast<const VideoStreamProperties&>(properties);
+
+ if (m_bOpen)
+ {
+ CloseStream();
+ m_bOpen = false;
+ }
+
+ const AVPixelFormat pixfmt = videoProperties.pixfmt;
+ const unsigned int nominalWidth = videoProperties.nominalWidth;
+ const unsigned int nominalHeight = videoProperties.nominalHeight;
+ const unsigned int maxWidth = videoProperties.maxWidth;
+ const unsigned int maxHeight = videoProperties.maxHeight;
+ const float pixelAspectRatio = videoProperties.pixelAspectRatio;
+
+ CLog::Log(LOGDEBUG,
+ "RetroPlayer[VIDEO]: Creating video stream - format {}, nominal {}x{}, max {}x{}",
+ CRenderTranslator::TranslatePixelFormat(pixfmt), nominalWidth, nominalHeight, maxWidth,
+ maxHeight);
+
+ m_processInfo.SetVideoPixelFormat(pixfmt);
+ m_processInfo.SetVideoDimensions(nominalWidth, nominalHeight); // Report nominal height for now
+
+ if (m_renderManager.Configure(pixfmt, nominalWidth, nominalHeight, maxWidth, maxHeight,
+ pixelAspectRatio))
+ m_bOpen = true;
+
+ return m_bOpen;
+}
+
+bool CRetroPlayerVideo::GetStreamBuffer(unsigned int width,
+ unsigned int height,
+ StreamBuffer& buffer)
+{
+ VideoStreamBuffer& videoBuffer = static_cast<VideoStreamBuffer&>(buffer);
+
+ if (m_bOpen)
+ return m_renderManager.GetVideoBuffer(width, height, videoBuffer);
+
+ return false;
+}
+
+void CRetroPlayerVideo::AddStreamData(const StreamPacket& packet)
+{
+ const VideoStreamPacket& videoPacket = static_cast<const VideoStreamPacket&>(packet);
+
+ if (m_bOpen)
+ {
+ unsigned int orientationDegCCW = 0;
+ switch (videoPacket.rotation)
+ {
+ case VideoRotation::ROTATION_90_CCW:
+ orientationDegCCW = 90;
+ break;
+ case VideoRotation::ROTATION_180_CCW:
+ orientationDegCCW = 180;
+ break;
+ case VideoRotation::ROTATION_270_CCW:
+ orientationDegCCW = 270;
+ break;
+ default:
+ break;
+ }
+
+ m_renderManager.AddFrame(videoPacket.data, videoPacket.size, videoPacket.width,
+ videoPacket.height, orientationDegCCW);
+ }
+}
+
+void CRetroPlayerVideo::CloseStream()
+{
+ if (m_bOpen)
+ {
+ CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Closing video stream");
+
+ m_renderManager.Flush();
+ m_bOpen = false;
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h
new file mode 100644
index 0000000..8d153ab
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IRetroPlayerStream.h"
+#include "cores/RetroPlayer/RetroPlayerTypes.h"
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+namespace KODI
+{
+namespace RETRO
+{
+class CRPProcessInfo;
+class CRPRenderManager;
+
+struct VideoStreamProperties : public StreamProperties
+{
+ VideoStreamProperties(AVPixelFormat pixfmt,
+ unsigned int nominalWidth,
+ unsigned int nominalHeight,
+ unsigned int maxWidth,
+ unsigned int maxHeight,
+ float pixelAspectRatio)
+ : pixfmt(pixfmt),
+ nominalWidth(nominalWidth),
+ nominalHeight(nominalHeight),
+ maxWidth(maxWidth),
+ maxHeight(maxHeight),
+ pixelAspectRatio(pixelAspectRatio)
+ {
+ }
+
+ AVPixelFormat pixfmt;
+ unsigned int nominalWidth;
+ unsigned int nominalHeight;
+ unsigned int maxWidth;
+ unsigned int maxHeight;
+ float pixelAspectRatio;
+};
+
+struct VideoStreamBuffer : public StreamBuffer
+{
+ VideoStreamBuffer() = default;
+
+ VideoStreamBuffer(
+ AVPixelFormat pixfmt, uint8_t* data, size_t size, DataAccess access, DataAlignment alignment)
+ : pixfmt(pixfmt), data(data), size(size), access(access), alignment(alignment)
+ {
+ }
+
+ AVPixelFormat pixfmt{AV_PIX_FMT_NONE};
+ uint8_t* data{nullptr};
+ size_t size{0};
+ DataAccess access{DataAccess::READ_WRITE};
+ DataAlignment alignment{DataAlignment::DATA_UNALIGNED};
+};
+
+struct VideoStreamPacket : public StreamPacket
+{
+ VideoStreamPacket(unsigned int width,
+ unsigned int height,
+ VideoRotation rotation,
+ const uint8_t* data,
+ size_t size)
+ : width(width), height(height), rotation(rotation), data(data), size(size)
+ {
+ }
+
+ unsigned int width;
+ unsigned int height;
+ VideoRotation rotation;
+ const uint8_t* data;
+ size_t size;
+};
+
+/*!
+ * \brief Renders video frames provided by the game loop
+ *
+ * \sa CRPRenderManager
+ */
+class CRetroPlayerVideo : public IRetroPlayerStream
+{
+public:
+ CRetroPlayerVideo(CRPRenderManager& m_renderManager, CRPProcessInfo& m_processInfo);
+ ~CRetroPlayerVideo() override;
+
+ // implementation of IRetroPlayerStream
+ bool OpenStream(const StreamProperties& properties) override;
+ bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) override;
+ void AddStreamData(const StreamPacket& packet) override;
+ void CloseStream() override;
+
+private:
+ // Construction parameters
+ CRPRenderManager& m_renderManager;
+ CRPProcessInfo& m_processInfo;
+
+ // Stream properties
+ bool m_bOpen = false;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp
new file mode 100644
index 0000000..6d52d3f
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BasicMemoryStream.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+CBasicMemoryStream::CBasicMemoryStream()
+{
+ Reset();
+}
+
+void CBasicMemoryStream::Init(size_t frameSize, uint64_t maxFrameCount)
+{
+ Reset();
+
+ m_frameSize = frameSize;
+}
+
+void CBasicMemoryStream::Reset()
+{
+ m_frameSize = 0;
+ m_frameBuffer.reset();
+ m_bHasFrame = false;
+}
+
+uint8_t* CBasicMemoryStream::BeginFrame()
+{
+ if (m_frameSize == 0)
+ return nullptr;
+
+ if (!m_frameBuffer)
+ m_frameBuffer.reset(new uint8_t[m_frameSize]);
+
+ m_bHasFrame = false;
+
+ return m_frameBuffer.get();
+}
+
+void CBasicMemoryStream::SubmitFrame()
+{
+ if (m_frameBuffer)
+ m_bHasFrame = true;
+}
+
+const uint8_t* CBasicMemoryStream::CurrentFrame() const
+{
+ return m_bHasFrame ? m_frameBuffer.get() : nullptr;
+}
diff --git a/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h
new file mode 100644
index 0000000..c3cc716
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IMemoryStream.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CBasicMemoryStream : public IMemoryStream
+{
+public:
+ CBasicMemoryStream();
+
+ ~CBasicMemoryStream() override = default;
+
+ // implementation of IMemoryStream
+ void Init(size_t frameSize, uint64_t maxFrameCount) override;
+ void Reset() override;
+ size_t FrameSize() const override { return m_frameSize; }
+ uint64_t MaxFrameCount() const override { return 1; }
+ void SetMaxFrameCount(uint64_t maxFrameCount) override {}
+ uint8_t* BeginFrame() override;
+ void SubmitFrame() override;
+ const uint8_t* CurrentFrame() const override;
+ uint64_t FutureFramesAvailable() const override { return 0; }
+ uint64_t AdvanceFrames(uint64_t frameCount) override { return 0; }
+ uint64_t PastFramesAvailable() const override { return 0; }
+ uint64_t RewindFrames(uint64_t frameCount) override { return 0; }
+ uint64_t GetFrameCounter() const override { return 0; }
+ void SetFrameCounter(uint64_t frameCount) override{};
+
+private:
+ size_t m_frameSize;
+ std::unique_ptr<uint8_t[]> m_frameBuffer;
+ bool m_bHasFrame;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt b/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt
new file mode 100644
index 0000000..7297bc6
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES BasicMemoryStream.cpp
+ DeltaPairMemoryStream.cpp
+ LinearMemoryStream.cpp
+)
+
+set(HEADERS BasicMemoryStream.h
+ DeltaPairMemoryStream.h
+ IMemoryStream.h
+ LinearMemoryStream.h
+)
+
+core_add_library(retroplayer_memory)
diff --git a/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp
new file mode 100644
index 0000000..eae65cd
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DeltaPairMemoryStream.h"
+
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+void CDeltaPairMemoryStream::Reset()
+{
+ CLinearMemoryStream::Reset();
+
+ m_rewindBuffer.clear();
+}
+
+void CDeltaPairMemoryStream::SubmitFrameInternal()
+{
+ m_rewindBuffer.emplace_back();
+ MemoryFrame& frame = m_rewindBuffer.back();
+
+ // Record frame history
+ frame.frameHistoryCount = m_currentFrameHistory++;
+
+ uint32_t* currentFrame = m_currentFrame.get();
+ uint32_t* nextFrame = m_nextFrame.get();
+
+ for (size_t i = 0; i < m_paddedFrameSize; i++)
+ {
+ uint32_t xor_val = currentFrame[i] ^ nextFrame[i];
+ if (xor_val)
+ {
+ DeltaPair pair = {i, xor_val};
+ frame.buffer.push_back(pair);
+ }
+ }
+
+ // Delta is generated, bring the new frame forward (m_nextFrame is now disposable)
+ std::swap(m_currentFrame, m_nextFrame);
+
+ m_bHasNextFrame = false;
+
+ if (PastFramesAvailable() + 1 > MaxFrameCount())
+ CullPastFrames(1);
+}
+
+uint64_t CDeltaPairMemoryStream::PastFramesAvailable() const
+{
+ return static_cast<uint64_t>(m_rewindBuffer.size());
+}
+
+uint64_t CDeltaPairMemoryStream::RewindFrames(uint64_t frameCount)
+{
+ uint64_t rewound;
+
+ for (rewound = 0; rewound < frameCount; rewound++)
+ {
+ if (m_rewindBuffer.empty())
+ break;
+
+ const MemoryFrame& frame = m_rewindBuffer.back();
+ const DeltaPair* buffer = frame.buffer.data();
+
+ size_t bufferSize = frame.buffer.size();
+
+ // buffer pointer redirection violates data-dependency requirements...
+ // no vectorization for us :(
+ for (size_t i = 0; i < bufferSize; i++)
+ m_currentFrame[buffer[i].pos] ^= buffer[i].delta;
+
+ // Restore frame history
+ m_currentFrameHistory = frame.frameHistoryCount;
+
+ m_rewindBuffer.pop_back();
+ }
+
+ return rewound;
+}
+
+void CDeltaPairMemoryStream::CullPastFrames(uint64_t frameCount)
+{
+ for (uint64_t removedCount = 0; removedCount < frameCount; removedCount++)
+ {
+ if (m_rewindBuffer.empty())
+ {
+ CLog::Log(LOGDEBUG,
+ "CDeltaPairMemoryStream: Tried to cull {} frames too many. Check your math!",
+ frameCount - removedCount);
+ break;
+ }
+ m_rewindBuffer.pop_front();
+ }
+}
diff --git a/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h
new file mode 100644
index 0000000..b2a7cc8
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "LinearMemoryStream.h"
+
+#include <deque>
+#include <vector>
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Implementation of a linear memory stream using XOR deltas
+ */
+class CDeltaPairMemoryStream : public CLinearMemoryStream
+{
+public:
+ CDeltaPairMemoryStream() = default;
+
+ ~CDeltaPairMemoryStream() override = default;
+
+ // implementation of IMemoryStream via CLinearMemoryStream
+ void Reset() override;
+ uint64_t PastFramesAvailable() const override;
+ uint64_t RewindFrames(uint64_t frameCount) override;
+
+protected:
+ // implementation of CLinearMemoryStream
+ void SubmitFrameInternal() override;
+ void CullPastFrames(uint64_t frameCount) override;
+
+ /*!
+ * Rewinding is implemented by applying XOR deltas on the specific parts of
+ * the save state buffer which have changed. In practice, this is very fast
+ * and simple (linear scan) and allows deltas to be compressed down to 1-3%
+ * of original save state size depending on the system. The algorithm runs
+ * on 32 bits at a time for speed.
+ *
+ * Use std::deque here to achieve amortized O(1) on pop/push to front and
+ * back.
+ */
+ struct DeltaPair
+ {
+ size_t pos;
+ uint32_t delta;
+ };
+
+ using DeltaPairVector = std::vector<DeltaPair>;
+
+ struct MemoryFrame
+ {
+ DeltaPairVector buffer;
+ uint64_t frameHistoryCount;
+ };
+
+ std::deque<MemoryFrame> m_rewindBuffer;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h
new file mode 100644
index 0000000..379a6a4
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+/*!
+ * \brief Stream of serialized states from game clients
+ *
+ * A memory stream is composed of "frames" of memory representing serialized
+ * states of the game client. For each video frame run by the game loop, the
+ * game client's state is serialized into a buffer provided by this interface.
+ *
+ * Implementation of three types of memory streams are provided:
+ *
+ * - Basic memory stream: has only a current frame, and supports neither
+ * rewind nor forward seeking.
+ *
+ * \sa CBasicMemoryStream
+ *
+ * - Linear memory stream: can grow in one direction. It is possible to
+ * rewind, but not fast-forward.
+ *
+ * \sa CLinearMemoryStream
+ *
+ * - Nonlinear memory stream: can have frames both ahead of and behind
+ * the current frame. If a stream is rewound, it is possible to
+ * recover these frames by seeking forward again.
+ *
+ * \sa CNonlinearMemoryStream (TODO)
+ */
+class IMemoryStream
+{
+public:
+ virtual ~IMemoryStream() = default;
+
+ /*!
+ * \brief Initialize memory stream
+ *
+ * \param frameSize The size of the serialized memory state
+ * \param maxFrameCount The maximum number of frames this stream can hold
+ */
+ virtual void Init(size_t frameSize, uint64_t maxFrameCount) = 0;
+
+ /*!
+ * \brief Free any resources used by this stream
+ */
+ virtual void Reset() = 0;
+
+ /*!
+ * \brief Return the frame size passed to Init()
+ */
+ virtual size_t FrameSize() const = 0;
+
+ /*!
+ * \brief Return the current max frame count
+ */
+ virtual uint64_t MaxFrameCount() const = 0;
+
+ /*!
+ * \brief Update the max frame count
+ *
+ * Old frames may be deleted if the max frame count is reduced.
+ */
+ virtual void SetMaxFrameCount(uint64_t maxFrameCount) = 0;
+
+ /*!
+ * \ brief Get a pointer to which FrameSize() bytes can be written
+ *
+ * The buffer exposed by this function is passed to the game client, which
+ * fills it with a serialization of its current state.
+ */
+ virtual uint8_t* BeginFrame() = 0;
+
+ /*!
+ * \brief Indicate that a frame of size FrameSize() has been written to the
+ * location returned from BeginFrame()
+ */
+ virtual void SubmitFrame() = 0;
+
+ /*!
+ * \brief Get a pointer to the current frame
+ *
+ * This function must have no side effects. The pointer is valid until the
+ * stream is modified.
+ *
+ * \return A buffer of size FrameSize(), or nullptr if the stream is empty
+ */
+ virtual const uint8_t* CurrentFrame() const = 0;
+
+ /*!
+ * \brief Return the number of frames ahead of the current frame
+ *
+ * If the stream supports forward seeking, frames that are passed over
+ * during a "rewind" operation can be recovered again.
+ */
+ virtual uint64_t FutureFramesAvailable() const = 0;
+
+ /*!
+ * \brief Seek ahead the specified number of frames
+ *
+ * \return The number of frames advanced
+ */
+ virtual uint64_t AdvanceFrames(uint64_t frameCount) = 0;
+
+ /*!
+ * \brief Return the number of frames behind the current frame
+ */
+ virtual uint64_t PastFramesAvailable() const = 0;
+
+ /*!
+ * \brief Seek backwards the specified number of frames
+ *
+ * \return The number of frames rewound
+ */
+ virtual uint64_t RewindFrames(uint64_t frameCount) = 0;
+
+ /*!
+ * \brief Get the total number of frames played until the current frame
+ *
+ * \return The history of the current frame, or 0 for unknown
+ */
+ virtual uint64_t GetFrameCounter() const = 0;
+
+ /*!
+ * \brief Set the total number of frames played until the current frame
+ *
+ * \param frameCount The history of the current frame
+ */
+ virtual void SetFrameCounter(uint64_t frameCount) = 0;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp
new file mode 100644
index 0000000..65a7959
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LinearMemoryStream.h"
+
+using namespace KODI;
+using namespace RETRO;
+
+// Pad forward to nearest boundary of bytes
+#define PAD_TO_CEIL(x, bytes) ((((x) + (bytes)-1) / (bytes)) * (bytes))
+
+CLinearMemoryStream::CLinearMemoryStream()
+{
+ Reset();
+}
+
+void CLinearMemoryStream::Init(size_t frameSize, uint64_t maxFrameCount)
+{
+ Reset();
+
+ m_frameSize = frameSize;
+ m_paddedFrameSize = PAD_TO_CEIL(m_frameSize, sizeof(uint32_t));
+ m_maxFrames = maxFrameCount;
+}
+
+void CLinearMemoryStream::Reset()
+{
+ m_frameSize = 0;
+ m_paddedFrameSize = 0;
+ m_maxFrames = 0;
+ m_currentFrame.reset();
+ m_nextFrame.reset();
+ m_bHasCurrentFrame = false;
+ m_bHasNextFrame = false;
+ m_currentFrameHistory = 0;
+}
+
+void CLinearMemoryStream::SetMaxFrameCount(uint64_t maxFrameCount)
+{
+ if (maxFrameCount == 0)
+ {
+ Reset();
+ }
+ else
+ {
+ const uint64_t frameCount = BufferSize();
+ if (maxFrameCount < frameCount)
+ CullPastFrames(frameCount - maxFrameCount);
+ }
+
+ m_maxFrames = maxFrameCount;
+}
+
+uint8_t* CLinearMemoryStream::BeginFrame()
+{
+ if (m_paddedFrameSize == 0)
+ return nullptr;
+
+ if (!m_bHasCurrentFrame)
+ {
+ if (!m_currentFrame)
+ m_currentFrame.reset(new uint32_t[m_paddedFrameSize]);
+ return reinterpret_cast<uint8_t*>(m_currentFrame.get());
+ }
+
+ if (!m_nextFrame)
+ m_nextFrame.reset(new uint32_t[m_paddedFrameSize]);
+ return reinterpret_cast<uint8_t*>(m_nextFrame.get());
+}
+
+const uint8_t* CLinearMemoryStream::CurrentFrame() const
+{
+ if (m_bHasCurrentFrame)
+ return reinterpret_cast<const uint8_t*>(m_currentFrame.get());
+
+ return nullptr;
+}
+
+void CLinearMemoryStream::SubmitFrame()
+{
+ if (!m_bHasCurrentFrame)
+ {
+ m_bHasCurrentFrame = true;
+ }
+ else if (!m_bHasNextFrame)
+ {
+ m_bHasNextFrame = true;
+ }
+
+ if (m_bHasNextFrame)
+ {
+ SubmitFrameInternal();
+ }
+}
+
+uint64_t CLinearMemoryStream::BufferSize() const
+{
+ return PastFramesAvailable() + (m_bHasCurrentFrame ? 1 : 0);
+}
diff --git a/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h
new file mode 100644
index 0000000..f7b9465
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IMemoryStream.h"
+
+#include <memory>
+#include <stdint.h>
+
+namespace KODI
+{
+namespace RETRO
+{
+class CLinearMemoryStream : public IMemoryStream
+{
+public:
+ CLinearMemoryStream();
+
+ ~CLinearMemoryStream() override = default;
+
+ // partial implementation of IMemoryStream
+ void Init(size_t frameSize, uint64_t maxFrameCount) override;
+ void Reset() override;
+ size_t FrameSize() const override { return m_frameSize; }
+ uint64_t MaxFrameCount() const override { return m_maxFrames; }
+ void SetMaxFrameCount(uint64_t maxFrameCount) override;
+ uint8_t* BeginFrame() override;
+ void SubmitFrame() override;
+ const uint8_t* CurrentFrame() const override;
+ uint64_t FutureFramesAvailable() const override { return 0; }
+ uint64_t AdvanceFrames(uint64_t frameCount) override { return 0; }
+ uint64_t PastFramesAvailable() const override = 0;
+ uint64_t RewindFrames(uint64_t frameCount) override = 0;
+ uint64_t GetFrameCounter() const override { return m_currentFrameHistory; }
+ void SetFrameCounter(uint64_t frameCount) override { m_currentFrameHistory = frameCount; }
+
+protected:
+ virtual void SubmitFrameInternal() = 0;
+ virtual void CullPastFrames(uint64_t frameCount) = 0;
+
+ // Helper function
+ uint64_t BufferSize() const;
+
+ size_t m_paddedFrameSize;
+ uint64_t m_maxFrames;
+
+ /**
+ * Simple double-buffering. After XORing the two states, the next becomes
+ * the current, and the current becomes a buffer for the next call to
+ * CGameClient::Serialize().
+ */
+ std::unique_ptr<uint32_t[]> m_currentFrame;
+ std::unique_ptr<uint32_t[]> m_nextFrame;
+ bool m_bHasCurrentFrame;
+ bool m_bHasNextFrame;
+
+ uint64_t m_currentFrameHistory;
+
+private:
+ size_t m_frameSize;
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/VideoPlayer/AudioSinkAE.cpp b/xbmc/cores/VideoPlayer/AudioSinkAE.cpp
new file mode 100644
index 0000000..0af936f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/AudioSinkAE.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AudioSinkAE.h"
+
+#include "DVDClock.h"
+#include "DVDCodecs/Audio/DVDAudioCodec.h"
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Utils/AEStreamData.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+CAudioSinkAE::CAudioSinkAE(CDVDClock *clock) : m_pClock(clock)
+{
+ m_bPassthrough = false;
+ m_iBitsPerSample = 0;
+ m_sampleRate = 0;
+ m_bPaused = true;
+ m_playingPts = DVD_NOPTS_VALUE; //silence coverity uninitialized warning, is set elsewhere
+ m_timeOfPts = 0.0; //silence coverity uninitialized warning, is set elsewhere
+ m_syncError = 0.0;
+ m_syncErrorTime = 0;
+}
+
+CAudioSinkAE::~CAudioSinkAE()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+}
+
+bool CAudioSinkAE::Create(const DVDAudioFrame &audioframe, AVCodecID codec, bool needresampler)
+{
+ CLog::Log(LOGINFO, "Creating audio stream (codec id: {}, channels: {}, sample rate: {}, {})",
+ codec, audioframe.format.m_channelLayout.Count(), audioframe.format.m_sampleRate,
+ audioframe.passthrough ? "pass-through" : "no pass-through");
+
+ // if passthrough isset do something else
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ unsigned int options = needresampler && !audioframe.passthrough ? AESTREAM_FORCE_RESAMPLE : 0;
+ options |= AESTREAM_PAUSED;
+
+ AEAudioFormat format = audioframe.format;
+ m_pAudioStream = CServiceBroker::GetActiveAE()->MakeStream(
+ format,
+ options,
+ this
+ );
+ if (!m_pAudioStream)
+ return false;
+
+ m_dataFormat = audioframe.format.m_dataFormat;
+ m_sampleRate = audioframe.format.m_sampleRate;
+ m_iBitsPerSample = audioframe.bits_per_sample;
+ m_bPassthrough = audioframe.passthrough;
+ m_channelLayout = audioframe.format.m_channelLayout;
+ m_dataType = audioframe.format.m_streamInfo.m_type;
+
+ return true;
+}
+
+void CAudioSinkAE::Destroy(bool finish)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_pAudioStream)
+ {
+ m_pAudioStream.get_deleter().setFinish(finish);
+ m_pAudioStream.reset();
+ }
+
+ m_pAudioStream = NULL;
+ m_sampleRate = 0;
+ m_iBitsPerSample = 0;
+ m_bPassthrough = false;
+ m_bPaused = true;
+ m_playingPts = DVD_NOPTS_VALUE;
+}
+
+unsigned int CAudioSinkAE::AddPackets(const DVDAudioFrame &audioframe)
+{
+ m_bAbort = false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (!m_pAudioStream)
+ return 0;
+
+ CAESyncInfo info = m_pAudioStream->GetSyncInfo();
+ if (info.state == CAESyncInfo::SYNC_INSYNC)
+ {
+ unsigned int newTime = info.errortime;
+ if (newTime != m_syncErrorTime)
+ {
+ m_syncErrorTime = info.errortime;
+ m_syncError = info.error / 1000 * DVD_TIME_BASE;
+ m_resampleRatio = info.rr;
+ }
+ }
+ else
+ {
+ m_syncErrorTime = 0;
+ m_syncError = 0.0;
+ }
+
+ //Calculate a timeout when this definitely should be done
+ double timeout;
+ timeout = DVD_SEC_TO_TIME(m_pAudioStream->GetDelay()) + audioframe.duration;
+ timeout += DVD_SEC_TO_TIME(1.0);
+ timeout += m_pClock->GetAbsoluteClock();
+
+ unsigned int total = audioframe.nb_frames - audioframe.framesOut;
+ unsigned int frames = total;
+ unsigned int offset = audioframe.framesOut;
+ do
+ {
+ IAEStream::ExtData ext;
+ if (offset == 0)
+ {
+ ext.pts = audioframe.pts / DVD_TIME_BASE * 1000;
+ }
+ if (audioframe.hasDownmix)
+ {
+ ext.hasDownmix = true;
+ ext.centerMixLevel = audioframe.centerMixLevel;
+ }
+ unsigned int copied = m_pAudioStream->AddData(audioframe.data, offset, frames, &ext);
+ offset += copied;
+ frames -= copied;
+ if (frames <= 0)
+ break;
+
+ if (copied == 0 && timeout < m_pClock->GetAbsoluteClock())
+ {
+ CLog::Log(LOGERROR, "CDVDAudio::AddPacketsRenderer - timeout adding data to renderer");
+ break;
+ }
+
+ lock.unlock();
+ KODI::TIME::Sleep(1ms);
+ lock.lock();
+ } while (!m_bAbort);
+
+ m_playingPts = audioframe.pts + audioframe.duration - GetDelay();
+ m_timeOfPts = m_pClock->GetAbsoluteClock();
+
+ return total - frames;
+}
+
+void CAudioSinkAE::Drain()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ m_pAudioStream->Drain(true);
+}
+
+void CAudioSinkAE::SetVolume(float volume)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ m_pAudioStream->SetVolume(volume);
+}
+
+void CAudioSinkAE::SetDynamicRangeCompression(long drc)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ m_pAudioStream->SetAmplification(powf(10.0f, (float)drc / 2000.0f));
+}
+
+void CAudioSinkAE::Pause()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ m_pAudioStream->Pause();
+ CLog::Log(LOGDEBUG,"CDVDAudio::Pause - pausing audio stream");
+ m_playingPts = DVD_NOPTS_VALUE;
+}
+
+void CAudioSinkAE::Resume()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ m_pAudioStream->Resume();
+ CLog::Log(LOGDEBUG,"CDVDAudio::Resume - resume audio stream");
+}
+
+double CAudioSinkAE::GetDelay()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ double delay = 0.3;
+ if(m_pAudioStream)
+ delay = m_pAudioStream->GetDelay();
+
+ return delay * DVD_TIME_BASE;
+}
+
+void CAudioSinkAE::Flush()
+{
+ m_bAbort = true;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_pAudioStream)
+ {
+ m_pAudioStream->Flush();
+ CLog::Log(LOGDEBUG,"CDVDAudio::Flush - flush audio stream");
+ }
+ m_playingPts = DVD_NOPTS_VALUE;
+ m_syncError = 0.0;
+ m_syncErrorTime = 0;
+}
+
+void CAudioSinkAE::AbortAddPackets()
+{
+ m_bAbort = true;
+}
+
+bool CAudioSinkAE::IsValidFormat(const DVDAudioFrame &audioframe)
+{
+ if (!m_pAudioStream)
+ return false;
+
+ if (audioframe.passthrough != m_bPassthrough)
+ return false;
+
+ if (m_dataFormat != audioframe.format.m_dataFormat ||
+ m_sampleRate != audioframe.format.m_sampleRate ||
+ m_iBitsPerSample != audioframe.bits_per_sample ||
+ m_channelLayout != audioframe.format.m_channelLayout)
+ return false;
+
+ if (m_bPassthrough &&
+ m_dataType != audioframe.format.m_streamInfo.m_type)
+ return false;
+
+ return true;
+}
+
+double CAudioSinkAE::GetCacheTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_pAudioStream)
+ return 0.0;
+
+ return m_pAudioStream->GetCacheTime();
+}
+
+double CAudioSinkAE::GetCacheTotal()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_pAudioStream)
+ return 0.0;
+ return m_pAudioStream->GetCacheTotal();
+}
+
+double CAudioSinkAE::GetMaxDelay()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_pAudioStream)
+ return 0.0;
+ return m_pAudioStream->GetMaxDelay();
+}
+
+double CAudioSinkAE::GetPlayingPts()
+{
+ if (m_playingPts == DVD_NOPTS_VALUE)
+ return 0.0;
+
+ double now = m_pClock->GetAbsoluteClock();
+ double diff = now - m_timeOfPts;
+ double cache = GetCacheTime();
+ double played = 0.0;
+
+ if (diff < cache)
+ played = diff;
+ else
+ played = cache;
+
+ m_timeOfPts = now;
+ m_playingPts += played;
+ return m_playingPts;
+}
+
+double CAudioSinkAE::GetSyncError()
+{
+ return m_syncError;
+}
+
+void CAudioSinkAE::SetSyncErrorCorrection(double correction)
+{
+ m_syncError += correction;
+}
+
+double CAudioSinkAE::GetResampleRatio()
+{
+ return m_resampleRatio;
+}
+
+void CAudioSinkAE::SetResampleMode(int mode)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if(m_pAudioStream)
+ {
+ m_pAudioStream->SetResampleMode(mode);
+ }
+}
+
+double CAudioSinkAE::GetClock()
+{
+ if (m_pClock)
+ return (m_pClock->GetClock() + m_pClock->GetVsyncAdjust()) / DVD_TIME_BASE * 1000;
+ else
+ return 0.0;
+}
+
+double CAudioSinkAE::GetClockSpeed()
+{
+ if (m_pClock)
+ return m_pClock->GetClockSpeed();
+ else
+ return 1.0;
+}
+
+CAEStreamInfo::DataType CAudioSinkAE::GetPassthroughStreamType(AVCodecID codecId,
+ int samplerate,
+ int profile)
+{
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_sampleRate = samplerate;
+ format.m_streamInfo.m_type = CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+ switch (codecId)
+ {
+ case AV_CODEC_ID_AC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_EAC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_DTS:
+ if (profile == FF_PROFILE_DTS_HD_HRA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD;
+ else if (profile == FF_PROFILE_DTS_HD_MA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_MA;
+ else
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_TRUEHD:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_TRUEHD;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ default:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_NULL;
+ }
+
+ bool supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+
+ if (!supports && codecId == AV_CODEC_ID_DTS &&
+ format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_DTSHD_CORE &&
+ CServiceBroker::GetActiveAE()->UsesDtsCoreFallback())
+ {
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+ }
+
+ if (supports)
+ return format.m_streamInfo.m_type;
+ else
+ return CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+}
diff --git a/xbmc/cores/VideoPlayer/AudioSinkAE.h b/xbmc/cores/VideoPlayer/AudioSinkAE.h
new file mode 100644
index 0000000..e2aff52
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/AudioSinkAE.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "cores/AudioEngine/Utils/AEChannelInfo.h"
+#include "threads/CriticalSection.h"
+
+#include <atomic>
+#include <mutex>
+
+#include "PlatformDefs.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+typedef struct stDVDAudioFrame DVDAudioFrame;
+
+class CDVDClock;
+
+class CAudioSinkAE : IAEClockCallback
+{
+public:
+ explicit CAudioSinkAE(CDVDClock *clock);
+ ~CAudioSinkAE() override;
+
+ void SetVolume(float fVolume);
+ void SetDynamicRangeCompression(long drc);
+ void Pause();
+ void Resume();
+ bool Create(const DVDAudioFrame &audioframe, AVCodecID codec, bool needresampler);
+ bool IsValidFormat(const DVDAudioFrame &audioframe);
+ void Destroy(bool finish);
+ unsigned int AddPackets(const DVDAudioFrame &audioframe);
+ double GetPlayingPts();
+ double GetCacheTime();
+ double GetCacheTotal(); // returns total time a stream can buffer
+ double GetMaxDelay(); // returns total time of audio in AE for the stream
+ double GetDelay(); // returns the time it takes to play a packet if we add one at this time
+ double GetSyncError();
+ void SetSyncErrorCorrection(double correction);
+
+ /*!
+ * \brief Returns the resample ratio, or 0.0 if unknown/invalid
+ */
+ double GetResampleRatio();
+
+ void SetResampleMode(int mode);
+ void Flush();
+ void Drain();
+ void AbortAddPackets();
+
+ double GetClock() override;
+ double GetClockSpeed() override;
+
+ CAEStreamInfo::DataType GetPassthroughStreamType(AVCodecID codecId, int samplerate, int profile);
+
+protected:
+ IAE::StreamPtr m_pAudioStream;
+ double m_playingPts;
+ double m_timeOfPts;
+ double m_syncError;
+ unsigned int m_syncErrorTime;
+ double m_resampleRatio = 0.0; // invalid
+ CCriticalSection m_critSection;
+
+ AEDataFormat m_dataFormat;
+ unsigned int m_sampleRate;
+ int m_iBitsPerSample;
+ bool m_bPassthrough;
+ CAEChannelInfo m_channelLayout;
+ CAEStreamInfo::DataType m_dataType;
+ bool m_bPaused;
+
+ std::atomic_bool m_bAbort;
+ CDVDClock *m_pClock;
+};
diff --git a/xbmc/cores/VideoPlayer/Buffers/CMakeLists.txt b/xbmc/cores/VideoPlayer/Buffers/CMakeLists.txt
new file mode 100644
index 0000000..a876912
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES VideoBuffer.cpp)
+set(HEADERS VideoBuffer.h)
+
+if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES VideoBufferDMA.cpp
+ VideoBufferDRMPRIME.cpp
+ VideoBufferPoolDMA.cpp)
+ list(APPEND HEADERS VideoBufferDMA.h
+ VideoBufferDRMPRIME.h
+ VideoBufferPoolDMA.h)
+endif()
+
+core_add_library(videoplayer-buffers)
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.cpp b/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.cpp
new file mode 100644
index 0000000..6586706
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.cpp
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoBuffer.h"
+
+#include <mutex>
+#include <string.h>
+#include <utility>
+
+//-----------------------------------------------------------------------------
+// CVideoBuffer
+//-----------------------------------------------------------------------------
+
+CVideoBuffer::CVideoBuffer(int id)
+{
+ m_id = id;
+ m_refCount = 0;
+}
+
+void CVideoBuffer::Acquire()
+{
+ m_refCount++;
+}
+
+void CVideoBuffer::Acquire(std::shared_ptr<IVideoBufferPool> pool)
+{
+ m_refCount++;
+ m_pool = std::move(pool);
+}
+
+void CVideoBuffer::Release()
+{
+ if (--m_refCount <= 0 && m_pool)
+ {
+ std::shared_ptr<IVideoBufferPool> pool = m_pool->GetPtr();
+ m_pool = nullptr;
+ pool->Return(m_id);
+ }
+}
+
+AVPixelFormat CVideoBuffer::GetFormat()
+{
+ return m_pixFormat;
+}
+
+bool CVideoBuffer::CopyPicture(YuvImage* pDst, YuvImage *pSrc)
+{
+ uint8_t *s = pSrc->plane[0];
+ uint8_t *d = pDst->plane[0];
+ int w = pDst->width * pDst->bpp;
+ int h = pDst->height;
+ if ((w == pSrc->stride[0]) && (pSrc->stride[0] == pDst->stride[0]))
+ {
+ memcpy(d, s, w*h);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w);
+ s += pSrc->stride[0];
+ d += pDst->stride[0];
+ }
+ }
+ s = pSrc->plane[1];
+ d = pDst->plane[1];
+ w =(pDst->width >> pDst->cshift_x) * pDst->bpp;
+ h =(pDst->height >> pDst->cshift_y);
+ if ((w == pSrc->stride[1]) && (pSrc->stride[1] == pDst->stride[1]))
+ {
+ memcpy(d, s, w*h);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w);
+ s += pSrc->stride[1];
+ d += pDst->stride[1];
+ }
+ }
+ s = pSrc->plane[2];
+ d = pDst->plane[2];
+ if ((w == pSrc->stride[2]) && (pSrc->stride[2] == pDst->stride[2]))
+ {
+ memcpy(d, s, w*h);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w);
+ s += pSrc->stride[2];
+ d += pDst->stride[2];
+ }
+ }
+ return true;
+}
+
+
+bool CVideoBuffer::CopyNV12Picture(YuvImage* pDst, YuvImage *pSrc)
+{
+ uint8_t *s = pSrc->plane[0];
+ uint8_t *d = pDst->plane[0];
+ int w = pDst->width;
+ int h = pDst->height;
+ // Copy Y
+ if ((w == pSrc->stride[0]) && (pSrc->stride[0] == pDst->stride[0]))
+ {
+ memcpy(d, s, w*h);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w);
+ s += pSrc->stride[0];
+ d += pDst->stride[0];
+ }
+ }
+
+ s = pSrc->plane[1];
+ d = pDst->plane[1];
+ w = pDst->width;
+ h = pDst->height >> 1;
+ // Copy packed UV (width is same as for Y as it's both U and V components)
+ if ((w == pSrc->stride[1]) && (pSrc->stride[1] == pDst->stride[1]))
+ {
+ memcpy(d, s, w*h);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w);
+ s += pSrc->stride[1];
+ d += pDst->stride[1];
+ }
+ }
+
+ return true;
+}
+
+bool CVideoBuffer::CopyYUV422PackedPicture(YuvImage* pDst, YuvImage *pSrc)
+{
+ uint8_t *s = pSrc->plane[0];
+ uint8_t *d = pDst->plane[0];
+ int w = pDst->width;
+ int h = pDst->height;
+
+ // Copy YUYV
+ if ((w * 2 == pSrc->stride[0]) && (pSrc->stride[0] == pDst->stride[0]))
+ {
+ memcpy(d, s, w*h*2);
+ }
+ else
+ {
+ for (int y = 0; y < h; y++)
+ {
+ memcpy(d, s, w*2);
+ s += pSrc->stride[0];
+ d += pDst->stride[0];
+ }
+ }
+
+ return true;
+}
+
+CVideoBufferSysMem::CVideoBufferSysMem(IVideoBufferPool &pool, int id, AVPixelFormat format, int size)
+: CVideoBuffer(id)
+{
+ m_pixFormat = format;
+ m_size = size;
+ memset(&m_image, 0, sizeof(YuvImage));
+}
+
+CVideoBufferSysMem::~CVideoBufferSysMem()
+{
+ delete[] m_data;
+}
+
+uint8_t* CVideoBufferSysMem::GetMemPtr()
+{
+ return m_data;
+}
+
+void CVideoBufferSysMem::GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES])
+{
+ planes[0] = m_image.plane[0];
+ planes[1] = m_image.plane[1];
+ planes[2] = m_image.plane[2];
+}
+
+void CVideoBufferSysMem::GetStrides(int(&strides)[YuvImage::MAX_PLANES])
+{
+ strides[0] = m_image.stride[0];
+ strides[1] = m_image.stride[1];
+ strides[2] = m_image.stride[2];
+}
+
+void CVideoBufferSysMem::SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES])
+{
+ m_width = width;
+ m_height = height;
+
+ m_image.width = m_width;
+ m_image.height = m_height;
+ m_image.stride[0] = strides[0];
+ m_image.stride[1] = strides[1];
+ m_image.stride[2] = strides[2];
+ m_image.cshift_x = 1;
+ m_image.cshift_y = 1;
+ m_image.bpp = 1;
+
+ if (m_pixFormat == AV_PIX_FMT_YUV420P ||
+ m_pixFormat == AV_PIX_FMT_YUV420P16 ||
+ m_pixFormat == AV_PIX_FMT_YUV420P14 ||
+ m_pixFormat == AV_PIX_FMT_YUV420P12 ||
+ m_pixFormat == AV_PIX_FMT_YUV420P10 ||
+ m_pixFormat == AV_PIX_FMT_YUV420P9)
+ {
+ if (m_pixFormat != AV_PIX_FMT_YUV420P)
+ m_image.bpp = 2;
+
+ m_image.planesize[0] = m_image.stride[0] * m_image.height;
+ m_image.planesize[1] = m_image.stride[1] * (m_image.height >> m_image.cshift_y);
+ m_image.planesize[2] = m_image.stride[2] * (m_image.height >> m_image.cshift_y);
+ }
+ else if (m_pixFormat == AV_PIX_FMT_NV12)
+ {
+ // Y plane
+ m_image.planesize[0] = m_image.stride[0] * m_image.height;
+ // packed UV plane
+ m_image.planesize[1] = m_image.stride[1] * m_image.height / 2;
+ // third plane is not used
+ m_image.planesize[2] = 0;
+ }
+ else if (m_pixFormat == AV_PIX_FMT_YUYV422 ||
+ m_pixFormat == AV_PIX_FMT_UYVY422)
+ {
+ // packed YUYV plane
+ m_image.planesize[0] = m_image.stride[0] * m_image.height;
+ // second plane is not used
+ m_image.planesize[1] = 0;
+ // third plane is not used
+ m_image.planesize[2] = 0;
+ }
+
+ m_image.plane[0] = m_data;
+ m_image.plane[1] = m_data + m_image.planesize[0];
+ m_image.plane[2] = m_image.plane[1] + m_image.planesize[1];
+}
+
+void CVideoBufferSysMem::SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES], const int (&planeOffsets)[YuvImage::MAX_PLANES])
+{
+ SetDimensions(width, height, strides);
+
+ m_image.plane[0] = m_data + planeOffsets[0];
+ m_image.plane[1] = m_data + planeOffsets[1];
+ m_image.plane[2] = m_data + planeOffsets[2];
+}
+
+bool CVideoBufferSysMem::Alloc()
+{
+ m_data = new uint8_t[m_size];
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// CVideoBufferPool
+//-----------------------------------------------------------------------------
+
+CVideoBufferPoolSysMem::~CVideoBufferPoolSysMem()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto buf : m_all)
+ {
+ delete buf;
+ }
+}
+
+CVideoBuffer* CVideoBufferPoolSysMem::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CVideoBufferSysMem *buf = nullptr;
+ if (!m_free.empty())
+ {
+ int idx = m_free.front();
+ m_free.pop_front();
+ m_used.push_back(idx);
+ buf = m_all[idx];
+ }
+ else
+ {
+ int id = m_all.size();
+ buf = new CVideoBufferSysMem(*this, id, m_pixFormat, m_size);
+ buf->Alloc();
+ m_all.push_back(buf);
+ m_used.push_back(id);
+ }
+
+ buf->Acquire(GetPtr());
+ return buf;
+}
+
+void CVideoBufferPoolSysMem::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ auto it = m_used.begin();
+ while (it != m_used.end())
+ {
+ if (*it == id)
+ {
+ m_used.erase(it);
+ break;
+ }
+ else
+ ++it;
+ }
+ m_free.push_back(id);
+
+ if (m_bm && m_used.empty())
+ {
+ (m_bm->*m_cbDispose)(this);
+ }
+}
+
+void CVideoBufferPoolSysMem::Configure(AVPixelFormat format, int size)
+{
+ m_pixFormat = format;
+ m_size = size;
+ m_configured = true;
+}
+
+inline bool CVideoBufferPoolSysMem::IsConfigured()
+{
+ return m_configured;
+}
+
+bool CVideoBufferPoolSysMem::IsCompatible(AVPixelFormat format, int size)
+{
+ if (m_pixFormat == format &&
+ m_size == size)
+ return true;
+
+ return false;
+}
+
+void CVideoBufferPoolSysMem::Discard(CVideoBufferManager *bm, ReadyToDispose cb)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bm = bm;
+ m_cbDispose = cb;
+
+ if (m_used.empty())
+ (m_bm->*m_cbDispose)(this);
+}
+
+std::shared_ptr<IVideoBufferPool> CVideoBufferPoolSysMem::CreatePool()
+{
+ return std::make_shared<CVideoBufferPoolSysMem>();
+}
+
+//-----------------------------------------------------------------------------
+// CVideoBufferManager
+//-----------------------------------------------------------------------------
+
+CVideoBufferManager::CVideoBufferManager()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ RegisterPoolFactory("SysMem", &CVideoBufferPoolSysMem::CreatePool);
+}
+
+void CVideoBufferManager::RegisterPool(const std::shared_ptr<IVideoBufferPool>& pool)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // preferred pools are to the front
+ m_pools.push_front(pool);
+}
+
+void CVideoBufferManager::RegisterPoolFactory(const std::string& id, CreatePoolFunc createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_poolFactories[id] = createFunc;
+}
+
+void CVideoBufferManager::ReleasePools()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::list<std::shared_ptr<IVideoBufferPool>> pools = m_pools;
+ m_pools.clear();
+
+ m_discardedPools = pools;
+
+ for (const auto& pool : pools)
+ {
+ pool->Discard(this, &CVideoBufferManager::ReadyForDisposal);
+ }
+}
+
+void CVideoBufferManager::ReleasePool(IVideoBufferPool *pool)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto it = m_pools.begin(); it != m_pools.end(); ++it)
+ {
+ if ((*it).get() == pool)
+ {
+ m_discardedPools.push_back(*it);
+ m_pools.erase(it);
+ pool->Discard(this, &CVideoBufferManager::ReadyForDisposal);
+ break;
+ }
+ }
+}
+
+void CVideoBufferManager::ReadyForDisposal(IVideoBufferPool *pool)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto it = m_discardedPools.begin(); it != m_discardedPools.end(); ++it)
+ {
+ if ((*it).get() == pool)
+ {
+ pool->Released(*this);
+ m_discardedPools.erase(it);
+ break;
+ }
+ }
+}
+
+CVideoBuffer* CVideoBufferManager::Get(AVPixelFormat format, int size, IVideoBufferPool **pPool)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ for (const auto& pool : m_pools)
+ {
+ if (!pool->IsConfigured())
+ {
+ pool->Configure(format, size);
+ }
+ if (pool->IsCompatible(format, size))
+ {
+ return pool->Get();
+ }
+ }
+
+ for (const auto& fact : m_poolFactories)
+ {
+ std::shared_ptr<IVideoBufferPool> pool = fact.second();
+ m_pools.push_front(pool);
+ pool->Configure(format, size);
+ if (pPool)
+ *pPool = pool.get();
+ return pool->Get();
+ }
+ return nullptr;
+}
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.h b/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.h
new file mode 100644
index 0000000..39c7d86
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBuffer.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include <atomic>
+#include <deque>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+struct YuvImage
+{
+ static const int MAX_PLANES = 3;
+
+ uint8_t* plane[MAX_PLANES];
+ int planesize[MAX_PLANES];
+ int stride[MAX_PLANES];
+ unsigned int width;
+ unsigned int height;
+ unsigned int cshift_x; // this is the chroma shift used
+ unsigned int cshift_y;
+ unsigned int bpp; // bytes per pixel
+};
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+#define BUFFER_STATE_DECODER 0x01;
+#define BUFFER_STATE_RENDER 0x02;
+
+class CVideoBuffer;
+class IVideoBufferPool;
+class CVideoBufferManager;
+
+typedef void (CVideoBufferManager::*ReadyToDispose)(IVideoBufferPool *pool);
+
+class IVideoBufferPool : public std::enable_shared_from_this<IVideoBufferPool>
+{
+public:
+ virtual ~IVideoBufferPool() = default;
+
+ // get a free buffer from the pool, sets ref count to 1
+ virtual CVideoBuffer* Get() = 0;
+
+ // called by buffer when ref count goes to zero
+ virtual void Return(int id) = 0;
+
+ // required if pool is registered with BufferManager BM call configure
+ // as soon as it knows parameters: pixFmx, size
+ virtual void Configure(AVPixelFormat format, int size) {}
+
+ // required if pool is registered with BufferManager
+ virtual bool IsConfigured() { return false; }
+
+ // required if pool is registered with BufferManager
+ // called before Get() to check if buffer pool is suitable
+ virtual bool IsCompatible(AVPixelFormat format, int size) { return false; }
+
+ // callback when BM releases buffer pool. i.e. before a new codec is created
+ // clients can register a new pool on this callback
+ virtual void Released(CVideoBufferManager& videoBufferManager) {}
+
+ // called by BM when buffer is discarded
+ // pool calls back when all buffers are back home
+ virtual void Discard(CVideoBufferManager* bm, ReadyToDispose cb) { (bm->*cb)(this); }
+
+ // call on Get() before returning buffer to caller
+ std::shared_ptr<IVideoBufferPool> GetPtr() { return shared_from_this(); }
+};
+
+class CVideoBuffer
+{
+public:
+ CVideoBuffer() = delete;
+ virtual ~CVideoBuffer() = default;
+ void Acquire();
+ void Acquire(std::shared_ptr<IVideoBufferPool> pool);
+ void Release();
+ int GetId() const { return m_id; }
+
+ virtual AVPixelFormat GetFormat();
+ virtual uint8_t* GetMemPtr() { return nullptr; }
+ virtual void GetPlanes(uint8_t* (&planes)[YuvImage::MAX_PLANES]) {}
+ virtual void GetStrides(int (&strides)[YuvImage::MAX_PLANES]) {}
+ virtual void SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES]) {}
+ virtual void SetDimensions(int width,
+ int height,
+ const int (&strides)[YuvImage::MAX_PLANES],
+ const int (&planeOffsets)[YuvImage::MAX_PLANES])
+ {
+ }
+
+ static bool CopyPicture(YuvImage* pDst, YuvImage *pSrc);
+ static bool CopyNV12Picture(YuvImage* pDst, YuvImage *pSrc);
+ static bool CopyYUV422PackedPicture(YuvImage* pDst, YuvImage *pSrc);
+
+protected:
+ explicit CVideoBuffer(int id);
+ AVPixelFormat m_pixFormat = AV_PIX_FMT_NONE;
+ std::atomic_int m_refCount;
+ int m_id;
+ std::shared_ptr<IVideoBufferPool> m_pool;
+};
+
+class CVideoBufferSysMem : public CVideoBuffer
+{
+public:
+ CVideoBufferSysMem(IVideoBufferPool &pool, int id, AVPixelFormat format, int size);
+ ~CVideoBufferSysMem() override;
+ uint8_t* GetMemPtr() override;
+ void GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES]) override;
+ void GetStrides(int(&strides)[YuvImage::MAX_PLANES]) override;
+ void SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES]) override;
+ void SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES], const int (&planeOffsets)[YuvImage::MAX_PLANES]) override;
+ bool Alloc();
+
+protected:
+ int m_width = 0;
+ int m_height = 0;
+ int m_size = 0;
+ uint8_t *m_data = nullptr;
+ YuvImage m_image;
+};
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+class CVideoBufferPoolSysMem : public IVideoBufferPool
+{
+public:
+ ~CVideoBufferPoolSysMem() override;
+ CVideoBuffer* Get() override;
+ void Return(int id) override;
+ void Configure(AVPixelFormat format, int size) override;
+ bool IsConfigured() override;
+ bool IsCompatible(AVPixelFormat format, int size) override;
+ void Discard(CVideoBufferManager *bm, ReadyToDispose cb) override;
+
+ static std::shared_ptr<IVideoBufferPool> CreatePool();
+
+protected:
+ int m_width = 0;
+ int m_height = 0;
+ int m_size = 0;
+ AVPixelFormat m_pixFormat = AV_PIX_FMT_NONE;
+ bool m_configured = false;
+ CCriticalSection m_critSection;
+ CVideoBufferManager *m_bm = nullptr;
+ ReadyToDispose m_cbDispose;
+
+ std::vector<CVideoBufferSysMem*> m_all;
+ std::deque<int> m_used;
+ std::deque<int> m_free;
+};
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+typedef std::shared_ptr<IVideoBufferPool> (*CreatePoolFunc)();
+
+class CVideoBufferManager
+{
+public:
+ CVideoBufferManager();
+ void RegisterPool(const std::shared_ptr<IVideoBufferPool>& pool);
+ void RegisterPoolFactory(const std::string& id, CreatePoolFunc createFunc);
+ void ReleasePools();
+ void ReleasePool(IVideoBufferPool *pool);
+ CVideoBuffer* Get(AVPixelFormat format, int size, IVideoBufferPool **pPool);
+ void ReadyForDisposal(IVideoBufferPool *pool);
+
+protected:
+ CCriticalSection m_critSection;
+ std::list<std::shared_ptr<IVideoBufferPool>> m_pools;
+ std::list<std::shared_ptr<IVideoBufferPool>> m_discardedPools;
+ std::map<std::string, CreatePoolFunc> m_poolFactories;
+
+private:
+ CVideoBufferManager (const CVideoBufferManager&) = delete;
+ CVideoBufferManager& operator= (const CVideoBufferManager&) = delete;
+};
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.cpp b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.cpp
new file mode 100644
index 0000000..2dd7c13
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoBufferDMA.h"
+
+#include "ServiceBroker.h"
+#include "utils/BufferObject.h"
+#include "utils/DRMHelpers.h"
+#include "utils/log.h"
+
+extern "C"
+{
+#include <libavutil/imgutils.h>
+#include <libavutil/pixdesc.h>
+}
+
+CVideoBufferDMA::CVideoBufferDMA(IVideoBufferPool& pool, int id, uint32_t fourcc, uint64_t size)
+ : CVideoBufferDRMPRIMEFFmpeg(pool, id),
+ m_bo(CBufferObject::GetBufferObject(true)),
+ m_fourcc(fourcc),
+ m_size(size)
+{
+}
+
+CVideoBufferDMA::~CVideoBufferDMA()
+{
+ Destroy();
+}
+
+AVDRMFrameDescriptor* CVideoBufferDMA::GetDescriptor() const
+{
+ return const_cast<AVDRMFrameDescriptor*>(&m_descriptor);
+}
+
+uint8_t* CVideoBufferDMA::GetMemPtr()
+{
+ return m_addr;
+}
+
+void CVideoBufferDMA::GetPlanes(uint8_t* (&planes)[YuvImage::MAX_PLANES])
+{
+ for (uint32_t i = 0; i < YuvImage::MAX_PLANES; i++)
+ planes[i] = m_addr + m_offsets[i];
+}
+
+void CVideoBufferDMA::GetStrides(int (&strides)[YuvImage::MAX_PLANES])
+{
+ for (uint32_t i = 0; i < YuvImage::MAX_PLANES; i++)
+ strides[i] = m_strides[i];
+}
+
+void CVideoBufferDMA::SetDimensions(int width, int height)
+{
+ SetDimensions(width, height, m_strides, m_offsets);
+}
+
+void CVideoBufferDMA::SetDimensions(int width,
+ int height,
+ const int (&strides)[YuvImage::MAX_PLANES])
+{
+ SetDimensions(width, height, strides, m_offsets);
+}
+
+void CVideoBufferDMA::SetDimensions(int width,
+ int height,
+ const int (&strides)[YuvImage::MAX_PLANES],
+ const int (&planeOffsets)[YuvImage::MAX_PLANES])
+{
+ m_width = width;
+ m_height = height;
+
+ AVDRMFrameDescriptor* descriptor = &m_descriptor;
+ descriptor->nb_objects = 1;
+ descriptor->objects[0].fd = m_fd;
+ descriptor->nb_layers = 1;
+
+ AVDRMLayerDescriptor* layer = &descriptor->layers[0];
+ layer->format = m_fourcc;
+ layer->nb_planes = m_planes;
+
+ for (uint32_t i = 0; i < m_planes; i++)
+ {
+ layer->planes[i].offset = planeOffsets[i];
+ layer->planes[i].pitch = strides[i];
+ }
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+ {
+ std::string planeStr;
+ for (uint32_t plane = 0; plane < m_planes; plane++)
+ planeStr.append(fmt::format("\nplane[{}]: stride={}\toffset={}", plane, strides[plane],
+ planeOffsets[plane]));
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVideoBufferDMA::{} - frame layout id={} fourcc={}{}",
+ __FUNCTION__, m_id, DRMHELPERS::FourCCToString(m_fourcc), planeStr);
+ }
+}
+
+bool CVideoBufferDMA::Alloc()
+{
+ if (!m_bo->CreateBufferObject(m_size))
+ return false;
+
+ m_fd = m_bo->GetFd();
+ m_addr = m_bo->GetMemory();
+ m_planes = 3; // CAddonVideoCodec only requests AV_PIX_FMT_YUV420P for now
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVideoBufferDMA::{} - id={} fourcc={} fd={} size={} addr={}",
+ __FUNCTION__, m_id, DRMHELPERS::FourCCToString(m_fourcc), m_fd, m_size,
+ fmt::ptr(m_addr));
+
+ return true;
+}
+
+void CVideoBufferDMA::Export(AVFrame* frame, uint32_t width, uint32_t height)
+{
+ m_planes = av_pix_fmt_count_planes(static_cast<AVPixelFormat>(frame->format));
+
+ if (m_planes < 2)
+ throw std::runtime_error(
+ "non-planar formats not supported: " +
+ std::string(av_get_pix_fmt_name(static_cast<AVPixelFormat>(frame->format))));
+
+ for (uint32_t plane = 0; plane < m_planes; plane++)
+ {
+ m_strides[plane] =
+ av_image_get_linesize(static_cast<AVPixelFormat>(frame->format), width, plane);
+ m_offsets[plane] =
+ plane == 0 ? 0 : (m_offsets[plane - 1] + m_strides[plane - 1] * (height >> (plane - 1)));
+ }
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+ {
+ std::string planeStr;
+ for (uint32_t plane = 0; plane < m_planes; plane++)
+ planeStr.append(fmt::format("\nplane[{}]: stride={}\toffset={}", plane, m_strides[plane],
+ m_offsets[plane]));
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVideoBufferDMA::{} - frame layout id={} fourcc={}{}",
+ __FUNCTION__, m_id, DRMHELPERS::FourCCToString(m_fourcc), planeStr);
+ }
+
+ for (uint32_t i = 0; i < AV_NUM_DATA_POINTERS; i++)
+ {
+ frame->data[i] = i < m_planes ? m_addr + m_offsets[i] : nullptr;
+ frame->linesize[i] = i < m_planes ? m_strides[i] : 0;
+ frame->buf[i] = i == 0 ? frame->opaque_ref : nullptr;
+ }
+
+ frame->extended_data = frame->data;
+ frame->opaque_ref = nullptr;
+}
+
+void CVideoBufferDMA::SyncStart()
+{
+ m_bo->SyncStart();
+}
+
+void CVideoBufferDMA::SyncEnd()
+{
+ m_bo->SyncEnd();
+}
+
+void CVideoBufferDMA::Destroy()
+{
+ m_bo->ReleaseMemory();
+ m_bo->DestroyBufferObject();
+
+ for (auto& offset : m_offsets)
+ offset = 0;
+
+ for (auto& stride : m_strides)
+ stride = 0;
+
+ m_planes = 0;
+ m_width = 0;
+ m_height = 0;
+ m_fourcc = 0;
+ m_size = 0;
+ m_addr = nullptr;
+ m_fd = -1;
+}
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.h
new file mode 100644
index 0000000..94eb1c8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDMA.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+
+#include <memory>
+
+class IBufferObject;
+
+class CVideoBufferDMA : public CVideoBufferDRMPRIMEFFmpeg
+{
+public:
+ CVideoBufferDMA(IVideoBufferPool& pool, int id, uint32_t fourcc, uint64_t size);
+ ~CVideoBufferDMA() override;
+
+ // implementation of CVideoBufferDRMPRIME via CVideoBufferDRMPRIMEFFmpeg
+ uint32_t GetWidth() const override { return m_width; }
+ uint32_t GetHeight() const override { return m_height; }
+ AVDRMFrameDescriptor* GetDescriptor() const override;
+
+ // implementation of CVideoBuffer via CVideoBufferDRMPRIMEFFmpeg
+ void GetPlanes(uint8_t* (&planes)[YuvImage::MAX_PLANES]) override;
+ void GetStrides(int (&strides)[YuvImage::MAX_PLANES]) override;
+ uint8_t* GetMemPtr() override;
+ void SetDimensions(int width, int height, const int (&strides)[YuvImage::MAX_PLANES]) override;
+ void SetDimensions(int width,
+ int height,
+ const int (&strides)[YuvImage::MAX_PLANES],
+ const int (&planeOffsets)[YuvImage::MAX_PLANES]) override;
+
+ void SetDimensions(int width, int height);
+ bool Alloc();
+ void Export(AVFrame* frame, uint32_t width, uint32_t height);
+
+ void SyncStart();
+ void SyncEnd();
+
+private:
+ void Destroy();
+
+ std::unique_ptr<IBufferObject> m_bo;
+
+ int m_offsets[YuvImage::MAX_PLANES]{0};
+ int m_strides[YuvImage::MAX_PLANES]{0};
+
+ AVDRMFrameDescriptor m_descriptor{};
+ uint32_t m_planes{0};
+ uint32_t m_width{0};
+ uint32_t m_height{0};
+ uint32_t m_fourcc{0};
+ uint64_t m_size{0};
+ uint8_t* m_addr{nullptr};
+ int m_fd{-1};
+};
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.cpp b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.cpp
new file mode 100644
index 0000000..b1c23ff
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoBufferDRMPRIME.h"
+
+#include <mutex>
+
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavutil/pixdesc.h>
+}
+
+namespace DRMPRIME
+{
+
+std::string GetColorEncoding(const VideoPicture& picture)
+{
+ switch (picture.color_space)
+ {
+ case AVCOL_SPC_BT2020_CL:
+ case AVCOL_SPC_BT2020_NCL:
+ return "ITU-R BT.2020 YCbCr";
+ case AVCOL_SPC_SMPTE170M:
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_FCC:
+ return "ITU-R BT.601 YCbCr";
+ case AVCOL_SPC_BT709:
+ return "ITU-R BT.709 YCbCr";
+ case AVCOL_SPC_RESERVED:
+ case AVCOL_SPC_UNSPECIFIED:
+ default:
+ if (picture.iWidth > 1024 || picture.iHeight >= 600)
+ return "ITU-R BT.709 YCbCr";
+ else
+ return "ITU-R BT.601 YCbCr";
+ }
+}
+
+std::string GetColorRange(const VideoPicture& picture)
+{
+ if (picture.color_range)
+ return "YCbCr full range";
+ return "YCbCr limited range";
+}
+
+uint8_t GetEOTF(const VideoPicture& picture)
+{
+ switch (picture.color_transfer)
+ {
+ case AVCOL_TRC_SMPTE2084:
+ return HDMI_EOTF_SMPTE_ST2084;
+ case AVCOL_TRC_ARIB_STD_B67:
+ case AVCOL_TRC_BT2020_10:
+ return HDMI_EOTF_BT_2100_HLG;
+ default:
+ return HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
+ }
+}
+
+const AVMasteringDisplayMetadata* GetMasteringDisplayMetadata(const VideoPicture& picture)
+{
+ return picture.hasDisplayMetadata ? &picture.displayMetadata : nullptr;
+}
+
+const AVContentLightMetadata* GetContentLightMetadata(const VideoPicture& picture)
+{
+ return picture.hasLightMetadata ? &picture.lightMetadata : nullptr;
+}
+
+} // namespace DRMPRIME
+
+CVideoBufferDRMPRIME::CVideoBufferDRMPRIME(int id) : CVideoBuffer(id)
+{
+ m_pixFormat = AV_PIX_FMT_DRM_PRIME;
+}
+
+CVideoBufferDRMPRIMEFFmpeg::CVideoBufferDRMPRIMEFFmpeg(IVideoBufferPool& pool, int id)
+ : CVideoBufferDRMPRIME(id)
+{
+ m_pFrame = av_frame_alloc();
+}
+
+CVideoBufferDRMPRIMEFFmpeg::~CVideoBufferDRMPRIMEFFmpeg()
+{
+ Unref();
+ av_frame_free(&m_pFrame);
+}
+
+void CVideoBufferDRMPRIMEFFmpeg::SetRef(AVFrame* frame)
+{
+ av_frame_move_ref(m_pFrame, frame);
+}
+
+void CVideoBufferDRMPRIMEFFmpeg::Unref()
+{
+ av_frame_unref(m_pFrame);
+}
+
+bool CVideoBufferDRMPRIMEFFmpeg::IsValid() const
+{
+ AVDRMFrameDescriptor* descriptor = GetDescriptor();
+ return descriptor && descriptor->nb_layers;
+}
+
+CVideoBufferPoolDRMPRIMEFFmpeg::~CVideoBufferPoolDRMPRIMEFFmpeg()
+{
+ for (auto buf : m_all)
+ delete buf;
+}
+
+CVideoBuffer* CVideoBufferPoolDRMPRIMEFFmpeg::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CVideoBufferDRMPRIMEFFmpeg* buf = nullptr;
+ if (!m_free.empty())
+ {
+ int idx = m_free.front();
+ m_free.pop_front();
+ m_used.push_back(idx);
+ buf = m_all[idx];
+ }
+ else
+ {
+ int id = m_all.size();
+ buf = new CVideoBufferDRMPRIMEFFmpeg(*this, id);
+ m_all.push_back(buf);
+ m_used.push_back(id);
+ }
+
+ buf->Acquire(GetPtr());
+ return buf;
+}
+
+void CVideoBufferPoolDRMPRIMEFFmpeg::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_all[id]->Unref();
+ auto it = m_used.begin();
+ while (it != m_used.end())
+ {
+ if (*it == id)
+ {
+ m_used.erase(it);
+ break;
+ }
+ else
+ ++it;
+ }
+ m_free.push_back(id);
+}
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h
new file mode 100644
index 0000000..e77f75b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+
+extern "C"
+{
+#include <libavutil/frame.h>
+#include <libavutil/hwcontext_drm.h>
+#include <libavutil/mastering_display_metadata.h>
+}
+
+namespace DRMPRIME
+{
+
+// HDR enums is copied from linux include/linux/hdmi.h (strangely not part of uapi)
+enum hdmi_metadata_type
+{
+ HDMI_STATIC_METADATA_TYPE1 = 0,
+};
+enum hdmi_eotf
+{
+ HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
+ HDMI_EOTF_TRADITIONAL_GAMMA_HDR,
+ HDMI_EOTF_SMPTE_ST2084,
+ HDMI_EOTF_BT_2100_HLG,
+};
+
+std::string GetColorEncoding(const VideoPicture& picture);
+std::string GetColorRange(const VideoPicture& picture);
+uint8_t GetEOTF(const VideoPicture& picture);
+const AVMasteringDisplayMetadata* GetMasteringDisplayMetadata(const VideoPicture& picture);
+const AVContentLightMetadata* GetContentLightMetadata(const VideoPicture& picture);
+
+} // namespace DRMPRIME
+
+class CVideoBufferDRMPRIME : public CVideoBuffer
+{
+public:
+ CVideoBufferDRMPRIME() = delete;
+ ~CVideoBufferDRMPRIME() override = default;
+
+ virtual void SetPictureParams(const VideoPicture& picture) { m_picture.SetParams(picture); }
+ virtual const VideoPicture& GetPicture() const { return m_picture; }
+ virtual uint32_t GetWidth() const { return GetPicture().iWidth; }
+ virtual uint32_t GetHeight() const { return GetPicture().iHeight; }
+
+ virtual AVDRMFrameDescriptor* GetDescriptor() const = 0;
+ virtual bool IsValid() const { return true; }
+ virtual bool AcquireDescriptor() { return true; }
+ virtual void ReleaseDescriptor() {}
+
+ uint32_t m_fb_id = 0;
+ uint32_t m_handles[AV_DRM_MAX_PLANES] = {};
+
+protected:
+ explicit CVideoBufferDRMPRIME(int id);
+
+ VideoPicture m_picture;
+};
+
+class CVideoBufferDRMPRIMEFFmpeg : public CVideoBufferDRMPRIME
+{
+public:
+ CVideoBufferDRMPRIMEFFmpeg(IVideoBufferPool& pool, int id);
+ ~CVideoBufferDRMPRIMEFFmpeg() override;
+ void SetRef(AVFrame* frame);
+ void Unref();
+
+ AVDRMFrameDescriptor* GetDescriptor() const override
+ {
+ return reinterpret_cast<AVDRMFrameDescriptor*>(m_pFrame->data[0]);
+ }
+ bool IsValid() const override;
+
+protected:
+ AVFrame* m_pFrame = nullptr;
+};
+
+class CVideoBufferPoolDRMPRIMEFFmpeg : public IVideoBufferPool
+{
+public:
+ ~CVideoBufferPoolDRMPRIMEFFmpeg() override;
+ void Return(int id) override;
+ CVideoBuffer* Get() override;
+
+protected:
+ CCriticalSection m_critSection;
+ std::vector<CVideoBufferDRMPRIMEFFmpeg*> m_all;
+ std::deque<int> m_used;
+ std::deque<int> m_free;
+};
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.cpp b/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.cpp
new file mode 100644
index 0000000..fb2dfc6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoBufferPoolDMA.h"
+
+#include "cores/VideoPlayer/Buffers/VideoBufferDMA.h"
+#include "utils/BufferObjectFactory.h"
+
+#include <mutex>
+
+#include <drm_fourcc.h>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+CVideoBufferPoolDMA::~CVideoBufferPoolDMA()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto buf : m_all)
+ delete buf;
+}
+
+CVideoBuffer* CVideoBufferPoolDMA::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CVideoBufferDMA* buf = nullptr;
+ if (!m_free.empty())
+ {
+ int idx = m_free.front();
+ m_free.pop_front();
+ m_used.push_back(idx);
+ buf = m_all[idx];
+ }
+ else
+ {
+ int id = m_all.size();
+ buf = new CVideoBufferDMA(*this, id, m_fourcc, m_size);
+
+ if (!buf->Alloc())
+ {
+ delete buf;
+ return nullptr;
+ }
+
+ m_all.push_back(buf);
+ m_used.push_back(id);
+ }
+
+ buf->Acquire(GetPtr());
+ return buf;
+}
+
+void CVideoBufferPoolDMA::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_all[id]->Unref();
+ auto it = m_used.begin();
+ while (it != m_used.end())
+ {
+ if (*it == id)
+ {
+ m_used.erase(it);
+ break;
+ }
+ else
+ ++it;
+ }
+ m_free.push_back(id);
+}
+
+void CVideoBufferPoolDMA::Configure(AVPixelFormat format, int size)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_fourcc = TranslateFormat(format);
+ m_size = static_cast<uint64_t>(size);
+}
+
+bool CVideoBufferPoolDMA::IsConfigured()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ return (m_fourcc != 0 && m_size != 0);
+}
+
+bool CVideoBufferPoolDMA::IsCompatible(AVPixelFormat format, int size)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_fourcc != TranslateFormat(format) || m_size != static_cast<uint64_t>(size))
+ return false;
+
+ return true;
+}
+
+void CVideoBufferPoolDMA::Released(CVideoBufferManager& videoBufferManager)
+{
+ if (!CBufferObjectFactory::CreateBufferObject(true))
+ return;
+
+ videoBufferManager.RegisterPool(std::make_shared<CVideoBufferPoolDMA>());
+}
+
+std::shared_ptr<IVideoBufferPool> CVideoBufferPoolDMA::CreatePool()
+{
+ return std::make_shared<CVideoBufferPoolDMA>();
+}
+
+uint32_t CVideoBufferPoolDMA::TranslateFormat(AVPixelFormat format)
+{
+ switch (format)
+ {
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_YUVJ420P:
+ return DRM_FORMAT_YUV420;
+ default:
+ return 0;
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h
new file mode 100644
index 0000000..a490fcb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+
+#include <memory>
+
+class CVideoBufferDMA;
+
+class CVideoBufferPoolDMA : public IVideoBufferPool
+{
+public:
+ CVideoBufferPoolDMA() = default;
+ ~CVideoBufferPoolDMA() override;
+
+ // implementation of IVideoBufferPool
+ CVideoBuffer* Get() override;
+ void Return(int id) override;
+ void Configure(AVPixelFormat format, int size) override;
+ bool IsConfigured() override;
+ bool IsCompatible(AVPixelFormat format, int size) override;
+ void Released(CVideoBufferManager& videoBufferManager) override;
+
+ static std::shared_ptr<IVideoBufferPool> CreatePool();
+
+protected:
+ CCriticalSection m_critSection;
+ std::vector<CVideoBufferDMA*> m_all;
+ std::deque<int> m_used;
+ std::deque<int> m_free;
+
+private:
+ uint32_t TranslateFormat(AVPixelFormat format);
+
+ uint32_t m_fourcc = 0;
+ uint64_t m_size = 0;
+};
diff --git a/xbmc/cores/VideoPlayer/CMakeLists.txt b/xbmc/cores/VideoPlayer/CMakeLists.txt
new file mode 100644
index 0000000..2c1042c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/CMakeLists.txt
@@ -0,0 +1,46 @@
+set(SOURCES AudioSinkAE.cpp
+ DVDClock.cpp
+ DVDDemuxSPU.cpp
+ DVDFileInfo.cpp
+ DVDMessage.cpp
+ DVDMessageQueue.cpp
+ DVDOverlayContainer.cpp
+ DVDStreamInfo.cpp
+ PTSTracker.cpp
+ Edl.cpp
+ VideoPlayer.cpp
+ VideoPlayerAudio.cpp
+ VideoPlayerAudioID3.cpp
+ VideoPlayerRadioRDS.cpp
+ VideoPlayerSubtitle.cpp
+ VideoPlayerTeletext.cpp
+ VideoPlayerVideo.cpp
+ VideoReferenceClock.cpp)
+
+set(HEADERS AudioSinkAE.h
+ DVDClock.h
+ DVDDemuxSPU.h
+ DVDFileInfo.h
+ DVDMessage.h
+ DVDMessageQueue.h
+ DVDOverlayContainer.h
+ DVDResource.h
+ DVDStreamInfo.h
+ Edl.h
+ IVideoPlayer.h
+ PTSTracker.h
+ VideoPlayer.h
+ VideoPlayerAudio.h
+ VideoPlayerAudioID3.h
+ VideoPlayerRadioRDS.h
+ VideoPlayerSubtitle.h
+ VideoPlayerTeletext.h
+ VideoPlayerVideo.h
+ VideoReferenceClock.h
+ Interface/StreamInfo.h
+ Interface/DemuxPacket.h
+ Interface/DemuxCrypto.h
+ Interface/InputStreamConstants.h
+ Interface/TimingConstants.h)
+
+core_add_library(VideoPlayer)
diff --git a/xbmc/cores/VideoPlayer/DVDClock.cpp b/xbmc/cores/VideoPlayer/DVDClock.cpp
new file mode 100644
index 0000000..cd2d024
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDClock.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDClock.h"
+
+#include "VideoReferenceClock.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+
+#include <inttypes.h>
+#include <math.h>
+#include <mutex>
+
+CDVDClock::CDVDClock()
+{
+ std::unique_lock<CCriticalSection> lock(m_systemsection);
+
+ m_pauseClock = 0;
+ m_bReset = true;
+ m_paused = false;
+ m_speedAfterPause = DVD_PLAYSPEED_PAUSE;
+ m_iDisc = 0;
+ m_maxspeedadjust = 5.0;
+ m_systemAdjust = 0;
+ m_speedAdjust = 0;
+ m_startClock = 0;
+ m_vSyncAdjust = 0;
+ m_frameTime = DVD_TIME_BASE / 60.0;
+
+ m_videoRefClock.reset(new CVideoReferenceClock());
+ m_lastSystemTime = m_videoRefClock->GetTime();
+ m_systemOffset = m_videoRefClock->GetTime();
+ m_systemFrequency = CurrentHostFrequency();
+ m_systemUsed = m_systemFrequency;
+}
+
+CDVDClock::~CDVDClock() = default;
+
+// Returns the current absolute clock in units of DVD_TIME_BASE (usually microseconds).
+double CDVDClock::GetAbsoluteClock(bool interpolated /*= true*/)
+{
+ std::unique_lock<CCriticalSection> lock(m_systemsection);
+
+ int64_t current;
+ current = m_videoRefClock->GetTime(interpolated);
+
+ return SystemToAbsolute(current);
+}
+
+double CDVDClock::GetClock(bool interpolated /*= true*/)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ int64_t current = m_videoRefClock->GetTime(interpolated);
+ m_systemAdjust += m_speedAdjust * (current - m_lastSystemTime);
+ m_lastSystemTime = current;
+
+ return SystemToPlaying(current);
+}
+
+double CDVDClock::GetClock(double& absolute, bool interpolated /*= true*/)
+{
+ int64_t current = m_videoRefClock->GetTime(interpolated);
+
+ std::unique_lock<CCriticalSection> lock(m_systemsection);
+ absolute = SystemToAbsolute(current);
+
+ m_systemAdjust += m_speedAdjust * (current - m_lastSystemTime);
+ m_lastSystemTime = current;
+
+ return SystemToPlaying(current);
+}
+
+void CDVDClock::SetVsyncAdjust(double adjustment)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_vSyncAdjust = adjustment;
+}
+
+double CDVDClock::GetVsyncAdjust()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_vSyncAdjust;
+}
+
+void CDVDClock::Pause(bool pause)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (pause && !m_paused)
+ {
+ if (!m_pauseClock)
+ m_speedAfterPause = m_systemFrequency * DVD_PLAYSPEED_NORMAL / m_systemUsed;
+ else
+ m_speedAfterPause = DVD_PLAYSPEED_PAUSE;
+
+ SetSpeed(DVD_PLAYSPEED_PAUSE);
+ m_paused = true;
+ }
+ else if (!pause && m_paused)
+ {
+ m_paused = false;
+ SetSpeed(m_speedAfterPause);
+ }
+}
+
+void CDVDClock::Advance(double time)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_pauseClock)
+ {
+ m_pauseClock += time / DVD_TIME_BASE * m_systemFrequency;
+ }
+}
+
+void CDVDClock::SetSpeed(int iSpeed)
+{
+ // this will sometimes be a little bit of due to rounding errors, ie clock might jump a bit when changing speed
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_paused)
+ {
+ m_speedAfterPause = iSpeed;
+ return;
+ }
+
+ if (iSpeed == DVD_PLAYSPEED_PAUSE)
+ {
+ if (!m_pauseClock)
+ m_pauseClock = m_videoRefClock->GetTime();
+ return;
+ }
+
+ int64_t current;
+ int64_t newfreq = m_systemFrequency * DVD_PLAYSPEED_NORMAL / iSpeed;
+
+ current = m_videoRefClock->GetTime();
+ if (m_pauseClock)
+ {
+ m_startClock += current - m_pauseClock;
+ m_pauseClock = 0;
+ }
+
+ m_startClock = current - (int64_t)((double)(current - m_startClock) * newfreq / m_systemUsed);
+ m_systemUsed = newfreq;
+}
+
+void CDVDClock::SetSpeedAdjust(double adjust)
+{
+ CLog::Log(LOGDEBUG, "CDVDClock::SetSpeedAdjust - adjusted:{:f}", adjust);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_speedAdjust = adjust;
+}
+
+double CDVDClock::GetSpeedAdjust()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_speedAdjust;
+}
+
+double CDVDClock::ErrorAdjust(double error, const char* log)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ double clock, absolute, adjustment;
+ clock = GetClock(absolute);
+
+ // skip minor updates while speed adjust is active
+ // -> adjusting buffer levels
+ if (m_speedAdjust != 0 && error < DVD_MSEC_TO_TIME(100))
+ {
+ return 0;
+ }
+
+ adjustment = error;
+
+ if (m_vSyncAdjust != 0)
+ {
+ // Audio ahead is more noticeable then audio behind video.
+ // Correct if aufio is more than 20ms ahead or more then
+ // 27ms behind. In a worst case scenario we switch from
+ // 20ms ahead to 21ms behind (for fps of 23.976)
+ if (error > 0.02 * DVD_TIME_BASE)
+ adjustment = m_frameTime;
+ else if (error < -0.027 * DVD_TIME_BASE)
+ adjustment = -m_frameTime;
+ else
+ adjustment = 0;
+ }
+
+ if (adjustment == 0)
+ return 0;
+
+ Discontinuity(clock+adjustment, absolute);
+
+ CLog::Log(LOGDEBUG, "CDVDClock::ErrorAdjust - {} - error:{:f}, adjusted:{:f}", log, error,
+ adjustment);
+ return adjustment;
+}
+
+void CDVDClock::Discontinuity(double clock, double absolute)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_startClock = AbsoluteToSystem(absolute);
+ if(m_pauseClock)
+ m_pauseClock = m_startClock;
+ m_iDisc = clock;
+ m_bReset = false;
+ m_systemAdjust = 0;
+ m_speedAdjust = 0;
+}
+
+void CDVDClock::SetMaxSpeedAdjust(double speed)
+{
+ std::unique_lock<CCriticalSection> lock(m_speedsection);
+
+ m_maxspeedadjust = speed;
+}
+
+//returns the refreshrate if the videoreferenceclock is running, -1 otherwise
+int CDVDClock::UpdateFramerate(double fps, double* interval /*= NULL*/)
+{
+ //sent with fps of 0 means we are not playing video
+ if(fps == 0.0)
+ return -1;
+
+ m_frameTime = 1/fps * DVD_TIME_BASE;
+
+ //check if the videoreferenceclock is running, will return -1 if not
+ double rate = m_videoRefClock->GetRefreshRate(interval);
+
+ if (rate <= 0)
+ return -1;
+
+ std::unique_lock<CCriticalSection> lock(m_speedsection);
+
+ double weight = (rate * 2) / fps;
+
+ //set the speed of the videoreferenceclock based on fps, refreshrate and maximum speed adjust set by user
+ if (m_maxspeedadjust > 0.05)
+ {
+ if (weight / MathUtils::round_int(weight) < 1.0 + m_maxspeedadjust / 100.0 &&
+ weight / MathUtils::round_int(weight) > 1.0 - m_maxspeedadjust / 100.0)
+ weight = MathUtils::round_int(weight);
+ }
+ double speed = (rate * 2.0 ) / (fps * weight);
+ lock.unlock();
+
+ m_videoRefClock->SetSpeed(speed);
+
+ return rate;
+}
+
+bool CDVDClock::GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const
+{
+ return m_videoRefClock->GetClockInfo(MissedVblanks, ClockSpeed, RefreshRate);
+}
+
+double CDVDClock::SystemToAbsolute(int64_t system)
+{
+ return DVD_TIME_BASE * (double)(system - m_systemOffset) / m_systemFrequency;
+}
+
+int64_t CDVDClock::AbsoluteToSystem(double absolute)
+{
+ return (int64_t)(absolute / DVD_TIME_BASE * m_systemFrequency) + m_systemOffset;
+}
+
+double CDVDClock::SystemToPlaying(int64_t system)
+{
+ int64_t current;
+
+ if (m_bReset)
+ {
+ m_startClock = system;
+ m_systemUsed = m_systemFrequency;
+ if(m_pauseClock)
+ m_pauseClock = m_startClock;
+ m_iDisc = 0;
+ m_systemAdjust = 0;
+ m_speedAdjust = 0;
+ m_vSyncAdjust = 0;
+ m_bReset = false;
+ }
+
+ if (m_pauseClock)
+ current = m_pauseClock;
+ else
+ current = system;
+
+ return DVD_TIME_BASE * (double)(current - m_startClock + m_systemAdjust) / m_systemUsed + m_iDisc;
+}
+
+double CDVDClock::GetClockSpeed()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ double speed = (double)m_systemFrequency / m_systemUsed;
+ return m_videoRefClock->GetSpeed() * speed + m_speedAdjust;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDClock.h b/xbmc/cores/VideoPlayer/DVDClock.h
new file mode 100644
index 0000000..3c3f673
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDClock.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <memory>
+#include <stdint.h>
+
+class CVideoReferenceClock;
+
+class CDVDClock
+{
+public:
+
+ CDVDClock();
+ ~CDVDClock();
+
+ double GetClock(bool interpolated = true);
+ double GetClock(double& absolute, bool interpolated = true);
+
+ double ErrorAdjust(double error, const char* log);
+ void Discontinuity(double clock, double absolute);
+ void Discontinuity(double clock = 0LL)
+ {
+ Discontinuity(clock, GetAbsoluteClock());
+ }
+
+ void Reset() { m_bReset = true; }
+ void SetSpeed(int iSpeed);
+ void SetSpeedAdjust(double adjust);
+ double GetSpeedAdjust();
+
+ double GetClockSpeed(); /**< get the current speed of the clock relative normal system time */
+
+ /* tells clock at what framerate video is, to *
+ * allow it to adjust speed for a better match */
+ int UpdateFramerate(double fps, double* interval = NULL);
+
+ void SetMaxSpeedAdjust(double speed);
+
+ double GetAbsoluteClock(bool interpolated = true);
+ double GetFrequency() { return (double)m_systemFrequency ; }
+
+ bool GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const;
+ void SetVsyncAdjust(double adjustment);
+ double GetVsyncAdjust();
+
+ void Pause(bool pause);
+ void Advance(double time);
+
+protected:
+ double SystemToAbsolute(int64_t system);
+ int64_t AbsoluteToSystem(double absolute);
+ double SystemToPlaying(int64_t system);
+
+ CCriticalSection m_critSection;
+ int64_t m_systemUsed;
+ int64_t m_startClock;
+ int64_t m_pauseClock;
+ double m_iDisc;
+ bool m_bReset;
+ bool m_paused;
+ int m_speedAfterPause;
+ std::unique_ptr<CVideoReferenceClock> m_videoRefClock;
+
+ int64_t m_systemFrequency;
+ int64_t m_systemOffset;
+ CCriticalSection m_systemsection;
+
+ int64_t m_systemAdjust;
+ int64_t m_lastSystemTime;
+ double m_speedAdjust;
+ double m_vSyncAdjust;
+ double m_frameTime;
+
+ double m_maxspeedadjust;
+ CCriticalSection m_speedsection;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/CMakeLists.txt
new file mode 100644
index 0000000..91957ce
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES DVDAudioCodecFFmpeg.cpp
+ DVDAudioCodecPassthrough.cpp)
+
+set(HEADERS DVDAudioCodec.h
+ DVDAudioCodecFFmpeg.h
+ DVDAudioCodecPassthrough.h)
+
+if(CORE_SYSTEM_NAME STREQUAL android)
+ list(APPEND SOURCES DVDAudioCodecAndroidMediaCodec.cpp)
+ list(APPEND HEADERS DVDAudioCodecAndroidMediaCodec.h)
+endif()
+
+core_add_library(dvdaudiocodecs)
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h
new file mode 100644
index 0000000..b92f41a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+#include <vector>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+struct AVStream;
+
+class CDVDStreamInfo;
+class CDVDCodecOption;
+class CDVDCodecOptions;
+
+typedef struct stDVDAudioFrame
+{
+ uint8_t* data[16];
+ double pts;
+ bool hasTimestamp;
+ double duration;
+ unsigned int nb_frames;
+ unsigned int framesOut;
+ unsigned int framesize;
+ unsigned int planes;
+
+ AEAudioFormat format;
+ int bits_per_sample;
+ bool passthrough;
+ enum AVAudioServiceType audio_service_type;
+ enum AVMatrixEncoding matrix_encoding;
+ int profile;
+ bool hasDownmix;
+ double centerMixLevel;
+} DVDAudioFrame;
+
+class CDVDAudioCodec
+{
+public:
+
+ explicit CDVDAudioCodec(CProcessInfo &processInfo) : m_processInfo(processInfo) {}
+ virtual ~CDVDAudioCodec() = default;
+
+ /*
+ * Open the decoder, returns true on success
+ */
+ virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) = 0;
+
+ /*
+ * Dispose, Free all resources
+ */
+ virtual void Dispose() = 0;
+
+ /*
+ * returns false on error
+ *
+ */
+ virtual bool AddData(const DemuxPacket &packet) = 0;
+
+ /*
+ * the data is valid until the next call
+ */
+ virtual void GetData(DVDAudioFrame &frame) = 0;
+
+ /*
+ * resets the decoder
+ */
+ virtual void Reset() = 0;
+
+ /*
+ * returns the format for the audio stream
+ */
+ virtual AEAudioFormat GetFormat() = 0;
+
+ /*
+ * should return the average input bit rate
+ */
+ virtual int GetBitRate() { return 0; }
+
+ /*
+ * returns if the codec requests to use passthrough
+ */
+ virtual bool NeedPassthrough() { return false; }
+
+ /*
+ * should return codecs name
+ */
+ virtual std::string GetName() = 0;
+
+ /*
+ * should return amount of data decoded has buffered in preparation for next audio frame
+ */
+ virtual int GetBufferSize() { return 0; }
+
+ /*
+ * should return the ffmpeg matrix encoding type
+ */
+ virtual enum AVMatrixEncoding GetMatrixEncoding() { return AV_MATRIX_ENCODING_NONE; }
+
+ /*
+ * should return the ffmpeg audio service type
+ */
+ virtual enum AVAudioServiceType GetAudioServiceType() { return AV_AUDIO_SERVICE_TYPE_MAIN; }
+
+ /*
+ * should return the ffmpeg profile value
+ */
+ virtual int GetProfile() { return 0; }
+
+protected:
+ CProcessInfo &m_processInfo;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp
new file mode 100644
index 0000000..9d38db2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2016 Christian Browet
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// http://developer.android.com/reference/android/media/MediaCodec.html
+//
+// Android MediaCodec class can be used to access low-level media codec,
+// i.e. encoder/decoder components. (android.media.MediaCodec). Requires
+// SDK16+ which is 4.1 Jellybean and above.
+//
+
+#include "DVDAudioCodecAndroidMediaCodec.h"
+
+#include "DVDAudioCodecFFmpeg.h"
+#include "DVDAudioCodecPassthrough.h"
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <cassert>
+#include <stdexcept>
+
+#include <androidjni/ByteBuffer.h>
+#include <androidjni/MediaCodec.h>
+#include <androidjni/MediaCodecCryptoInfo.h>
+#include <androidjni/MediaCodecInfo.h>
+#include <androidjni/MediaCodecList.h>
+#include <androidjni/MediaCrypto.h>
+#include <androidjni/MediaFormat.h>
+#include <androidjni/Surface.h>
+#include <androidjni/UUID.h>
+
+static const AEChannel KnownChannels[] = { AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_BLOC, AE_CH_BROC, AE_CH_NULL };
+
+static bool IsDownmixDecoder(const std::string &name)
+{
+ static const char *downmixDecoders[] = {
+ "OMX.dolby",
+ // End of list
+ NULL
+ };
+ for (const char **ptr = downmixDecoders; *ptr; ptr++)
+ {
+ if (!StringUtils::CompareNoCase(*ptr, name, strlen(*ptr)))
+ return true;
+ }
+ return false;
+}
+
+static bool IsDecoderWhitelisted(const std::string &name)
+{
+ static const char *whitelistDecoders[] = {
+ // End of list
+ NULL
+ };
+ for (const char **ptr = whitelistDecoders; *ptr; ptr++)
+ {
+ if (!StringUtils::CompareNoCase(*ptr, name, strlen(*ptr)))
+ return true;
+ }
+ return false;
+}
+
+/****************************/
+
+CDVDAudioCodecAndroidMediaCodec::CDVDAudioCodecAndroidMediaCodec(CProcessInfo &processInfo) :
+ CDVDAudioCodec(processInfo),
+ m_formatname("mediacodec"),
+ m_opened(false),
+ m_codecIsFed(false),
+ m_samplerate(0),
+ m_channels(0),
+ m_buffer(NULL),
+ m_bufferSize(0),
+ m_bufferUsed(0),
+ m_currentPts(DVD_NOPTS_VALUE),
+ m_crypto(nullptr)
+{
+}
+
+CDVDAudioCodecAndroidMediaCodec::~CDVDAudioCodecAndroidMediaCodec()
+{
+ Dispose();
+}
+
+std::unique_ptr<CDVDAudioCodec> CDVDAudioCodecAndroidMediaCodec::Create(CProcessInfo& processInfo)
+{
+ return std::make_unique<CDVDAudioCodecAndroidMediaCodec>(processInfo);
+}
+
+bool CDVDAudioCodecAndroidMediaCodec::Register()
+{
+ CDVDFactoryCodec::RegisterHWAudioCodec("mediacodec_dec", &CDVDAudioCodecAndroidMediaCodec::Create);
+ return true;
+}
+
+bool CDVDAudioCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ m_hints = hints;
+
+ CLog::Log(LOGDEBUG,
+ "CDVDAudioCodecAndroidMediaCodec::Open codec({}), profile({}), tag({}), extrasize({})",
+ hints.codec, hints.profile, hints.codec_tag, hints.extrasize);
+
+ // First check if passthrough decoder is supported
+ CAEStreamInfo::DataType ptStreamType = CAEStreamInfo::STREAM_TYPE_NULL;
+ for (const auto &key : options.m_keys)
+ if (key.m_name == "ptstreamtype")
+ {
+ ptStreamType = static_cast<CAEStreamInfo::DataType>(atoi(key.m_value.c_str()));
+ break;
+ }
+
+ if (ptStreamType != CAEStreamInfo::STREAM_TYPE_NULL)
+ {
+ //Look if the PT decoder can be opened
+ m_decryptCodec = std::shared_ptr<CDVDAudioCodec>(new CDVDAudioCodecPassthrough(m_processInfo, ptStreamType));
+ if (m_decryptCodec->Open(hints, options))
+ goto PROCESSDECODER;
+ }
+
+ switch(m_hints.codec)
+ {
+ case AV_CODEC_ID_AAC:
+ case AV_CODEC_ID_AAC_LATM:
+ if (!m_hints.extrasize)
+ {
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec: extradata required for aac decoder!");
+ return false;
+ }
+
+ m_mime = "audio/mp4a-latm";
+ m_formatname = "amc-aac";
+ break;
+
+ case AV_CODEC_ID_MP2:
+ m_mime = "audio/mpeg-L2";
+ m_formatname = "amc-mp2";
+ break;
+
+ case AV_CODEC_ID_MP3:
+ m_mime = "audio/mpeg";
+ m_formatname = "amc-mp3";
+ break;
+
+ case AV_CODEC_ID_VORBIS:
+ m_mime = "audio/vorbis";
+ m_formatname = "amc-ogg";
+
+ //TODO
+ return false;
+
+ break;
+
+ case AV_CODEC_ID_WMAPRO:
+ m_mime = "audio/wmapro";
+ m_formatname = "amc-wma";
+
+ //TODO
+ return false;
+
+ break;
+
+ case AV_CODEC_ID_WMAV1:
+ case AV_CODEC_ID_WMAV2:
+ m_mime = "audio/x-ms-wma";
+ m_formatname = "amc-wma";
+ //TODO
+ return false;
+
+ break;
+
+ case AV_CODEC_ID_AC3:
+ m_mime = "audio/ac3";
+ m_formatname = "amc-ac3";
+ break;
+
+ case AV_CODEC_ID_EAC3:
+ m_mime = "audio/eac3";
+ m_formatname = "amc-eac3";
+ break;
+
+ default:
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec: Unknown hints.codec({})", hints.codec);
+ return false;
+ break;
+ }
+
+ {
+ //StereoDownmixAllowed is true if the user has selected 2.0 Audio channels in settings
+ bool stereoDownmixAllowed = CServiceBroker::GetActiveAE()->HasStereoAudioChannelCount();
+ const std::vector<CJNIMediaCodecInfo> codecInfos =
+ CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS).getCodecInfos();
+ std::vector<std::string> mimeTypes;
+
+ for (const CJNIMediaCodecInfo& codec_info : codecInfos)
+ {
+ if (codec_info.isEncoder())
+ continue;
+
+ std::string codecName = codec_info.getName();
+
+ if (!IsDecoderWhitelisted(codecName))
+ continue;
+
+ if (m_hints.channels > 2 && !stereoDownmixAllowed && IsDownmixDecoder(codecName))
+ continue;
+
+ mimeTypes = codec_info.getSupportedTypes();
+ if (std::find(mimeTypes.begin(), mimeTypes.end(), m_mime) != mimeTypes.end())
+ {
+ m_codec = std::shared_ptr<CJNIMediaCodec>(new CJNIMediaCodec(CJNIMediaCodec::createByCodecName(codecName)));
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ m_codec = NULL;
+ continue;
+ }
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec: Selected audio decoder: {}",
+ codecName);
+ break;
+ }
+ }
+ }
+
+PROCESSDECODER:
+
+ if (m_crypto)
+ delete m_crypto;
+
+ if (m_hints.cryptoSession)
+ {
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec::Open Initializing MediaCrypto");
+
+ CJNIUUID uuid(jni::jhobject(NULL));
+ if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_WIDEVINE)
+ uuid = CJNIUUID(0xEDEF8BA979D64ACEULL, 0xA3C827DCD51D21EDULL);
+ else if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_PLAYREADY)
+ uuid = CJNIUUID(0x9A04F07998404286ULL, 0xAB92E65BE0885F95ULL);
+ else
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::Open Unsupported crypto-keysystem:{}",
+ m_hints.cryptoSession->keySystem);
+ return false;
+ }
+
+ m_crypto =
+ new CJNIMediaCrypto(uuid, std::vector<char>(m_hints.cryptoSession->sessionId.begin(),
+ m_hints.cryptoSession->sessionId.end()));
+
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "MediaCrypto::ExceptionCheck: <init>");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return false;
+ }
+ }
+ else
+ m_crypto = new CJNIMediaCrypto(jni::jhobject(NULL));
+
+ if (!m_codec)
+ {
+ if (m_hints.cryptoSession)
+ {
+ m_mime = "audio/raw";
+
+ // Workaround for old Android devices
+ // Prefer the Google raw decoder over the MediaTek one
+ const std::vector<CJNIMediaCodecInfo> codecInfos =
+ CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS).getCodecInfos();
+
+ bool mtk_raw_decoder = false;
+ bool google_raw_decoder = false;
+
+ for (const CJNIMediaCodecInfo& codec_info : codecInfos)
+ {
+ if (codec_info.isEncoder())
+ continue;
+
+ if (codec_info.getName() == "OMX.MTK.AUDIO.DECODER.RAW")
+ mtk_raw_decoder = true;
+ if (codec_info.getName() == "OMX.google.raw.decoder")
+ google_raw_decoder = true;
+ }
+
+ if (CJNIBase::GetSDKVersion() <= 27 && mtk_raw_decoder && google_raw_decoder)
+ {
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec::Open Prefer the Google raw decoder "
+ "over the MediaTek one");
+ m_codec = std::shared_ptr<CJNIMediaCodec>(
+ new CJNIMediaCodec(CJNIMediaCodec::createByCodecName("OMX.google.raw.decoder")));
+ }
+ else
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CDVDAudioCodecAndroidMediaCodec::Open Use the raw decoder proposed by the platform");
+ m_codec = std::shared_ptr<CJNIMediaCodec>(
+ new CJNIMediaCodec(CJNIMediaCodec::createDecoderByType(m_mime)));
+ }
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::Open Failed creating raw decoder");
+ return false;
+ }
+ if (!m_decryptCodec)
+ {
+ CDVDStreamInfo ffhints = hints;
+ ffhints.cryptoSession = nullptr;
+
+ m_decryptCodec = std::shared_ptr<CDVDAudioCodec>(new CDVDAudioCodecFFmpeg(m_processInfo));
+ if (!m_decryptCodec->Open(ffhints, options))
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::Open() Failed opening FFmpeg decoder");
+ return false;
+ }
+ }
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec Use raw decoder and decode using {}",
+ m_decryptCodec->GetName());
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec::Open() Use default handling for non encrypted stream");
+ return false;
+ }
+ }
+
+ if (!ConfigureMediaCodec())
+ {
+ m_codec.reset();
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDVDAudioCodecAndroidMediaCodec Open Android MediaCodec {}", m_formatname);
+
+ m_opened = true;
+ m_codecIsFed = false;
+ m_currentPts = DVD_NOPTS_VALUE;
+ return m_opened;
+}
+
+std::string CDVDAudioCodecAndroidMediaCodec::GetName()
+{
+ if (m_decryptCodec)
+ return "amc-raw/" + m_decryptCodec->GetName();
+ return m_formatname;
+};
+
+void CDVDAudioCodecAndroidMediaCodec::Dispose()
+{
+ if (!m_opened)
+ return;
+
+ m_opened = false;
+
+ if (m_codec)
+ {
+ m_codec->stop();
+ m_codec->release();
+ m_codec.reset();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+ }
+
+ if (m_crypto)
+ {
+ delete m_crypto;
+ m_crypto = nullptr;
+ }
+ m_decryptCodec = nullptr;
+}
+
+bool CDVDAudioCodecAndroidMediaCodec::AddData(const DemuxPacket &packet)
+{
+ CLog::Log(LOGDEBUG, LOGAUDIO,
+ "CDVDAudioCodecAndroidMediaCodec::AddData dts:{:0.4f} pts:{:0.4f} size({})", packet.dts,
+ packet.pts, packet.iSize);
+
+ if (packet.pData)
+ {
+ // try to fetch an input buffer
+ int64_t timeout_us = 5000;
+ int index = m_codec->dequeueInputBuffer(timeout_us);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ std::string err = CJNIBase::ExceptionToString();
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::AddData ExceptionCheck \n {}", err);
+ }
+ else if (index >= 0)
+ {
+ CJNIByteBuffer buffer = m_codec->getInputBuffer(index);
+ int size = buffer.capacity();
+
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDMediaCodecInfo::AddData getInputBuffers ExceptionCheck");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+
+ if (packet.iSize > size)
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::AddData, iSize({}) > size({})",
+ packet.iSize, size);
+ return packet.iSize;
+ }
+ // fetch a pointer to the ByteBuffer backing store
+ uint8_t *dst_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(buffer.get_raw());
+
+ if (dst_ptr)
+ {
+ // Codec specifics
+ switch(m_hints.codec)
+ {
+ default:
+ memcpy(dst_ptr, packet.pData, packet.iSize);
+ break;
+ }
+ }
+ else
+ return false;
+
+ CJNIMediaCodecCryptoInfo *cryptoInfo(0);
+ if (!!m_crypto->get_raw() && packet.cryptoInfo)
+ {
+ if (CJNIBase::GetSDKVersion() < 25 &&
+ packet.cryptoInfo->mode == CJNIMediaCodec::CRYPTO_MODE_AES_CBC)
+ {
+ CLog::LogF(LOGERROR, "Device API does not support CBCS decryption");
+ return false;
+ }
+ cryptoInfo = new CJNIMediaCodecCryptoInfo();
+ cryptoInfo->set(
+ packet.cryptoInfo->numSubSamples,
+ std::vector<int>(packet.cryptoInfo->clearBytes,
+ packet.cryptoInfo->clearBytes + packet.cryptoInfo->numSubSamples),
+ std::vector<int>(packet.cryptoInfo->cipherBytes,
+ packet.cryptoInfo->cipherBytes + packet.cryptoInfo->numSubSamples),
+ std::vector<char>(std::begin(packet.cryptoInfo->kid), std::end(packet.cryptoInfo->kid)),
+ std::vector<char>(std::begin(packet.cryptoInfo->iv), std::end(packet.cryptoInfo->iv)),
+ packet.cryptoInfo->mode == CJNIMediaCodec::CRYPTO_MODE_AES_CBC
+ ? CJNIMediaCodec::CRYPTO_MODE_AES_CBC
+ : CJNIMediaCodec::CRYPTO_MODE_AES_CTR);
+
+ CJNIMediaCodecCryptoInfoPattern cryptoInfoPattern(packet.cryptoInfo->cryptBlocks,
+ packet.cryptoInfo->skipBlocks);
+ cryptoInfo->setPattern(cryptoInfoPattern);
+ }
+
+ int flags = 0;
+ int offset = 0;
+ int64_t presentationTimeUs = static_cast<int64_t>(packet.pts);
+
+ if (!cryptoInfo)
+ m_codec->queueInputBuffer(index, offset, packet.iSize, presentationTimeUs, flags);
+ else
+ {
+ m_codec->queueSecureInputBuffer(index, offset, *cryptoInfo, presentationTimeUs, flags);
+ delete cryptoInfo;
+ }
+
+ // clear any jni exceptions, jni gets upset if we do not.
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::Decode ExceptionCheck");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+ m_codecIsFed = true;
+ }
+ }
+
+ if (m_decryptCodec)
+ {
+ DemuxPacket newPkt;
+ newPkt.iSize = GetData(&newPkt.pData);
+ newPkt.pts = m_currentPts;
+ newPkt.iStreamId = packet.iStreamId;
+ newPkt.demuxerId = packet.demuxerId;
+ newPkt.iGroupId = packet.iGroupId;
+ newPkt.pSideData = packet.pSideData;
+ newPkt.duration = packet.duration;
+ newPkt.dispTime = packet.dispTime;
+ newPkt.recoveryPoint = packet.recoveryPoint;
+ if (!packet.pData || newPkt.iSize)
+ m_decryptCodec->AddData(newPkt);
+ }
+ else
+ {
+ m_format.m_dataFormat = GetDataFormat();
+ m_format.m_channelLayout = GetChannelMap();
+ m_format.m_sampleRate = GetSampleRate();
+ m_format.m_frameSize = m_format.m_channelLayout.Count() * CAEUtil::DataFormatToBits(m_format.m_dataFormat) >> 3;
+ }
+ return true;
+}
+
+void CDVDAudioCodecAndroidMediaCodec::Reset()
+{
+ if (!m_opened)
+ return;
+
+ if (m_codec && m_codecIsFed)
+ {
+ // now we can flush the actual MediaCodec object
+ m_codec->flush();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::Reset ExceptionCheck");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+ }
+ m_codecIsFed = false;
+
+ if (m_decryptCodec)
+ m_decryptCodec->Reset();
+
+ m_currentPts = DVD_NOPTS_VALUE;
+}
+
+AEAudioFormat CDVDAudioCodecAndroidMediaCodec::GetFormat()
+{
+ if (m_decryptCodec)
+ return m_decryptCodec->GetFormat();
+
+ return m_format;
+}
+
+CAEChannelInfo CDVDAudioCodecAndroidMediaCodec::GetChannelMap()
+{
+ CAEChannelInfo chaninfo;
+
+ for (int i=0; i<m_channels; ++i)
+ chaninfo += KnownChannels[i];
+
+ return chaninfo;
+}
+
+bool CDVDAudioCodecAndroidMediaCodec::ConfigureMediaCodec(void)
+{
+ // setup a MediaFormat to match the audio content,
+ // used by codec during configure
+ CJNIMediaFormat mediaformat(
+ CJNIMediaFormat::createAudioFormat(m_mime, m_hints.samplerate, m_hints.channels));
+
+ if (!m_decryptCodec)
+ {
+ // handle codec extradata
+ if (m_hints.extrasize)
+ {
+ size_t size = m_hints.extrasize;
+ void *src_ptr = m_hints.extradata;
+ // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer,
+ // since the latter doesn't allocate storage of its own, and we don't know how long
+ // the codec uses the buffer.
+ CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size);
+ void *dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw());
+ memcpy(dts_ptr, src_ptr, size);
+ // codec will automatically handle buffers as extradata
+ // using entries with keys "csd-0", "csd-1", etc.
+ mediaformat.setByteBuffer("csd-0", bytebuffer);
+ }
+ else if (m_hints.codec == AV_CODEC_ID_AAC || m_hints.codec == AV_CODEC_ID_AAC_LATM)
+ {
+ mediaformat.setInteger(CJNIMediaFormat::KEY_IS_ADTS, 1);
+ }
+ }
+
+ // configure and start the codec.
+ // use the MediaFormat that we have setup.
+ // use a null MediaCrypto, our content is not encrypted.
+ // use a null Surface
+ int flags = 0;
+ CJNISurface surface(jni::jhobject(NULL));
+ m_codec->configure(mediaformat, surface, *m_crypto, flags);
+
+ // always, check/clear jni exceptions.
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::ExceptionCheck: configure");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return false;
+ }
+
+ m_codec->start();
+ // always, check/clear jni exceptions.
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::ExceptionCheck: start");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return false;
+ }
+
+ // There is no guarantee we'll get an INFO_OUTPUT_FORMAT_CHANGED (up to Android 4.3)
+ // Configure the output with defaults
+ if (!m_decryptCodec)
+ ConfigureOutputFormat(&mediaformat);
+
+ return true;
+}
+
+void CDVDAudioCodecAndroidMediaCodec::GetData(DVDAudioFrame &frame)
+{
+ if (m_decryptCodec)
+ {
+ m_decryptCodec->GetData(frame);
+ return;
+ }
+
+ frame.passthrough = false;
+ frame.nb_frames = 0;
+ frame.framesOut = 0;
+ frame.format.m_dataFormat = m_format.m_dataFormat;
+ frame.format.m_channelLayout = m_format.m_channelLayout;
+ frame.framesize = (CAEUtil::DataFormatToBits(frame.format.m_dataFormat) >> 3) * frame.format.m_channelLayout.Count();
+
+ if (frame.framesize == 0)
+ return;
+
+ if (!m_codecIsFed)
+ return;
+
+ frame.nb_frames = GetData(frame.data)/frame.framesize;
+ frame.planes = AE_IS_PLANAR(frame.format.m_dataFormat) ? frame.format.m_channelLayout.Count() : 1;
+ frame.bits_per_sample = CAEUtil::DataFormatToBits(frame.format.m_dataFormat);
+ frame.format.m_sampleRate = m_format.m_sampleRate;
+ frame.pts = m_currentPts;
+ m_currentPts = DVD_NOPTS_VALUE;
+ frame.matrix_encoding = GetMatrixEncoding();
+ frame.audio_service_type = GetAudioServiceType();
+ frame.profile = GetProfile();
+ // compute duration.
+ if (frame.format.m_sampleRate)
+ frame.duration = ((double)frame.nb_frames * DVD_TIME_BASE) / frame.format.m_sampleRate;
+ else
+ frame.duration = 0.0;
+ if (frame.nb_frames > 0 && CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
+ CLog::Log(LOGDEBUG, "MediaCodecAudio::GetData: frames:{} pts: {:0.4f}", frame.nb_frames,
+ frame.pts);
+}
+
+int CDVDAudioCodecAndroidMediaCodec::GetData(uint8_t** dst)
+{
+ m_bufferUsed = 0;
+
+ int64_t timeout_us = 10000;
+ CJNIMediaCodecBufferInfo bufferInfo;
+ int index = m_codec->dequeueOutputBuffer(bufferInfo, timeout_us);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ std::string err = CJNIBase::ExceptionToString();
+ CLog::Log(LOGERROR,
+ "CDVDAudioCodecAndroidMediaCodec::GetData ExceptionCheck; dequeueOutputBuffer \n {}",
+ err);
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return 0;
+ }
+ if (index >= 0)
+ {
+ CJNIByteBuffer buffer = m_codec->getOutputBuffer(index);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR,
+ "CDVDAudioCodecAndroidMediaCodec::GetData ExceptionCheck: getOutputBuffer({})",
+ index);
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return 0;
+ }
+
+ int flags = bufferInfo.flags();
+ if (flags & CJNIMediaCodec::BUFFER_FLAG_SYNC_FRAME)
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec:: BUFFER_FLAG_SYNC_FRAME");
+
+ if (flags & CJNIMediaCodec::BUFFER_FLAG_CODEC_CONFIG)
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec:: BUFFER_FLAG_CODEC_CONFIG");
+
+ if (flags & CJNIMediaCodec::BUFFER_FLAG_END_OF_STREAM)
+ {
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec:: BUFFER_FLAG_END_OF_STREAM");
+ m_codec->releaseOutputBuffer(index, false);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::GetData ExceptionCheck: releaseOutputBuffer");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ return 0;
+ }
+ return 0;
+ }
+
+ int size = bufferInfo.size();
+ int offset = bufferInfo.offset();
+
+ if (!buffer.isDirect())
+ CLog::Log(LOGWARNING, "CDVDAudioCodecAndroidMediaCodec:: buffer.isDirect == false");
+
+ if (size && buffer.capacity())
+ {
+ uint8_t *src_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(buffer.get_raw());
+ src_ptr += offset;
+
+ if (size > m_bufferSize)
+ {
+ m_bufferSize = size;
+ m_buffer = (uint8_t*)realloc(m_buffer, m_bufferSize);
+ if (m_buffer == nullptr)
+ throw std::runtime_error("Failed to realloc memory, insufficient memory available");
+ }
+
+ memcpy(m_buffer, src_ptr, size);
+ m_bufferUsed = size;
+ }
+ else
+ return 0;
+
+ m_codec->releaseOutputBuffer(index, false);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::GetData ExceptionCheck: releaseOutputBuffer");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+
+ CLog::Log(LOGDEBUG, LOGAUDIO, "CDVDAudioCodecAndroidMediaCodec::GetData index({}), size({})",
+ index, m_bufferUsed);
+
+ m_currentPts = bufferInfo.presentationTimeUs() == (int64_t)DVD_NOPTS_VALUE ? DVD_NOPTS_VALUE : bufferInfo.presentationTimeUs();
+
+ // always, check/clear jni exceptions.
+ if (xbmc_jnienv()->ExceptionCheck())
+ xbmc_jnienv()->ExceptionClear();
+ }
+ else if (index == CJNIMediaCodec::INFO_OUTPUT_BUFFERS_CHANGED)
+ {
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec:: GetData OUTPUT_BUFFERS_CHANGED");
+ }
+ else if (index == CJNIMediaCodec::INFO_OUTPUT_FORMAT_CHANGED)
+ {
+ CJNIMediaFormat mediaformat = m_codec->getOutputFormat();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::GetData(INFO_OUTPUT_FORMAT_CHANGED) ExceptionCheck: getOutputBuffers");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+ ConfigureOutputFormat(&mediaformat);
+ }
+ else if (index == CJNIMediaCodec::INFO_TRY_AGAIN_LATER)
+ {
+ // normal dequeueOutputBuffer timeout, ignore it.
+ m_bufferUsed = 0;
+ }
+ else
+ {
+ // we should never get here
+ CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::GetData unknown index({})", index);
+ }
+
+ *dst = m_buffer;
+ return m_bufferUsed;
+}
+
+void CDVDAudioCodecAndroidMediaCodec::ConfigureOutputFormat(CJNIMediaFormat* mediaformat)
+{
+ m_samplerate = 0;
+ m_channels = 0;
+
+ if (mediaformat->containsKey("sample-rate"))
+ m_samplerate = mediaformat->getInteger("sample-rate");
+ if (mediaformat->containsKey("channel-count"))
+ m_channels = mediaformat->getInteger("channel-count");
+
+#if 1 //defined(DEBUG_VERBOSE)
+ CLog::Log(LOGDEBUG,
+ "CDVDAudioCodecAndroidMediaCodec:: "
+ "sample_rate({}), channel_count({})",
+ m_samplerate, m_channels);
+#endif
+
+ // clear any jni exceptions
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h
new file mode 100644
index 0000000..f084765
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 Christian Browet
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDAudioCodec.h"
+#include "DVDStreamInfo.h"
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+
+#include <memory>
+#include <queue>
+#include <vector>
+
+class CJNIMediaCodec;
+class CJNIMediaCrypto;
+class CJNIMediaFormat;
+class CJNIByteBuffer;
+class CProcessInfo;
+
+struct DemuxPacket;
+
+class CDVDAudioCodecAndroidMediaCodec : public CDVDAudioCodec
+{
+public:
+ CDVDAudioCodecAndroidMediaCodec(CProcessInfo &processInfo);
+ ~CDVDAudioCodecAndroidMediaCodec() override;
+
+ // registration
+ static std::unique_ptr<CDVDAudioCodec> Create(CProcessInfo& processInfo);
+ static bool Register();
+
+ // required overrides
+public:
+ bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override;
+ void Dispose() override;
+ bool AddData(const DemuxPacket &packet) override;
+ void GetData(DVDAudioFrame &frame) override;
+ void Reset() override;
+ AEAudioFormat GetFormat() override;
+ std::string GetName() override;
+
+protected:
+ int GetData(uint8_t** dst);
+ int GetChannels() { return m_channels; }
+ int GetEncodedChannels() { return m_channels; }
+ CAEChannelInfo GetChannelMap();
+ int GetSampleRate() { return m_samplerate; }
+ int GetEncodedSampleRate() { return m_samplerate; }
+ enum AEDataFormat GetDataFormat() { return AE_FMT_S16NE; }
+
+ bool ConfigureMediaCodec(void);
+ void ConfigureOutputFormat(CJNIMediaFormat* mediaformat);
+
+ CDVDStreamInfo m_hints;
+ std::string m_mime;
+ std::string m_codecname;
+ std::string m_formatname;
+ bool m_opened, m_codecIsFed;
+ int m_samplerate;
+ int m_channels;
+ uint8_t* m_buffer;
+ int m_bufferSize;
+ int m_bufferUsed;
+ AEAudioFormat m_format;
+ double m_currentPts;
+
+ std::shared_ptr<CJNIMediaCodec> m_codec;
+ CJNIMediaCrypto *m_crypto;
+ std::shared_ptr<CDVDAudioCodec> m_decryptCodec;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.cpp
new file mode 100644
index 0000000..25f3f08
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.cpp
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDAudioCodecFFmpeg.h"
+#include "ServiceBroker.h"
+#include "../../DVDStreamInfo.h"
+#include "utils/log.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "DVDCodecs/DVDCodecs.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+
+extern "C" {
+#include <libavutil/opt.h>
+}
+
+CDVDAudioCodecFFmpeg::CDVDAudioCodecFFmpeg(CProcessInfo &processInfo) : CDVDAudioCodec(processInfo)
+{
+ m_pCodecContext = NULL;
+
+ m_channels = 0;
+ m_layout = 0;
+
+ m_pFrame = nullptr;
+ m_eof = false;
+}
+
+CDVDAudioCodecFFmpeg::~CDVDAudioCodecFFmpeg()
+{
+ Dispose();
+}
+
+bool CDVDAudioCodecFFmpeg::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ if (hints.cryptoSession)
+ {
+ CLog::Log(LOGERROR,"CDVDAudioCodecFFmpeg::Open() CryptoSessions unsupported!");
+ return false;
+ }
+
+ AVCodec* pCodec = NULL;
+ bool allowdtshddecode = true;
+
+ // set any special options
+ for(std::vector<CDVDCodecOption>::iterator it = options.m_keys.begin(); it != options.m_keys.end(); ++it)
+ if (it->m_name == "allowdtshddecode")
+ allowdtshddecode = atoi(it->m_value.c_str()) != 0;
+
+ if (hints.codec == AV_CODEC_ID_DTS && allowdtshddecode)
+ pCodec = avcodec_find_decoder_by_name("dcadec");
+
+ if (!pCodec)
+ pCodec = avcodec_find_decoder(hints.codec);
+
+ if (!pCodec)
+ {
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecFFmpeg::Open() Unable to find codec {}", hints.codec);
+ return false;
+ }
+
+ m_pCodecContext = avcodec_alloc_context3(pCodec);
+ if (!m_pCodecContext)
+ return false;
+
+ m_pCodecContext->debug = 0;
+ m_pCodecContext->workaround_bugs = 1;
+
+ if (pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
+ m_pCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED;
+
+ m_matrixEncoding = AV_MATRIX_ENCODING_NONE;
+ m_channels = 0;
+ m_pCodecContext->channels = hints.channels;
+ m_hint_layout = hints.channellayout;
+ m_pCodecContext->sample_rate = hints.samplerate;
+ m_pCodecContext->block_align = hints.blockalign;
+ m_pCodecContext->bit_rate = hints.bitrate;
+ m_pCodecContext->bits_per_coded_sample = hints.bitspersample;
+
+ if(m_pCodecContext->bits_per_coded_sample == 0)
+ m_pCodecContext->bits_per_coded_sample = 16;
+
+ if( hints.extradata && hints.extrasize > 0 )
+ {
+ m_pCodecContext->extradata = (uint8_t*)av_mallocz(hints.extrasize + AV_INPUT_BUFFER_PADDING_SIZE);
+ if(m_pCodecContext->extradata)
+ {
+ m_pCodecContext->extradata_size = hints.extrasize;
+ memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize);
+ }
+ }
+
+ float applyDrc = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioApplyDrc;
+ if (applyDrc >= 0.0f)
+ av_opt_set_double(m_pCodecContext, "drc_scale", static_cast<double>(applyDrc),
+ AV_OPT_SEARCH_CHILDREN);
+
+ if (avcodec_open2(m_pCodecContext, pCodec, NULL) < 0)
+ {
+ CLog::Log(LOGDEBUG,"CDVDAudioCodecFFmpeg::Open() Unable to open codec");
+ Dispose();
+ return false;
+ }
+
+ m_pFrame = av_frame_alloc();
+ if (!m_pFrame)
+ {
+ Dispose();
+ return false;
+ }
+
+ m_iSampleFormat = AV_SAMPLE_FMT_NONE;
+ m_matrixEncoding = AV_MATRIX_ENCODING_NONE;
+ m_hasDownmix = false;
+
+ m_codecName = "ff-" + std::string(m_pCodecContext->codec->name);
+
+ CLog::Log(LOGINFO, "CDVDAudioCodecFFmpeg::Open() Successful opened audio decoder {}",
+ m_pCodecContext->codec->name);
+
+ return true;
+}
+
+void CDVDAudioCodecFFmpeg::Dispose()
+{
+ av_frame_free(&m_pFrame);
+ avcodec_free_context(&m_pCodecContext);
+}
+
+bool CDVDAudioCodecFFmpeg::AddData(const DemuxPacket &packet)
+{
+ if (!m_pCodecContext)
+ return false;
+
+ if (m_eof)
+ {
+ Reset();
+ }
+
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDAudioCodecFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ avpkt->data = packet.pData;
+ avpkt->size = packet.iSize;
+ avpkt->dts = (packet.dts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.dts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->pts = (packet.pts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.pts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->side_data = static_cast<AVPacketSideData*>(packet.pSideData);
+ avpkt->side_data_elems = packet.iSideDataElems;
+
+ int ret = avcodec_send_packet(m_pCodecContext, avpkt);
+
+ //! @todo: properly handle avpkt side_data. this works around our improper use of the side_data
+ // as we pass pointers to ffmpeg allocated memory for the side_data. we should really be allocating
+ // and storing our own AVPacket. This will require some extensive changes.
+ av_buffer_unref(&avpkt->buf);
+ av_free(avpkt);
+
+ // try again
+ if (ret == AVERROR(EAGAIN))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void CDVDAudioCodecFFmpeg::GetData(DVDAudioFrame &frame)
+{
+ frame.nb_frames = 0;
+
+ uint8_t* data[16]{};
+ int bytes = GetData(data);
+ if (!bytes)
+ {
+ return;
+ }
+
+ frame.passthrough = false;
+ frame.format.m_dataFormat = m_format.m_dataFormat;
+ frame.format.m_channelLayout = m_format.m_channelLayout;
+ frame.framesize = (CAEUtil::DataFormatToBits(frame.format.m_dataFormat) >> 3) * frame.format.m_channelLayout.Count();
+ if(frame.framesize == 0)
+ return;
+
+ frame.nb_frames = bytes/frame.framesize;
+ frame.framesOut = 0;
+ frame.planes = AE_IS_PLANAR(frame.format.m_dataFormat) ? frame.format.m_channelLayout.Count() : 1;
+
+ for (unsigned int i=0; i<frame.planes; i++)
+ frame.data[i] = data[i];
+
+ frame.bits_per_sample = CAEUtil::DataFormatToBits(frame.format.m_dataFormat);
+ frame.format.m_sampleRate = m_format.m_sampleRate;
+ frame.matrix_encoding = GetMatrixEncoding();
+ frame.audio_service_type = GetAudioServiceType();
+ frame.profile = GetProfile();
+ // compute duration.
+ if (frame.format.m_sampleRate)
+ frame.duration = ((double)frame.nb_frames * DVD_TIME_BASE) / frame.format.m_sampleRate;
+ else
+ frame.duration = 0.0;
+
+ int64_t bpts = m_pFrame->best_effort_timestamp;
+ if(bpts != AV_NOPTS_VALUE)
+ frame.pts = (double)bpts * DVD_TIME_BASE / AV_TIME_BASE;
+ else
+ frame.pts = DVD_NOPTS_VALUE;
+
+ frame.hasDownmix = m_hasDownmix;
+ if (frame.hasDownmix)
+ {
+ frame.centerMixLevel = m_downmixInfo.center_mix_level;
+ }
+}
+
+int CDVDAudioCodecFFmpeg::GetData(uint8_t** dst)
+{
+ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame);
+ if (!ret)
+ {
+ if (m_pFrame->nb_side_data)
+ {
+ for (int i = 0; i < m_pFrame->nb_side_data; i++)
+ {
+ AVFrameSideData *sd = m_pFrame->side_data[i];
+ if (sd->data)
+ {
+ if (sd->type == AV_FRAME_DATA_MATRIXENCODING)
+ {
+ m_matrixEncoding = *(enum AVMatrixEncoding*)sd->data;
+ }
+ else if (sd->type == AV_FRAME_DATA_DOWNMIX_INFO)
+ {
+ m_downmixInfo = *(AVDownmixInfo*)sd->data;
+ m_hasDownmix = true;
+ }
+ }
+ }
+ }
+
+ m_format.m_dataFormat = GetDataFormat();
+ m_format.m_channelLayout = GetChannelMap();
+ m_format.m_sampleRate = GetSampleRate();
+ m_format.m_frameSize = m_format.m_channelLayout.Count() *
+ CAEUtil::DataFormatToBits(m_format.m_dataFormat) >> 3;
+
+ int planes = av_sample_fmt_is_planar(m_pCodecContext->sample_fmt) ? m_pFrame->channels : 1;
+ for (int i=0; i<planes; i++)
+ dst[i] = m_pFrame->extended_data[i];
+
+ return m_pFrame->nb_samples * m_pFrame->channels *
+ av_get_bytes_per_sample(m_pCodecContext->sample_fmt);
+ }
+
+ return 0;
+}
+
+void CDVDAudioCodecFFmpeg::Reset()
+{
+ if (m_pCodecContext) avcodec_flush_buffers(m_pCodecContext);
+ m_eof = false;
+}
+
+int CDVDAudioCodecFFmpeg::GetChannels()
+{
+ return m_pCodecContext->channels;
+}
+
+int CDVDAudioCodecFFmpeg::GetSampleRate()
+{
+ if (m_pCodecContext)
+ return m_pCodecContext->sample_rate;
+ return 0;
+}
+
+enum AEDataFormat CDVDAudioCodecFFmpeg::GetDataFormat()
+{
+ switch(m_pCodecContext->sample_fmt)
+ {
+ case AV_SAMPLE_FMT_U8 : return AE_FMT_U8;
+ case AV_SAMPLE_FMT_U8P : return AE_FMT_U8P;
+ case AV_SAMPLE_FMT_S16: return AE_FMT_S16NE;
+ case AV_SAMPLE_FMT_S16P: return AE_FMT_S16NEP;
+ case AV_SAMPLE_FMT_S32: return AE_FMT_S32NE;
+ case AV_SAMPLE_FMT_S32P: return AE_FMT_S32NEP;
+ case AV_SAMPLE_FMT_FLT: return AE_FMT_FLOAT;
+ case AV_SAMPLE_FMT_FLTP: return AE_FMT_FLOATP;
+ case AV_SAMPLE_FMT_DBL: return AE_FMT_DOUBLE;
+ case AV_SAMPLE_FMT_DBLP: return AE_FMT_DOUBLEP;
+ case AV_SAMPLE_FMT_NONE:
+ default:
+ CLog::Log(LOGERROR, "CDVDAudioCodecFFmpeg::GetDataFormat - invalid data format");
+ return AE_FMT_INVALID;
+ }
+}
+
+int CDVDAudioCodecFFmpeg::GetBitRate()
+{
+ if (m_pCodecContext)
+ return static_cast<int>(m_pCodecContext->bit_rate);
+ return 0;
+}
+
+enum AVMatrixEncoding CDVDAudioCodecFFmpeg::GetMatrixEncoding()
+{
+ return m_matrixEncoding;
+}
+
+enum AVAudioServiceType CDVDAudioCodecFFmpeg::GetAudioServiceType()
+{
+ if (m_pCodecContext)
+ return m_pCodecContext->audio_service_type;
+ return AV_AUDIO_SERVICE_TYPE_MAIN;
+}
+
+int CDVDAudioCodecFFmpeg::GetProfile()
+{
+ if (m_pCodecContext)
+ return m_pCodecContext->profile;
+ return 0;
+}
+
+static unsigned count_bits(int64_t value)
+{
+ unsigned bits = 0;
+ for(;value;++bits)
+ value &= value - 1;
+ return bits;
+}
+
+void CDVDAudioCodecFFmpeg::BuildChannelMap()
+{
+ if (m_channels == m_pCodecContext->channels && m_layout == m_pCodecContext->channel_layout)
+ return; //nothing to do here
+
+ m_channels = m_pCodecContext->channels;
+ m_layout = m_pCodecContext->channel_layout;
+
+ int64_t layout;
+
+ int bits = count_bits(m_pCodecContext->channel_layout);
+ if (bits == m_pCodecContext->channels)
+ layout = m_pCodecContext->channel_layout;
+ else
+ {
+ CLog::Log(LOGINFO,
+ "CDVDAudioCodecFFmpeg::GetChannelMap - FFmpeg reported {} channels, but the layout "
+ "contains {} - trying hints",
+ m_pCodecContext->channels, bits);
+ if (static_cast<int>(count_bits(m_hint_layout)) == m_pCodecContext->channels)
+ layout = m_hint_layout;
+ else
+ {
+ layout = av_get_default_channel_layout(m_pCodecContext->channels);
+ CLog::Log(LOGINFO, "Using default layout...");
+ }
+ }
+
+ m_channelLayout.Reset();
+
+ if (layout & AV_CH_FRONT_LEFT ) m_channelLayout += AE_CH_FL ;
+ if (layout & AV_CH_FRONT_RIGHT ) m_channelLayout += AE_CH_FR ;
+ if (layout & AV_CH_FRONT_CENTER ) m_channelLayout += AE_CH_FC ;
+ if (layout & AV_CH_LOW_FREQUENCY ) m_channelLayout += AE_CH_LFE ;
+ if (layout & AV_CH_BACK_LEFT ) m_channelLayout += AE_CH_BL ;
+ if (layout & AV_CH_BACK_RIGHT ) m_channelLayout += AE_CH_BR ;
+ if (layout & AV_CH_FRONT_LEFT_OF_CENTER ) m_channelLayout += AE_CH_FLOC;
+ if (layout & AV_CH_FRONT_RIGHT_OF_CENTER) m_channelLayout += AE_CH_FROC;
+ if (layout & AV_CH_BACK_CENTER ) m_channelLayout += AE_CH_BC ;
+ if (layout & AV_CH_SIDE_LEFT ) m_channelLayout += AE_CH_SL ;
+ if (layout & AV_CH_SIDE_RIGHT ) m_channelLayout += AE_CH_SR ;
+ if (layout & AV_CH_TOP_CENTER ) m_channelLayout += AE_CH_TC ;
+ if (layout & AV_CH_TOP_FRONT_LEFT ) m_channelLayout += AE_CH_TFL ;
+ if (layout & AV_CH_TOP_FRONT_CENTER ) m_channelLayout += AE_CH_TFC ;
+ if (layout & AV_CH_TOP_FRONT_RIGHT ) m_channelLayout += AE_CH_TFR ;
+ if (layout & AV_CH_TOP_BACK_LEFT ) m_channelLayout += AE_CH_BL ;
+ if (layout & AV_CH_TOP_BACK_CENTER ) m_channelLayout += AE_CH_BC ;
+ if (layout & AV_CH_TOP_BACK_RIGHT ) m_channelLayout += AE_CH_BR ;
+
+ m_channels = m_pCodecContext->channels;
+}
+
+CAEChannelInfo CDVDAudioCodecFFmpeg::GetChannelMap()
+{
+ BuildChannelMap();
+ return m_channelLayout;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.h b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.h
new file mode 100644
index 0000000..3441d8f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecFFmpeg.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDAudioCodec.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#include <libavutil/channel_layout.h>
+#include <libswresample/swresample.h>
+#include <libavutil/downmix_info.h>
+}
+
+class CProcessInfo;
+
+class CDVDAudioCodecFFmpeg : public CDVDAudioCodec
+{
+public:
+ explicit CDVDAudioCodecFFmpeg(CProcessInfo &processInfo);
+ ~CDVDAudioCodecFFmpeg() override;
+ bool Open(CDVDStreamInfo &hints,
+ CDVDCodecOptions &options) override;
+ void Dispose() override;
+ bool AddData(const DemuxPacket &packet) override;
+ void GetData(DVDAudioFrame &frame) override;
+ void Reset() override;
+ AEAudioFormat GetFormat() override { return m_format; }
+ std::string GetName() override { return m_codecName; }
+ enum AVMatrixEncoding GetMatrixEncoding() override;
+ enum AVAudioServiceType GetAudioServiceType() override;
+ int GetProfile() override;
+
+protected:
+ int GetData(uint8_t** dst);
+ enum AEDataFormat GetDataFormat();
+ int GetSampleRate();
+ int GetChannels();
+ CAEChannelInfo GetChannelMap();
+ int GetBitRate() override;
+ void BuildChannelMap();
+
+ AEAudioFormat m_format;
+ AVCodecContext* m_pCodecContext;
+ enum AVSampleFormat m_iSampleFormat = AV_SAMPLE_FMT_NONE;
+ CAEChannelInfo m_channelLayout;
+ enum AVMatrixEncoding m_matrixEncoding = AV_MATRIX_ENCODING_NONE;
+ AVFrame* m_pFrame;
+ AVDownmixInfo m_downmixInfo;
+ bool m_hasDownmix = false;
+ bool m_eof;
+ int m_channels;
+ uint64_t m_layout;
+ std::string m_codecName;
+ uint64_t m_hint_layout;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.cpp
new file mode 100644
index 0000000..3fa6c58
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDAudioCodecPassthrough.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDStreamInfo.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+}
+
+namespace
+{
+constexpr auto TRUEHD_BUF_SIZE = 61440;
+}
+
+CDVDAudioCodecPassthrough::CDVDAudioCodecPassthrough(CProcessInfo &processInfo, CAEStreamInfo::DataType streamType) :
+ CDVDAudioCodec(processInfo)
+{
+ m_format.m_streamInfo.m_type = streamType;
+ m_deviceIsRAW = processInfo.WantsRawPassthrough();
+}
+
+CDVDAudioCodecPassthrough::~CDVDAudioCodecPassthrough(void)
+{
+ Dispose();
+}
+
+bool CDVDAudioCodecPassthrough::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ m_parser.SetCoreOnly(false);
+ switch (m_format.m_streamInfo.m_type)
+ {
+ case CAEStreamInfo::STREAM_TYPE_AC3:
+ m_codecName = "pt-ac3";
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_EAC3:
+ m_codecName = "pt-eac3";
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
+ m_codecName = "pt-dtshd";
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD:
+ m_codecName = "pt-dtshd";
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
+ m_codecName = "pt-dts";
+ m_parser.SetCoreOnly(true);
+ break;
+
+ case CAEStreamInfo::STREAM_TYPE_TRUEHD:
+ m_codecName = "pt-truehd";
+
+ CLog::Log(LOGDEBUG, "CDVDAudioCodecPassthrough::{} - passthrough output device is {}",
+ __func__, m_deviceIsRAW ? "RAW" : "IEC");
+ break;
+
+ default:
+ return false;
+ }
+
+ m_dataSize = 0;
+ m_bufferSize = 0;
+ m_backlogSize = 0;
+ m_currentPts = DVD_NOPTS_VALUE;
+ m_nextPts = DVD_NOPTS_VALUE;
+ return true;
+}
+
+void CDVDAudioCodecPassthrough::Dispose()
+{
+ if (m_buffer)
+ {
+ delete[] m_buffer;
+ m_buffer = NULL;
+ }
+
+ free(m_backlogBuffer);
+ m_backlogBuffer = nullptr;
+ m_backlogBufferSize = 0;
+
+ m_bufferSize = 0;
+}
+
+bool CDVDAudioCodecPassthrough::AddData(const DemuxPacket &packet)
+{
+ if (m_backlogSize)
+ {
+ m_dataSize = m_bufferSize;
+ unsigned int consumed = m_parser.AddData(m_backlogBuffer, m_backlogSize, &m_buffer, &m_dataSize);
+ m_bufferSize = std::max(m_bufferSize, m_dataSize);
+ if (consumed != m_backlogSize)
+ {
+ memmove(m_backlogBuffer, m_backlogBuffer+consumed, m_backlogSize-consumed);
+ }
+ m_backlogSize -= consumed;
+ }
+
+ unsigned char *pData(const_cast<uint8_t*>(packet.pData));
+ int iSize(packet.iSize);
+
+ if (pData)
+ {
+ if (m_currentPts == DVD_NOPTS_VALUE)
+ {
+ if (m_nextPts != DVD_NOPTS_VALUE)
+ {
+ m_currentPts = m_nextPts;
+ m_nextPts = packet.pts;
+ }
+ else if (packet.pts != DVD_NOPTS_VALUE)
+ {
+ m_currentPts = packet.pts;
+ }
+ }
+ else
+ {
+ m_nextPts = packet.pts;
+ }
+ }
+
+ if (pData && !m_backlogSize)
+ {
+ if (iSize <= 0)
+ return true;
+
+ m_dataSize = m_bufferSize;
+ int used = m_parser.AddData(pData, iSize, &m_buffer, &m_dataSize);
+ m_bufferSize = std::max(m_bufferSize, m_dataSize);
+
+ if (used != iSize)
+ {
+ if (m_backlogBufferSize < static_cast<unsigned int>(iSize - used))
+ {
+ m_backlogBufferSize = std::max(TRUEHD_BUF_SIZE, iSize - used);
+ m_backlogBuffer = static_cast<uint8_t*>(realloc(m_backlogBuffer, m_backlogBufferSize));
+ }
+ m_backlogSize = iSize - used;
+ memcpy(m_backlogBuffer, pData + used, m_backlogSize);
+ }
+ }
+ else if (pData)
+ {
+ if (m_backlogBufferSize < (m_backlogSize + iSize))
+ {
+ m_backlogBufferSize = std::max(TRUEHD_BUF_SIZE, static_cast<int>(m_backlogSize + iSize));
+ m_backlogBuffer = static_cast<uint8_t*>(realloc(m_backlogBuffer, m_backlogBufferSize));
+ }
+ memcpy(m_backlogBuffer + m_backlogSize, pData, iSize);
+ m_backlogSize += iSize;
+ }
+
+ if (!m_dataSize)
+ return true;
+
+ m_format.m_dataFormat = AE_FMT_RAW;
+ m_format.m_streamInfo = m_parser.GetStreamInfo();
+ m_format.m_sampleRate = m_parser.GetSampleRate();
+ m_format.m_frameSize = 1;
+ CAEChannelInfo layout;
+ for (unsigned int i = 0; i < m_parser.GetChannels(); i++)
+ {
+ layout += AE_CH_RAW;
+ }
+ m_format.m_channelLayout = layout;
+
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ if (m_trueHDBuffer.empty())
+ {
+ m_trueHDBuffer.resize(TRUEHD_BUF_SIZE);
+ m_trueHDoffset = 0;
+ }
+
+ if (m_trueHDoffset == 0)
+ m_trueHDframes = 0;
+
+ memcpy(m_trueHDBuffer.data() + m_trueHDoffset, m_buffer, m_dataSize);
+ m_trueHDoffset += m_dataSize;
+
+ m_trueHDframes++;
+
+ // Only 12 audio units are packed in the buffer to avoid overflows and reduce latency.
+ // Compensates for the small increased latency in CAEBitstreamPacker::PackTrueHD (IEC)
+ // Android IEC packer (RAW) needs 24 audio units.
+ const unsigned int nFrames = m_deviceIsRAW ? 24 : 12;
+
+ if (m_trueHDframes == nFrames)
+ {
+ m_dataSize = TRUEHD_BUF_SIZE;
+ m_trueHDoffset = 0;
+ m_trueHDframes = 0;
+ }
+ else
+ m_dataSize = 0;
+ }
+
+ return true;
+}
+
+void CDVDAudioCodecPassthrough::GetData(DVDAudioFrame &frame)
+{
+ frame.nb_frames = GetData(frame.data);
+ frame.framesOut = 0;
+
+ if (frame.nb_frames == 0)
+ return;
+
+ frame.passthrough = true;
+ frame.format = m_format;
+ frame.planes = 1;
+ frame.bits_per_sample = 8;
+ frame.duration = DVD_MSEC_TO_TIME(frame.format.m_streamInfo.GetDuration());
+ frame.pts = m_currentPts;
+ m_currentPts = DVD_NOPTS_VALUE;
+}
+
+int CDVDAudioCodecPassthrough::GetData(uint8_t** dst)
+{
+ if (!m_dataSize)
+ AddData(DemuxPacket());
+
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ *dst = m_trueHDBuffer.data();
+ else
+ *dst = m_buffer;
+
+ int bytes = m_dataSize;
+ m_dataSize = 0;
+ return bytes;
+}
+
+void CDVDAudioCodecPassthrough::Reset()
+{
+ m_trueHDoffset = 0;
+ m_dataSize = 0;
+ m_bufferSize = 0;
+ m_backlogSize = 0;
+ m_currentPts = DVD_NOPTS_VALUE;
+ m_nextPts = DVD_NOPTS_VALUE;
+ m_parser.Reset();
+}
+
+int CDVDAudioCodecPassthrough::GetBufferSize()
+{
+ return (int)m_parser.GetBufferSize();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.h b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.h
new file mode 100644
index 0000000..09d5b12
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecPassthrough.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDAudioCodec.h"
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "cores/AudioEngine/Utils/AEBitstreamPacker.h"
+#include "cores/AudioEngine/Utils/AEStreamInfo.h"
+
+#include <list>
+#include <vector>
+
+class CProcessInfo;
+
+class CDVDAudioCodecPassthrough : public CDVDAudioCodec
+{
+public:
+ CDVDAudioCodecPassthrough(CProcessInfo &processInfo, CAEStreamInfo::DataType streamType);
+ ~CDVDAudioCodecPassthrough() override;
+
+ bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override;
+ void Dispose() override;
+ bool AddData(const DemuxPacket &packet) override;
+ void GetData(DVDAudioFrame &frame) override;
+ void Reset() override;
+ AEAudioFormat GetFormat() override { return m_format; }
+ bool NeedPassthrough() override { return true; }
+ std::string GetName() override { return m_codecName; }
+ int GetBufferSize() override;
+
+private:
+ int GetData(uint8_t** dst);
+ CAEStreamParser m_parser;
+ uint8_t* m_buffer = nullptr;
+ unsigned int m_bufferSize = 0;
+ unsigned int m_dataSize = 0;
+ AEAudioFormat m_format;
+ uint8_t *m_backlogBuffer = nullptr;
+ unsigned int m_backlogBufferSize = 0;
+ unsigned int m_backlogSize = 0;
+ double m_currentPts = DVD_NOPTS_VALUE;
+ double m_nextPts = DVD_NOPTS_VALUE;
+ std::string m_codecName;
+
+ // TrueHD specifics
+ std::vector<uint8_t> m_trueHDBuffer;
+ unsigned int m_trueHDoffset = 0;
+ unsigned int m_trueHDframes = 0;
+ bool m_deviceIsRAW{false};
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/CMakeLists.txt
new file mode 100644
index 0000000..16f0141
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES DVDCodecUtils.cpp
+ DVDFactoryCodec.cpp)
+
+set(HEADERS DVDCodecUtils.h
+ DVDCodecs.h
+ DVDFactoryCodec.h)
+
+core_add_library(dvdcodecs)
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.cpp
new file mode 100644
index 0000000..5b7f34d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDCodecUtils.h"
+
+#include "cores/FFmpeg.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+
+#include <array>
+
+extern "C" {
+#include <libswscale/swscale.h>
+}
+
+bool CDVDCodecUtils::IsVP3CompatibleWidth(int width)
+{
+ // known hardware limitation of purevideo 3 (VP3). (the Nvidia 9400 is a purevideo 3 chip)
+ // from nvidia's linux vdpau README: All current third generation PureVideo hardware
+ // (G98, MCP77, MCP78, MCP79, MCP7A) cannot decode H.264 for the following horizontal resolutions:
+ // 769-784, 849-864, 929-944, 1009–1024, 1793–1808, 1873–1888, 1953–1968 and 2033-2048 pixel.
+ // This relates to the following macroblock sizes.
+ int unsupported[] = {49, 54, 59, 64, 113, 118, 123, 128};
+ for (int u : unsupported)
+ {
+ if (u == (width + 15) / 16)
+ return false;
+ }
+ return true;
+}
+
+double CDVDCodecUtils::NormalizeFrameduration(double frameduration, bool *match)
+{
+ //if the duration is within 20 microseconds of a common duration, use that
+ // clang-format off
+ constexpr std::array<double, 8> durations = {
+ DVD_TIME_BASE * 1.001 / 24.0,
+ DVD_TIME_BASE / 24.0,
+ DVD_TIME_BASE / 25.0,
+ DVD_TIME_BASE * 1.001 / 30.0,
+ DVD_TIME_BASE / 30.0,
+ DVD_TIME_BASE / 50.0,
+ DVD_TIME_BASE * 1.001 / 60.0,
+ DVD_TIME_BASE / 60.0
+ };
+ // clang-format on
+
+ double lowestdiff = DVD_TIME_BASE;
+ int selected = -1;
+ for (size_t i = 0; i < durations.size(); i++)
+ {
+ double diff = fabs(frameduration - durations[i]);
+ if (diff < DVD_MSEC_TO_TIME(0.02) && diff < lowestdiff)
+ {
+ selected = i;
+ lowestdiff = diff;
+ }
+ }
+
+ if (selected != -1)
+ {
+ if (match)
+ *match = true;
+ return durations[selected];
+ }
+ else
+ {
+ if (match)
+ *match = false;
+ return frameduration;
+ }
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h
new file mode 100644
index 0000000..37872d1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+
+class CDVDCodecUtils
+{
+public:
+ static bool IsVP3CompatibleWidth(int width);
+ static double NormalizeFrameduration(double frameduration, bool *match = nullptr);
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecs.h b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecs.h
new file mode 100644
index 0000000..6b68823
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDCodecs.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+// special options that can be passed to a codec
+class CDVDCodecOption
+{
+public:
+ CDVDCodecOption(const std::string& name, const std::string& value) : m_name(name), m_value(value) {}
+ std::string m_name;
+ std::string m_value;
+};
+
+class CDVDCodecOptions
+{
+public:
+ std::vector<CDVDCodecOption> m_keys;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp
new file mode 100644
index 0000000..6f9769b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDFactoryCodec.h"
+
+#include "Audio/DVDAudioCodec.h"
+#include "Audio/DVDAudioCodecFFmpeg.h"
+#include "Audio/DVDAudioCodecPassthrough.h"
+#include "DVDStreamInfo.h"
+#include "Overlay/DVDOverlayCodec.h"
+#include "Overlay/DVDOverlayCodecCCText.h"
+#include "Overlay/DVDOverlayCodecFFmpeg.h"
+#include "Overlay/DVDOverlayCodecSSA.h"
+#include "Overlay/DVDOverlayCodecTX3G.h"
+#include "Overlay/DVDOverlayCodecText.h"
+#include "Overlay/OverlayCodecWebVTT.h"
+#include "Video/AddonVideoCodec.h"
+#include "Video/DVDVideoCodec.h"
+#include "Video/DVDVideoCodecFFmpeg.h"
+#include "addons/AddonProvider.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecs.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <utility>
+
+//------------------------------------------------------------------------------
+// Video
+//------------------------------------------------------------------------------
+
+std::map<std::string, CreateHWVideoCodec> CDVDFactoryCodec::m_hwVideoCodecs;
+std::map<std::string, CreateHWAudioCodec> CDVDFactoryCodec::m_hwAudioCodecs;
+
+std::map<std::string, CreateHWAccel> CDVDFactoryCodec::m_hwAccels;
+
+CCriticalSection videoCodecSection, audioCodecSection;
+
+std::unique_ptr<CDVDVideoCodec> CDVDFactoryCodec::CreateVideoCodec(CDVDStreamInfo& hint,
+ CProcessInfo& processInfo)
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ std::unique_ptr<CDVDVideoCodec> pCodec;
+ CDVDCodecOptions options;
+
+ // addon handler for this stream ?
+
+ if (hint.externalInterfaces)
+ {
+ ADDON::AddonInfoPtr addonInfo;
+ KODI_HANDLE parentInstance;
+ hint.externalInterfaces->GetAddonInstance(ADDON::IAddonProvider::INSTANCE_VIDEOCODEC, addonInfo, parentInstance);
+ if (addonInfo && parentInstance)
+ {
+ pCodec = std::make_unique<CAddonVideoCodec>(processInfo, addonInfo, parentInstance);
+ if (pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+ }
+ return nullptr;
+ }
+
+ // platform specifig video decoders
+ if (!(hint.codecOptions & CODEC_FORCE_SOFTWARE))
+ {
+ for (auto &codec : m_hwVideoCodecs)
+ {
+ pCodec = CreateVideoCodecHW(codec.first, processInfo);
+ if (pCodec && pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+ }
+ if (!(hint.codecOptions & CODEC_ALLOW_FALLBACK))
+ return nullptr;
+ }
+
+ pCodec = std::make_unique<CDVDVideoCodecFFmpeg>(processInfo);
+ if (pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+
+ return nullptr;
+}
+
+std::unique_ptr<CDVDVideoCodec> CDVDFactoryCodec::CreateVideoCodecHW(const std::string& id,
+ CProcessInfo& processInfo)
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ auto it = m_hwVideoCodecs.find(id);
+ if (it != m_hwVideoCodecs.end())
+ {
+ return it->second(processInfo);
+ }
+
+ return nullptr;
+}
+
+IHardwareDecoder* CDVDFactoryCodec::CreateVideoCodecHWAccel(const std::string& id,
+ CDVDStreamInfo& hint,
+ CProcessInfo& processInfo,
+ AVPixelFormat fmt)
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ auto it = m_hwAccels.find(id);
+ if (it != m_hwAccels.end())
+ {
+ return it->second(hint, processInfo, fmt);
+ }
+
+ return nullptr;
+}
+
+
+void CDVDFactoryCodec::RegisterHWVideoCodec(const std::string& id, CreateHWVideoCodec createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ m_hwVideoCodecs[id] = std::move(createFunc);
+}
+
+void CDVDFactoryCodec::ClearHWVideoCodecs()
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ m_hwVideoCodecs.clear();
+}
+
+std::vector<std::string> CDVDFactoryCodec::GetHWAccels()
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ std::vector<std::string> ret;
+ ret.reserve(m_hwAccels.size());
+ for (auto &hwaccel : m_hwAccels)
+ {
+ ret.push_back(hwaccel.first);
+ }
+ return ret;
+}
+
+void CDVDFactoryCodec::RegisterHWAccel(const std::string& id, CreateHWAccel createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ m_hwAccels[id] = std::move(createFunc);
+}
+
+void CDVDFactoryCodec::ClearHWAccels()
+{
+ std::unique_lock<CCriticalSection> lock(videoCodecSection);
+
+ m_hwAccels.clear();
+}
+
+//------------------------------------------------------------------------------
+// Audio
+//------------------------------------------------------------------------------
+
+std::unique_ptr<CDVDAudioCodec> CDVDFactoryCodec::CreateAudioCodec(
+ CDVDStreamInfo& hint,
+ CProcessInfo& processInfo,
+ bool allowpassthrough,
+ bool allowdtshddecode,
+ CAEStreamInfo::DataType ptStreamType)
+{
+ std::unique_ptr<CDVDAudioCodec> pCodec;
+ CDVDCodecOptions options;
+
+ if (allowpassthrough && ptStreamType != CAEStreamInfo::STREAM_TYPE_NULL)
+ options.m_keys.emplace_back("ptstreamtype", StringUtils::SizeToString(ptStreamType));
+
+ if (!allowdtshddecode)
+ options.m_keys.emplace_back("allowdtshddecode", "0");
+
+ // platform specifig audio decoders
+ for (auto &codec : m_hwAudioCodecs)
+ {
+ pCodec = CreateAudioCodecHW(codec.first, processInfo);
+ if (pCodec && pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+ }
+
+ // we don't use passthrough if "sync playback to display" is enabled
+ if (allowpassthrough && ptStreamType != CAEStreamInfo::STREAM_TYPE_NULL)
+ {
+ pCodec = std::make_unique<CDVDAudioCodecPassthrough>(processInfo, ptStreamType);
+ if (pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+ }
+
+ pCodec = std::make_unique<CDVDAudioCodecFFmpeg>(processInfo);
+ if (pCodec->Open(hint, options))
+ {
+ return pCodec;
+ }
+
+ return nullptr;
+}
+
+void CDVDFactoryCodec::RegisterHWAudioCodec(const std::string& id, CreateHWAudioCodec createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(audioCodecSection);
+
+ m_hwAudioCodecs[id] = std::move(createFunc);
+}
+
+void CDVDFactoryCodec::ClearHWAudioCodecs()
+{
+ std::unique_lock<CCriticalSection> lock(audioCodecSection);
+
+ m_hwAudioCodecs.clear();
+}
+
+std::unique_ptr<CDVDAudioCodec> CDVDFactoryCodec::CreateAudioCodecHW(const std::string& id,
+ CProcessInfo& processInfo)
+{
+ std::unique_lock<CCriticalSection> lock(audioCodecSection);
+
+ auto it = m_hwAudioCodecs.find(id);
+ if (it != m_hwAudioCodecs.end())
+ {
+ return it->second(processInfo);
+ }
+
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+// Overlay
+//------------------------------------------------------------------------------
+
+std::unique_ptr<CDVDOverlayCodec> CDVDFactoryCodec::CreateOverlayCodec(CDVDStreamInfo& hint)
+{
+ std::unique_ptr<CDVDOverlayCodec> pCodec;
+ CDVDCodecOptions options;
+
+ switch (hint.codec)
+ {
+ case AV_CODEC_ID_TEXT:
+ {
+ if (hint.source == STREAM_SOURCE_VIDEOMUX)
+ pCodec = std::make_unique<CDVDOverlayCodecCCText>();
+ else
+ pCodec = std::make_unique<CDVDOverlayCodecText>();
+ break;
+ }
+ case AV_CODEC_ID_SUBRIP:
+ pCodec = std::make_unique<CDVDOverlayCodecText>();
+ break;
+
+ case AV_CODEC_ID_SSA:
+ case AV_CODEC_ID_ASS:
+ pCodec = std::make_unique<CDVDOverlayCodecSSA>();
+ break;
+
+ case AV_CODEC_ID_MOV_TEXT:
+ pCodec = std::make_unique<CDVDOverlayCodecTX3G>();
+ break;
+
+ case AV_CODEC_ID_WEBVTT:
+ pCodec = std::make_unique<COverlayCodecWebVTT>();
+ break;
+
+ default:
+ pCodec = std::make_unique<CDVDOverlayCodecFFmpeg>();
+ break;
+ }
+
+ if (pCodec->Open(hint, options))
+ return pCodec;
+
+ return nullptr;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h
new file mode 100644
index 0000000..5c32add
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEStreamInfo.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+class CDVDVideoCodec;
+class CDVDAudioCodec;
+class CDVDOverlayCodec;
+class IHardwareDecoder;
+
+class CDemuxStreamVideo;
+class CDVDStreamInfo;
+class CDVDCodecOption;
+class CDVDCodecOptions;
+
+using CreateHWVideoCodec =
+ std::function<std::unique_ptr<CDVDVideoCodec>(CProcessInfo& processInfo)>;
+using CreateHWAccel = std::function<IHardwareDecoder*(
+ CDVDStreamInfo& hint, CProcessInfo& processInfo, AVPixelFormat fmt)>;
+using CreateHWAudioCodec =
+ std::function<std::unique_ptr<CDVDAudioCodec>(CProcessInfo& processInfo)>;
+
+class CDVDFactoryCodec
+{
+public:
+ static std::unique_ptr<CDVDVideoCodec> CreateVideoCodec(CDVDStreamInfo& hint,
+ CProcessInfo& processInfo);
+
+ static IHardwareDecoder* CreateVideoCodecHWAccel(const std::string& id,
+ CDVDStreamInfo& hint,
+ CProcessInfo& processInfo,
+ AVPixelFormat fmt);
+
+ static std::unique_ptr<CDVDAudioCodec> CreateAudioCodec(CDVDStreamInfo& hint,
+ CProcessInfo& processInfo,
+ bool allowpassthrough,
+ bool allowdtshddecode,
+ CAEStreamInfo::DataType ptStreamType);
+
+ static std::unique_ptr<CDVDOverlayCodec> CreateOverlayCodec(CDVDStreamInfo& hint);
+
+ static void RegisterHWVideoCodec(const std::string& id, CreateHWVideoCodec createFunc);
+ static void ClearHWVideoCodecs();
+
+ static void RegisterHWAccel(const std::string& id, CreateHWAccel createFunc);
+ static std::vector<std::string> GetHWAccels();
+ static void ClearHWAccels();
+
+ static void RegisterHWAudioCodec(const std::string& id, CreateHWAudioCodec createFunc);
+ static void ClearHWAudioCodecs();
+
+
+protected:
+ static std::unique_ptr<CDVDVideoCodec> CreateVideoCodecHW(const std::string& id,
+ CProcessInfo& processInfo);
+ static std::unique_ptr<CDVDAudioCodec> CreateAudioCodecHW(const std::string& id,
+ CProcessInfo& processInfo);
+
+ static std::map<std::string, CreateHWVideoCodec> m_hwVideoCodecs;
+ static std::map<std::string, CreateHWAccel> m_hwAccels;
+ static std::map<std::string, CreateHWAudioCodec> m_hwAudioCodecs;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/CMakeLists.txt
new file mode 100644
index 0000000..666b3bc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(SOURCES DVDOverlayCodec.cpp
+ DVDOverlayCodecFFmpeg.cpp
+ DVDOverlayCodecSSA.cpp
+ DVDOverlayCodecText.cpp
+ DVDOverlayCodecCCText.cpp
+ DVDOverlayCodecTX3G.cpp
+ OverlayCodecWebVTT.cpp
+ contrib/cc_decoder.c
+ contrib/cc_decoder708.cpp)
+
+set(HEADERS DVDOverlay.h
+ DVDOverlayCodec.h
+ DVDOverlayCodecFFmpeg.h
+ DVDOverlayCodecSSA.h
+ DVDOverlayCodecTX3G.h
+ DVDOverlayCodecText.h
+ DVDOverlayCodecCCText.h
+ DVDOverlayImage.h
+ DVDOverlayLibass.h
+ DVDOverlaySSA.h
+ DVDOverlaySpu.h
+ DVDOverlayText.h
+ OverlayCodecWebVTT.h
+ contrib/cc_decoder.h
+ contrib/cc_decoder708.h)
+
+core_add_library(dvdoverlaycodecs)
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h
new file mode 100644
index 0000000..bf8ff7a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2006-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <atomic>
+#include <stdexcept>
+#include <vector>
+
+enum DVDOverlayType
+{
+ DVDOVERLAY_TYPE_NONE = -1,
+ DVDOVERLAY_TYPE_SPU = 1,
+ DVDOVERLAY_TYPE_TEXT = 2,
+ DVDOVERLAY_TYPE_IMAGE = 3,
+ DVDOVERLAY_TYPE_SSA = 4,
+ DVDOVERLAY_TYPE_GROUP = 5,
+};
+
+class CDVDOverlay
+{
+public:
+ explicit CDVDOverlay(DVDOverlayType type)
+ {
+ m_type = type;
+
+ iPTSStartTime = 0LL;
+ iPTSStopTime = 0LL;
+ bForced = false;
+ replace = false;
+ m_references = 1;
+ m_textureid = 0;
+ m_enableTextAlign = false;
+ m_overlayContainerFlushable = true;
+ m_setForcedMargins = false;
+ }
+
+ CDVDOverlay(const CDVDOverlay& src)
+ {
+ m_type = src.m_type;
+ iPTSStartTime = src.iPTSStartTime;
+ iPTSStopTime = src.iPTSStopTime;
+ bForced = src.bForced;
+ replace = src.replace;
+ m_references = 1;
+ m_textureid = 0;
+ m_enableTextAlign = src.m_enableTextAlign;
+ m_overlayContainerFlushable = src.m_overlayContainerFlushable;
+ m_setForcedMargins = src.m_setForcedMargins;
+ }
+
+ virtual ~CDVDOverlay()
+ {
+ assert(m_references == 0);
+ }
+
+ /**
+ * increase the reference counter by one.
+ */
+ CDVDOverlay* Acquire()
+ {
+ m_references++;
+ return this;
+ }
+
+ /**
+ * decrease the reference counter by one.
+ */
+ int Release()
+ {
+ m_references--;
+ int ret = m_references;
+ if (m_references == 0)
+ delete this;
+ return ret;
+ }
+
+ /**
+ * static release function for use with shared ptr for example
+ */
+ static void Release(CDVDOverlay* ov)
+ {
+ ov->Release();
+ }
+
+ bool IsOverlayType(DVDOverlayType type) { return (m_type == type); }
+
+ /**
+ * return a copy to VideoPlayerSubtitle in order to have hw resources cleared
+ * after rendering
+ */
+ virtual CDVDOverlay* Clone() { return Acquire(); }
+
+ /*
+ * \brief Enable the use of text alignment (left/center/right).
+ */
+ virtual void SetTextAlignEnabled(bool enable)
+ {
+ throw std::logic_error("EnableTextAlign method not implemented.");
+ }
+
+ /*
+ * \brief Return true if the text alignment (left/center/right) is enabled otherwise false.
+ */
+ bool IsTextAlignEnabled() { return m_enableTextAlign; }
+
+ /*
+ * \brief Allow/Disallow the overlay container to flush the overlay.
+ */
+ void SetOverlayContainerFlushable(bool isFlushable) { m_overlayContainerFlushable = isFlushable; }
+
+ /*
+ * \brief Return true when the overlay container can flush the overlay on flush events.
+ */
+ bool IsOverlayContainerFlushable() { return m_overlayContainerFlushable; }
+
+ /*
+ * \brief Specify if the margins are handled by the subtitle codec/parser.
+ */
+ void SetForcedMargins(bool setForcedMargins) { m_setForcedMargins = setForcedMargins; }
+
+ /*
+ * \brief Return true if the margins are handled by the subtitle codec/parser.
+ */
+ bool IsForcedMargins() const { return m_setForcedMargins; }
+
+ double iPTSStartTime;
+ double iPTSStopTime;
+ bool bForced; // display, no matter what
+ bool replace; // replace by next nomatter what stoptime it has
+ unsigned long m_textureid;
+
+protected:
+ DVDOverlayType m_type;
+ bool m_enableTextAlign;
+ bool m_overlayContainerFlushable;
+ bool m_setForcedMargins;
+
+private:
+ std::atomic_int m_references;
+};
+
+typedef std::vector<CDVDOverlay*> VecOverlays;
+typedef std::vector<CDVDOverlay*>::iterator VecOverlaysIter;
+
+
+class CDVDOverlayGroup : public CDVDOverlay
+{
+
+public:
+ ~CDVDOverlayGroup() override
+ {
+ for(VecOverlaysIter it = m_overlays.begin(); it != m_overlays.end(); ++it)
+ (*it)->Release();
+ m_overlays.clear();
+ }
+
+ CDVDOverlayGroup()
+ : CDVDOverlay(DVDOVERLAY_TYPE_GROUP)
+ {
+ }
+
+ CDVDOverlayGroup(CDVDOverlayGroup& src)
+ : CDVDOverlay(src)
+ {
+ for(VecOverlaysIter it = src.m_overlays.begin(); it != src.m_overlays.end(); ++it)
+ m_overlays.push_back((*it)->Clone());
+ }
+ VecOverlays m_overlays;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.cpp
new file mode 100644
index 0000000..27d33c7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "DVDOverlayCodec.h"
+
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+
+
+void CDVDOverlayCodec::GetAbsoluteTimes(double& starttime, double& stoptime, DemuxPacket* pkt)
+{
+ if (!pkt)
+ return;
+
+ double duration = 0.0;
+ double pts = starttime;
+
+ // we assume pts from packet is better than what
+ // decoder gives us, only take duration
+ // from decoder if available
+ if (stoptime > starttime)
+ duration = stoptime - starttime;
+ else if (pkt->duration != DVD_NOPTS_VALUE)
+ duration = pkt->duration;
+
+ if (pkt->pts != DVD_NOPTS_VALUE)
+ pts = pkt->pts;
+ else if (pkt->dts != DVD_NOPTS_VALUE)
+ pts = pkt->dts;
+
+ starttime = pts;
+ if (duration > 0)
+ stoptime = pts + duration;
+ else
+ stoptime = 0;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h
new file mode 100644
index 0000000..05c336d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
+
+#include <string>
+
+#include "PlatformDefs.h"
+
+// VC_ messages, messages can be combined
+enum class OverlayMessage
+{
+ // an error occurred, no other messages will be returned
+ OC_ERROR = 0x00000001,
+
+ // the decoder needs more data
+ OC_BUFFER = 0x00000002,
+
+ // the decoder decoded an overlay, call Decode(NULL, 0) again to parse the rest of the data
+ OC_OVERLAY = 0x00000004,
+
+ // the decoder has decoded the packet, no overlay will be provided because the previous one is still valid
+ OC_DONE = 0x00000008,
+};
+
+class CDVDOverlay;
+class CDVDStreamInfo;
+class CDVDCodecOption;
+class CDVDCodecOptions;
+
+class CDVDOverlayCodec
+{
+public:
+
+ explicit CDVDOverlayCodec(const char* name) : m_codecName(name) {}
+
+ virtual ~CDVDOverlayCodec() = default;
+
+ /*
+ * Open the decoder, returns true on success
+ */
+ virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) = 0;
+
+ /*
+ * Dispose, Free all resources
+ */
+ virtual void Dispose() = 0;
+
+ /*
+ * returns one or a combination of VC_ messages
+ * pData and iSize can be NULL, this means we should flush the rest of the data.
+ */
+ virtual OverlayMessage Decode(DemuxPacket* pPacket) = 0;
+
+ /*
+ * Reset the decoder.
+ * Should be the same as calling Dispose and Open after each other
+ */
+ virtual void Reset() = 0;
+
+ /*
+ * Flush the current working packet
+ * This may leave the internal state intact
+ */
+ virtual void Flush() = 0;
+
+ /*
+ * returns a valid overlay or NULL
+ * the data is valid until the next Decode call
+ */
+ virtual CDVDOverlay* GetOverlay() = 0;
+
+ /*
+ * return codecs name
+ */
+ const std::string& GetName() const { return m_codecName; }
+
+protected:
+ /*
+ * \brief Adapts startTime, stopTIme from the subtitle stream (which is relative to stream pts)
+ * so that it returns the absolute start and stop timestamps.
+ */
+ static void GetAbsoluteTimes(double& starttime, double& stoptime, DemuxPacket* pkt);
+
+ struct SubtitlePacketExtraData
+ {
+ double m_chapterStartTime;
+ };
+
+private:
+ std::string m_codecName;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.cpp
new file mode 100644
index 0000000..347c8b6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayCodecCCText.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDOverlayText.h"
+#include "DVDStreamInfo.h"
+#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+namespace
+{
+// Muxed Closed Caption subtitles don't have the stop PTS value,
+// the stop value can be taken from: the start PTS of the next subtitle,
+// or, the start PTS of the next subtitle without text -if any-,
+// otherwise we fallback to 20 secs duration (long duration so as not
+// to cause side effects on texts that expand like karaoke, but slowly)
+constexpr double DEFAULT_DURATION = 20.0 * (double)DVD_TIME_BASE;
+} // namespace
+
+CDVDOverlayCodecCCText::CDVDOverlayCodecCCText() : CDVDOverlayCodec("CC Text Subtitle Decoder")
+{
+ m_pOverlay = nullptr;
+ m_prevSubId = NO_SUBTITLE_ID;
+ m_prevPTSStart = 0.0;
+ m_prevText.clear();
+ m_changePrevStopTime = false;
+}
+
+CDVDOverlayCodecCCText::~CDVDOverlayCodecCCText()
+{
+ Dispose();
+}
+
+bool CDVDOverlayCodecCCText::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ Dispose();
+
+ return Initialize();
+}
+
+void CDVDOverlayCodecCCText::Dispose()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+}
+
+OverlayMessage CDVDOverlayCodecCCText::Decode(DemuxPacket* pPacket)
+{
+ if (!pPacket)
+ return OverlayMessage::OC_ERROR;
+
+ uint8_t* data = pPacket->pData;
+ std::string text((char*)data, (char*)(data + pPacket->iSize));
+
+ // We delete some old Events to avoid allocating too much,
+ // this can easily happen with karaoke text or live videos
+ int lastId = DeleteSubtitles(50, 200);
+ if (m_prevSubId != NO_SUBTITLE_ID)
+ {
+ if (lastId == NO_SUBTITLE_ID)
+ {
+ m_prevPTSStart = 0.0;
+ m_prevText.clear();
+ m_changePrevStopTime = false;
+ }
+ m_prevSubId = lastId;
+ }
+
+ double PTSStartTime = 0;
+ double PTSStopTime = 0;
+
+ CDVDOverlayCodec::GetAbsoluteTimes(PTSStartTime, PTSStopTime, pPacket);
+ CDVDSubtitleTagSami TagConv;
+
+ PTSStopTime = PTSStartTime + DEFAULT_DURATION;
+
+ if (TagConv.Init())
+ {
+ TagConv.ConvertLine(text);
+ TagConv.CloseTag(text);
+
+ if (!text.empty())
+ {
+ if (m_prevText == text)
+ {
+ // Extend the duration of previously added event
+ ChangeSubtitleStopTime(m_prevSubId, PTSStartTime + DEFAULT_DURATION);
+ }
+ else
+ {
+ // Set the stop time of previously added event based on current start PTS
+ if (m_changePrevStopTime)
+ ChangeSubtitleStopTime(m_prevSubId, PTSStartTime);
+
+ m_prevSubId = AddSubtitle(text, PTSStartTime, PTSStopTime);
+ m_changePrevStopTime = true;
+ }
+ }
+ else if (m_prevText != text)
+ {
+ // Set the stop time of previously added event based on current start PTS
+ ChangeSubtitleStopTime(m_prevSubId, PTSStartTime);
+ m_changePrevStopTime = false;
+ }
+
+ m_prevText = text;
+ m_prevPTSStart = PTSStartTime;
+ }
+ else
+ CLog::Log(LOGERROR, "{} - Failed to initialize tag converter", __FUNCTION__);
+
+ return m_pOverlay ? OverlayMessage::OC_DONE : OverlayMessage::OC_OVERLAY;
+}
+
+void CDVDOverlayCodecCCText::PostProcess(std::string& text)
+{
+ // The data that come from InputStream could contains \r chars
+ // we have to remove them all because it causes to display empty box "tofu"
+ //! @todo This must be removed after the rework of the CC decoders
+ StringUtils::Replace(text, "\r", "");
+ CSubtitlesAdapter::PostProcess(text);
+}
+
+void CDVDOverlayCodecCCText::Reset()
+{
+ Dispose();
+ Flush();
+}
+
+void CDVDOverlayCodecCCText::Flush()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+
+ m_prevSubId = NO_SUBTITLE_ID;
+ m_prevPTSStart = 0.0;
+ m_prevText.clear();
+ m_changePrevStopTime = false;
+
+ FlushSubtitles();
+}
+
+CDVDOverlay* CDVDOverlayCodecCCText::GetOverlay()
+{
+ if (m_pOverlay)
+ return nullptr;
+ m_pOverlay = CreateOverlay();
+ m_pOverlay->SetTextAlignEnabled(true);
+ return m_pOverlay->Acquire();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.h
new file mode 100644
index 0000000..808b45e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecCCText.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/SubtitlesAdapter.h"
+
+class CDVDOverlay;
+
+class CDVDOverlayCodecCCText : public CDVDOverlayCodec, private CSubtitlesAdapter
+{
+public:
+ CDVDOverlayCodecCCText();
+ ~CDVDOverlayCodecCCText() override;
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+ // Specialization of CSubtitlesAdapter
+ void PostProcess(std::string& text) override;
+
+private:
+ void Dispose() override;
+ CDVDOverlay* m_pOverlay;
+ int m_prevSubId;
+ double m_prevPTSStart;
+ std::string m_prevText;
+ bool m_changePrevStopTime;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.cpp
new file mode 100644
index 0000000..8c26cad
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayCodecFFmpeg.h"
+
+#include "DVDOverlayImage.h"
+#include "DVDStreamInfo.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/EndianSwap.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+CDVDOverlayCodecFFmpeg::CDVDOverlayCodecFFmpeg() : CDVDOverlayCodec("FFmpeg Subtitle Decoder")
+{
+ m_pCodecContext = NULL;
+ m_SubtitleIndex = -1;
+ m_width = 0;
+ m_height = 0;
+ m_StartTime = 0.0;
+ m_StopTime = 0.0;
+ memset(&m_Subtitle, 0, sizeof(m_Subtitle));
+}
+
+CDVDOverlayCodecFFmpeg::~CDVDOverlayCodecFFmpeg()
+{
+ Dispose();
+}
+
+bool CDVDOverlayCodecFFmpeg::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+
+ // decoding of this kind of subs does not work reliable
+ if (hints.codec == AV_CODEC_ID_EIA_608)
+ return false;
+
+ AVCodec* pCodec = avcodec_find_decoder(hints.codec);
+ if (!pCodec)
+ {
+ CLog::Log(LOGDEBUG, "{} - Unable to find codec {}", __FUNCTION__, hints.codec);
+ return false;
+ }
+
+ m_pCodecContext = avcodec_alloc_context3(pCodec);
+ if (!m_pCodecContext)
+ return false;
+
+ m_pCodecContext->debug = 0;
+ m_pCodecContext->workaround_bugs = FF_BUG_AUTODETECT;
+ m_pCodecContext->codec_tag = hints.codec_tag;
+ m_pCodecContext->time_base.num = 1;
+ m_pCodecContext->time_base.den = DVD_TIME_BASE;
+ m_pCodecContext->pkt_timebase.num = 1;
+ m_pCodecContext->pkt_timebase.den = DVD_TIME_BASE;
+
+ if( hints.extradata && hints.extrasize > 0 )
+ {
+ m_pCodecContext->extradata_size = hints.extrasize;
+ m_pCodecContext->extradata = (uint8_t*)av_mallocz(hints.extrasize + AV_INPUT_BUFFER_PADDING_SIZE);
+ memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize);
+
+ // start parsing of extra data - create a copy to be safe and make it zero-terminating to avoid access violations!
+ unsigned int parse_extrasize = hints.extrasize;
+ char* parse_extra = new char[parse_extrasize + 1];
+ memcpy(parse_extra, hints.extradata, parse_extrasize);
+ parse_extra[parse_extrasize] = '\0';
+
+ // assume that the extra data is formatted as a concatenation of lines ('\n' terminated)
+ char *ptr = parse_extra;
+ do // read line by line
+ {
+ if (!strncmp(ptr, "size:", 5))
+ {
+ int width = 0, height = 0;
+ sscanf(ptr, "size: %dx%d", &width, &height);
+ if (width > 0 && height > 0)
+ {
+ m_pCodecContext->width = width;
+ m_pCodecContext->height = height;
+ CLog::Log(LOGDEBUG, "{} - parsed extradata: size: {} x {}", __FUNCTION__, width, height);
+ }
+ }
+ /*
+ // leaving commented code: these items don't work yet... but they may be meaningful
+ if (!strncmp(ptr, "palette:", 8))
+ if (sscanf(ptr, "palette: %x, %x, %x, %x, %x, %x, %x, %x,"
+ " %x, %x, %x, %x, %x, %x, %x, %x", ...
+ if (!StringUtils::CompareNoCase(ptr, "forced subs: on", 15))
+ forced_subs_only = 1;
+ */
+ // if tried all possibilities, then read newline char and move to next line
+ ptr = strchr(ptr, '\n');
+ if (ptr != NULL) ptr++;
+ }
+ while (ptr != NULL && ptr <= parse_extra + parse_extrasize);
+
+ delete[] parse_extra;
+ }
+
+ if (avcodec_open2(m_pCodecContext, pCodec, NULL) < 0)
+ {
+ CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Unable to open codec");
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+
+ return true;
+}
+
+void CDVDOverlayCodecFFmpeg::Dispose()
+{
+ avsubtitle_free(&m_Subtitle);
+ avcodec_free_context(&m_pCodecContext);
+}
+
+OverlayMessage CDVDOverlayCodecFFmpeg::Decode(DemuxPacket* pPacket)
+{
+ if (!m_pCodecContext || !pPacket)
+ return OverlayMessage::OC_ERROR;
+
+ int gotsub = 0, len = 0;
+
+ avsubtitle_free(&m_Subtitle);
+
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDOverlayCodecFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return OverlayMessage::OC_ERROR;
+ }
+
+ avpkt->data = pPacket->pData;
+ avpkt->size = pPacket->iSize;
+ avpkt->pts = pPacket->pts == DVD_NOPTS_VALUE ? AV_NOPTS_VALUE : (int64_t)pPacket->pts;
+ avpkt->dts = pPacket->dts == DVD_NOPTS_VALUE ? AV_NOPTS_VALUE : (int64_t)pPacket->dts;
+
+ len = avcodec_decode_subtitle2(m_pCodecContext, &m_Subtitle, &gotsub, avpkt);
+
+ int size = avpkt->size;
+
+ av_packet_free(&avpkt);
+
+ if (len < 0)
+ {
+ CLog::Log(LOGERROR, "{} - avcodec_decode_subtitle returned failure", __FUNCTION__);
+ Flush();
+ return OverlayMessage::OC_ERROR;
+ }
+
+ if (len != size)
+ CLog::Log(LOGWARNING, "{} - avcodec_decode_subtitle didn't consume the full packet",
+ __FUNCTION__);
+
+ if (!gotsub)
+ return OverlayMessage::OC_BUFFER;
+
+ double pts_offset = 0.0;
+
+ if (m_pCodecContext->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE && m_Subtitle.format == 0)
+ {
+ // for pgs subtitles the packet pts of the end_segments are wrong
+ // instead use the subtitle pts to calc the offset here
+ // see http://git.videolan.org/?p=ffmpeg.git;a=commit;h=2939e258f9d1fff89b3b68536beb931b54611585
+
+ if (m_Subtitle.pts != AV_NOPTS_VALUE && pPacket->pts != DVD_NOPTS_VALUE)
+ {
+ pts_offset = m_Subtitle.pts - pPacket->pts ;
+ }
+ }
+
+ m_StartTime = DVD_MSEC_TO_TIME(m_Subtitle.start_display_time);
+ m_StopTime = DVD_MSEC_TO_TIME(m_Subtitle.end_display_time);
+
+ //adapt start and stop time to our packet pts
+ CDVDOverlayCodec::GetAbsoluteTimes(m_StartTime, m_StopTime, pPacket);
+
+ m_StartTime += pts_offset;
+ if (m_StopTime > 0)
+ m_StopTime += pts_offset;
+
+ m_SubtitleIndex = 0;
+
+ return OverlayMessage::OC_OVERLAY;
+}
+
+void CDVDOverlayCodecFFmpeg::Reset()
+{
+ Flush();
+}
+
+void CDVDOverlayCodecFFmpeg::Flush()
+{
+ avsubtitle_free(&m_Subtitle);
+ m_SubtitleIndex = -1;
+
+ avcodec_flush_buffers(m_pCodecContext);
+}
+
+CDVDOverlay* CDVDOverlayCodecFFmpeg::GetOverlay()
+{
+ if(m_SubtitleIndex<0)
+ return nullptr;
+
+ if(m_Subtitle.num_rects == 0 && m_SubtitleIndex == 0)
+ {
+ // we must add an empty overlay to replace the previous one
+ CDVDOverlay* o = new CDVDOverlay(DVDOVERLAY_TYPE_NONE);
+ o->iPTSStartTime = m_StartTime;
+ o->iPTSStopTime = 0;
+ o->replace = true;
+ m_SubtitleIndex++;
+ return o;
+ }
+
+ if(m_Subtitle.format == 0)
+ {
+ if(m_SubtitleIndex >= (int)m_Subtitle.num_rects)
+ return nullptr;
+
+ if(m_Subtitle.rects[m_SubtitleIndex] == NULL)
+ return nullptr;
+
+ AVSubtitleRect rect = *m_Subtitle.rects[m_SubtitleIndex];
+ if (rect.data[0] == NULL)
+ return nullptr;
+
+ m_height = m_pCodecContext->height;
+ m_width = m_pCodecContext->width;
+
+ if (m_pCodecContext->codec_id == AV_CODEC_ID_DVB_SUBTITLE)
+ {
+ // ETSI EN 300 743 V1.3.1
+ // 5.3.1
+ // Absence of a DDS in a stream implies that the stream is coded in accordance with EN 300 743 (V1.2.1) [5] and that a
+ // display width of 720 pixels and a display height of 576 lines may be assumed.
+ if (!m_height && !m_width)
+ {
+ m_width = 720;
+ m_height = 576;
+ }
+ }
+
+ RENDER_STEREO_MODE render_stereo_mode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
+ if (render_stereo_mode != RENDER_STEREO_MODE_OFF &&
+ render_stereo_mode != RENDER_STEREO_MODE_HARDWAREBASED)
+ {
+ if (rect.h > m_height / 2)
+ {
+ m_height /= 2;
+ rect.h /= 2;
+ }
+ else if (rect.w > m_width / 2)
+ {
+ m_width /= 2;
+ rect.w /= 2;
+ }
+ }
+
+ CDVDOverlayImage* overlay = new CDVDOverlayImage();
+
+ overlay->iPTSStartTime = m_StartTime;
+ overlay->iPTSStopTime = m_StopTime;
+ overlay->replace = true;
+ overlay->linesize = rect.w;
+ overlay->pixels.resize(rect.w * rect.h);
+ overlay->palette.resize(rect.nb_colors);
+ overlay->x = rect.x;
+ overlay->y = rect.y;
+ overlay->width = rect.w;
+ overlay->height = rect.h;
+ overlay->bForced = rect.flags != 0;
+ overlay->source_width = m_width;
+ overlay->source_height = m_height;
+
+ uint8_t* s = rect.data[0];
+ uint8_t* t = overlay->pixels.data();
+
+ for (int i = 0; i < rect.h; i++)
+ {
+ memcpy(t, s, rect.w);
+ s += rect.linesize[0];
+ t += overlay->linesize;
+ }
+
+ for (int i = 0; i < rect.nb_colors; i++)
+ overlay->palette[i] = Endian_SwapLE32(((uint32_t *)rect.data[1])[i]);
+
+ m_SubtitleIndex++;
+
+ return overlay;
+ }
+
+ return nullptr;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.h
new file mode 100644
index 0000000..a95ce15
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecFFmpeg.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+}
+
+class CDVDOverlaySpu;
+class CDVDOverlayText;
+
+class CDVDOverlayCodecFFmpeg : public CDVDOverlayCodec
+{
+public:
+ CDVDOverlayCodecFFmpeg();
+ ~CDVDOverlayCodecFFmpeg() override;
+ bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+private:
+ void Dispose() override;
+ AVCodecContext* m_pCodecContext;
+ AVSubtitle m_Subtitle;
+ int m_SubtitleIndex;
+ double m_StartTime;
+ double m_StopTime;
+
+ int m_width;
+ int m_height;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.cpp
new file mode 100644
index 0000000..7b6d0be
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayCodecSSA.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDOverlaySSA.h"
+#include "DVDStreamInfo.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SubtitlesSettings.h"
+#include "utils/StringUtils.h"
+
+#include <memory>
+
+using namespace KODI;
+
+CDVDOverlayCodecSSA::CDVDOverlayCodecSSA()
+ : CDVDOverlayCodec("SSA Subtitle Decoder"), m_libass(std::make_shared<CDVDSubtitlesLibass>())
+{
+ m_pOverlay = nullptr;
+ m_order = 0;
+ m_libass->Configure();
+}
+
+CDVDOverlayCodecSSA::~CDVDOverlayCodecSSA()
+{
+ Dispose();
+}
+
+bool CDVDOverlayCodecSSA::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ if (hints.codec != AV_CODEC_ID_SSA && hints.codec != AV_CODEC_ID_ASS)
+ return false;
+
+ Dispose();
+
+ return m_libass->DecodeHeader(static_cast<char*>(hints.extradata), hints.extrasize);
+}
+
+void CDVDOverlayCodecSSA::Dispose()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+}
+
+OverlayMessage CDVDOverlayCodecSSA::Decode(DemuxPacket* pPacket)
+{
+ if (!pPacket)
+ return OverlayMessage::OC_ERROR;
+
+ double pts = pPacket->dts != DVD_NOPTS_VALUE ? pPacket->dts : pPacket->pts;
+ // libass only has a precision of msec
+ pts = round(pts / 1000) * 1000;
+
+ uint8_t* data = pPacket->pData;
+ int size = pPacket->iSize;
+ double duration = pPacket->duration;
+ if (duration == DVD_NOPTS_VALUE)
+ duration = 0.0;
+
+ if (strncmp((const char*)data, "Dialogue:", 9) == 0)
+ {
+ int sh, sm, ss, sc, eh, em, es, ec;
+ double beg, end;
+ size_t pos;
+ std::string line, line2;
+ std::vector<std::string> lines;
+ StringUtils::Tokenize((const char*)data, lines, "\r\n");
+ for (size_t i = 0; i < lines.size(); i++)
+ {
+ line = lines[i];
+ StringUtils::Trim(line);
+ std::unique_ptr<char[]> layer(new char[line.length() + 1]);
+
+ if (sscanf(line.c_str(), "%*[^:]:%[^,],%d:%d:%d%*c%d,%d:%d:%d%*c%d", layer.get(), &sh, &sm,
+ &ss, &sc, &eh, &em, &es, &ec) != 9)
+ continue;
+
+ end = 10000 * ((eh * 360000.0) + (em * 6000.0) + (es * 100.0) + ec);
+ beg = 10000 * ((sh * 360000.0) + (sm * 6000.0) + (ss * 100.0) + sc);
+
+ pos = line.find_first_of(',', 0);
+ pos = line.find_first_of(',', pos + 1);
+ pos = line.find_first_of(',', pos + 1);
+ if (pos == std::string::npos)
+ continue;
+
+ line2 = StringUtils::Format("{},{},{}", m_order++, layer.get(), line.substr(pos + 1));
+
+ m_libass->DecodeDemuxPkt(line2.c_str(), static_cast<int>(line2.length()), beg, end - beg);
+ }
+ }
+ else
+ {
+ m_libass->DecodeDemuxPkt((char*)data, size, pts, duration);
+ }
+
+ return m_pOverlay ? OverlayMessage::OC_DONE : OverlayMessage::OC_OVERLAY;
+}
+
+void CDVDOverlayCodecSSA::Reset()
+{
+ Dispose();
+ Flush();
+}
+
+void CDVDOverlayCodecSSA::Flush()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+
+ m_order = 0;
+}
+
+CDVDOverlay* CDVDOverlayCodecSSA::GetOverlay()
+{
+ if (m_pOverlay)
+ return nullptr;
+ m_pOverlay = new CDVDOverlaySSA(m_libass);
+ m_pOverlay->iPTSStartTime = 0;
+ m_pOverlay->iPTSStopTime = DVD_NOPTS_VALUE;
+ auto overrideStyles{
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetOverrideStyles()};
+ m_pOverlay->SetForcedMargins(overrideStyles != SUBTITLES::OverrideStyles::STYLES_POSITIONS &&
+ overrideStyles != SUBTITLES::OverrideStyles::POSITIONS);
+ return m_pOverlay->Acquire();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.h
new file mode 100644
index 0000000..a31882f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecSSA.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/DVDSubtitlesLibass.h"
+
+class CDVDOverlaySSA;
+
+class CDVDOverlayCodecSSA : public CDVDOverlayCodec
+{
+public:
+ CDVDOverlayCodecSSA();
+ ~CDVDOverlayCodecSSA() override;
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+private:
+ void Dispose() override;
+ std::shared_ptr<CDVDSubtitlesLibass> m_libass;
+ CDVDOverlaySSA* m_pOverlay;
+ int m_order;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp
new file mode 100644
index 0000000..bdc92e5
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayCodecTX3G.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDOverlayText.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/SubtitlesStyle.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "utils/CharArrayParser.h"
+#include "utils/ColorUtils.h"
+#include "utils/StreamUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <vector>
+
+// 3GPP/TX3G (aka MPEG-4 Timed Text) Subtitle support
+// 3GPP -> 3rd Generation Partnership Program
+// adapted from https://github.com/HandBrake/HandBrake/blob/master/libhb/dectx3gsub.c;
+
+namespace
+{
+enum FaceStyleFlag
+{
+ BOLD = 0x1,
+ ITALIC = 0x2,
+ UNDERLINE = 0x4
+};
+
+struct StyleRecord
+{
+ uint16_t startChar; // index in terms of character (not byte) position
+ uint16_t endChar; // index in terms of character (not byte) position
+ uint16_t fontID;
+ uint8_t faceStyleFlags; // FaceStyleFlag
+ uint8_t fontSize;
+ UTILS::COLOR::Color textColorARGB;
+ unsigned int textColorAlphaCh;
+};
+
+constexpr uint32_t BOX_TYPE_UUID = StreamUtils::MakeFourCC('u', 'u', 'i', 'd');
+constexpr uint32_t BOX_TYPE_STYL = StreamUtils::MakeFourCC('s', 't', 'y', 'l'); // TextStyleBox
+
+void ConvertStyleToTags(std::string& strUTF8, const StyleRecord& style, bool closingTags)
+{
+ if (style.faceStyleFlags & BOLD)
+ strUTF8.append(closingTags ? "{\\b0}" : "{\\b1}");
+ if (style.faceStyleFlags & ITALIC)
+ strUTF8.append(closingTags ? "{\\i0}" : "{\\i1}");
+ if (style.faceStyleFlags & UNDERLINE)
+ strUTF8.append(closingTags ? "{\\u0}" : "{\\u1}");
+ if (style.textColorARGB != UTILS::COLOR::WHITE)
+ {
+ if (closingTags)
+ strUTF8 += "{\\c}";
+ else
+ {
+ UTILS::COLOR::Color color = UTILS::COLOR::ConvertToBGR(style.textColorARGB);
+ strUTF8 += StringUtils::Format("{{\\c&H{:06x}&}}", color);
+ }
+ }
+ if (style.textColorAlphaCh != 255)
+ {
+ // Libass use inverted alpha channel 0==opaque
+ unsigned int alpha = 0;
+ if (!closingTags)
+ alpha = 255 - style.textColorAlphaCh;
+ strUTF8 += StringUtils::Format("{{\\1a&H{:02x}&}}", alpha);
+ }
+}
+} // unnamed namespace
+
+CDVDOverlayCodecTX3G::CDVDOverlayCodecTX3G() : CDVDOverlayCodec("TX3G Subtitle Decoder")
+{
+}
+
+CDVDOverlayCodecTX3G::~CDVDOverlayCodecTX3G()
+{
+ Dispose();
+}
+
+bool CDVDOverlayCodecTX3G::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ if (hints.codec != AV_CODEC_ID_MOV_TEXT)
+ return false;
+
+ Dispose();
+
+ return Initialize();
+}
+
+void CDVDOverlayCodecTX3G::Dispose()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+}
+
+OverlayMessage CDVDOverlayCodecTX3G::Decode(DemuxPacket* pPacket)
+{
+ double PTSStartTime = 0;
+ double PTSStopTime = 0;
+
+ CDVDOverlayCodec::GetAbsoluteTimes(PTSStartTime, PTSStopTime, pPacket);
+
+ char* data = reinterpret_cast<char*>(pPacket->pData);
+
+ // Parse the packet as a TX3G TextSample.
+ CCharArrayParser sampleData;
+ sampleData.Reset(data, pPacket->iSize);
+
+ uint16_t textLength = 0;
+ char* text = nullptr;
+ if (sampleData.CharsLeft() >= 2)
+ textLength = sampleData.ReadNextUnsignedShort();
+ if (sampleData.CharsLeft() >= textLength)
+ {
+ text = data + sampleData.GetPosition();
+ sampleData.SkipChars(textLength);
+ }
+ if (!text)
+ return OverlayMessage::OC_ERROR;
+
+ std::vector<StyleRecord> styleRecords;
+
+ // Read all TextSampleModifierBox types
+ while (sampleData.CharsLeft() > 0)
+ {
+ if (sampleData.CharsLeft() < MP4_BOX_HEADER_SIZE)
+ {
+ CLog::Log(LOGWARNING, "{} - Incomplete box header found", __FUNCTION__);
+ break;
+ }
+
+ uint32_t boxSize = sampleData.ReadNextUnsignedInt();
+ uint32_t boxType = sampleData.ReadNextUnsignedInt();
+
+ if (boxType == BOX_TYPE_UUID)
+ {
+ CLog::Log(LOGDEBUG, "{} - Sample data has unsupported extended type 'uuid'", __FUNCTION__);
+ }
+ else if (boxType == BOX_TYPE_STYL)
+ {
+ // Parse the contained StyleRecords
+ if (styleRecords.size() != 0)
+ {
+ CLog::Log(LOGDEBUG, "{} - Found additional TextStyleBox, skipping", __FUNCTION__);
+ sampleData.SkipChars(boxSize - MP4_BOX_HEADER_SIZE);
+ continue;
+ }
+
+ if (sampleData.CharsLeft() < 2)
+ {
+ CLog::Log(LOGWARNING, "{} - Incomplete TextStyleBox header found", __FUNCTION__);
+ return OverlayMessage::OC_ERROR;
+ }
+ uint16_t styleCount = sampleData.ReadNextUnsignedShort();
+
+ // Get the data of each style record
+ // Each style is ordered by starting character offset, and the starting
+ // offset of one style record shall be greater than or equal to the
+ // ending character offset of the preceding record,
+ // styles records shall not overlap their character ranges.
+ for (int i = 0; i < styleCount; i++)
+ {
+ if (sampleData.CharsLeft() < 12)
+ {
+ CLog::Log(LOGWARNING, "{} - Incomplete StyleRecord found, skipping", __FUNCTION__);
+ sampleData.SkipChars(sampleData.CharsLeft());
+ continue;
+ }
+
+ StyleRecord styleRec;
+ styleRec.startChar = sampleData.ReadNextUnsignedShort();
+ styleRec.endChar = sampleData.ReadNextUnsignedShort();
+ styleRec.fontID = sampleData.ReadNextUnsignedShort();
+ styleRec.faceStyleFlags = sampleData.ReadNextUnsignedChar();
+ styleRec.fontSize = sampleData.ReadNextUnsignedChar();
+ styleRec.textColorARGB = UTILS::COLOR::ConvertToARGB(sampleData.ReadNextUnsignedInt());
+ styleRec.textColorAlphaCh = (styleRec.textColorARGB & 0xFF000000) >> 24;
+ // clamp bgnChar/bgnChar to textLength, we alloc enough space above and
+ // this fixes broken encoders that do not handle endChar correctly.
+ if (styleRec.startChar > textLength)
+ styleRec.startChar = textLength;
+ if (styleRec.endChar > textLength)
+ styleRec.endChar = textLength;
+
+ // Skip zero-length style
+ if (styleRec.startChar == styleRec.endChar)
+ continue;
+
+ styleRecords.emplace_back(styleRec);
+ }
+ }
+ else
+ {
+ // Other types of TextSampleModifierBox are not supported
+ sampleData.SkipChars(boxSize - MP4_BOX_HEADER_SIZE);
+ }
+ }
+
+ uint16_t charIndex = 0;
+ size_t styleIndex = 0;
+ std::string strUTF8;
+ bool skipChars = false;
+ // Parse the text to add the converted styles records,
+ // index over textLength chars to include broken encoders,
+ // so we pickup closing styles on broken encoders
+ for (char* curPos = text; curPos <= text + textLength; curPos++)
+ {
+ if ((*curPos & 0xC0) == 0x80)
+ {
+ // Is a non-first byte of a multi-byte UTF-8 character
+ strUTF8.append(static_cast<const char*>(curPos), 1);
+ continue; // ...without incrementing 'charIndex'
+ }
+
+ // Go through styles, a style can end where another one begins
+ while (styleIndex < styleRecords.size())
+ {
+ if (styleRecords[styleIndex].startChar == charIndex)
+ {
+ ConvertStyleToTags(strUTF8, styleRecords[styleIndex], false);
+ break;
+ }
+ else if (styleRecords[styleIndex].endChar == charIndex)
+ {
+ ConvertStyleToTags(strUTF8, styleRecords[styleIndex], true);
+ styleIndex++;
+ }
+ else
+ break;
+ }
+
+ if (*curPos == '{') // erase unsupported tags
+ skipChars = true;
+
+ // Skip all \r because it causes the line to display empty box "tofu"
+ if (!skipChars && *curPos != '\0' && *curPos != '\r')
+ strUTF8.append(static_cast<const char*>(curPos), 1);
+
+ if (*curPos == '}')
+ skipChars = false;
+
+ charIndex++;
+ }
+
+ if (strUTF8.empty())
+ return OverlayMessage::OC_BUFFER;
+
+ AddSubtitle(strUTF8, PTSStartTime, PTSStopTime);
+
+ return m_pOverlay ? OverlayMessage::OC_DONE : OverlayMessage::OC_OVERLAY;
+}
+
+void CDVDOverlayCodecTX3G::PostProcess(std::string& text)
+{
+ if (text[text.size() - 1] == '\n')
+ text.erase(text.size() - 1);
+ CSubtitlesAdapter::PostProcess(text);
+}
+
+void CDVDOverlayCodecTX3G::Reset()
+{
+ Dispose();
+ Flush();
+}
+
+void CDVDOverlayCodecTX3G::Flush()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+
+ FlushSubtitles();
+}
+
+CDVDOverlay* CDVDOverlayCodecTX3G::GetOverlay()
+{
+ if (m_pOverlay)
+ return nullptr;
+ m_pOverlay = CreateOverlay();
+ return m_pOverlay->Acquire();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.h
new file mode 100644
index 0000000..0c4983d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+#include "DVDSubtitles/SubtitlesAdapter.h"
+
+class CDVDOverlay;
+
+class CDVDOverlayCodecTX3G : public CDVDOverlayCodec, private CSubtitlesAdapter
+{
+public:
+ CDVDOverlayCodecTX3G();
+ ~CDVDOverlayCodecTX3G() override;
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+ // Specialization of CSubtitlesAdapter
+ void PostProcess(std::string& text) override;
+
+private:
+ void Dispose() override;
+ CDVDOverlay* m_pOverlay{nullptr};
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.cpp
new file mode 100644
index 0000000..0c4d817
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayCodecText.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDOverlayText.h"
+#include "DVDStreamInfo.h"
+#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <memory>
+
+namespace
+{
+// Subtitle packets reaching the decoder may not have the stop PTS value,
+// the stop value can be taken from the start PTS of the next subtitle.
+// Otherwise we fallback to a default of 20 secs duration. This is only
+// applied if the subtitle packets have 0 duration (stop > start).
+constexpr double DEFAULT_DURATION = 20.0 * static_cast<double>(DVD_TIME_BASE);
+} // namespace
+
+CDVDOverlayCodecText::CDVDOverlayCodecText() : CDVDOverlayCodec("Text Subtitle Decoder")
+{
+ m_pOverlay = nullptr;
+}
+
+CDVDOverlayCodecText::~CDVDOverlayCodecText()
+{
+ Dispose();
+}
+
+bool CDVDOverlayCodecText::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ if (hints.codec != AV_CODEC_ID_TEXT && hints.codec != AV_CODEC_ID_SSA &&
+ hints.codec != AV_CODEC_ID_SUBRIP)
+ return false;
+
+ m_codecId = hints.codec;
+
+ Dispose();
+
+ return Initialize();
+}
+
+void CDVDOverlayCodecText::Dispose()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+}
+
+OverlayMessage CDVDOverlayCodecText::Decode(DemuxPacket* pPacket)
+{
+ if (!pPacket)
+ return OverlayMessage::OC_ERROR;
+
+ uint8_t* data = pPacket->pData;
+ char* start = (char*)data;
+ char* end = (char*)(data + pPacket->iSize);
+
+ if (m_codecId == AV_CODEC_ID_SSA)
+ {
+ // currently just skip the prefixed ssa fields (8 fields)
+ int nFieldCount = 8;
+ while (nFieldCount > 0 && start < end)
+ {
+ if (*start == ',')
+ nFieldCount--;
+
+ start++;
+ }
+ }
+
+ std::string text(start, end);
+ double PTSStartTime = 0;
+ double PTSStopTime = 0;
+
+ CDVDOverlayCodec::GetAbsoluteTimes(PTSStartTime, PTSStopTime, pPacket);
+ CDVDSubtitleTagSami TagConv;
+
+ if (TagConv.Init())
+ {
+ TagConv.ConvertLine(text);
+ TagConv.CloseTag(text);
+
+ // similar to CC text, if the subtitle duration is invalid (stop > start)
+ // we might want to assume the stop time will be set by the next received
+ // packet
+ if (PTSStopTime < PTSStartTime)
+ {
+ if (m_changePrevStopTime)
+ {
+ ChangeSubtitleStopTime(m_prevSubId, PTSStartTime);
+ m_changePrevStopTime = false;
+ }
+
+ PTSStopTime = PTSStartTime + DEFAULT_DURATION;
+ m_changePrevStopTime = true;
+ }
+
+ m_prevSubId = AddSubtitle(text, PTSStartTime, PTSStopTime);
+ }
+ else
+ CLog::Log(LOGERROR, "{} - Failed to initialize tag converter", __FUNCTION__);
+
+ return m_pOverlay ? OverlayMessage::OC_DONE : OverlayMessage::OC_OVERLAY;
+}
+
+void CDVDOverlayCodecText::PostProcess(std::string& text)
+{
+ // The data that come from InputStream could contains \r chars
+ // we have to remove them all because it causes to display empty box "tofu"
+ StringUtils::Replace(text, "\r", "");
+ CSubtitlesAdapter::PostProcess(text);
+}
+
+void CDVDOverlayCodecText::Reset()
+{
+ Dispose();
+ Flush();
+}
+
+void CDVDOverlayCodecText::Flush()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+
+ FlushSubtitles();
+}
+
+CDVDOverlay* CDVDOverlayCodecText::GetOverlay()
+{
+ if (m_pOverlay)
+ return nullptr;
+ m_pOverlay = CreateOverlay();
+ return m_pOverlay->Acquire();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.h
new file mode 100644
index 0000000..5a5c3bf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecText.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/SubtitlesAdapter.h"
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+}
+
+class CDVDOverlay;
+
+class CDVDOverlayCodecText : public CDVDOverlayCodec, private CSubtitlesAdapter
+{
+public:
+ CDVDOverlayCodecText();
+ ~CDVDOverlayCodecText() override;
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+ // Specialization of CSubtitlesAdapter
+ void PostProcess(std::string& text) override;
+
+private:
+ void Dispose() override;
+ CDVDOverlay* m_pOverlay;
+ CDVDStreamInfo m_hints;
+ int m_prevSubId{-1};
+ bool m_changePrevStopTime{false};
+ AVCodecID m_codecId{AV_CODEC_ID_NONE};
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h
new file mode 100644
index 0000000..3af66d8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlay.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "PlatformDefs.h"
+
+class CDVDOverlayImage : public CDVDOverlay
+{
+public:
+ CDVDOverlayImage() : CDVDOverlay(DVDOVERLAY_TYPE_IMAGE)
+ {
+ }
+
+ CDVDOverlayImage(const CDVDOverlayImage& src, int sub_x, int sub_y, int sub_w, int sub_h)
+ : CDVDOverlay(src)
+ {
+ int bpp;
+ if (!src.palette.empty())
+ {
+ bpp = 1;
+ palette = src.palette;
+ }
+ else
+ {
+ bpp = 4;
+ palette.clear();
+ }
+
+ linesize = sub_w * bpp;
+ x = sub_x;
+ y = sub_y;
+ width = sub_w;
+ height = sub_h;
+ source_width = src.source_width;
+ source_height = src.source_height;
+
+ pixels.resize(sub_h * linesize);
+
+ uint8_t* s = src.data_at(sub_x, sub_y);
+ uint8_t* t = pixels.data();
+
+ for (int row = 0; row < sub_h; ++row)
+ {
+ memcpy(t, s, linesize);
+ s += src.linesize;
+ t += linesize;
+ }
+
+ m_textureid = 0;
+ }
+
+ ~CDVDOverlayImage() override = default;
+
+ CDVDOverlayImage* Clone() override
+ {
+ return new CDVDOverlayImage(*this);
+ }
+
+ uint8_t* data_at(int sub_x, int sub_y) const
+ {
+ const int bpp = palette.empty() ? 4 : 1;
+ return const_cast<uint8_t*>(pixels.data() + ((sub_y - y) * linesize + (sub_x - x) * bpp));
+ }
+
+ std::vector<uint8_t> pixels;
+ std::vector<uint32_t> palette;
+
+ int linesize{0};
+ int x{0};
+ int y{0};
+ int width{0};
+ int height{0};
+ int source_width{0};
+ int source_height{0};
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h
new file mode 100644
index 0000000..db80da2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2006-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlay.h"
+#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h"
+
+#include <memory>
+
+class CDVDOverlayLibass : public CDVDOverlay
+{
+public:
+ explicit CDVDOverlayLibass(const std::shared_ptr<CDVDSubtitlesLibass>& libass,
+ DVDOverlayType type)
+ : CDVDOverlay(type), m_libass(libass)
+ {
+ }
+
+ CDVDOverlayLibass(const CDVDOverlayLibass& src) : CDVDOverlay(src), m_libass(src.m_libass) {}
+
+ /*!
+ \brief Getter for Libass handler
+ \return The Libass handler.
+ */
+ std::shared_ptr<CDVDSubtitlesLibass> GetLibassHandler() const { return m_libass; }
+
+private:
+ std::shared_ptr<CDVDSubtitlesLibass> m_libass;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h
new file mode 100644
index 0000000..c7b899a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayLibass.h"
+#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h"
+
+#include <memory>
+
+class CDVDOverlaySSA : public CDVDOverlayLibass
+{
+public:
+ explicit CDVDOverlaySSA(const std::shared_ptr<CDVDSubtitlesLibass>& libass)
+ : CDVDOverlayLibass(libass, DVDOVERLAY_TYPE_SSA)
+ {
+ replace = true;
+ }
+
+ ~CDVDOverlaySSA() override = default;
+
+ CDVDOverlaySSA* Clone() override { return new CDVDOverlaySSA(*this); }
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h
new file mode 100644
index 0000000..10a45e8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlay.h"
+
+#include <stdint.h>
+#include <string.h>
+
+class CDVDOverlaySpu : public CDVDOverlay
+{
+public:
+ CDVDOverlaySpu() : CDVDOverlay(DVDOVERLAY_TYPE_SPU)
+ {
+ pTFData = 0;
+ pBFData = 0;
+ x = 0;
+ y = 0;
+ width = 0;
+ height = 0;
+
+ crop_i_x_end = 0;
+ crop_i_y_end = 0;
+ crop_i_x_start = 0;
+ crop_i_y_start = 0;
+
+ bHasColor = false;
+ bHasAlpha = false;
+
+ memset(result, 0, sizeof(result));
+ memset(alpha, 0, sizeof(alpha));
+ memset(color, 0, sizeof(color));
+ memset(highlight_alpha, 0, sizeof(highlight_alpha));
+ memset(highlight_color, 0, sizeof(highlight_color));
+ }
+
+ CDVDOverlaySpu(const CDVDOverlaySpu& src)
+ : CDVDOverlay(src)
+ {
+ pTFData = src.pTFData;
+ pBFData = src.pBFData;
+ x = src.x;
+ y = src.y;
+ width = src.width;
+ height = src.height;
+
+ crop_i_x_end = src.crop_i_x_end;
+ crop_i_y_end = src.crop_i_y_end;
+ crop_i_x_start = src.crop_i_x_start;
+ crop_i_y_start = src.crop_i_y_start;
+
+ bHasColor = src.bHasColor;
+ bHasAlpha = src.bHasAlpha;
+
+ memcpy(result , src.result , sizeof(result));
+ memcpy(alpha , src.alpha , sizeof(alpha));
+ memcpy(color , src.color , sizeof(color));
+ memcpy(highlight_alpha, src.highlight_alpha, sizeof(highlight_alpha));
+ memcpy(highlight_color, src.highlight_color, sizeof(highlight_color));
+ }
+
+ uint8_t result[2*65536 + 20]; // rle data
+ int pTFData; // pointer to top field picture data (needs rle parsing)
+ int pBFData; // pointer to bottom field picture data (needs rle parsing)
+ int x;
+ int y;
+ int width;
+ int height;
+
+ // the four contrasts, [0] = background
+ int alpha[4];
+ bool bHasAlpha;
+
+ // the four yuv colors, containing [][0] = Y, [][1] = Cr, [][2] = Cb
+ // [0][] = background, [1][] = pattern, [2][] = emphasis1, [3][] = emphasis2
+ int color[4][3];
+ bool bHasColor;
+
+ // used for cropping overlays
+ int crop_i_x_end;
+ int crop_i_y_end;
+ int crop_i_x_start;
+ int crop_i_y_start;
+
+ // provided by the navigator engine
+ // should be used on the highlighted areas
+ int highlight_color[4][3];
+ int highlight_alpha[4];
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayText.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayText.h
new file mode 100644
index 0000000..72cf8f1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayText.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayLibass.h"
+#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h"
+
+#include <memory>
+
+class CDVDOverlayText : public CDVDOverlayLibass
+{
+public:
+ explicit CDVDOverlayText(const std::shared_ptr<CDVDSubtitlesLibass>& libass)
+ : CDVDOverlayLibass(libass, DVDOVERLAY_TYPE_TEXT)
+ {
+ replace = true;
+ }
+
+ ~CDVDOverlayText() override = default;
+
+ CDVDOverlayText* Clone() override { return new CDVDOverlayText(*this); }
+
+ void SetTextAlignEnabled(bool enable) override { m_enableTextAlign = enable; }
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.cpp
new file mode 100644
index 0000000..a2fd14a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverlayCodecWebVTT.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDOverlayText.h"
+#include "DVDStreamInfo.h"
+#include "cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "utils/CharArrayParser.h"
+
+#include <cstring>
+#include <memory>
+#include <string>
+
+using namespace KODI;
+
+COverlayCodecWebVTT::COverlayCodecWebVTT() : CDVDOverlayCodec("WebVTT Subtitle Decoder")
+{
+ m_pOverlay = nullptr;
+}
+
+COverlayCodecWebVTT::~COverlayCodecWebVTT()
+{
+ Dispose();
+}
+
+bool COverlayCodecWebVTT::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ Dispose();
+
+ if (!Initialize())
+ return false;
+
+ if (!m_webvttHandler.Initialize())
+ return false;
+
+ // Extradata can be provided by Inputstream addons (e.g. inputstream.adaptive)
+ if (hints.extradata)
+ {
+ std::string extradata{static_cast<char*>(hints.extradata), hints.extrasize};
+ if (extradata == "file")
+ {
+ // WebVTT data like single file are sent one time only,
+ // then we have to prevent the flush performed by video seek
+ m_allowFlush = false;
+ }
+ else if (extradata == "fmp4")
+ {
+ // WebVTT in MP4 encapsulated subtitles (ISO/IEC 14496-30:2014)
+ m_isISOFormat = true;
+ }
+ }
+
+ return true;
+}
+
+void COverlayCodecWebVTT::Dispose()
+{
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+}
+
+OverlayMessage COverlayCodecWebVTT::Decode(DemuxPacket* pPacket)
+{
+ if (!pPacket)
+ return OverlayMessage::OC_ERROR;
+
+ const char* data = reinterpret_cast<const char*>(pPacket->pData);
+ std::vector<subtitleData> subtitleList;
+
+ m_webvttHandler.Reset();
+
+ // WebVTT subtitles has no relation with packet PTS then if
+ // a period/chapter change happens (e.g. HLS streaming) VP can detect a discontinuity
+ // and adjust the packet PTS by substracting the pts offset correction value,
+ // so here we have to adjust WebVTT subtitles PTS by substracting it at same way
+ m_webvttHandler.SetPeriodStart(pPacket->m_ptsOffsetCorrection * -1);
+
+ if (m_isISOFormat)
+ {
+ double prevSubStopTime = 0.0;
+
+ m_webvttHandler.DecodeStream(data, pPacket->iSize, pPacket->dts, &subtitleList,
+ prevSubStopTime);
+
+ // Set stop time to all previously added subtitles
+ if (prevSubStopTime > 0)
+ {
+ for (auto& subId : m_previousSubIds)
+ {
+ ChangeSubtitleStopTime(subId, prevSubStopTime);
+ }
+ m_previousSubIds.clear();
+ }
+ }
+ else
+ {
+ if (!m_webvttHandler.CheckSignature(data))
+ return OverlayMessage::OC_ERROR;
+
+ CCharArrayParser charArrayParser;
+ charArrayParser.Reset(data, pPacket->iSize);
+ std::string line;
+
+ while (charArrayParser.ReadNextLine(line))
+ {
+ m_webvttHandler.DecodeLine(line, &subtitleList);
+ }
+
+ // We send an empty line to mark the end of the last Cue
+ m_webvttHandler.DecodeLine("", &subtitleList);
+ }
+
+ for (auto& subData : subtitleList)
+ {
+ SUBTITLES::STYLE::subtitleOpts opts;
+ opts.useMargins = subData.useMargins;
+ opts.marginLeft = subData.marginLeft;
+ opts.marginRight = subData.marginRight;
+ opts.marginVertical = subData.marginVertical;
+
+ int subId = AddSubtitle(subData.text, subData.startTime, subData.stopTime, &opts);
+
+ if (m_isISOFormat)
+ m_previousSubIds.emplace_back(subId);
+ }
+
+ return m_pOverlay ? OverlayMessage::OC_DONE : OverlayMessage::OC_OVERLAY;
+}
+
+void COverlayCodecWebVTT::Reset()
+{
+ Flush();
+}
+
+void COverlayCodecWebVTT::Flush()
+{
+ if (m_allowFlush)
+ {
+ if (m_pOverlay)
+ {
+ m_pOverlay->Release();
+ m_pOverlay = nullptr;
+ }
+ m_previousSubIds.clear();
+ FlushSubtitles();
+ }
+}
+
+CDVDOverlay* COverlayCodecWebVTT::GetOverlay()
+{
+ if (m_pOverlay)
+ return nullptr;
+ m_pOverlay = CreateOverlay();
+ m_pOverlay->SetOverlayContainerFlushable(m_allowFlush);
+ m_pOverlay->SetForcedMargins(m_webvttHandler.IsForcedMargins());
+ return m_pOverlay->Acquire();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.h
new file mode 100644
index 0000000..46c46c8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/OverlayCodecWebVTT.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDOverlayCodec.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/SubtitlesAdapter.h"
+#include "cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.h"
+
+#include <vector>
+
+class CDVDOverlay;
+
+class COverlayCodecWebVTT : public CDVDOverlayCodec, private CSubtitlesAdapter
+{
+public:
+ COverlayCodecWebVTT();
+ ~COverlayCodecWebVTT() override;
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ OverlayMessage Decode(DemuxPacket* pPacket) override;
+ void Reset() override;
+ void Flush() override;
+ CDVDOverlay* GetOverlay() override;
+
+private:
+ void Dispose() override;
+ CDVDOverlay* m_pOverlay;
+ CWebVTTISOHandler m_webvttHandler;
+ bool m_isISOFormat{false};
+ bool m_allowFlush{true};
+ std::vector<int> m_previousSubIds;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.c b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.c
new file mode 100644
index 0000000..2d2d7b6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.c
@@ -0,0 +1,874 @@
+/*
+ * Copyright (C) 2008-2020 Team Kodi
+ *
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * Copyright (C) Christian Vogler
+ * cvogler@gradient.cis.upenn.edu - December 2001
+ *
+ * This file is part of xine, a free video player.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ *
+ * stuff needed to provide closed captioning decoding and display
+ *
+ * Some small bits and pieces of the EIA-608 captioning decoder were
+ * adapted from CCDecoder 0.9.1 by Mike Baker. The latest version is
+ * available at http://sourceforge.net/projects/ccdecoder/.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cc_decoder.h"
+
+/* colors specified by the EIA 608 standard */
+enum { WHITE, GREEN, BLUE, CYAN, RED, YELLOW, MAGENTA, BLACK };
+
+/* --------------------- misc. EIA 608 definitions -------------------*/
+
+/* mapping from PAC row code to actual CC row */
+static int rowdata[] = {10, -1, 0, 1, 2, 3, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9};
+
+/* CC codes use odd parity for error detection, since they originally were */
+/* transmitted via noisy video signals */
+static int parity_table[256];
+
+static cc_buffer_t* active_ccbuffer(cc_decoder_t* dec);
+
+/* --------------------- EIA 608 charsets -----------------------------*/
+// for all charsets, CC codes are essentially identical to ASCII apart for a few exceptions
+// see https://en.wikipedia.org/wiki/EIA-608#Characters for reference
+
+enum cc_charset
+{
+ CCSET_BASIC_AMERICAN = 0,
+ CCSET_SPECIAL_AMERICAN,
+ CCSET_EXTENDED_SPANISH_FRENCH_MISC,
+ CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH
+};
+
+const char* get_char_override(uint8_t charset, uint8_t c)
+{
+ if (charset == CCSET_BASIC_AMERICAN)
+ {
+ switch (c)
+ {
+ case 0x27:
+ return u8"\u2019";
+ case 0x2a:
+ return u8"\u00e1";
+ case 0x5c:
+ return u8"\u00e9";
+ case 0x5e:
+ return u8"\u00ed";
+ case 0x5f:
+ return u8"\u00f3";
+ case 0x60:
+ return u8"\u00fa";
+ case 0x7b:
+ return u8"\u00e7";
+ case 0x7c:
+ return u8"\u00f7";
+ case 0x7d:
+ return u8"\u00d1";
+ case 0x7e:
+ return u8"\u00f1";
+ case 0x7f:
+ return u8"\u2588";
+ default:
+ break;
+ }
+ }
+ else if (charset == CCSET_SPECIAL_AMERICAN)
+ {
+ switch (c)
+ {
+ case 0x30:
+ return u8"\u00ae";
+ case 0x31:
+ return u8"\u00b0";
+ case 0x32:
+ return u8"\u00bd";
+ case 0x33:
+ return u8"\u00bf";
+ case 0x34:
+ return u8"\u2122";
+ case 0x35:
+ return u8"\u00a2";
+ case 0x36:
+ return u8"\u00a3";
+ case 0x37:
+ return u8"\u266a";
+ case 0x38:
+ return u8"\u00e0";
+ case 0x39:
+ return u8"\u00A0";
+ case 0x3a:
+ return u8"\u00e8";
+ case 0x3b:
+ return u8"\u00e2";
+ case 0x3c:
+ return u8"\u00ea";
+ case 0x3d:
+ return u8"\u00ee";
+ case 0x3e:
+ return u8"\u00f4";
+ case 0x3f:
+ return u8"\u00fb";
+ default:
+ break;
+ }
+ }
+ else if (charset == CCSET_EXTENDED_SPANISH_FRENCH_MISC)
+ {
+ switch (c)
+ {
+ case 0x20:
+ return u8"\u00c1";
+ case 0x21:
+ return u8"\u00c9";
+ case 0x22:
+ return u8"\u00d3";
+ case 0x23:
+ return u8"\u00da";
+ case 0x24:
+ return u8"\u00dc";
+ case 0x25:
+ return u8"\u00fc";
+ case 0x26:
+ return u8"\u00b4";
+ case 0x27:
+ return u8"\u00a1";
+ case 0x28:
+ return u8"*";
+ case 0x29:
+ return u8"\u2018";
+ case 0x2a:
+ return u8"-";
+ case 0x2b:
+ return u8"\u00a9";
+ case 0x2c:
+ return u8"\u2120";
+ case 0x2d:
+ return u8"\u00b7";
+ case 0x2e:
+ return u8"\u201c";
+ case 0x2f:
+ return u8"\u201d";
+ case 0x30:
+ return u8"\u00c0";
+ case 0x31:
+ return u8"\u00c2";
+ case 0x32:
+ return u8"\u00c7";
+ case 0x33:
+ return u8"\u00c8";
+ case 0x34:
+ return u8"\u00ca";
+ case 0x35:
+ return u8"\u00cb";
+ case 0x36:
+ return u8"\u00eb";
+ case 0x37:
+ return u8"\u00ce";
+ case 0x38:
+ return u8"\u00cf";
+ case 0x39:
+ return u8"\u00ef";
+ case 0x3a:
+ return u8"\u00d4";
+ case 0x3b:
+ return u8"\u00d9";
+ case 0x3c:
+ return u8"\u00f9";
+ case 0x3d:
+ return u8"\u00db";
+ case 0x3e:
+ return u8"\u00ab";
+ case 0x3f:
+ return u8"\u00bb";
+ default:
+ break;
+ }
+ }
+ else if (charset == CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH)
+ {
+ switch (c)
+ {
+ case 0x20:
+ return u8"\u00c3";
+ case 0x21:
+ return u8"\u00e3";
+ case 0x22:
+ return u8"\u00cd";
+ case 0x23:
+ return u8"\u00cc";
+ case 0x24:
+ return u8"\u00ec";
+ case 0x25:
+ return u8"\u00d2";
+ case 0x26:
+ return u8"\u00f2";
+ case 0x27:
+ return u8"\u00d5";
+ case 0x28:
+ return u8"\u00f5";
+ case 0x29:
+ return u8"{";
+ case 0x2a:
+ return u8"}";
+ case 0x2b:
+ return u8"\\";
+ case 0x2c:
+ return u8"^";
+ case 0x2d:
+ return u8"_";
+ case 0x2e:
+ return u8"|";
+ case 0x2f:
+ return u8"~";
+ case 0x30:
+ return u8"\u00c4";
+ case 0x31:
+ return u8"\u00e4";
+ case 0x32:
+ return u8"\u00d6";
+ case 0x33:
+ return u8"\u00f6";
+ case 0x34:
+ return u8"\u00df";
+ case 0x35:
+ return u8"\u00a5";
+ case 0x36:
+ return u8"\u00a4";
+ case 0x37:
+ return u8"\u00a6";
+ case 0x38:
+ return u8"\u00c5";
+ case 0x39:
+ return u8"\u00e5";
+ case 0x3a:
+ return u8"\u00d8";
+ case 0x3b:
+ return u8"\u00f8";
+ case 0x3c:
+ return u8"\u250c";
+ case 0x3d:
+ return u8"\u2510";
+ case 0x3e:
+ return u8"\u2514";
+ case 0x3f:
+ return u8"\u2518";
+ default:
+ break;
+ }
+ }
+ // regular ascii char
+ return NULL;
+}
+
+/*---------------- general utility functions ---------------------*/
+
+static int parity(uint8_t byte)
+{
+ int i;
+ int ones = 0;
+
+ for (i = 0; i < 7; i++) {
+ if (byte & (1 << i))
+ ones++;
+ }
+
+ return ones & 1;
+}
+
+static void build_parity_table(void)
+{
+ uint8_t byte;
+ for (byte = 0; byte <= 127; byte++) {
+ int parity_v = parity(byte);
+ /* CC uses odd parity (i.e., # of 1's in byte is odd.) */
+ parity_table[byte] = parity_v;
+ parity_table[byte | 0x80] = !parity_v;
+ }
+}
+
+static int good_parity(uint16_t data)
+{
+ int ret = parity_table[data & 0xff] && parity_table[(data & 0xff00) >> 8];
+ if (! ret)
+ printf("Bad parity in EIA-608 data (%x)\n", data);
+ return ret;
+}
+
+static void ccbuf_add_char(cc_buffer_t* buf, uint8_t c, uint8_t charset)
+{
+ cc_row_t *rowbuf = &buf->rows[buf->rowpos];
+ int pos = rowbuf->pos;
+ int left_displayable = (pos > 0) && (pos <= rowbuf->num_chars);
+
+ if (pos >= CC_COLUMNS)
+ {
+ return;
+ }
+
+ /* midrow PAC attributes are applied only if there is no displayable */
+ /* character to the immediate left. This makes the implementation rather */
+ /* complicated, but this is what the EIA-608 standard specifies. :-( */
+ if (rowbuf->pac_attr_chg && !rowbuf->attr_chg && !left_displayable)
+ {
+ rowbuf->attr_chg = 1;
+ rowbuf->cells[pos].attributes = rowbuf->pac_attr;
+ }
+
+ rowbuf->cells[pos].c = c;
+ rowbuf->cells[pos].charset = charset;
+ rowbuf->cells[pos].midrow_attr = rowbuf->attr_chg;
+ rowbuf->pos++;
+
+ if (rowbuf->num_chars < rowbuf->pos)
+ rowbuf->num_chars = rowbuf->pos;
+
+ rowbuf->attr_chg = 0;
+ rowbuf->pac_attr_chg = 0;
+}
+
+
+static void ccbuf_set_cursor(cc_buffer_t *buf, int row, int column,
+ int underline, int italics, int color)
+{
+ cc_row_t *rowbuf = &buf->rows[row];
+ cc_attribute_t attr;
+
+ attr.italic = italics;
+ attr.underline = underline;
+ attr.foreground = color;
+ attr.background = BLACK;
+
+ rowbuf->pac_attr = attr;
+ rowbuf->pac_attr_chg = 1;
+
+ buf->rowpos = row;
+ rowbuf->pos = column;
+ rowbuf->attr_chg = 0;
+}
+
+
+static void ccbuf_apply_attribute(cc_buffer_t *buf, cc_attribute_t *attr)
+{
+ cc_row_t *rowbuf = &buf->rows[buf->rowpos];
+ int pos = rowbuf->pos;
+
+ rowbuf->attr_chg = 1;
+ rowbuf->cells[pos].attributes = *attr;
+ /* A midrow attribute always counts as a space */
+ ccbuf_add_char(buf, (unsigned int)' ', CCSET_BASIC_AMERICAN);
+}
+
+
+static void ccbuf_tab(cc_buffer_t *buf, int tabsize)
+{
+ cc_row_t *rowbuf = &buf->rows[buf->rowpos];
+ rowbuf->pos += tabsize;
+ if (rowbuf->pos > CC_COLUMNS)
+ {
+ rowbuf->pos = CC_COLUMNS;
+ return;
+ }
+ /* tabs have no effect on pending PAC attribute changes */
+}
+
+/*----------------- cc_memory_t methods --------------------------------*/
+
+static void ccrow_der(cc_row_t *row, int pos)
+{
+ int i;
+ for (i = pos; i < CC_COLUMNS; i++)
+ {
+ row->cells[i].c = ' ';
+ }
+}
+
+static void ccmem_clear(cc_memory_t *buf)
+{
+ int i;
+ memset(buf, 0, sizeof (cc_memory_t));
+ for (i = 0; i < CC_ROWS; i++)
+ {
+ ccrow_der(&buf->channel[0].rows[i], 0);
+ ccrow_der(&buf->channel[1].rows[i], 0);
+ }
+}
+
+static void ccmem_init(cc_memory_t *buf)
+{
+ ccmem_clear(buf);
+}
+
+static void ccmem_exit(cc_memory_t *buf)
+{
+/*FIXME: anything to deallocate?*/
+}
+
+void ccmem_tobuf(cc_decoder_t *dec)
+{
+ cc_buffer_t *buf = &dec->on_buf->channel[dec->on_buf->channel_no];
+ int empty = 1;
+ dec->textlen = 0;
+ int i,j;
+ for (i = 0; i < CC_ROWS; i++)
+ {
+ for (j = 0; j<CC_COLUMNS; j++)
+ if (buf->rows[i].cells[j].c != ' ')
+ {
+ empty = 0;
+ break;
+ }
+ if (!empty)
+ break;
+ }
+ if (empty)
+ return; // Nothing to write
+
+ for (i = 0; i<CC_ROWS; i++)
+ {
+ int empty = 1;
+ for (j = 0; j<CC_COLUMNS; j++)
+ if (buf->rows[i].cells[j].c != ' ')
+ empty = 0;
+ if (!empty)
+ {
+ int f, l; // First,last used char
+ for (f = 0; f<CC_COLUMNS; f++)
+ if (buf->rows[i].cells[f].c != ' ')
+ break;
+ for (l = CC_COLUMNS-1; l>0; l--)
+ if (buf->rows[i].cells[l].c != ' ')
+ break;
+ for (j = f; j <= l; j++)
+ {
+ const char* chbytes =
+ get_char_override(buf->rows[i].cells[j].charset, buf->rows[i].cells[j].c);
+ if (chbytes != NULL)
+ {
+ for (; *chbytes != '\0'; chbytes++)
+ {
+ dec->text[dec->textlen++] = *chbytes;
+ }
+ }
+ else
+ {
+ // ascii char
+ dec->text[dec->textlen++] = (unsigned char)buf->rows[i].cells[j].c;
+ }
+ }
+ dec->text[dec->textlen++] = '\n';
+ }
+ }
+
+ // FIXME: the end-of-string char is often wrong cause unexpected behaviours
+ if (dec->textlen > 0)
+ {
+ if (dec->text[dec->textlen - 1] == '\n' && dec->text[dec->textlen] != '\0')
+ {
+ dec->text[dec->textlen] = '\0';
+ }
+ else if (dec->text[dec->textlen] != '\0')
+ {
+ dec->text[dec->textlen++] = '\n';
+ dec->text[dec->textlen++] = '\0';
+ }
+ }
+
+ dec->callback(0, dec->userdata);
+}
+
+/*----------------- cc_decoder_t methods --------------------------------*/
+
+static void cc_set_channel(cc_decoder_t *dec, int channel)
+{
+ (*dec->active)->channel_no = channel;
+}
+
+static cc_buffer_t *active_ccbuffer(cc_decoder_t *dec)
+{
+ cc_memory_t *mem = *dec->active;
+ return &mem->channel[mem->channel_no];
+}
+
+static void cc_swap_buffers(cc_decoder_t *dec)
+{
+ cc_memory_t *temp;
+
+ /* hide caption in displayed memory */
+ /* cc_hide_displayed(dec); */
+
+ temp = dec->on_buf;
+ dec->on_buf = dec->off_buf;
+ dec->off_buf = temp;
+
+ /* show new displayed memory */
+ /* cc_show_displayed(dec); */
+}
+
+static void cc_roll_up(cc_decoder_t *dec)
+{
+ cc_buffer_t *buf = active_ccbuffer(dec);
+ int i, j;
+ for (i = buf->rowpos - dec->rollup_rows + 1; i < buf->rowpos; i++)
+ {
+ if (i < 0)
+ continue;
+
+ for (j = 0; j < CC_COLUMNS; j++)
+ {
+ buf->rows[i].cells[j] = buf->rows[i + 1].cells[j];
+ }
+ }
+ for (j = 0; j < CC_COLUMNS; j++)
+ {
+ buf->rows[buf->rowpos].cells[j].c = ' ';
+ }
+ buf->rows[buf->rowpos].pos = 0;
+}
+
+static void cc_decode_standard_char(cc_decoder_t *dec, uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf = active_ccbuffer(dec);
+ /* c1 always is a valid character */
+ ccbuf_add_char(buf, c1, CCSET_BASIC_AMERICAN);
+ /* c2 might not be a printable character, even if c1 was */
+ if (c2 & 0x60)
+ ccbuf_add_char(buf, c2, CCSET_BASIC_AMERICAN);
+}
+
+
+static void cc_decode_PAC(cc_decoder_t *dec, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf;
+ int row, column = 0;
+ int underline, italics = 0, color;
+
+ /* There is one invalid PAC code combination. Ignore it. */
+ if (c1 == 0x10 && c2 > 0x5f)
+ return;
+
+ cc_set_channel(dec, channel);
+ buf = active_ccbuffer(dec);
+
+ row = rowdata[((c1 & 0x07) << 1) | ((c2 & 0x20) >> 5)];
+ if (c2 & 0x10)
+ {
+ column = ((c2 & 0x0e) >> 1) * 4; /* preamble indentation */
+ color = WHITE; /* indented lines have white color */
+ }
+ else if ((c2 & 0x0e) == 0x0e)
+ {
+ italics = 1; /* italics, they are always white */
+ color = WHITE;
+ }
+ else
+ color = (c2 & 0x0e) >> 1;
+ underline = c2 & 0x01;
+
+ ccbuf_set_cursor(buf, row, column, underline, italics, color);
+}
+
+static void cc_decode_ext_attribute(cc_decoder_t* dec, int channel)
+{
+ cc_set_channel(dec, channel);
+}
+
+static void cc_decode_special_char(cc_decoder_t* dec, int channel, uint8_t charset, uint8_t c2)
+{
+ cc_buffer_t *buf;
+
+ cc_set_channel(dec, channel);
+ buf = active_ccbuffer(dec);
+ ccbuf_add_char(buf, c2, charset);
+}
+
+static void cc_decode_midrow_attr(cc_decoder_t* dec, int channel, uint8_t c2)
+{
+ cc_buffer_t *buf;
+ cc_attribute_t attr;
+
+ cc_set_channel(dec, channel);
+ buf = active_ccbuffer(dec);
+ if (c2 < 0x2e)
+ {
+ attr.italic = 0;
+ attr.foreground = (c2 & 0xe) >> 1;
+ }
+ else
+ {
+ attr.italic = 1;
+ attr.foreground = WHITE;
+ }
+ attr.underline = c2 & 0x1;
+ attr.background = BLACK;
+
+ ccbuf_apply_attribute(buf, &attr);
+}
+
+
+static void cc_decode_misc_control_code(cc_decoder_t* dec, int channel, uint8_t c2)
+{
+ cc_set_channel(dec, channel);
+ cc_buffer_t *buf;
+
+ switch (c2)
+ { /* 0x20 <= c2 <= 0x2f */
+ case 0x20: /* RCL */
+ dec->style = CC_POPON;
+ dec->active = &dec->off_buf;
+ break;
+
+ case 0x21: /* backspace */
+ break;
+
+ case 0x24: /* DER */
+ buf = active_ccbuffer(dec);
+ ccrow_der(&buf->rows[buf->rowpos], buf->rows[buf->rowpos].pos);
+ break;
+
+ case 0x25: /* RU2 */
+ dec->rollup_rows = 2;
+ dec->style = CC_ROLLUP;
+ dec->active = &dec->on_buf;
+ break;
+
+ case 0x26: /* RU3 */
+ dec->rollup_rows = 3;
+ dec->style = CC_ROLLUP;
+ dec->active = &dec->on_buf;
+ break;
+
+ case 0x27: /* RU4 */
+ dec->rollup_rows = 4;
+ dec->style = CC_ROLLUP;
+ dec->active = &dec->on_buf;
+ break;
+
+ case 0x28: /* FON */
+ break;
+
+ case 0x29: /* RDC */
+ dec->style = CC_PAINTON;
+ dec->active = &dec->on_buf;
+ break;
+
+ case 0x2a: /* TR */
+ break;
+
+ case 0x2b: /* RTD */
+ break;
+
+ case 0x2c: /* EDM - erase displayed memory */
+ /* cc_hide_displayed(dec); */
+ ccmem_clear(dec->on_buf);
+ break;
+
+ case 0x2d: /* carriage return */
+ if (dec->style == CC_ROLLUP)
+ {
+ cc_roll_up(dec);
+ }
+ break;
+
+ case 0x2e: /* ENM - erase non-displayed memory */
+ ccmem_clear(dec->off_buf);
+ break;
+
+ case 0x2f: /* EOC - swap displayed and non displayed memory */
+ cc_swap_buffers(dec);
+ dec->style = CC_POPON;
+ dec->active = &dec->off_buf;
+ ccmem_tobuf(dec);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void cc_decode_tab(cc_decoder_t* dec, int channel, uint8_t c2)
+{
+ cc_buffer_t *buf;
+
+ cc_set_channel(dec, channel);
+ buf = active_ccbuffer(dec);
+ ccbuf_tab(buf, c2 & 0x3);
+}
+
+static void cc_decode_EIA608(cc_decoder_t *dec, uint16_t data)
+{
+ uint8_t c1 = data & 0x7f;
+ uint8_t c2 = (data >> 8) & 0x7f;
+
+ if (c1 & 0x60)
+ { /* normal character, 0x20 <= c1 <= 0x7f */
+ if (dec->style == CC_NOTSET)
+ return;
+
+ cc_decode_standard_char(dec, c1, c2);
+ if (dec->style == CC_ROLLUP)
+ {
+ ccmem_tobuf(dec);
+ }
+ }
+ else if (c1 & 0x10)
+ {
+ /* control code or special character */
+ /* 0x10 <= c1 <= 0x1f */
+ int channel = (c1 & 0x08) >> 3;
+ c1 &= ~0x08;
+
+ /* control sequences are often repeated. In this case, we should */
+ /* evaluate it only once. */
+ if (data != dec->lastcode)
+ {
+ if (c2 & 0x40)
+ { /* preamble address code: 0x40 <= c2 <= 0x7f */
+ cc_decode_PAC(dec, channel, c1, c2);
+ }
+ else
+ {
+ switch (c1)
+ {
+ case 0x10: /* extended background attribute code */
+ cc_decode_ext_attribute(dec, channel);
+ break;
+
+ case 0x11: /* attribute or special character */
+ if (dec->style == CC_NOTSET)
+ return;
+
+ if ((c2 & 0x30) == 0x30)
+ {
+ /* special char: 0x30 <= c2 <= 0x3f */
+ /* CCSET_SPECIAL_AMERICAN */
+ cc_decode_special_char(dec, channel, CCSET_SPECIAL_AMERICAN, c2);
+ if (dec->style == CC_ROLLUP)
+ {
+ ccmem_tobuf(dec);
+ }
+ }
+ else if (c2 & 0x20)
+ {
+ /* midrow attribute: 0x20 <= c2 <= 0x2f */
+ cc_decode_midrow_attr(dec, channel, c2);
+ }
+ break;
+
+ case 0x12: /* CCSET_EXTENDED_SPANISH_FRENCH_MISC */
+ cc_decode_special_char(dec, channel, CCSET_EXTENDED_SPANISH_FRENCH_MISC, c2);
+ if (dec->style == CC_ROLLUP)
+ {
+ ccmem_tobuf(dec);
+ }
+ break;
+
+ case 0x13: /* CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH */
+ cc_decode_special_char(dec, channel, CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH, c2);
+ if (dec->style == CC_ROLLUP)
+ {
+ ccmem_tobuf(dec);
+ }
+ break;
+
+ case 0x14: /* possibly miscellaneous control code */
+ cc_decode_misc_control_code(dec, channel, c2);
+ break;
+
+ case 0x17: /* possibly misc. control code TAB offset */
+ /* 0x21 <= c2 <= 0x23 */
+ if (c2 >= 0x21 && c2 <= 0x23)
+ {
+ cc_decode_tab(dec, channel, c2);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ dec->lastcode = data;
+}
+
+
+void decode_cc(cc_decoder_t *dec, const uint8_t *buffer, uint32_t buf_len)
+{
+ uint32_t i;
+ for (i = 0; i<buf_len; i += 3)
+ {
+ unsigned char cc_type = buffer[i] & 0x03;
+
+ uint8_t data1 = buffer[i + 1];
+ uint8_t data2 = buffer[i + 2];
+
+ switch (cc_type)
+ {
+ case 0:
+ if (good_parity(data1 | (data2 << 8)))
+ {
+ cc_decode_EIA608(dec, data1 | (data2 << 8));
+ }
+ break;
+
+ case 1:
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+cc_decoder_t *cc_decoder_open()
+{
+ cc_decoder_t *dec = (cc_decoder_t *) calloc(1, sizeof (cc_decoder_t));
+ if (!dec)
+ return NULL;
+
+ ccmem_init(&dec->buffer[0]);
+ ccmem_init(&dec->buffer[1]);
+ dec->on_buf = &dec->buffer[0];
+ dec->off_buf = &dec->buffer[1];
+ dec->active = &dec->off_buf;
+
+ dec->lastcode = 0;
+
+ return dec;
+}
+
+void cc_decoder_close(cc_decoder_t *dec)
+{
+ ccmem_exit(&dec->buffer[0]);
+ ccmem_exit(&dec->buffer[1]);
+
+ free(dec);
+}
+
+
+/*--------------- initialization methods --------------------------*/
+
+void cc_decoder_init(void)
+{
+ build_parity_table();
+}
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.h
new file mode 100644
index 0000000..d9fc266
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * Copyright (C) Christian Vogler
+ * cvogler@gradient.cis.upenn.edu - December 2001
+ *
+ * This file is part of xine, a free video player.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ *
+ * stuff needed to provide closed captioning decoding and display
+ *
+ * Some small bits and pieces of the EIA-608 captioning decoder were
+ * adapted from CCDecoder 0.9.1 by Mike Baker. The latest version is
+ * available at http://sourceforge.net/projects/ccdecoder/.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#define CC_ROWS 15
+#define CC_COLUMNS 32
+#define CC_CHANNELS 2
+
+typedef struct cc_attribute_s {
+ uint8_t italic;
+ uint8_t underline;
+ uint8_t foreground;
+ uint8_t background;
+} cc_attribute_t;
+
+/* CC character cell */
+typedef struct cc_char_cell_s
+{
+ uint8_t c; /* character code, not the same as ASCII */
+ cc_attribute_t attributes; /* attributes of this character, if changed */
+ /* here */
+ uint8_t charset; /* charset type */
+ int midrow_attr; /* true if this cell changes an attribute */
+} cc_char_cell_t;
+
+/* a single row in the closed captioning memory */
+typedef struct cc_row_s
+{
+ cc_char_cell_t cells[CC_COLUMNS];
+ int pos; /* position of the cursor */
+ int num_chars; /* how many characters in the row are data */
+ int attr_chg; /* true if midrow attr. change at cursor pos */
+ int pac_attr_chg; /* true if attribute has changed via PAC */
+ cc_attribute_t pac_attr; /* PAC attr. that hasn't been applied yet */
+} cc_row_t;
+
+/* closed captioning memory for a single channel */
+typedef struct cc_buffer_s
+{
+ cc_row_t rows[CC_ROWS];
+ int rowpos; /* row cursor position */
+} cc_buffer_t;
+
+/* captioning memory for all channels */
+typedef struct cc_memory_s
+{
+ cc_buffer_t channel[CC_CHANNELS];
+ int channel_no; /* currently active channel */
+} cc_memory_t;
+
+enum cc_style
+{
+ CC_NOTSET = 0,
+ CC_ROLLUP,
+ CC_PAINTON,
+ CC_POPON
+};
+
+/* The closed captioning decoder data structure */
+struct cc_decoder_s
+{
+ /* CC decoder buffer - one onscreen, one offscreen */
+ cc_memory_t buffer[2];
+ /* onscreen, offscreen buffer ptrs */
+ cc_memory_t *on_buf;
+ cc_memory_t *off_buf;
+ /* which buffer is active for receiving data */
+ cc_memory_t **active;
+
+ /* the last captioning code seen (control codes are often sent twice
+ in a row, but should be processed only once) */
+ uint32_t lastcode;
+
+ uint16_t rollup_rows;
+ enum cc_style style;
+
+ void *userdata;
+ void(*callback)(int service, void *userdata);
+ char text[CC_ROWS*CC_COLUMNS + 1];
+ int textlen;
+};
+
+typedef struct cc_decoder_s cc_decoder_t;
+
+cc_decoder_t *cc_decoder_open();
+void cc_decoder_close(cc_decoder_t *this_obj);
+void cc_decoder_init(void);
+
+void decode_cc(cc_decoder_t *dec, const uint8_t *buffer, uint32_t buf_len);
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.cpp
new file mode 100644
index 0000000..910b724
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.cpp
@@ -0,0 +1,1192 @@
+/*
+ * this is mostly borrowed from ccextractor http://ccextractor.sourceforge.net/
+ */
+
+#include "cc_decoder708.h"
+
+#include "utils/log.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/********************************************************
+256 BYTES IS ENOUGH FOR ALL THE SUPPORTED CHARACTERS IN
+EIA-708, SO INTERNALLY WE USE THIS TABLE (FOR CONVENIENCE)
+
+00-1F -> Characters that are in the G2 group in 20-3F,
+ except for 06, which is used for the closed captions
+ sign "CC" which is defined in group G3 as 00. (this
+ is by the article 33).
+20-7F -> Group G0 as is - corresponds to the ASCII code
+80-9F -> Characters that are in the G2 group in 60-7F
+ (there are several blank characters here, that's OK)
+A0-FF -> Group G1 as is - non-English characters and symbols
+*/
+
+unsigned char get_internal_from_G0 (unsigned char g0_char)
+{
+ return g0_char;
+}
+
+unsigned char get_internal_from_G1 (unsigned char g1_char)
+{
+ return g1_char;
+}
+
+//! @todo Probably not right
+// G2: Extended Control Code Set 1
+unsigned char get_internal_from_G2 (unsigned char g2_char)
+{
+ // according to the comment a few lines above those lines are indeed wrong
+ /*
+ if (g2_char>=0x20 && g2_char<=0x3F)
+ return g2_char-0x20;
+ if (g2_char>=0x60 && g2_char<=0x7F)
+ return g2_char+0x20;
+ */
+ // Rest unmapped, so we return a blank space
+ return 0x20;
+}
+
+//! @todo Probably not right
+// G3: Future Characters and Icon Expansion
+unsigned char get_internal_from_G3 (unsigned char g3_char)
+{
+ if (g3_char==0xa0) // The "CC" (closed captions) sign
+ return 0x06;
+ // Rest unmapped, so we return a blank space
+ return 0x20;
+}
+
+void clearTV (cc708_service_decoder *decoder);
+
+const char *COMMANDS_C0[32]=
+{
+ "NUL", // 0 = NUL
+ NULL, // 1 = Reserved
+ NULL, // 2 = Reserved
+ "ETX", // 3 = ETX
+ NULL, // 4 = Reserved
+ NULL, // 5 = Reserved
+ NULL, // 6 = Reserved
+ NULL, // 7 = Reserved
+ "BS", // 8 = Backspace
+ NULL, // 9 = Reserved
+ NULL, // A = Reserved
+ NULL, // B = Reserved
+ "FF", // C = FF
+ "CR", // D = CR
+ "HCR", // E = HCR
+ NULL, // F = Reserved
+ "EXT1",// 0x10 = EXT1,
+ NULL, // 0x11 = Reserved
+ NULL, // 0x12 = Reserved
+ NULL, // 0x13 = Reserved
+ NULL, // 0x14 = Reserved
+ NULL, // 0x15 = Reserved
+ NULL, // 0x16 = Reserved
+ NULL, // 0x17 = Reserved
+ "P16", // 0x18 = P16
+ NULL, // 0x19 = Reserved
+ NULL, // 0x1A = Reserved
+ NULL, // 0x1B = Reserved
+ NULL, // 0x1C = Reserved
+ NULL, // 0x1D = Reserved
+ NULL, // 0x1E = Reserved
+ NULL, // 0x1F = Reserved
+};
+
+struct S_COMMANDS_C1 COMMANDS_C1[32]=
+{
+ {CW0,"CW0","SetCurrentWindow0", 1},
+ {CW1,"CW1","SetCurrentWindow1", 1},
+ {CW2,"CW2","SetCurrentWindow2", 1},
+ {CW3,"CW3","SetCurrentWindow3", 1},
+ {CW4,"CW4","SetCurrentWindow4", 1},
+ {CW5,"CW5","SetCurrentWindow5", 1},
+ {CW6,"CW6","SetCurrentWindow6", 1},
+ {CW7,"CW7","SetCurrentWindow7", 1},
+ {CLW,"CLW","ClearWindows", 2},
+ {DSW,"DSW","DisplayWindows", 2},
+ {HDW,"HDW","HideWindows", 2},
+ {TGW,"TGW","ToggleWindows", 2},
+ {DLW,"DLW","DeleteWindows", 2},
+ {DLY,"DLY","Delay", 2},
+ {DLC,"DLC","DelayCancel", 1},
+ {RST,"RST","Reset", 1},
+ {SPA,"SPA","SetPenAttributes", 3},
+ {SPC,"SPC","SetPenColor", 4},
+ {SPL,"SPL","SetPenLocation", 3},
+ {RSV93,"RSV93","Reserved", 1},
+ {RSV94,"RSV94","Reserved", 1},
+ {RSV95,"RSV95","Reserved", 1},
+ {RSV96,"RSV96","Reserved", 1},
+ {SWA,"SWA","SetWindowAttributes", 5},
+ {DF0,"DF0","DefineWindow0", 7},
+ {DF1,"DF0","DefineWindow1", 7},
+ {DF2,"DF0","DefineWindow2", 7},
+ {DF3,"DF0","DefineWindow3", 7},
+ {DF4,"DF0","DefineWindow4", 7},
+ {DF5,"DF0","DefineWindow5", 7},
+ {DF6,"DF0","DefineWindow6", 7},
+ {DF7,"DF0","DefineWindow7", 7}
+};
+
+void clear_packet(cc708_service_decoder *decoder)
+{
+ decoder->parent->m_current_packet_length = 0;
+}
+
+void cc708_service_reset(cc708_service_decoder *decoder)
+{
+ // There's lots of other stuff that we need to do, such as canceling delays
+ for (e708Window& window : decoder->windows)
+ {
+ window.is_defined=0;
+ window.visible=0;
+ window.memory_reserved=0;
+ window.is_empty=1;
+ memset (window.commands, 0,
+ sizeof (window.commands));
+ }
+ decoder->current_window=-1;
+ clearTV(decoder);
+ decoder->inited=1;
+}
+
+void cc708_reset(cc708_service_decoder *decoders)
+{
+ for (int i = 0; i<CCX_DECODERS_708_MAX_SERVICES; i++)
+ {
+ cc708_service_reset (&decoders[i]);
+ }
+ // Empty packet buffer
+ clear_packet(&decoders[0]);
+ decoders[0].parent->m_last_seq = -1;
+}
+
+int compWindowsPriorities (const void *a, const void *b)
+{
+ const e708Window *w1=*(e708Window * const*)a;
+ const e708Window *w2=*(e708Window * const*)b;
+ return w1->priority-w2->priority;
+}
+
+void clearTV (cc708_service_decoder *decoder)
+{
+ for (unsigned char (&row)[I708_SCREENGRID_COLUMNS + 1] : decoder->tv.chars)
+ {
+ memset (row, ' ', I708_SCREENGRID_COLUMNS);
+ row[I708_SCREENGRID_COLUMNS]=0;
+ }
+};
+
+void printTVtoBuf (cc708_service_decoder *decoder)
+{
+ int empty=1;
+ decoder->textlen = 0;
+ for (unsigned char(&row)[I708_SCREENGRID_COLUMNS + 1] : decoder->tv.chars)
+ {
+ for (int j=0;j<210;j++)
+ if (row[j] != ' ')
+ {
+ empty=0;
+ break;
+ }
+ if (!empty)
+ break;
+ }
+ if (empty)
+ return; // Nothing to write
+
+ for (unsigned char(&row)[I708_SCREENGRID_COLUMNS + 1] : decoder->tv.chars)
+ {
+ int empty=1;
+ for (int j=0;j<210;j++)
+ if (row[j] != ' ')
+ empty=0;
+ if (!empty)
+ {
+ int f,l; // First,last used char
+ for (f=0;f<210;f++)
+ if (row[f] != ' ')
+ break;
+ for (l=209;l>0;l--)
+ if (row[l]!=' ')
+ break;
+ for (int j=f;j<=l;j++)
+ decoder->text[decoder->textlen++] = row[j];
+ decoder->text[decoder->textlen++] = '\r';
+ decoder->text[decoder->textlen++] = '\n';
+ }
+ }
+
+ // FIXME: the end-of-string char is often wrong cause unexpected behaviours
+ if (decoder->textlen >= 2)
+ {
+ if (decoder->text[decoder->textlen - 2] == '\r' &&
+ decoder->text[decoder->textlen - 1] == '\n' && decoder->text[decoder->textlen] != '\0')
+ {
+ decoder->text[decoder->textlen] = '\0';
+ }
+ else if (decoder->text[decoder->textlen] != '\0')
+ {
+ decoder->text[decoder->textlen++] = '\r';
+ decoder->text[decoder->textlen++] = '\n';
+ decoder->text[decoder->textlen++] = '\0';
+ }
+ }
+}
+
+void updateScreen (cc708_service_decoder *decoder)
+{
+ clearTV (decoder);
+
+ // THIS FUNCTION WILL DO THE MAGIC OF ACTUALLY EXPORTING THE DECODER STATUS
+ // TO SEVERAL FILES
+ e708Window *wnd[I708_MAX_WINDOWS]; // We'll store here the visible windows that contain anything
+ int visible=0;
+ for (e708Window& window : decoder->windows)
+ {
+ if (window.is_defined && window.visible && !window.is_empty)
+ wnd[visible++]=&window;
+ }
+ qsort (wnd,visible,sizeof (e708Window *),compWindowsPriorities);
+
+ for (int i=0;i<visible;i++)
+ {
+ int top,left;
+ // For each window we calculate the top,left position depending on the
+ // anchor
+ switch (wnd[i]->anchor_point)
+ {
+ case anchorpoint_top_left:
+ top=wnd[i]->anchor_vertical;
+ left=wnd[i]->anchor_horizontal;
+ break;
+ case anchorpoint_top_center:
+ top=wnd[i]->anchor_vertical;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count/2;
+ break;
+ case anchorpoint_top_right:
+ top=wnd[i]->anchor_vertical;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count;
+ break;
+ case anchorpoint_middle_left:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count/2;
+ left=wnd[i]->anchor_horizontal;
+ break;
+ case anchorpoint_middle_center:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count/2;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count/2;
+ break;
+ case anchorpoint_middle_right:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count/2;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count;
+ break;
+ case anchorpoint_bottom_left:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count;
+ left=wnd[i]->anchor_horizontal;
+ break;
+ case anchorpoint_bottom_center:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count/2;
+ break;
+ case anchorpoint_bottom_right:
+ top=wnd[i]->anchor_vertical - wnd[i]->row_count;
+ left=wnd[i]->anchor_horizontal - wnd[i]->col_count;
+ break;
+ default: // Shouldn't happen, but skip the window just in case
+ continue;
+ }
+ if (top<0)
+ top=0;
+ if (left<0)
+ left=0;
+ int copyrows=top + wnd[i]->row_count >= I708_SCREENGRID_ROWS ?
+ I708_SCREENGRID_ROWS - top : wnd[i]->row_count;
+ int copycols=left + wnd[i]->col_count >= I708_SCREENGRID_COLUMNS ?
+ I708_SCREENGRID_COLUMNS - left : wnd[i]->col_count;
+ for (int j=0;j<copyrows;j++)
+ {
+ memcpy (decoder->tv.chars[top+j],wnd[i]->rows[j],copycols);
+ }
+ }
+ printTVtoBuf(decoder);
+ decoder->callback(decoder->service, decoder->userdata);
+}
+
+void rollupWindow(cc708_service_decoder *decoder, int window)
+{
+ for (int row=0; row<decoder->windows[window].row_count - 1; row++)
+ {
+ memcpy(decoder->windows[window].rows[row], decoder->windows[window].rows[row+1], decoder->windows[window].col_count);
+ }
+ memset(decoder->windows[window].rows[decoder->windows[window].row_count-1], ' ', decoder->windows[window].col_count);
+}
+
+/* This function handles future codes. While by definition we can't do any work on them, we must return
+how many bytes would be consumed if these codes were supported, as defined in the specs.
+Note: EXT1 not included */
+// C2: Extended Miscellaneous Control Codes
+//! @todo This code is completely untested due to lack of samples. Just following specs!
+int handle_708_C2 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ if (data[0]<=0x07) // 00-07...
+ return 1; // ... Single-byte control bytes (0 additional bytes)
+ else if (data[0]<=0x0f) // 08-0F ...
+ return 2; // ..two-byte control codes (1 additional byte)
+ else if (data[0]<=0x17) // 10-17 ...
+ return 3; // ..three-byte control codes (2 additional bytes)
+ return 4; // 18-1F => four-byte control codes (3 additional bytes)
+}
+
+int handle_708_C3 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ if (data[0] < 0x80 || data[0] > 0x9F)
+ CLog::Log(LOGERROR, "{} - Entry in handle_708_C3 with an out of range value", __FUNCTION__);
+ if (data[0]<=0x87) // 80-87...
+ return 5; // ... Five-byte control bytes (4 additional bytes)
+ else if (data[0]<=0x8F) // 88-8F ...
+ return 6; // ..Six-byte control codes (5 additional byte)
+ // If here, then 90-9F ...
+
+ // These are variable length commands, that can even span several segments
+ // (they allow even downloading fonts or graphics).
+ //! @todo Implement if a sample ever appears
+ return 0; // Unreachable, but otherwise there's compilers warnings
+}
+
+// This function handles extended codes (EXT1 + code), from the extended sets
+// G2 (20-7F) => Mostly unmapped, except for a few characters.
+// G3 (A0-FF) => A0 is the CC symbol, everything else reserved for future expansion in EIA708-B
+// C2 (00-1F) => Reserved for future extended misc. control and captions command codes
+//! @todo This code is completely untested due to lack of samples. Just following specs!
+// Returns number of used bytes, usually 1 (since EXT1 is not counted).
+int handle_708_extended_char (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ int used;
+ unsigned char c=0x20; // Default to space
+ unsigned char code=data[0];
+ if (/* data[i]>=0x00 && */ code<=0x1F) // Comment to silence warning
+ {
+ used=handle_708_C2 (decoder, data, data_length);
+ }
+ // Group G2 - Extended Miscellaneous Characters
+ else if (code>=0x20 && code<=0x7F)
+ {
+ c=get_internal_from_G2 (code);
+ used=1;
+ process_character (decoder, c);
+ }
+ // Group C3
+ else if (code>=0x80 && code<=0x9F)
+ {
+ used=handle_708_C3 (decoder, data, data_length);
+ //! @todo Something
+ }
+ // Group G3
+ else
+ {
+ c=get_internal_from_G3 (code);
+ used=1;
+ process_character (decoder, c);
+ }
+ return used;
+}
+
+void process_cr (cc708_service_decoder *decoder)
+{
+ switch (decoder->windows[decoder->current_window].attribs.print_dir)
+ {
+ case pd_left_to_right:
+ decoder->windows[decoder->current_window].pen_column=0;
+ if (decoder->windows[decoder->current_window].pen_row+1 < decoder->windows[decoder->current_window].row_count)
+ decoder->windows[decoder->current_window].pen_row++;
+ break;
+ case pd_right_to_left:
+ decoder->windows[decoder->current_window].pen_column=decoder->windows[decoder->current_window].col_count;
+ if (decoder->windows[decoder->current_window].pen_row+1 < decoder->windows[decoder->current_window].row_count)
+ decoder->windows[decoder->current_window].pen_row++;
+ break;
+ case pd_top_to_bottom:
+ decoder->windows[decoder->current_window].pen_row=0;
+ if (decoder->windows[decoder->current_window].pen_column+1 < decoder->windows[decoder->current_window].col_count)
+ decoder->windows[decoder->current_window].pen_column++;
+ break;
+ case pd_bottom_to_top:
+ decoder->windows[decoder->current_window].pen_row=decoder->windows[decoder->current_window].row_count;
+ if (decoder->windows[decoder->current_window].pen_column+1 < decoder->windows[decoder->current_window].col_count)
+ decoder->windows[decoder->current_window].pen_column++;
+ break;
+ }
+
+ if (decoder->windows[decoder->current_window].anchor_point == anchorpoint_bottom_left ||
+ decoder->windows[decoder->current_window].anchor_point == anchorpoint_bottom_center)
+ {
+ rollupWindow(decoder, decoder->current_window);
+ updateScreen(decoder);
+ }
+}
+
+int handle_708_C0 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ const char *name=COMMANDS_C0[data[0]];
+ if (name==NULL)
+ name="Reserved";
+ int len=-1;
+ // These commands have a known length even if they are reserved.
+ if (/* data[0]>=0x00 && */ data[0]<=0xF) // Comment to silence warning
+ {
+ switch (data[0])
+ {
+ case 0x0d: //CR
+ process_cr (decoder);
+ break;
+ case 0x0e: // HCR (Horizontal Carriage Return)
+ //! @todo Process HDR
+ break;
+ case 0x0c: // FF (Form Feed)
+ //! @todo Process FF
+ break;
+ }
+ len=1;
+ }
+ else if (data[0]>=0x10 && data[0]<=0x17)
+ {
+ // Note that 0x10 is actually EXT1 and is dealt with somewhere else. Rest is undefined as per
+ // CEA-708-D
+ len=2;
+ }
+ else if (data[0]>=0x18 && data[0]<=0x1F)
+ {
+ // Only PE16 is defined.
+ if (data[0]==0x18) // PE16
+ {
+ ; //! @todo Handle PE16
+ }
+ len=3;
+ }
+ if (len==-1)
+ {
+ return -1;
+ }
+ if (len>data_length)
+ {
+ return -1;
+ }
+ //! @todo Do something useful eventually
+ return len;
+}
+
+
+void process_character(cc708_service_decoder* decoder, unsigned char internal_char)
+{
+ if (decoder->current_window == -1 ||
+ !decoder->windows[decoder->current_window]
+ .is_defined) // Writing to a non existing window, skipping
+ return;
+ decoder->windows[decoder->current_window].is_empty = 0;
+ decoder->windows[decoder->current_window]
+ .rows[decoder->windows[decoder->current_window].pen_row]
+ [decoder->windows[decoder->current_window].pen_column] = internal_char;
+ /* Not positive this interpretation is correct. Word wrapping is optional, so
+ let's assume we don't need to autoscroll */
+ switch (decoder->windows[decoder->current_window].attribs.print_dir)
+ {
+ case pd_left_to_right:
+ if (decoder->windows[decoder->current_window].pen_column + 1 <
+ decoder->windows[decoder->current_window].col_count)
+ decoder->windows[decoder->current_window].pen_column++;
+ break;
+ case pd_right_to_left:
+ if (decoder->windows->pen_column > 0)
+ decoder->windows[decoder->current_window].pen_column--;
+ break;
+ case pd_top_to_bottom:
+ if (decoder->windows[decoder->current_window].pen_row + 1 <
+ decoder->windows[decoder->current_window].row_count)
+ decoder->windows[decoder->current_window].pen_row++;
+ break;
+ case pd_bottom_to_top:
+ if (decoder->windows[decoder->current_window].pen_row > 0)
+ decoder->windows[decoder->current_window].pen_row--;
+ break;
+ }
+}
+
+// G0 - Code Set - ASCII printable characters
+int handle_708_G0 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ //! @todo Substitution of the music note character for the ASCII DEL character
+ unsigned char c=get_internal_from_G0 (data[0]);
+ process_character (decoder, c);
+ return 1;
+}
+
+// G1 Code Set - ISO 8859-1 LATIN-1 Character Set
+int handle_708_G1 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ unsigned char c=get_internal_from_G1 (data[0]);
+ process_character (decoder, c);
+ return 1;
+}
+
+/*-------------------------------------------------------
+ WINDOW COMMANDS
+ ------------------------------------------------------- */
+void handle_708_CWx_SetCurrentWindow (cc708_service_decoder *decoder, int new_window)
+{
+ if (decoder->windows[new_window].is_defined)
+ decoder->current_window=new_window;
+}
+
+void clearWindowText(e708Window *window)
+{
+ for (int i = 0; i<I708_MAX_ROWS; i++)
+ {
+ memset(window->rows[i], ' ', I708_MAX_COLUMNS);
+ window->rows[i][I708_MAX_COLUMNS] = 0;
+ }
+ memset(window->rows[I708_MAX_ROWS], 0, I708_MAX_COLUMNS + 1);
+ window->is_empty = 1;
+
+}
+
+void clearWindow (cc708_service_decoder *decoder, int window)
+{
+ if (decoder->windows[window].is_defined)
+ clearWindowText(&decoder->windows[window]);
+}
+
+void handle_708_CLW_ClearWindows (cc708_service_decoder *decoder, int windows_bitmap)
+{
+ if (windows_bitmap==0)
+ ;//ccx_common_logging.debug_ftn(CCX_DMT_708, "None\n");
+ else
+ {
+ for (int i=0; i<8; i++)
+ {
+ if (windows_bitmap & 1)
+ {
+ clearWindow (decoder, i);
+ }
+ windows_bitmap>>=1;
+ }
+ }
+}
+
+void handle_708_DSW_DisplayWindows (cc708_service_decoder *decoder, int windows_bitmap)
+{
+ if (windows_bitmap==0)
+ ;//ccx_common_logging.debug_ftn(CCX_DMT_708, "None\n");
+ else
+ {
+ int changes=0;
+ for (e708Window& window : decoder->windows)
+ {
+ if (windows_bitmap & 1)
+ {
+ if (!window.visible)
+ {
+ changes=1;
+ window.visible=1;
+ }
+ }
+ windows_bitmap>>=1;
+ }
+ if (changes)
+ updateScreen (decoder);
+ }
+}
+
+void handle_708_HDW_HideWindows (cc708_service_decoder *decoder, int windows_bitmap)
+{
+ if (windows_bitmap==0)
+ ;//ccx_common_logging.debug_ftn(CCX_DMT_708, "None\n");
+ else
+ {
+ int changes=0;
+ for (e708Window& window : decoder->windows)
+ {
+ if (windows_bitmap & 1)
+ {
+ if (window.is_defined && window.visible && !window.is_empty)
+ {
+ changes=1;
+ window.visible=0;
+ }
+ //! @todo Actually Hide Window
+ }
+ windows_bitmap>>=1;
+ }
+ if (changes)
+ updateScreen (decoder);
+ }
+}
+
+void handle_708_TGW_ToggleWindows (cc708_service_decoder *decoder, int windows_bitmap)
+{
+ if (windows_bitmap==0)
+ ;//ccx_common_logging.debug_ftn(CCX_DMT_708, "None\n");
+ else
+ {
+ for (e708Window& window : decoder->windows)
+ {
+ if (windows_bitmap & 1)
+ {
+ window.visible=!window.visible;
+ }
+ windows_bitmap>>=1;
+ }
+ updateScreen(decoder);
+ }
+}
+
+void handle_708_DFx_DefineWindow (cc708_service_decoder *decoder, int window, unsigned char *data)
+{
+ if (decoder->windows[window].is_defined &&
+ memcmp (decoder->windows[window].commands, data+1, 6)==0)
+ {
+ return;
+ }
+ decoder->windows[window].number=window;
+ int priority = (data[1] ) & 0x7;
+ int col_lock = (data[1]>>3) & 0x1;
+ int row_lock = (data[1]>>4) & 0x1;
+ int visible = (data[1]>>5) & 0x1;
+ int anchor_vertical = data[2] & 0x7f;
+ int relative_pos = (data[2]>>7);
+ int anchor_horizontal = data[3];
+ int row_count = data[4] & 0xf;
+ int anchor_point = data[4]>>4;
+ int col_count = data[5] & 0x3f;
+ int pen_style = data[6] & 0x7;
+ int win_style = (data[6]>>3) & 0x7;
+ col_count++; // These increments seems to be needed but no documentation
+ row_count++; // backs it up
+
+ if (anchor_vertical > I708_SCREENGRID_ROWS)
+ anchor_vertical = I708_SCREENGRID_ROWS;
+
+ decoder->windows[window].priority=priority;
+ decoder->windows[window].col_lock=col_lock;
+ decoder->windows[window].row_lock=row_lock;
+ decoder->windows[window].visible=visible;
+ decoder->windows[window].anchor_vertical=anchor_vertical;
+ decoder->windows[window].relative_pos=relative_pos;
+ decoder->windows[window].anchor_horizontal=anchor_horizontal;
+ decoder->windows[window].row_count=row_count;
+ decoder->windows[window].anchor_point=anchor_point;
+ decoder->windows[window].col_count=col_count;
+ decoder->windows[window].pen_style=pen_style;
+ decoder->windows[window].win_style=win_style;
+ if (!decoder->windows[window].is_defined)
+ {
+ // If the window is being created, all character positions in the window
+ // are set to the fill color...
+ //! @todo COLORS
+ // ...and the pen location is set to (0,0)
+ decoder->windows[window].pen_column=0;
+ decoder->windows[window].pen_row=0;
+ if (!decoder->windows[window].memory_reserved)
+ {
+ for (int i=0;i<=I708_MAX_ROWS;i++)
+ {
+ decoder->windows[window].rows[i]=(unsigned char *) malloc (I708_MAX_COLUMNS+1);
+ if (decoder->windows[window].rows[i]==NULL) // Great
+ {
+ decoder->windows[window].is_defined=0;
+ decoder->current_window=-1;
+ for (int j=0;j<i;j++)
+ free (decoder->windows[window].rows[j]);
+ return; //! @todo Warn somehow
+ }
+ }
+ decoder->windows[window].memory_reserved=1;
+ }
+ decoder->windows[window].is_defined=1;
+ memset(&decoder->windows[window].attribs, 0, sizeof(e708Window_attribs));
+ clearWindowText (&decoder->windows[window]);
+ }
+
+ // ...also makes the defined windows the current window (setCurrentWindow)
+ handle_708_CWx_SetCurrentWindow (decoder, window);
+ memcpy (decoder->windows[window].commands, data+1, 6);
+}
+
+void handle_708_SWA_SetWindowAttributes (cc708_service_decoder *decoder, unsigned char *data)
+{
+ int fill_color = (data[1] ) & 0x3f;
+ int fill_opacity = (data[1]>>6) & 0x03;
+ int border_color = (data[2] ) & 0x3f;
+ int border_type01 = (data[2]>>6) & 0x03;
+ int justify = (data[3] ) & 0x03;
+ int scroll_dir = (data[3]>>2) & 0x03;
+ int print_dir = (data[3]>>4) & 0x03;
+ int word_wrap = (data[3]>>6) & 0x01;
+ int border_type = (data[3]>>5) | border_type01;
+ int display_eff = (data[4] ) & 0x03;
+ int effect_dir = (data[4]>>2) & 0x03;
+ int effect_speed = (data[4]>>4) & 0x0f;
+ if (decoder->current_window==-1)
+ {
+ // Can't do anything yet - we need a window to be defined first.
+ return;
+ }
+ decoder->windows[decoder->current_window].attribs.fill_color=fill_color;
+ decoder->windows[decoder->current_window].attribs.fill_opacity=fill_opacity;
+ decoder->windows[decoder->current_window].attribs.border_color=border_color;
+ decoder->windows[decoder->current_window].attribs.border_type01=border_type01;
+ decoder->windows[decoder->current_window].attribs.justify=justify;
+ decoder->windows[decoder->current_window].attribs.scroll_dir=scroll_dir;
+ decoder->windows[decoder->current_window].attribs.print_dir=print_dir;
+ decoder->windows[decoder->current_window].attribs.word_wrap=word_wrap;
+ decoder->windows[decoder->current_window].attribs.border_type=border_type;
+ decoder->windows[decoder->current_window].attribs.display_eff=display_eff;
+ decoder->windows[decoder->current_window].attribs.effect_dir=effect_dir;
+ decoder->windows[decoder->current_window].attribs.effect_speed=effect_speed;
+
+}
+
+void deleteWindow (cc708_service_decoder *decoder, int window)
+{
+ if (window==decoder->current_window)
+ {
+ // If the current window is deleted, then the decoder's current window ID
+ // is unknown and must be reinitialized with either the SetCurrentWindow
+ // or DefineWindow command.
+ decoder->current_window=-1;
+ }
+ //! @todo Do the actual deletion (remove from display if needed, etc), mark as
+ // not defined, etc
+ if (decoder->windows[window].is_defined)
+ {
+ clearWindowText(&decoder->windows[window]);
+ }
+ decoder->windows[window].is_defined=0;
+}
+
+void handle_708_DLW_DeleteWindows (cc708_service_decoder *decoder, int windows_bitmap)
+{
+ int changes=0;
+ if (windows_bitmap==0)
+ ; //ccx_common_logging.debug_ftn(CCX_DMT_708, "None\n");
+ else
+ {
+ for (int i=0; i<8; i++)
+ {
+ if (windows_bitmap & 1)
+ {
+ if (decoder->windows[i].is_defined && decoder->windows[i].visible && !decoder->windows[i].is_empty)
+ changes=1;
+ deleteWindow (decoder, i);
+ }
+ windows_bitmap>>=1;
+ }
+ }
+ if (changes)
+ updateScreen (decoder);
+
+}
+
+/*-------------------------------------------------------
+ WINDOW COMMANDS
+ ------------------------------------------------------- */
+void handle_708_SPA_SetPenAttributes (cc708_service_decoder *decoder, unsigned char *data)
+{
+ int pen_size = (data[1] ) & 0x3;
+ int offset = (data[1]>>2) & 0x3;
+ int text_tag = (data[1]>>4) & 0xf;
+ int font_tag = (data[2] ) & 0x7;
+ int edge_type = (data[2]>>3) & 0x7;
+ int underline = (data[2]>>4) & 0x1;
+ int italic = (data[2]>>5) & 0x1;
+ if (decoder->current_window==-1)
+ {
+ // Can't do anything yet - we need a window to be defined first.
+ return;
+ }
+ decoder->windows[decoder->current_window].pen.pen_size=pen_size;
+ decoder->windows[decoder->current_window].pen.offset=offset;
+ decoder->windows[decoder->current_window].pen.text_tag=text_tag;
+ decoder->windows[decoder->current_window].pen.font_tag=font_tag;
+ decoder->windows[decoder->current_window].pen.edge_type=edge_type;
+ decoder->windows[decoder->current_window].pen.underline=underline;
+ decoder->windows[decoder->current_window].pen.italic=italic;
+}
+
+void handle_708_SPC_SetPenColor (cc708_service_decoder *decoder, unsigned char *data)
+{
+ int fg_color = (data[1] ) & 0x3f;
+ int fg_opacity = (data[1]>>6) & 0x03;
+ int bg_color = (data[2] ) & 0x3f;
+ int bg_opacity = (data[2]>>6) & 0x03;
+ int edge_color = (data[3]>>6) & 0x3f;
+ if (decoder->current_window==-1)
+ {
+ // Can't do anything yet - we need a window to be defined first.
+ return;
+ }
+
+ decoder->windows[decoder->current_window].pen_color.fg_color=fg_color;
+ decoder->windows[decoder->current_window].pen_color.fg_opacity=fg_opacity;
+ decoder->windows[decoder->current_window].pen_color.bg_color=bg_color;
+ decoder->windows[decoder->current_window].pen_color.bg_opacity=bg_opacity;
+ decoder->windows[decoder->current_window].pen_color.edge_color=edge_color;
+}
+
+
+void handle_708_SPL_SetPenLocation (cc708_service_decoder *decoder, unsigned char *data)
+{
+ int row = data[1] & 0x0f;
+ int col = data[2] & 0x3f;
+ if (decoder->current_window==-1)
+ {
+ // Can't do anything yet - we need a window to be defined first.
+ return;
+ }
+ decoder->windows[decoder->current_window].pen_row=row;
+ decoder->windows[decoder->current_window].pen_column=col;
+}
+
+
+/*-------------------------------------------------------
+ SYNCHRONIZATION COMMANDS
+ ------------------------------------------------------- */
+void handle_708_DLY_Delay (cc708_service_decoder *decoder, int tenths_of_sec)
+{
+ //! @todo Probably ask for the current FTS and wait for this time before resuming -
+ // not sure it's worth it though
+}
+
+void handle_708_DLC_DelayCancel (cc708_service_decoder *decoder)
+{
+ //! @todo See above
+}
+
+// C1 Code Set - Captioning Commands Control Codes
+int handle_708_C1 (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ struct S_COMMANDS_C1 com=COMMANDS_C1[data[0]-0x80];
+ if (com.length>data_length)
+ {
+ return -1;
+ }
+ switch (com.code)
+ {
+ case CW0: /* SetCurrentWindow */
+ case CW1:
+ case CW2:
+ case CW3:
+ case CW4:
+ case CW5:
+ case CW6:
+ case CW7:
+ handle_708_CWx_SetCurrentWindow (decoder, com.code-CW0); /* Window 0 to 7 */
+ break;
+ case CLW:
+ handle_708_CLW_ClearWindows (decoder, data[1]);
+ break;
+ case DSW:
+ handle_708_DSW_DisplayWindows (decoder, data[1]);
+ break;
+ case HDW:
+ handle_708_HDW_HideWindows (decoder, data[1]);
+ break;
+ case TGW:
+ handle_708_TGW_ToggleWindows (decoder, data[1]);
+ break;
+ case DLW:
+ handle_708_DLW_DeleteWindows (decoder, data[1]);
+ break;
+ case DLY:
+ handle_708_DLY_Delay (decoder, data[1]);
+ break;
+ case DLC:
+ handle_708_DLC_DelayCancel (decoder);
+ break;
+ case RST:
+ cc708_service_reset(decoder);
+ break;
+ case SPA:
+ handle_708_SPA_SetPenAttributes (decoder, data);
+ break;
+ case SPC:
+ handle_708_SPC_SetPenColor (decoder, data);
+ break;
+ case SPL:
+ handle_708_SPL_SetPenLocation (decoder, data);
+ break;
+ case RSV93:
+ case RSV94:
+ case RSV95:
+ case RSV96:
+ break;
+ case SWA:
+ handle_708_SWA_SetWindowAttributes (decoder, data);
+ break;
+ case DF0:
+ case DF1:
+ case DF2:
+ case DF3:
+ case DF4:
+ case DF5:
+ case DF6:
+ case DF7:
+ handle_708_DFx_DefineWindow (decoder, com.code-DF0, data); /* Window 0 to 7 */
+ break;
+ default:
+ break;
+ }
+
+ return com.length;
+}
+
+
+void process_service_block (cc708_service_decoder *decoder, unsigned char *data, int data_length)
+{
+ int i=0;
+ while (i<data_length)
+ {
+ int used=-1;
+ if (data[i]!=EXT1)
+ {
+ // Group C0
+ if (/* data[i]>=0x00 && */ data[i]<=0x1F) // Comment to silence warning
+ {
+ used=handle_708_C0 (decoder,data+i,data_length-i);
+ }
+ // Group G0
+ else if (data[i]>=0x20 && data[i]<=0x7F)
+ {
+ used=handle_708_G0 (decoder,data+i,data_length-i);
+ }
+ // Group C1
+ else if (data[i]>=0x80 && data[i]<=0x9F)
+ {
+ used=handle_708_C1 (decoder,data+i,data_length-i);
+ }
+ // Group C2
+ else
+ used=handle_708_G1 (decoder,data+i,data_length-i);
+ if (used==-1)
+ {
+ //! @todo Not sure if a local reset is going to be helpful here.
+ cc708_service_reset (decoder);
+ return;
+ }
+ }
+ else // Use extended set
+ {
+ used=handle_708_extended_char (decoder, data+i+1,data_length-1);
+ used++; // Since we had EXT1
+ }
+ i+=used;
+ }
+
+ // update rollup windows
+ int update = 0;
+ for (e708Window& window : decoder->windows)
+ {
+ if (window.is_defined && window.visible &&
+ (window.anchor_point == anchorpoint_bottom_left ||
+ window.anchor_point == anchorpoint_bottom_center))
+ {
+ update++;
+ break;
+ }
+ }
+ if (update)
+ {
+ updateScreen(decoder);
+ }
+}
+
+bool check_current_packet_complete (cc708_service_decoder *decoders)
+{
+ int len = decoders[0].parent->m_current_packet[0] & 0x3F; // 6 least significants bits
+ if (decoders[0].parent->m_current_packet_length == 0)
+ return false;
+
+ if (len==0) // This is well defined in EIA-708; no magic.
+ len=128;
+ else
+ len=len*2;
+
+ // Note that len here is the length including the header
+ if (decoders[0].parent->m_current_packet_length == len) // Is this possible?
+ {
+ return true;
+ }
+ return false;
+}
+
+void process_current_packet (cc708_service_decoder *decoders)
+{
+ int seq = (decoders[0].parent->m_current_packet[0] & 0xC0) >> 6; // Two most significants bits
+ int len = decoders[0].parent->m_current_packet[0] & 0x3F; // 6 least significants bits
+ if (decoders[0].parent->m_current_packet_length == 0)
+ return;
+
+ if (len==0) // This is well defined in EIA-708; no magic.
+ len=128;
+ else
+ len=len*2;
+ // Note that len here is the length including the header
+ if (decoders[0].parent->m_current_packet_length != len) // Is this possible?
+ {
+ cc708_reset(decoders);
+ return;
+ }
+ int last_seq = decoders[0].parent->m_last_seq;
+ if ((last_seq != -1) && ((last_seq+1)%4 != seq))
+ {
+ cc708_reset(decoders);
+ return;
+ }
+ decoders[0].parent->m_last_seq = seq;
+
+ unsigned char *pos = decoders[0].parent->m_current_packet + 1;
+
+ while (pos < decoders[0].parent->m_current_packet + len)
+ {
+ int service_number=(pos[0] & 0xE0)>>5; // 3 more significant bits
+ int block_length = (pos[0] & 0x1F); // 5 less significant bits
+
+ if (service_number==7) // There is an extended header
+ {
+ pos++;
+ service_number=(pos[0] & 0x3F); // 6 more significant bits
+ if (service_number<7)
+ {
+ }
+ pos = decoders[0].parent->m_current_packet + len;
+ break;
+ }
+
+ pos++; // Move to service data
+ if (service_number==0 && block_length!=0) // Illegal, but specs say what to do...
+ {
+ pos = decoders[0].parent->m_current_packet + len; // Move to end
+ break;
+ }
+
+ if (service_number>0 && decoders[service_number].inited)
+ process_service_block (&decoders[service_number], pos, block_length);
+
+ pos+=block_length; // Skip data
+ }
+
+ clear_packet(&decoders[0]);
+
+ if (pos != decoders[0].parent->m_current_packet + len) // For some reason we didn't parse the whole packet
+ {
+ cc708_reset(decoders);
+ }
+
+ if (len<128 && *pos) // Null header is mandatory if there is room
+ {
+ ;//ccx_common_logging.debug_ftn(CCX_DMT_708, "Warning: Null header expected but not found.\n");
+ }
+}
+
+void decode_708 (const unsigned char *data, int datalength, cc708_service_decoder* decoders)
+{
+ /* Note: The data has this format:
+ 1 byte for cc_valid and cc_type
+ 2 bytes for the actual data */
+ for (int i=0; i<datalength; i+=3)
+ {
+ unsigned char cc_valid=data[i] & 0x04;
+ unsigned char cc_type=data[i] & 0x03;
+
+ switch (cc_type)
+ {
+ case 0:
+ // only use 608 as fallback
+ if (!decoders[0].parent->m_seen708)
+ decode_cc(decoders[0].parent->m_cc608decoder, (const uint8_t*)data+i, 3);
+ break;
+ case 2:
+ if (cc_valid==0) // This ends the previous packet if complete
+ {
+ if (check_current_packet_complete(decoders))
+ {
+ process_current_packet(decoders);
+ }
+ }
+ else
+ {
+ if (decoders[0].parent->m_current_packet_length < 254)
+ {
+ decoders[0].parent->m_current_packet[decoders[0].parent->m_current_packet_length++]=data[i+1];
+ decoders[0].parent->m_current_packet[decoders[0].parent->m_current_packet_length++]=data[i+2];
+ }
+ }
+ break;
+ case 3:
+ process_current_packet(decoders);
+ if (cc_valid)
+ {
+ if (decoders[0].parent->m_current_packet_length < 128)
+ {
+ decoders[0].parent->m_current_packet[decoders[0].parent->m_current_packet_length++]=data[i+1];
+ decoders[0].parent->m_current_packet[decoders[0].parent->m_current_packet_length++]=data[i+2];
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void ccx_decoders_708_init(cc708_service_decoder *decoders, void (*handler)(int service, void *userdata), void *userdata, CDecoderCC708 *parent)
+{
+ for (int i = 0; i<CCX_DECODERS_708_MAX_SERVICES; i++)
+ {
+ cc708_service_reset (&decoders[i]);
+ decoders[i].srt_counter=0;
+ decoders[i].service = i;
+ decoders[i].callback = handler;
+ decoders[i].userdata = userdata;
+ decoders[i].parent = parent;
+ }
+ decoders[0].parent->m_cc608decoder->callback = handler;
+ decoders[0].parent->m_cc608decoder->userdata = userdata;
+
+ decoders[0].parent->m_current_packet_length = 0;
+ decoders[0].parent->m_last_seq = -1;
+ decoders[0].parent->m_seen708 = false;
+ decoders[0].parent->m_seen608 = false;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+CDecoderCC708::CDecoderCC708()
+{
+ m_inited = false;
+ cc_decoder_init();
+}
+
+CDecoderCC708::~CDecoderCC708()
+{
+ delete [] m_cc708decoders;
+ cc_decoder_close(m_cc608decoder);
+}
+
+void CDecoderCC708::Init(void (*handler)(int service, void *userdata), void *userdata)
+{
+ m_cc608decoder = cc_decoder_open();
+ m_cc708decoders = new cc708_service_decoder[8];
+ ccx_decoders_708_init(m_cc708decoders, handler, userdata, this);
+}
+
+void CDecoderCC708::Decode(const unsigned char *data, int datalength)
+{
+ decode_708(data, datalength, m_cc708decoders);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.h
new file mode 100644
index 0000000..a891012
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.h
@@ -0,0 +1,304 @@
+#pragma once
+
+#include <sys/stat.h>
+
+extern "C"{
+#include "cc_decoder.h"
+}
+
+#define MAX_708_PACKET_LENGTH 128
+#define CCX_DECODERS_708_MAX_SERVICES 8
+#define I708_MAX_ROWS 15
+#define I708_MAX_COLUMNS 42
+#define I708_SCREENGRID_ROWS 75
+#define I708_SCREENGRID_COLUMNS 210
+#define I708_MAX_WINDOWS 8
+
+enum COMMANDS_C0_CODES
+{
+ NUL=0,
+ ETX=3,
+ BS=8,
+ FF=0xC,
+ CR=0xD,
+ HCR=0xE,
+ EXT1=0x10,
+ P16=0x18
+};
+
+enum COMMANDS_C1_CODES
+{
+ CW0=0x80,
+ CW1=0x81,
+ CW2=0x82,
+ CW3=0x83,
+ CW4=0x84,
+ CW5=0x85,
+ CW6=0x86,
+ CW7=0x87,
+ CLW=0x88,
+ DSW=0x89,
+ HDW=0x8A,
+ TGW=0x8B,
+ DLW=0x8C,
+ DLY=0x8D,
+ DLC=0x8E,
+ RST=0x8F,
+ SPA=0x90,
+ SPC=0x91,
+ SPL=0x92,
+ RSV93=0x93,
+ RSV94=0x94,
+ RSV95=0x95,
+ RSV96=0x96,
+ SWA=0x97,
+ DF0=0x98,
+ DF1=0x99,
+ DF2=0x9A,
+ DF3=0x9B,
+ DF4=0x9C,
+ DF5=0x9D,
+ DF6=0x9E,
+ DF7=0x9F
+};
+
+struct S_COMMANDS_C1
+{
+ int code;
+ const char *name;
+ const char *description;
+ int length;
+};
+
+
+enum eWindowsAttribJustify
+{
+ left=0,
+ right=1,
+ center=2,
+ full=3
+};
+
+enum eWindowsAttribPrintDirection
+{
+ pd_left_to_right=0,
+ pd_right_to_left=1,
+ pd_top_to_bottom=2,
+ pd_bottom_to_top=3
+};
+
+enum eWindowsAttribScrollDirection
+{
+ sd_left_to_right=0,
+ sd_right_to_left=1,
+ sd_top_to_bottom=2,
+ sd_bottom_to_top=3
+};
+
+enum eWindowsAttribScrollDisplayEffect
+{
+ snap=0,
+ fade=1,
+ wipe=2
+};
+
+enum eWindowsAttribEffectDirection
+{
+ left_to_right=0,
+ right_to_left=1,
+ top_to_bottom=2,
+ bottom_to_top=3
+};
+
+enum eWindowsAttribFillOpacity
+{
+ solid=0,
+ flash=1,
+ translucent=2,
+ transparent=3
+};
+
+enum eWindowsAttribBorderType
+{
+ none=0,
+ raised=1,
+ depressed=2,
+ uniform=3,
+ shadow_left=4,
+ shadow_right=5
+};
+
+enum ePenAttribSize
+{
+ pensize_small=0,
+ pensize_standard=1,
+ pensize_large=2
+};
+
+enum ePenAttribFontStyle
+{
+ fontstyle_default_or_undefined=0,
+ monospaced_with_serifs=1,
+ proportionally_spaced_with_serifs=2,
+ monospaced_without_serifs=3,
+ proportionally_spaced_without_serifs=4,
+ casual_font_type=5,
+ cursive_font_type=6,
+ small_capitals=7
+};
+
+enum ePanAttribTextTag
+{
+ texttag_dialog=0,
+ texttag_source_or_speaker_id=1,
+ texttag_electronic_voice=2,
+ texttag_foreign_language=3,
+ texttag_voiceover=4,
+ texttag_audible_translation=5,
+ texttag_subtitle_translation=6,
+ texttag_voice_quality_description=7,
+ texttag_song_lyrics=8,
+ texttag_sound_effect_description=9,
+ texttag_musical_score_description=10,
+ texttag_expletive=11,
+ texttag_undefined_12=12,
+ texttag_undefined_13=13,
+ texttag_undefined_14=14,
+ texttag_not_to_be_displayed=15
+};
+
+enum ePanAttribOffset
+{
+ offset_subscript=0,
+ offset_normal=1,
+ offset_superscript=2
+};
+
+enum ePanAttribEdgeType
+{
+ edgetype_none=0,
+ edgetype_raised=1,
+ edgetype_depressed=2,
+ edgetype_uniform=3,
+ edgetype_left_drop_shadow=4,
+ edgetype_right_drop_shadow=5
+};
+
+enum eAnchorPoints
+{
+ anchorpoint_top_left = 0,
+ anchorpoint_top_center = 1,
+ anchorpoint_top_right =2,
+ anchorpoint_middle_left = 3,
+ anchorpoint_middle_center = 4,
+ anchorpoint_middle_right = 5,
+ anchorpoint_bottom_left = 6,
+ anchorpoint_bottom_center = 7,
+ anchorpoint_bottom_right = 8
+};
+
+typedef struct e708Pen_color
+{
+ int fg_color;
+ int fg_opacity;
+ int bg_color;
+ int bg_opacity;
+ int edge_color;
+} e708Pen_color;
+
+typedef struct e708Pen_attribs
+{
+ int pen_size;
+ int offset;
+ int text_tag;
+ int font_tag;
+ int edge_type;
+ int underline;
+ int italic;
+} e708Pen_attribs;
+
+typedef struct e708Window_attribs
+{
+ int fill_color;
+ int fill_opacity;
+ int border_color;
+ int border_type01;
+ int justify;
+ int scroll_dir;
+ int print_dir;
+ int word_wrap;
+ int border_type;
+ int display_eff;
+ int effect_dir;
+ int effect_speed;
+} e708Window_attribs;
+
+typedef struct e708Window
+{
+ int is_defined;
+ int number; // Handy, in case we only have a pointer to the window
+ int priority;
+ int col_lock;
+ int row_lock;
+ int visible;
+ int anchor_vertical;
+ int relative_pos;
+ int anchor_horizontal;
+ int row_count;
+ int anchor_point;
+ int col_count;
+ int pen_style;
+ int win_style;
+ unsigned char commands[6]; // Commands used to create this window
+ e708Window_attribs attribs;
+ e708Pen_attribs pen;
+ e708Pen_color pen_color;
+ int pen_row;
+ int pen_column;
+ unsigned char *rows[I708_MAX_ROWS+1]; // Max is 15, but we define an extra one for convenience
+ int memory_reserved;
+ int is_empty;
+} e708Window;
+
+typedef struct tvscreen
+{
+ unsigned char chars[I708_SCREENGRID_ROWS][I708_SCREENGRID_COLUMNS+1];
+}
+tvscreen;
+
+class CDecoderCC708;
+typedef struct cc708_service_decoder
+{
+ e708Window windows[I708_MAX_WINDOWS];
+ int current_window;
+ int inited;
+ int service;
+ tvscreen tv;
+ int is_empty_tv;
+ int srt_counter;
+ void *userdata;
+ void (*callback)(int service, void *userdata);
+ char text[I708_SCREENGRID_ROWS*I708_SCREENGRID_COLUMNS+1];
+ int textlen;
+ CDecoderCC708 *parent;
+}
+cc708_service_decoder;
+
+void process_character (cc708_service_decoder *decoder, unsigned char internal_char);
+
+class CDecoderCC708
+{
+public:
+ CDecoderCC708();
+ virtual ~CDecoderCC708();
+ void Init(void (*handler)(int service, void *userdata), void *userdata);
+ void Decode(const unsigned char *data, int datalength);
+ bool m_inited;
+ cc708_service_decoder* m_cc708decoders;
+ cc_decoder_t *m_cc608decoder;
+ unsigned char m_current_packet[MAX_708_PACKET_LENGTH]; // Length according to EIA-708B, part 5
+ int m_current_packet_length;
+ int m_last_seq;
+ bool m_seen708;
+ bool m_seen608;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp
new file mode 100644
index 0000000..8a11885
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AddonVideoCodec.h"
+
+#include "addons/addoninfo/AddonInfo.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecs.h"
+#include "cores/VideoPlayer/DVDStreamInfo.h"
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+CAddonVideoCodec::CAddonVideoCodec(CProcessInfo& processInfo,
+ ADDON::AddonInfoPtr& addonInfo,
+ KODI_HANDLE parentInstance)
+ : CDVDVideoCodec(processInfo),
+ IAddonInstanceHandler(
+ ADDON_INSTANCE_VIDEOCODEC, addonInfo, ADDON::ADDON_INSTANCE_ID_UNUSED, parentInstance),
+ m_codecFlags(0),
+ m_displayAspect(0.0f)
+{
+ m_ifc.videocodec = new AddonInstance_VideoCodec;
+ m_ifc.videocodec->props = new AddonProps_VideoCodec();
+ m_ifc.videocodec->toAddon = new KodiToAddonFuncTable_VideoCodec();
+ m_ifc.videocodec->toKodi = new AddonToKodiFuncTable_VideoCodec();
+
+ m_ifc.videocodec->toKodi->kodiInstance = this;
+ m_ifc.videocodec->toKodi->get_frame_buffer = get_frame_buffer;
+ m_ifc.videocodec->toKodi->release_frame_buffer = release_frame_buffer;
+ if (CreateInstance() != ADDON_STATUS_OK || !m_ifc.videocodec->toAddon->open)
+ {
+ CLog::Log(LOGERROR, "CInputStreamAddon: Failed to create add-on instance for '{}'",
+ addonInfo->ID());
+ return;
+ }
+}
+
+CAddonVideoCodec::~CAddonVideoCodec()
+{
+ //free remaining buffers
+ Reset();
+
+ DestroyInstance();
+
+ // Delete "C" interface structures
+ delete m_ifc.videocodec->toAddon;
+ delete m_ifc.videocodec->toKodi;
+ delete m_ifc.videocodec->props;
+ delete m_ifc.videocodec;
+}
+
+bool CAddonVideoCodec::CopyToInitData(VIDEOCODEC_INITDATA &initData, CDVDStreamInfo &hints)
+{
+ initData.codecProfile = STREAMCODEC_PROFILE::CodecProfileNotNeeded;
+ switch (hints.codec)
+ {
+ case AV_CODEC_ID_H264:
+ initData.codec = VIDEOCODEC_H264;
+ switch (hints.profile)
+ {
+ case 0:
+ case FF_PROFILE_UNKNOWN:
+ initData.codecProfile = STREAMCODEC_PROFILE::CodecProfileUnknown;
+ break;
+ case FF_PROFILE_H264_BASELINE:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileBaseline;
+ break;
+ case FF_PROFILE_H264_MAIN:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileMain;
+ break;
+ case FF_PROFILE_H264_EXTENDED:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileExtended;
+ break;
+ case FF_PROFILE_H264_HIGH:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileHigh;
+ break;
+ case FF_PROFILE_H264_HIGH_10:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileHigh10;
+ break;
+ case FF_PROFILE_H264_HIGH_422:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileHigh422;
+ break;
+ case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+ initData.codecProfile = STREAMCODEC_PROFILE::H264CodecProfileHigh444Predictive;
+ break;
+ default:
+ return false;
+ }
+ break;
+ case AV_CODEC_ID_VP8:
+ initData.codec = VIDEOCODEC_VP8;
+ break;
+ case AV_CODEC_ID_VP9:
+ initData.codec = VIDEOCODEC_VP9;
+ switch (hints.profile)
+ {
+ case FF_PROFILE_UNKNOWN:
+ initData.codecProfile = STREAMCODEC_PROFILE::CodecProfileUnknown;
+ break;
+ case FF_PROFILE_VP9_0:
+ initData.codecProfile = STREAMCODEC_PROFILE::VP9CodecProfile0;
+ break;
+ case FF_PROFILE_VP9_1:
+ initData.codecProfile = STREAMCODEC_PROFILE::VP9CodecProfile1;
+ break;
+ case FF_PROFILE_VP9_2:
+ initData.codecProfile = STREAMCODEC_PROFILE::VP9CodecProfile2;
+ break;
+ case FF_PROFILE_VP9_3:
+ initData.codecProfile = STREAMCODEC_PROFILE::VP9CodecProfile3;
+ break;
+ default:
+ return false;
+ }
+ break;
+ case AV_CODEC_ID_AV1:
+ initData.codec = VIDEOCODEC_AV1;
+ switch (hints.profile)
+ {
+ case FF_PROFILE_UNKNOWN:
+ initData.codecProfile = STREAMCODEC_PROFILE::CodecProfileUnknown;
+ break;
+ case FF_PROFILE_AV1_MAIN:
+ initData.codecProfile = STREAMCODEC_PROFILE::AV1CodecProfileMain;
+ break;
+ case FF_PROFILE_AV1_HIGH:
+ initData.codecProfile = STREAMCODEC_PROFILE::AV1CodecProfileHigh;
+ break;
+ case FF_PROFILE_AV1_PROFESSIONAL:
+ initData.codecProfile = STREAMCODEC_PROFILE::AV1CodecProfileProfessional;
+ break;
+ default:
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ if (hints.cryptoSession)
+ {
+ switch (hints.cryptoSession->keySystem)
+ {
+ case CRYPTO_SESSION_SYSTEM_NONE:
+ initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_NONE;
+ break;
+ case CRYPTO_SESSION_SYSTEM_WIDEVINE:
+ initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_WIDEVINE;
+ break;
+ case CRYPTO_SESSION_SYSTEM_PLAYREADY:
+ initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_PLAYREADY;
+ break;
+ case CRYPTO_SESSION_SYSTEM_WISEPLAY:
+ initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_WISEPLAY;
+ break;
+ default:
+ return false;
+ }
+
+ strncpy(initData.cryptoSession.sessionId, hints.cryptoSession->sessionId.c_str(),
+ sizeof(initData.cryptoSession.sessionId) - 1);
+ }
+
+ initData.extraData = reinterpret_cast<const uint8_t*>(hints.extradata);
+ initData.extraDataSize = hints.extrasize;
+ initData.width = hints.width;
+ initData.height = hints.height;
+ initData.videoFormats = m_formats;
+
+ m_displayAspect = (hints.aspect > 0.0 && !hints.forced_aspect) ? static_cast<float>(hints.aspect) : 0.0f;
+ m_width = hints.width;
+ m_height = hints.height;
+
+ m_processInfo.SetVideoDimensions(hints.width, hints.height);
+ m_processInfo.SetVideoDAR(m_displayAspect);
+ if (hints.fpsscale)
+ m_processInfo.SetVideoFps(static_cast<float>(hints.fpsrate) / hints.fpsscale);
+
+ return true;
+}
+
+bool CAddonVideoCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ if (!m_ifc.videocodec->toAddon->open)
+ return false;
+
+ unsigned int nformats(0);
+ m_formats[nformats++] = VIDEOCODEC_FORMAT_YV12;
+ m_formats[nformats] = VIDEOCODEC_FORMAT_UNKNOWN;
+
+ VIDEOCODEC_INITDATA initData;
+ if (!CopyToInitData(initData, hints))
+ return false;
+
+ bool ret = m_ifc.videocodec->toAddon->open(m_ifc.videocodec, &initData);
+ m_processInfo.SetVideoDecoderName(GetName(), false);
+
+ return ret;
+}
+
+bool CAddonVideoCodec::Reconfigure(CDVDStreamInfo &hints)
+{
+ if (!m_ifc.videocodec->toAddon->reconfigure)
+ return false;
+
+ VIDEOCODEC_INITDATA initData;
+ if (!CopyToInitData(initData, hints))
+ return false;
+
+ return m_ifc.videocodec->toAddon->reconfigure(m_ifc.videocodec, &initData);
+}
+
+bool CAddonVideoCodec::AddData(const DemuxPacket &packet)
+{
+ if (!m_ifc.videocodec->toAddon->add_data)
+ return false;
+
+ return m_ifc.videocodec->toAddon->add_data(m_ifc.videocodec, &packet);
+}
+
+CDVDVideoCodec::VCReturn CAddonVideoCodec::GetPicture(VideoPicture* pVideoPicture)
+{
+ if (!m_ifc.videocodec->toAddon->get_picture)
+ return CDVDVideoCodec::VC_ERROR;
+
+ VIDEOCODEC_PICTURE picture;
+ picture.flags = (m_codecFlags & DVD_CODEC_CTRL_DRAIN) ? VIDEOCODEC_PICTURE_FLAG_DRAIN
+ : VIDEOCODEC_PICTURE_FLAG_DROP;
+
+ switch (m_ifc.videocodec->toAddon->get_picture(m_ifc.videocodec, &picture))
+ {
+ case VIDEOCODEC_RETVAL::VC_NONE:
+ return CDVDVideoCodec::VC_NONE;
+ case VIDEOCODEC_RETVAL::VC_ERROR:
+ return CDVDVideoCodec::VC_ERROR;
+ case VIDEOCODEC_RETVAL::VC_BUFFER:
+ return CDVDVideoCodec::VC_BUFFER;
+ case VIDEOCODEC_RETVAL::VC_PICTURE:
+ pVideoPicture->iWidth = picture.width;
+ pVideoPicture->iHeight = picture.height;
+ pVideoPicture->pts = static_cast<double>(picture.pts);
+ pVideoPicture->dts = DVD_NOPTS_VALUE;
+ pVideoPicture->iFlags = 0;
+ pVideoPicture->chroma_position = 0;
+ pVideoPicture->colorBits = 8;
+ pVideoPicture->color_primaries = AVColorPrimaries::AVCOL_PRI_UNSPECIFIED;
+ pVideoPicture->color_range = 0;
+ pVideoPicture->color_space = AVCOL_SPC_UNSPECIFIED;
+ pVideoPicture->color_transfer = 0;
+ pVideoPicture->hasDisplayMetadata = false;
+ pVideoPicture->hasLightMetadata = false;
+ pVideoPicture->iDuration = 0;
+ pVideoPicture->iFrameType = 0;
+ pVideoPicture->iRepeatPicture = 0;
+ pVideoPicture->pict_type = 0;
+ pVideoPicture->qp_table = nullptr;
+ pVideoPicture->qscale_type = 0;
+ pVideoPicture->qstride = 0;
+ pVideoPicture->stereoMode.clear();
+
+ if (m_codecFlags & DVD_CODEC_CTRL_DROP)
+ pVideoPicture->iFlags |= DVP_FLAG_DROPPED;
+
+ if (pVideoPicture->videoBuffer)
+ pVideoPicture->videoBuffer->Release();
+
+ pVideoPicture->videoBuffer = static_cast<CVideoBuffer*>(picture.videoBufferHandle);
+
+ int strides[YuvImage::MAX_PLANES], planeOffsets[YuvImage::MAX_PLANES];
+ for (int i = 0; i<YuvImage::MAX_PLANES; ++i)
+ strides[i] = picture.stride[i];
+ for (int i = 0; i<YuvImage::MAX_PLANES; ++i)
+ planeOffsets[i] = picture.planeOffsets[i];
+
+ pVideoPicture->videoBuffer->SetDimensions(picture.width, picture.height, strides, planeOffsets);
+
+ pVideoPicture->iDisplayWidth = pVideoPicture->iWidth;
+ pVideoPicture->iDisplayHeight = pVideoPicture->iHeight;
+ if (m_displayAspect > 0.0f)
+ {
+ pVideoPicture->iDisplayWidth = ((int)lrint(pVideoPicture->iHeight * m_displayAspect)) & ~3;
+ if (pVideoPicture->iDisplayWidth > pVideoPicture->iWidth)
+ {
+ pVideoPicture->iDisplayWidth = pVideoPicture->iWidth;
+ pVideoPicture->iDisplayHeight = ((int)lrint(pVideoPicture->iWidth / m_displayAspect)) & ~3;
+ }
+ }
+
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CAddonVideoCodec: GetPicture::VC_PICTURE with pts {} {}x{} ({}x{}) {} {}:{} "
+ "offset:{},{},{}, stride:{},{},{}",
+ picture.pts, pVideoPicture->iWidth, pVideoPicture->iHeight,
+ pVideoPicture->iDisplayWidth, pVideoPicture->iDisplayHeight, m_displayAspect,
+ fmt::ptr(picture.decodedData), picture.decodedDataSize, picture.planeOffsets[0],
+ picture.planeOffsets[1], picture.planeOffsets[2], picture.stride[0],
+ picture.stride[1], picture.stride[2]);
+
+ if (picture.width != m_width || picture.height != m_height)
+ {
+ m_width = picture.width;
+ m_height = picture.height;
+ m_processInfo.SetVideoDimensions(m_width, m_height);
+ }
+
+ return CDVDVideoCodec::VC_PICTURE;
+ case VIDEOCODEC_RETVAL::VC_EOF:
+ CLog::Log(LOGINFO, "CAddonVideoCodec: GetPicture: EOF");
+ return CDVDVideoCodec::VC_EOF;
+ default:
+ return CDVDVideoCodec::VC_ERROR;
+ }
+}
+
+const char* CAddonVideoCodec::GetName()
+{
+ if (m_ifc.videocodec->toAddon->get_name)
+ return m_ifc.videocodec->toAddon->get_name(m_ifc.videocodec);
+ return "";
+}
+
+void CAddonVideoCodec::Reset()
+{
+ CVideoBuffer *videoBuffer;
+
+ CLog::Log(LOGDEBUG, "CAddonVideoCodec: Reset");
+
+ // Get the remaining pictures out of the external decoder
+ VIDEOCODEC_PICTURE picture;
+ picture.flags = VIDEOCODEC_PICTURE_FLAG_DRAIN;
+
+ VIDEOCODEC_RETVAL ret;
+ while ((ret = m_ifc.videocodec->toAddon->get_picture(m_ifc.videocodec, &picture)) !=
+ VIDEOCODEC_RETVAL::VC_EOF)
+ {
+ if (ret == VIDEOCODEC_RETVAL::VC_PICTURE)
+ {
+ videoBuffer = static_cast<CVideoBuffer*>(picture.videoBufferHandle);
+ if (videoBuffer)
+ videoBuffer->Release();
+ }
+ }
+ if (m_ifc.videocodec->toAddon->reset)
+ m_ifc.videocodec->toAddon->reset(m_ifc.videocodec);
+}
+
+bool CAddonVideoCodec::GetFrameBuffer(VIDEOCODEC_PICTURE &picture)
+{
+ CVideoBuffer *videoBuffer = m_processInfo.GetVideoBufferManager().Get(AV_PIX_FMT_YUV420P, picture.decodedDataSize, nullptr);
+ if (!videoBuffer)
+ {
+ CLog::Log(LOGERROR,"CAddonVideoCodec::GetFrameBuffer Failed to allocate buffer");
+ return false;
+ }
+ picture.decodedData = videoBuffer->GetMemPtr();
+ picture.videoBufferHandle = videoBuffer;
+
+ return true;
+}
+
+void CAddonVideoCodec::ReleaseFrameBuffer(KODI_HANDLE videoBufferHandle)
+{
+ if (videoBufferHandle)
+ static_cast<CVideoBuffer*>(videoBufferHandle)->Release();
+}
+
+/********************* ADDON-TO-KODI **********************/
+
+bool CAddonVideoCodec::get_frame_buffer(void* kodiInstance, VIDEOCODEC_PICTURE *picture)
+{
+ if (!kodiInstance)
+ return false;
+
+ return static_cast<CAddonVideoCodec*>(kodiInstance)->GetFrameBuffer(*picture);
+}
+
+void CAddonVideoCodec::release_frame_buffer(void* kodiInstance, KODI_HANDLE videoBufferHandle)
+{
+ if (!kodiInstance)
+ return;
+
+ static_cast<CAddonVideoCodec*>(kodiInstance)->ReleaseFrameBuffer(videoBufferHandle);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.h
new file mode 100644
index 0000000..8e3015c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDVideoCodec.h"
+#include "addons/AddonProvider.h"
+#include "addons/binary-addons/AddonInstanceHandler.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/VideoCodec.h"
+
+class BufferPool;
+
+class CAddonVideoCodec
+ : public CDVDVideoCodec
+ , public ADDON::IAddonInstanceHandler
+{
+public:
+ CAddonVideoCodec(CProcessInfo& processInfo,
+ ADDON::AddonInfoPtr& addonInfo,
+ KODI_HANDLE parentInstance);
+ ~CAddonVideoCodec() override;
+
+ bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override;
+ bool Reconfigure(CDVDStreamInfo &hints) override;
+ bool AddData(const DemuxPacket &packet) override;
+ void Reset() override;
+ VCReturn GetPicture(VideoPicture* pVideoPicture) override;
+ const char* GetName() override;
+ void SetCodecControl(int flags) override { m_codecFlags = flags; }
+
+private:
+ bool CopyToInitData(VIDEOCODEC_INITDATA &initData, CDVDStreamInfo &hints);
+
+ /*!
+ * @brief All picture members can be expected to be set correctly except decodedData and pts.
+ * GetFrameBuffer has to set decodedData to a valid memory address and return true.
+ * In case buffer allocation fails, return false.
+ */
+ bool GetFrameBuffer(VIDEOCODEC_PICTURE &picture);
+ void ReleaseFrameBuffer(KODI_HANDLE videoBufferHandle);
+
+ static bool get_frame_buffer(void* kodiInstance, VIDEOCODEC_PICTURE *picture);
+ static void release_frame_buffer(void* kodiInstance, KODI_HANDLE videoBufferHandle);
+
+ int m_codecFlags;
+ VIDEOCODEC_FORMAT m_formats[VIDEOCODEC_FORMAT_MAXFORMATS + 1];
+ float m_displayAspect;
+ unsigned int m_width, m_height;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt
new file mode 100644
index 0000000..517acc3
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt
@@ -0,0 +1,44 @@
+set(SOURCES AddonVideoCodec.cpp
+ DVDVideoCodec.cpp
+ DVDVideoCodecFFmpeg.cpp)
+
+set(HEADERS AddonVideoCodec.h
+ DVDVideoCodec.h
+ DVDVideoCodecFFmpeg.h)
+
+if(NOT ENABLE_EXTERNAL_LIBAV)
+ list(APPEND SOURCES DVDVideoPPFFmpeg.cpp)
+ list(APPEND HEADERS DVDVideoPPFFmpeg.h)
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES DXVA.cpp)
+ list(APPEND HEADERS DXVA.h)
+endif()
+
+if(VDPAU_FOUND)
+ list(APPEND SOURCES VDPAU.cpp)
+ list(APPEND HEADERS VDPAU.h)
+endif()
+
+if(VAAPI_FOUND)
+ list(APPEND SOURCES VAAPI.cpp)
+ list(APPEND HEADERS VAAPI.h)
+endif()
+
+if(APPLE)
+ list(APPEND SOURCES VTB.cpp)
+ list(APPEND HEADERS VTB.h)
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL android)
+ list(APPEND SOURCES DVDVideoCodecAndroidMediaCodec.cpp)
+ list(APPEND HEADERS DVDVideoCodecAndroidMediaCodec.h)
+endif()
+
+if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES DVDVideoCodecDRMPRIME.cpp)
+ list(APPEND HEADERS DVDVideoCodecDRMPRIME.h)
+endif()
+
+core_add_library(dvdvideocodecs)
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp
new file mode 100644
index 0000000..5e02594
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDVideoCodec.h"
+
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+
+#include <string>
+#include <vector>
+
+//******************************************************************************
+// VideoPicture
+//******************************************************************************
+
+VideoPicture::VideoPicture() = default;
+
+VideoPicture::~VideoPicture()
+{
+ if (videoBuffer)
+ {
+ videoBuffer->Release();
+ }
+}
+
+void VideoPicture::Reset()
+{
+ if (videoBuffer)
+ videoBuffer->Release();
+ videoBuffer = nullptr;
+ pts = DVD_NOPTS_VALUE;
+ dts = DVD_NOPTS_VALUE;
+ iFlags = 0;
+ iRepeatPicture = 0;
+ iDuration = 0;
+ iFrameType = 0;
+ color_space = AVCOL_SPC_UNSPECIFIED;
+ color_range = 0;
+ chroma_position = 0;
+ color_primaries = AVColorPrimaries::AVCOL_PRI_UNSPECIFIED;
+ color_transfer = 0;
+ colorBits = 8;
+ stereoMode.clear();
+
+ qp_table = nullptr;
+ qstride = 0;
+ qscale_type = 0;
+ pict_type = 0;
+
+ hasDisplayMetadata = false;
+ hasLightMetadata = false;
+
+ iWidth = 0;
+ iHeight = 0;
+ iDisplayWidth = 0;
+ iDisplayHeight = 0;
+}
+
+VideoPicture& VideoPicture::CopyRef(const VideoPicture &pic)
+{
+ if (videoBuffer)
+ videoBuffer->Release();
+ *this = pic;
+ if (videoBuffer)
+ videoBuffer->Acquire();
+ return *this;
+}
+
+VideoPicture& VideoPicture::SetParams(const VideoPicture &pic)
+{
+ if (videoBuffer)
+ videoBuffer->Release();
+ *this = pic;
+ videoBuffer = nullptr;
+ return *this;
+}
+
+VideoPicture::VideoPicture(VideoPicture const&) = default;
+VideoPicture& VideoPicture::operator=(VideoPicture const&) = default;
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
new file mode 100644
index 0000000..b23fd48
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDResource.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavutil/mastering_display_metadata.h>
+}
+
+#include <vector>
+#include <string>
+#include <map>
+
+class CSetting;
+
+// when modifying these structures, make sure you update all codecs accordingly
+#define FRAME_TYPE_UNDEF 0
+#define FRAME_TYPE_I 1
+#define FRAME_TYPE_P 2
+#define FRAME_TYPE_B 3
+#define FRAME_TYPE_D 4
+
+
+// should be entirely filled by all codecs
+struct VideoPicture
+{
+public:
+ VideoPicture();
+ ~VideoPicture();
+ VideoPicture& CopyRef(const VideoPicture &pic);
+ VideoPicture& SetParams(const VideoPicture &pic);
+ void Reset(); // reinitialize members, videoBuffer will be released if set!
+
+ CVideoBuffer *videoBuffer = nullptr;
+
+ double pts; // timestamp in seconds, used in the CVideoPlayer class to keep track of pts
+ double dts;
+ unsigned int iFlags;
+ double iRepeatPicture;
+ double iDuration;
+ unsigned int iFrameType : 4; //< see defines above // 1->I, 2->P, 3->B, 0->Undef
+ unsigned int color_space;
+ unsigned int color_range : 1; //< 1 indicate if we have a full range of color
+ unsigned int chroma_position;
+ unsigned int color_primaries;
+ unsigned int color_transfer;
+ unsigned int colorBits = 8;
+ std::string stereoMode;
+
+ int8_t* qp_table; //< Quantization parameters, primarily used by filters
+ int qstride;
+ int qscale_type;
+ int pict_type;
+
+ bool hasDisplayMetadata = false;
+ AVMasteringDisplayMetadata displayMetadata;
+ bool hasLightMetadata = false;
+ AVContentLightMetadata lightMetadata;
+
+ AVPixelFormat pixelFormat; //< source pixel format
+
+ unsigned int iWidth;
+ unsigned int iHeight;
+ unsigned int iDisplayWidth; //< width of the picture without black bars
+ unsigned int iDisplayHeight; //< height of the picture without black bars
+
+private:
+ VideoPicture(VideoPicture const&);
+ VideoPicture& operator=(VideoPicture const&);
+};
+
+#define DVP_FLAG_TOP_FIELD_FIRST 0x00000001
+#define DVP_FLAG_REPEAT_TOP_FIELD 0x00000002 //< Set to indicate that the top field should be repeated
+#define DVP_FLAG_INTERLACED 0x00000008 //< Set to indicate that this frame is interlaced
+#define DVP_FLAG_DROPPED 0x00000010 //< indicate that this picture has been dropped in decoder stage, will have no data
+
+#define DVD_CODEC_CTRL_SKIPDEINT 0x01000000 //< request to skip a deinterlacing cycle, if possible
+#define DVD_CODEC_CTRL_NO_POSTPROC 0x02000000 //< see GetCodecStats
+#define DVD_CODEC_CTRL_HURRY 0x04000000 //< see GetCodecStats
+#define DVD_CODEC_CTRL_DROP 0x08000000 //< drop in decoder or set DVP_FLAG_DROPPED, no render of frame
+#define DVD_CODEC_CTRL_DROP_ANY 0x10000000 //< drop some non-reference frame
+#define DVD_CODEC_CTRL_DRAIN 0x20000000 //< squeeze out pictured without feeding new packets
+#define DVD_CODEC_CTRL_ROTATE 0x40000000 //< rotate if renderer does not support it
+
+// DVP_FLAG 0x00000100 - 0x00000f00 is in use by libmpeg2!
+
+#define DVP_QSCALE_UNKNOWN 0
+#define DVP_QSCALE_MPEG1 1
+#define DVP_QSCALE_MPEG2 2
+#define DVP_QSCALE_H264 3
+
+class CDVDStreamInfo;
+class CDVDCodecOption;
+class CDVDCodecOptions;
+
+class CDVDVideoCodec
+{
+public:
+
+ enum VCReturn
+ {
+ VC_NONE = 0,
+ VC_ERROR, //< an error occurred, no other messages will be returned
+ VC_FATAL, //< non recoverable error
+ VC_BUFFER, //< the decoder needs more data
+ VC_PICTURE, //< the decoder got a picture, call Decode(NULL, 0) again to parse the rest of the data
+ VC_FLUSHED, //< the decoder lost it's state, we need to restart decoding again
+ VC_NOBUFFER, //< last FFmpeg GetBuffer failed
+ VC_REOPEN, //< decoder request to re-open
+ VC_EOF //< EOF
+ };
+
+ explicit CDVDVideoCodec(CProcessInfo &processInfo) : m_processInfo(processInfo) {}
+ virtual ~CDVDVideoCodec() = default;
+
+ /**
+ * Open the decoder, returns true on success
+ * Decoders not capable of running multiple instances should return false in case
+ * there is already a instance open
+ */
+ virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) = 0;
+
+ /**
+ * Reconfigure the decoder, returns true on success
+ * Decoders not capable of running multiple instances may be capable of reconfiguring
+ * the running instance. If Reconfigure returns false, player will close / open
+ * the decoder
+ */
+ virtual bool Reconfigure(CDVDStreamInfo &hints)
+ {
+ return false;
+ }
+
+ /**
+ * add data, decoder has to consume the entire packet
+ * returns true if the packet was consumed or if resubmitting it is useless
+ */
+ virtual bool AddData(const DemuxPacket &packet) = 0;
+
+ /**
+ * Reset the decoder.
+ * Should be the same as calling Dispose and Open after each other
+ */
+ virtual void Reset() = 0;
+
+ /**
+ * GetPicture controls decoding. Player calls it on every cycle
+ * it can signal a picture, request a buffer, or return none, if nothing applies
+ * the data is valid until the next GetPicture return VC_PICTURE
+ */
+ virtual VCReturn GetPicture(VideoPicture* pVideoPicture) = 0;
+
+ /**
+ * will be called by video player indicating the playback speed. see DVD_PLAYSPEED_NORMAL,
+ * DVD_PLAYSPEED_PAUSE and friends.
+ */
+ virtual void SetSpeed(int iSpeed) {}
+
+ /**
+ * should return codecs name
+ */
+ virtual const char* GetName() = 0;
+
+ /**
+ * How many packets should player remember, so codec can recover should
+ * something cause it to flush outside of players control
+ */
+ virtual unsigned GetConvergeCount()
+ {
+ return 0;
+ }
+
+ /**
+ * Number of references to old pictures that are allowed to be retained when
+ * calling decode on the next demux packet
+ */
+ virtual unsigned GetAllowedReferences() { return 0; }
+
+ /**
+ * For calculation of dropping requirements player asks for some information.
+ * - pts : right after decoder, used to detect gaps (dropped frames in decoder)
+ * - droppedFrames : indicates if decoder has dropped a frame
+ * -1 means that decoder has no info on this.
+ * - skippedPics : indicates if postproc has skipped a already decoded picture
+ * -1 means that decoder has no info on this.
+ *
+ * If codec does not implement this method, pts of decoded frame at input
+ * video player is used. In case decoder does post-proc and de-interlacing there
+ * may be quite some frames queued up between exit decoder and entry player.
+ */
+ virtual bool GetCodecStats(double &pts, int &droppedFrames, int &skippedPics)
+ {
+ droppedFrames = -1;
+ skippedPics = -1;
+ return false;
+ }
+
+ /**
+ * Codec can be informed by player with the following flags:
+ *
+ * DVD_CODEC_CTRL_NO_POSTPROC :
+ * if speed is not normal the codec can switch off
+ * postprocessing and de-interlacing
+ *
+ * DVD_CODEC_CTRL_HURRY :
+ * codecs may do postprocessing and de-interlacing.
+ * If video buffers in RenderManager are about to run dry,
+ * this is signaled to codec. Codec can wait for post-proc
+ * to be finished instead of returning empty and getting another
+ * packet.
+ *
+ * DVD_CODEC_CTRL_DRAIN :
+ * instruct decoder to deliver last pictures without requesting
+ * new packets
+ *
+ * DVD_CODEC_CTRL_DROP :
+ * this packet is going to be dropped. decoder is free to use it
+ * for decoding
+ *
+ */
+ virtual void SetCodecControl(int flags) {}
+
+ /**
+ * Re-open the decoder.
+ * Decoder request to re-open
+ */
+ virtual void Reopen() {}
+
+protected:
+ CProcessInfo &m_processInfo;
+};
+
+// callback interface for ffmpeg hw accelerators
+class IHardwareDecoder : public IDVDResourceCounted<IHardwareDecoder>
+{
+public:
+ IHardwareDecoder() = default;
+ ~IHardwareDecoder() override = default;
+ virtual bool Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat) = 0;
+ virtual CDVDVideoCodec::VCReturn Decode(AVCodecContext* avctx, AVFrame* frame) = 0;
+ virtual bool GetPicture(AVCodecContext* avctx, VideoPicture* picture) = 0;
+ virtual CDVDVideoCodec::VCReturn Check(AVCodecContext* avctx) = 0;
+ virtual void Reset() {}
+ virtual unsigned GetAllowedReferences() { return 0; }
+ virtual bool CanSkipDeint() {return false; }
+ virtual const std::string Name() = 0;
+ virtual void SetCodecControl(int flags) {}
+};
+
+class ICallbackHWAccel
+{
+public:
+ virtual ~ICallbackHWAccel() = default;
+ virtual IHardwareDecoder* GetHWAccel() = 0;
+ virtual bool GetPictureCommon(VideoPicture* pVideoPicture) = 0;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp
new file mode 100644
index 0000000..48050cf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// http://developer.android.com/reference/android/media/MediaCodec.html
+//
+// Android MediaCodec class can be used to access low-level media codec,
+// i.e. encoder/decoder components. (android.media.MediaCodec). Requires SDK21+
+//
+
+#include "DVDVideoCodecAndroidMediaCodec.h"
+
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "media/decoderfilter/DecoderFilterManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/BitstreamConverter.h"
+#include "utils/BitstreamWriter.h"
+#include "utils/CPUInfo.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/android/AndroidUtils.h"
+
+#include "platform/android/activity/JNIXBMCSurfaceTextureOnFrameAvailableListener.h"
+#include "platform/android/activity/XBMCApp.h"
+
+#include <cassert>
+#include <memory>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <androidjni/ByteBuffer.h>
+#include <androidjni/MediaCodec.h>
+#include <androidjni/MediaCodecBufferInfo.h>
+#include <androidjni/MediaCodecCryptoInfo.h>
+#include <androidjni/MediaCodecInfo.h>
+#include <androidjni/MediaCodecList.h>
+#include <androidjni/MediaCrypto.h>
+#include <androidjni/Surface.h>
+#include <androidjni/SurfaceTexture.h>
+#include <androidjni/UUID.h>
+
+using namespace KODI::MESSAGING;
+
+enum MEDIACODEC_STATES
+{
+ MEDIACODEC_STATE_UNINITIALIZED,
+ MEDIACODEC_STATE_CONFIGURED,
+ MEDIACODEC_STATE_FLUSHED,
+ MEDIACODEC_STATE_RUNNING,
+ MEDIACODEC_STATE_WAIT_ENDOFSTREAM,
+ MEDIACODEC_STATE_ENDOFSTREAM,
+ MEDIACODEC_STATE_ERROR,
+ MEDIACODEC_STATE_STOPPED
+};
+
+static bool IsSupportedColorFormat(int color_format)
+{
+ static const int supported_colorformats[] = {
+ CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420Planar,
+ CJNIMediaCodecInfoCodecCapabilities::COLOR_TI_FormatYUV420PackedSemiPlanar,
+ CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420SemiPlanar,
+ CJNIMediaCodecInfoCodecCapabilities::COLOR_QCOM_FormatYUV420SemiPlanar,
+ CJNIMediaCodecInfoCodecCapabilities::OMX_QCOM_COLOR_FormatYVU420SemiPlanarInterlace,
+ -1
+ };
+ for (const int *ptr = supported_colorformats; *ptr != -1; ptr++)
+ {
+ if (color_format == *ptr)
+ return true;
+ }
+ return false;
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+class CDVDMediaCodecOnFrameAvailable : public CEvent, public CJNIXBMCSurfaceTextureOnFrameAvailableListener
+{
+public:
+ CDVDMediaCodecOnFrameAvailable(std::shared_ptr<CJNISurfaceTexture> &surfaceTexture)
+ : CJNIXBMCSurfaceTextureOnFrameAvailableListener()
+ , m_surfaceTexture(surfaceTexture)
+ {
+ m_surfaceTexture->setOnFrameAvailableListener(*this);
+ }
+
+ ~CDVDMediaCodecOnFrameAvailable() override
+ {
+ // unhook the callback
+ CJNIXBMCSurfaceTextureOnFrameAvailableListener nullListener(jni::jhobject(NULL));
+ m_surfaceTexture->setOnFrameAvailableListener(nullListener);
+ }
+
+protected:
+ void onFrameAvailable(CJNISurfaceTexture) override { Set(); }
+
+private:
+ std::shared_ptr<CJNISurfaceTexture> m_surfaceTexture;
+};
+
+/*****************************************************************************/
+/*****************************************************************************/
+void CMediaCodecVideoBuffer::Set(int bufferId, int textureId,
+ std::shared_ptr<CJNISurfaceTexture> surfacetexture,
+ std::shared_ptr<CDVDMediaCodecOnFrameAvailable> frameready,
+ std::shared_ptr<CJNIXBMCVideoView> videoview)
+{
+ m_bufferId = bufferId;
+ m_textureId = textureId;
+ m_surfacetexture = std::move(surfacetexture);
+ m_frameready = std::move(frameready);
+ m_videoview = std::move(videoview);
+}
+
+bool CMediaCodecVideoBuffer::WaitForFrame(int millis)
+{
+ return m_frameready->Wait(std::chrono::milliseconds(millis));
+}
+
+void CMediaCodecVideoBuffer::ReleaseOutputBuffer(bool render, int64_t displayTime, CMediaCodecVideoBufferPool* pool)
+{
+ std::shared_ptr<CJNIMediaCodec> codec(
+ static_cast<CMediaCodecVideoBufferPool*>(pool ? pool : m_pool.get())->GetMediaCodec());
+
+ if (m_bufferId < 0 || !codec)
+ return;
+
+ // release OutputBuffer and render if indicated
+ // then wait for rendered frame to become available.
+
+ if (render)
+ if (m_frameready)
+ m_frameready->Reset();
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+ {
+ int64_t diff = displayTime ? displayTime - CurrentHostCounter() : 0;
+ CLog::Log(
+ LOGDEBUG,
+ "CMediaCodecVideoBuffer::ReleaseOutputBuffer index({}), render({}), time:{}, offset:{}",
+ m_bufferId, render, displayTime, diff);
+ }
+
+ if (!render || displayTime == 0)
+ codec->releaseOutputBuffer(m_bufferId, render);
+ else
+ codec->releaseOutputBufferAtTime(m_bufferId, displayTime);
+ m_bufferId = -1; //mark released
+
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CMediaCodecVideoBuffer::ReleaseOutputBuffer error in render({})", render);
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+}
+
+int CMediaCodecVideoBuffer::GetBufferId() const
+{
+ // since m_texture never changes,
+ // we do not need a m_section lock here.
+ return m_bufferId;
+}
+
+int CMediaCodecVideoBuffer::GetTextureId() const
+{
+ // since m_texture never changes,
+ // we do not need a m_section lock here.
+ return m_textureId;
+}
+
+void CMediaCodecVideoBuffer::GetTransformMatrix(float *textureMatrix)
+{
+ m_surfacetexture->getTransformMatrix(textureMatrix);
+}
+
+void CMediaCodecVideoBuffer::UpdateTexImage()
+{
+ // updateTexImage will check and spew any prior gl errors,
+ // clear them before we call updateTexImage.
+ glGetError();
+
+ // this is key, after calling releaseOutputBuffer, we must
+ // wait a little for MediaCodec to render to the surface.
+ // Then we can updateTexImage without delay. If we do not
+ // wait, then video playback gets jerky. To optimize this,
+ // we hook the SurfaceTexture OnFrameAvailable callback
+ // using CJNISurfaceTextureOnFrameAvailableListener and wait
+ // on a CEvent to fire. 50ms seems to be a good max fallback.
+ WaitForFrame(50);
+
+ m_surfacetexture->updateTexImage();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CMediaCodecVideoBuffer::UpdateTexImage updateTexImage:ExceptionCheck");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ CLog::Log(LOGERROR, "CMediaCodecVideoBuffer::UpdateTexImage getTimestamp:ExceptionCheck");
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ }
+}
+
+void CMediaCodecVideoBuffer::RenderUpdate(const CRect &DestRect, int64_t displayTime)
+{
+ CRect surfRect = m_videoview->getSurfaceRect();
+ if (DestRect != surfRect)
+ {
+ CRect adjRect = CXBMCApp::Get().MapRenderToDroid(DestRect);
+ if (adjRect != surfRect)
+ {
+ m_videoview->setSurfaceRect(adjRect);
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CMediaCodecVideoBuffer::RenderUpdate: Dest - {:f}+{:f}-{:f}x{:f}", DestRect.x1,
+ DestRect.y1, DestRect.Width(), DestRect.Height());
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CMediaCodecVideoBuffer::RenderUpdate: Adj - {:f}+{:f}-{:f}x{:f}", adjRect.x1,
+ adjRect.y1, adjRect.Width(), adjRect.Height());
+
+ // setVideoViewSurfaceRect is async, so skip rendering this frame
+ ReleaseOutputBuffer(false, 0);
+ }
+ else
+ ReleaseOutputBuffer(true, displayTime);
+ }
+ else
+ ReleaseOutputBuffer(true, displayTime);
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+CMediaCodecVideoBufferPool::~CMediaCodecVideoBufferPool()
+{
+ CLog::Log(LOGDEBUG,
+ "CMediaCodecVideoBufferPool::~CMediaCodecVideoBufferPool Releasing {} buffers",
+ static_cast<unsigned int>(m_videoBuffers.size()));
+ for (auto buffer : m_videoBuffers)
+ delete buffer;
+}
+
+CVideoBuffer* CMediaCodecVideoBufferPool::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+
+ if (m_freeBuffers.empty())
+ {
+ m_freeBuffers.push_back(m_videoBuffers.size());
+ m_videoBuffers.push_back(new CMediaCodecVideoBuffer(static_cast<int>(m_videoBuffers.size())));
+ }
+ int bufferIdx(m_freeBuffers.back());
+ m_freeBuffers.pop_back();
+
+ m_videoBuffers[bufferIdx]->Acquire(shared_from_this());
+
+ return m_videoBuffers[bufferIdx];
+}
+
+void CMediaCodecVideoBufferPool::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ m_videoBuffers[id]->ReleaseOutputBuffer(false, 0, this);
+ m_freeBuffers.push_back(id);
+}
+
+std::shared_ptr<CJNIMediaCodec> CMediaCodecVideoBufferPool::GetMediaCodec()
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ return m_codec;
+}
+
+void CMediaCodecVideoBufferPool::ResetMediaCodec()
+{
+ ReleaseMediaCodecBuffers();
+
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ m_codec = nullptr;
+}
+
+void CMediaCodecVideoBufferPool::ReleaseMediaCodecBuffers()
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ for (auto buffer : m_videoBuffers)
+ buffer->ReleaseOutputBuffer(false, 0, this);
+}
+
+
+/*****************************************************************************/
+/*****************************************************************************/
+CDVDVideoCodecAndroidMediaCodec::CDVDVideoCodecAndroidMediaCodec(CProcessInfo &processInfo, bool surface_render)
+: CDVDVideoCodec(processInfo)
+, m_formatname("mediacodec")
+, m_opened(false)
+, m_jnivideoview(nullptr)
+, m_textureId(0)
+, m_OutputDuration(0)
+, m_fpsDuration(0)
+, m_lastPTS(-1)
+, m_dtsShift(DVD_NOPTS_VALUE)
+, m_bitstream(nullptr)
+, m_render_surface(surface_render)
+, m_mpeg2_sequence(nullptr)
+, m_useDTSforPTS(false)
+{
+ m_videobuffer.Reset();
+}
+
+CDVDVideoCodecAndroidMediaCodec::~CDVDVideoCodecAndroidMediaCodec()
+{
+ Dispose();
+
+ if (m_crypto)
+ {
+ delete m_crypto;
+ m_crypto = nullptr;
+ }
+ if (m_mpeg2_sequence)
+ {
+ delete (m_mpeg2_sequence);
+ m_mpeg2_sequence = nullptr;
+ }
+}
+
+std::unique_ptr<CDVDVideoCodec> CDVDVideoCodecAndroidMediaCodec::Create(CProcessInfo& processInfo)
+{
+ return std::make_unique<CDVDVideoCodecAndroidMediaCodec>(processInfo);
+}
+
+bool CDVDVideoCodecAndroidMediaCodec::Register()
+{
+ CDVDFactoryCodec::RegisterHWVideoCodec("mediacodec_dec", &CDVDVideoCodecAndroidMediaCodec::Create);
+ return true;
+}
+
+std::atomic<bool> CDVDVideoCodecAndroidMediaCodec::m_InstanceGuard(false);
+
+bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ std::vector<CJNIMediaCodecInfo> codecInfos;
+ int profile(0);
+ CJNIUUID uuid(0, 0);
+
+ m_opened = false;
+ m_needSecureDecoder = false;
+ // allow only 1 instance here
+ if (m_InstanceGuard.exchange(true))
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open - InstanceGuard locked");
+ return false;
+ }
+
+ // mediacodec crashes with null size. Trap this...
+ if (!hints.width || !hints.height)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open - {}", "null size, cannot handle");
+ goto FAIL;
+ }
+ else if (hints.orientation && m_render_surface && CJNIBase::GetSDKVersion() < 23)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open - {}",
+ "Surface does not support orientation before API 23");
+ goto FAIL;
+ }
+ else if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEMEDIACODEC) &&
+ !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE))
+ goto FAIL;
+
+ CLog::Log(
+ LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec::Open hints: Width {} x Height {}, Fpsrate {} / Fpsscale "
+ "{}, CodecID {}, Level {}, Profile {}, PTS_invalid {}, Tag {}, Extradata-Size: {}",
+ hints.width, hints.height, hints.fpsrate, hints.fpsscale, hints.codec, hints.level,
+ hints.profile, hints.ptsinvalid, hints.codec_tag, hints.extrasize);
+
+ m_render_surface = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE);
+ m_state = MEDIACODEC_STATE_UNINITIALIZED;
+ m_codecControlFlags = 0;
+ m_hints = hints;
+ m_indexInputBuffer = -1;
+ m_dtsShift = DVD_NOPTS_VALUE;
+ m_useDTSforPTS = false;
+
+ switch(m_hints.codec)
+ {
+ case AV_CODEC_ID_MPEG2VIDEO:
+ m_mime = "video/mpeg2";
+ m_mpeg2_sequence = new mpeg2_sequence;
+ m_mpeg2_sequence->width = m_hints.width;
+ m_mpeg2_sequence->height = m_hints.height;
+ m_mpeg2_sequence->ratio = m_hints.aspect;
+ m_mpeg2_sequence->fps_scale = m_hints.fpsscale;
+ m_mpeg2_sequence->fps_rate = m_hints.fpsrate;
+ m_useDTSforPTS = true;
+ m_formatname = "amc-mpeg2";
+ break;
+ case AV_CODEC_ID_MPEG4:
+ m_mime = "video/mp4v-es";
+ m_formatname = "amc-mpeg4";
+ m_useDTSforPTS = true;
+ break;
+ case AV_CODEC_ID_H263:
+ m_mime = "video/3gpp";
+ m_formatname = "amc-h263";
+ break;
+ case AV_CODEC_ID_VP6:
+ case AV_CODEC_ID_VP6F:
+ m_mime = "video/x-vnd.on2.vp6";
+ m_formatname = "amc-vp6";
+ break;
+ case AV_CODEC_ID_VP8:
+ m_mime = "video/x-vnd.on2.vp8";
+ m_formatname = "amc-vp8";
+ break;
+ case AV_CODEC_ID_VP9:
+ switch(hints.profile)
+ {
+ case FF_PROFILE_VP9_0:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::VP9Profile0;
+ break;
+ case FF_PROFILE_VP9_1:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::VP9Profile1;
+ break;
+ case FF_PROFILE_VP9_2:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::VP9Profile2;
+ break;
+ case FF_PROFILE_VP9_3:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::VP9Profile3;
+ break;
+ default:;
+ }
+ m_mime = "video/x-vnd.on2.vp9";
+ m_formatname = "amc-vp9";
+ free(m_hints.extradata);
+ m_hints.extradata = nullptr;
+ m_hints.extrasize = 0;
+ break;
+ case AV_CODEC_ID_AVS:
+ case AV_CODEC_ID_CAVS:
+ case AV_CODEC_ID_H264:
+ switch(hints.profile)
+ {
+ case FF_PROFILE_H264_HIGH_10:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::AVCProfileHigh10;
+ break;
+ case FF_PROFILE_H264_HIGH_422:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::AVCProfileHigh422;
+ break;
+ case FF_PROFILE_H264_HIGH_444:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::AVCProfileHigh444;
+ break;
+ // All currently not supported formats
+ case FF_PROFILE_H264_HIGH_10_INTRA:
+ case FF_PROFILE_H264_HIGH_422_INTRA:
+ case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+ case FF_PROFILE_H264_HIGH_444_INTRA:
+ case FF_PROFILE_H264_CAVLC_444:
+ goto FAIL;
+ default:
+ break;
+ }
+ m_mime = "video/avc";
+ m_formatname = "amc-h264";
+ // check for h264-avcC and convert to h264-annex-b
+ if (m_hints.extradata && !m_hints.cryptoSession)
+ {
+ m_bitstream = std::make_unique<CBitstreamConverter>();
+ if (!m_bitstream->Open(m_hints.codec, (uint8_t*)m_hints.extradata, m_hints.extrasize, true))
+ {
+ m_bitstream.reset();
+ }
+ }
+ break;
+ case AV_CODEC_ID_HEVC:
+ {
+ if (m_hints.profile == FF_PROFILE_HEVC_REXT)
+ {
+ // No known h/w decoder supporting Hi10P
+ goto FAIL;
+ }
+
+ m_mime = "video/hevc";
+ m_formatname = "amc-hevc";
+
+ bool isDvhe = (m_hints.codec_tag == MKTAG('d', 'v', 'h', 'e'));
+ bool isDvh1 = (m_hints.codec_tag == MKTAG('d', 'v', 'h', '1'));
+
+ if (isDvhe || isDvh1)
+ {
+ bool displaySupportsDovi = CAndroidUtils::GetDisplayHDRCapabilities().SupportsDolbyVision();
+ bool mediaCodecSupportsDovi =
+ CAndroidUtils::SupportsMediaCodecMimeType("video/dolby-vision");
+
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec::Open Dolby Vision playback support: "
+ "Display: {}, MediaCodec: {}",
+ displaySupportsDovi, mediaCodecSupportsDovi);
+
+ if (displaySupportsDovi && mediaCodecSupportsDovi)
+ {
+ m_mime = "video/dolby-vision";
+ m_formatname = isDvhe ? "amc-dvhe" : "amc-dvh1";
+ }
+ }
+
+ // check for hevc-hvcC and convert to h265-annex-b
+ if (m_hints.extradata && !m_hints.cryptoSession)
+ {
+ m_bitstream = std::make_unique<CBitstreamConverter>();
+ if (!m_bitstream->Open(m_hints.codec, (uint8_t*)m_hints.extradata, m_hints.extrasize, true))
+ {
+ m_bitstream.reset();
+ }
+ }
+ break;
+ }
+ case AV_CODEC_ID_WMV3:
+ if (m_hints.extrasize == 4 || m_hints.extrasize == 5)
+ {
+ // Convert to SMPTE 421M-2006 Annex-L
+ static uint8_t annexL_hdr1[] = {0x8e, 0x01, 0x00, 0xc5, 0x04, 0x00, 0x00, 0x00};
+ static uint8_t annexL_hdr2[] = {0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ free(m_hints.extradata);
+ m_hints.extrasize = 36;
+ m_hints.extradata = malloc(m_hints.extrasize);
+
+ unsigned int offset = 0;
+ char buf[4];
+ memcpy(m_hints.extradata, annexL_hdr1, sizeof(annexL_hdr1));
+ offset += sizeof(annexL_hdr1);
+ memcpy(&((char *)(m_hints.extradata))[offset], hints.extradata, 4);
+ offset += 4;
+ BS_WL32(buf, hints.height);
+ memcpy(&((char *)(m_hints.extradata))[offset], buf, 4);
+ offset += 4;
+ BS_WL32(buf, hints.width);
+ memcpy(&((char *)(m_hints.extradata))[offset], buf, 4);
+ offset += 4;
+ memcpy(&((char *)(m_hints.extradata))[offset], annexL_hdr2, sizeof(annexL_hdr2));
+ }
+
+ m_mime = "video/x-ms-wmv";
+ m_formatname = "amc-wmv";
+ break;
+ case AV_CODEC_ID_VC1:
+ {
+ if (m_hints.extrasize < 16)
+ goto FAIL;
+
+ // Reduce extradata to first SEQ header
+ unsigned int seq_offset = 0;
+ for (; seq_offset <= m_hints.extrasize-4; ++seq_offset)
+ {
+ char *ptr = &((char*)m_hints.extradata)[seq_offset];
+ if (ptr[0] == 0x00 && ptr[1] == 0x00 && ptr[2] == 0x01 && ptr[3] == 0x0f)
+ break;
+ }
+ if (seq_offset > m_hints.extrasize-4)
+ goto FAIL;
+
+ if (seq_offset)
+ {
+ free(m_hints.extradata);
+ m_hints.extrasize -= seq_offset;
+ m_hints.extradata = malloc(m_hints.extrasize);
+ memcpy(m_hints.extradata, &((char *)(hints.extradata))[seq_offset], m_hints.extrasize);
+ }
+
+ m_mime = "video/wvc1";
+ m_formatname = "amc-vc1";
+ break;
+ }
+ case AV_CODEC_ID_AV1:
+ {
+ switch (hints.profile)
+ {
+ case FF_PROFILE_AV1_MAIN:
+ profile = CJNIMediaCodecInfoCodecProfileLevel::AV1ProfileMain8;
+ break;
+ case FF_PROFILE_AV1_HIGH:
+ case FF_PROFILE_AV1_PROFESSIONAL:
+ goto FAIL;
+ break;
+ default:
+ break;
+ }
+ m_mime = "video/av01";
+ m_formatname = "amc-av1";
+ free(m_hints.extradata);
+ m_hints.extradata = nullptr;
+ m_hints.extrasize = 0;
+ break;
+ }
+ default:
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Open Unknown hints.codec({})",
+ hints.codec);
+ goto FAIL;
+ break;
+ }
+
+ if (m_crypto)
+ {
+ delete m_crypto;
+ m_crypto = nullptr;
+ }
+
+ if (m_hints.cryptoSession)
+ {
+ if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_WIDEVINE)
+ uuid = CJNIUUID(0xEDEF8BA979D64ACELL, 0xA3C827DCD51D21EDLL);
+ else if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_PLAYREADY)
+ uuid = CJNIUUID(0x9A04F07998404286LL, 0xAB92E65BE0885F95LL);
+ else if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_WISEPLAY)
+ uuid = CJNIUUID(0X3D5E6D359B9A41E8LL, 0XB843DD3C6E72C42CLL);
+ else
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open Unsupported crypto-keysystem {}",
+ m_hints.cryptoSession->keySystem);
+ goto FAIL;
+ }
+ CJNIMediaCrypto crypto(uuid, std::vector<char>(m_hints.cryptoSession->sessionId.begin(),
+ m_hints.cryptoSession->sessionId.end()));
+ m_needSecureDecoder =
+ crypto.requiresSecureDecoderComponent(m_mime) &&
+ (m_hints.cryptoSession->flags & DemuxCryptoSession::FLAG_SECURE_DECODER) != 0;
+
+ CLog::Log(
+ LOGINFO,
+ "CDVDVideoCodecAndroidMediaCodec::Open: Secure decoder requested: {} (stream flags: {})",
+ m_needSecureDecoder ? "true" : "false", m_hints.cryptoSession->flags);
+ }
+
+ m_codec = nullptr;
+ m_colorFormat = -1;
+ codecInfos = CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS).getCodecInfos();
+
+ for (const CJNIMediaCodecInfo& codec_info : codecInfos)
+ {
+ if (codec_info.isEncoder())
+ continue;
+
+ m_codecname = codec_info.getName();
+ if (!CServiceBroker::GetDecoderFilterManager()->isValid(m_codecname, m_hints))
+ continue;
+
+ CLog::Log(LOGINFO, "CDVDVideoCodecAndroidMediaCodec::Open Testing codec:{}", m_codecname);
+
+ CJNIMediaCodecInfoCodecCapabilities codec_caps = codec_info.getCapabilitiesForType(m_mime);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ // Unsupported type?
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ continue;
+ }
+
+ bool codecIsSecure(
+ m_codecname.find(".secure") != std::string::npos ||
+ codec_caps.isFeatureSupported(CJNIMediaCodecInfoCodecCapabilities::FEATURE_SecurePlayback));
+ if (m_needSecureDecoder)
+ {
+ if (!codecIsSecure)
+ m_codecname += ".secure";
+ }
+ else if (codecIsSecure)
+ {
+ CLog::Log(LOGINFO, "CDVDVideoCodecAndroidMediaCodec::Open: skipping insecure decoder while "
+ "secure decoding is required");
+ continue;
+ }
+
+ std::vector<int> color_formats = codec_caps.colorFormats();
+
+ if (profile)
+ {
+ std::vector<CJNIMediaCodecInfoCodecProfileLevel> profileLevels = codec_caps.profileLevels();
+ if (std::find_if(profileLevels.cbegin(), profileLevels.cend(),
+ [&](const CJNIMediaCodecInfoCodecProfileLevel& profileLevel) {
+ return profileLevel.profile() == profile;
+ }) == profileLevels.cend())
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open: profile not supported: {}",
+ profile);
+ continue;
+ }
+ }
+
+ std::vector<std::string> types = codec_info.getSupportedTypes();
+ // return the 1st one we find, that one is typically 'the best'
+ for (size_t j = 0; j < types.size(); ++j)
+ {
+ if (types[j] == m_mime)
+ {
+ m_codec = std::shared_ptr<CJNIMediaCodec>(
+ new CJNIMediaCodec(CJNIMediaCodec::createByCodecName(m_codecname)));
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ m_codec = nullptr;
+ }
+ if (!m_codec)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open cannot create codec");
+ continue;
+ }
+
+ for (size_t k = 0; k < color_formats.size(); ++k)
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec::Open "
+ "m_codecname({}), colorFormat({})",
+ m_codecname, color_formats[k]);
+ if (IsSupportedColorFormat(color_formats[k]))
+ m_colorFormat = color_formats[k]; // Save color format for initial output configuration
+ }
+ break;
+ }
+ }
+ if (m_codec)
+ break;
+ }
+ if (!m_codec)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec:: Failed to create Android MediaCodec");
+ goto FAIL;
+ }
+
+ if (m_hints.cryptoSession)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Open Initializing MediaCrypto");
+
+ m_crypto =
+ new CJNIMediaCrypto(uuid, std::vector<char>(m_hints.cryptoSession->sessionId.begin(),
+ m_hints.cryptoSession->sessionId.end()));
+
+ if (!m_crypto)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open: MediaCrypto creation failed");
+ goto FAIL;
+ }
+ }
+
+ if (m_render_surface)
+ {
+ m_jnivideoview.reset(CJNIXBMCVideoView::createVideoView(this));
+ if (!m_jnivideoview || !m_jnivideoview->waitForSurface(2000))
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec: VideoView creation failed!!");
+ goto FAIL;
+ }
+ }
+
+ // setup a YUV420P VideoPicture buffer.
+ // first make sure all properties are reset.
+ m_videobuffer.Reset();
+
+ m_videobuffer.iWidth = m_hints.width;
+ m_videobuffer.iHeight = m_hints.height;
+ // these will get reset to crop values later
+ m_videobuffer.iDisplayWidth = m_hints.width;
+ m_videobuffer.iDisplayHeight = m_hints.height;
+
+ if (!ConfigureMediaCodec())
+ goto FAIL;
+
+ if (m_codecname.find("OMX.Nvidia", 0, 10) == 0)
+ m_invalidPTSValue = AV_NOPTS_VALUE;
+ else if (m_codecname.find("OMX.MTK", 0, 7) == 0)
+ m_invalidPTSValue = -1; //Use DTS
+ else
+ m_invalidPTSValue = 0;
+
+ CLog::Log(LOGINFO,
+ "CDVDVideoCodecAndroidMediaCodec:: "
+ "Open Android MediaCodec {}",
+ m_codecname);
+
+ m_opened = true;
+
+ m_processInfo.SetVideoDecoderName(m_formatname, true );
+ m_processInfo.SetVideoPixelFormat(m_render_surface ? "Surface" : "EGL");
+ m_processInfo.SetVideoDimensions(m_hints.width, m_hints.height);
+ m_processInfo.SetVideoDeintMethod("hardware");
+ m_processInfo.SetVideoDAR(m_hints.aspect);
+
+ m_videoBufferPool = std::shared_ptr<CMediaCodecVideoBufferPool>(new CMediaCodecVideoBufferPool(m_codec));
+
+ UpdateFpsDuration();
+
+ return true;
+
+FAIL:
+ m_InstanceGuard.exchange(false);
+ if (m_crypto)
+ {
+ delete m_crypto;
+ m_crypto = nullptr;
+ }
+
+ if (m_jnivideoview)
+ {
+ m_jnivideoview->release();
+ m_jnivideoview.reset();
+ }
+
+ m_codec = nullptr;
+
+ m_bitstream.reset();
+
+ return false;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::Dispose()
+{
+ if (!m_opened)
+ return;
+
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::{}", __func__);
+
+ // invalidate any inflight outputbuffers
+ FlushInternal();
+
+ if (m_videoBufferPool)
+ {
+ m_videoBufferPool->ResetMediaCodec();
+ m_videoBufferPool = nullptr;
+ }
+
+ m_videobuffer.iFlags = 0;
+
+ if (m_codec)
+ {
+ m_codec->stop();
+ xbmc_jnienv()->ExceptionClear();
+ m_codec->release();
+ xbmc_jnienv()->ExceptionClear();
+ m_codec = nullptr;
+ m_state = MEDIACODEC_STATE_STOPPED;
+ }
+ ReleaseSurfaceTexture();
+
+ m_InstanceGuard.exchange(false);
+ if (m_render_surface)
+ {
+ m_jnivideoview->release();
+ m_jnivideoview.reset();
+ }
+
+ m_bitstream.reset();
+
+ m_opened = false;
+}
+
+bool CDVDVideoCodecAndroidMediaCodec::AddData(const DemuxPacket &packet)
+{
+ if (!m_opened || m_state == MEDIACODEC_STATE_STOPPED)
+ return false;
+
+ double pts(packet.pts), dts(packet.dts);
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec::AddData dts:{:0.2f} pts:{:0.2f} sz:{} "
+ "indexBuffer:{} current state ({})",
+ dts, pts, packet.iSize, m_indexInputBuffer, m_state);
+ else if (m_state != MEDIACODEC_STATE_RUNNING)
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::AddData current state ({})", m_state);
+
+ if (m_hints.ptsinvalid)
+ pts = DVD_NOPTS_VALUE;
+
+ uint8_t *pData(packet.pData);
+ size_t iSize(packet.iSize);
+
+ if (m_state == MEDIACODEC_STATE_ENDOFSTREAM || m_state == MEDIACODEC_STATE_ERROR)
+ {
+ // We received a packet but already reached EOS or Error. Reset...
+ Reset();
+ }
+
+ if (pData && iSize)
+ {
+ if (m_indexInputBuffer >= 0)
+ {
+ if (!(m_state == MEDIACODEC_STATE_FLUSHED || m_state == MEDIACODEC_STATE_RUNNING))
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::AddData: Wrong state ({})", m_state);
+ return false;
+ }
+
+ if (m_mpeg2_sequence && CBitstreamConverter::mpeg2_sequence_header(pData, iSize, m_mpeg2_sequence))
+ {
+ m_hints.fpsrate = m_mpeg2_sequence->fps_rate;
+ m_hints.fpsscale = m_mpeg2_sequence->fps_scale;
+ m_hints.width = m_mpeg2_sequence->width;
+ m_hints.height = m_mpeg2_sequence->height;
+ m_hints.aspect = static_cast<double>(m_mpeg2_sequence->ratio);
+
+ m_processInfo.SetVideoDAR(m_hints.aspect);
+ UpdateFpsDuration();
+ }
+
+ // we have an input buffer, fill it.
+ if (pData && m_bitstream)
+ {
+ m_bitstream->Convert(pData, iSize);
+
+ if (m_state == MEDIACODEC_STATE_FLUSHED && !m_bitstream->CanStartDecode())
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::AddData: waiting for keyframe (bitstream)");
+ return true;
+ }
+
+ iSize = m_bitstream->GetConvertSize();
+ pData = m_bitstream->GetConvertBuffer();
+ }
+
+ if (m_state == MEDIACODEC_STATE_FLUSHED)
+ m_state = MEDIACODEC_STATE_RUNNING;
+
+ CJNIByteBuffer buffer = m_codec->getInputBuffer(m_indexInputBuffer);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::AddData: getInputBuffer failed");
+ return false;
+ }
+
+ size_t out_size = buffer.capacity();
+ if ((size_t)iSize > out_size)
+ {
+ CLog::Log(LOGINFO, "CDVDVideoCodecAndroidMediaCodec::AddData: iSize({}) > size({})", iSize,
+ out_size);
+ iSize = out_size;
+ }
+ uint8_t* dst_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(buffer.get_raw());
+
+ CJNIMediaCodecCryptoInfo* cryptoInfo(nullptr);
+ if (m_crypto && packet.cryptoInfo)
+ {
+ std::vector<int> clearBytes(packet.cryptoInfo->clearBytes,
+ packet.cryptoInfo->clearBytes +
+ packet.cryptoInfo->numSubSamples);
+ std::vector<int> cipherBytes(packet.cryptoInfo->cipherBytes,
+ packet.cryptoInfo->cipherBytes +
+ packet.cryptoInfo->numSubSamples);
+
+ cryptoInfo = new CJNIMediaCodecCryptoInfo();
+ if (CJNIBase::GetSDKVersion() < 25 &&
+ packet.cryptoInfo->mode == CJNIMediaCodec::CRYPTO_MODE_AES_CBC)
+ {
+ CLog::LogF(LOGERROR, "Device API does not support CBCS decryption");
+ return false;
+ }
+
+ cryptoInfo->set(
+ packet.cryptoInfo->numSubSamples, clearBytes, cipherBytes,
+ std::vector<char>(std::begin(packet.cryptoInfo->kid), std::end(packet.cryptoInfo->kid)),
+ std::vector<char>(std::begin(packet.cryptoInfo->iv), std::end(packet.cryptoInfo->iv)),
+ packet.cryptoInfo->mode == CJNIMediaCodec::CRYPTO_MODE_AES_CBC
+ ? CJNIMediaCodec::CRYPTO_MODE_AES_CBC
+ : CJNIMediaCodec::CRYPTO_MODE_AES_CTR);
+
+ CJNIMediaCodecCryptoInfoPattern cryptoInfoPattern(packet.cryptoInfo->cryptBlocks,
+ packet.cryptoInfo->skipBlocks);
+ cryptoInfo->setPattern(cryptoInfoPattern);
+ }
+ if (dst_ptr)
+ {
+ // Codec specifics
+ switch(m_hints.codec)
+ {
+ case AV_CODEC_ID_VC1:
+ {
+ if (iSize >= 4 && pData[0] == 0x00 && pData[1] == 0x00 && pData[2] == 0x01 && (pData[3] == 0x0d || pData[3] == 0x0f))
+ memcpy(dst_ptr, pData, iSize);
+ else
+ {
+ dst_ptr[0] = 0x00;
+ dst_ptr[1] = 0x00;
+ dst_ptr[2] = 0x01;
+ dst_ptr[3] = 0x0d;
+ memcpy(dst_ptr+4, pData, iSize);
+ iSize += 4;
+ }
+
+ break;
+ }
+
+ default:
+ memcpy(dst_ptr, pData, iSize);
+ break;
+ }
+ }
+
+
+ // Translate from VideoPlayer dts/pts to MediaCodec pts,
+ // pts WILL get re-ordered by MediaCodec if needed.
+ // Do not try to pass pts as a unioned double/int64_t,
+ // some android devices will diddle with presentationTimeUs
+ // and you will get NaN back and VideoPlayerVideo will barf.
+ if (m_dtsShift == DVD_NOPTS_VALUE)
+ m_dtsShift = (dts == DVD_NOPTS_VALUE) ? 0 : dts;
+
+ int64_t presentationTimeUs = m_invalidPTSValue;
+ if (pts != DVD_NOPTS_VALUE)
+ {
+ presentationTimeUs = (pts - m_dtsShift);
+ m_useDTSforPTS = false;
+ }
+ else if ((presentationTimeUs < 0 || m_useDTSforPTS) && dts != DVD_NOPTS_VALUE)
+ presentationTimeUs = (dts - m_dtsShift);
+ else
+ presentationTimeUs = 0;
+
+ int flags = 0;
+ int offset = 0;
+
+ if (!cryptoInfo)
+ m_codec->queueInputBuffer(m_indexInputBuffer, offset, iSize, presentationTimeUs, flags);
+ else
+ {
+ m_codec->queueSecureInputBuffer(m_indexInputBuffer, offset, *cryptoInfo, presentationTimeUs,
+ flags);
+ delete cryptoInfo, cryptoInfo = nullptr;
+ }
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::AddData error");
+ }
+ m_indexInputBuffer = -1;
+ }
+ else
+ return false;
+ }
+ return true;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::Reset()
+{
+ if (!m_opened)
+ return;
+
+ if (m_codec)
+ {
+ // now we can flush the actual MediaCodec object
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Reset Current state ({})", m_state);
+ m_codec->flush();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Reset: flush failed");
+ }
+
+ if (m_state == MEDIACODEC_STATE_ERROR)
+ {
+ m_codec->stop();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Reset: stop failed");
+ }
+ ConfigureMediaCodec();
+ }
+ else
+ {
+ CJNIMediaFormat mediaFormat = m_codec->getOutputFormat();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Reset: getOutputFormat failed");
+ }
+ else
+ InjectExtraData(mediaFormat);
+ }
+
+ m_state = MEDIACODEC_STATE_FLUSHED;
+
+ // Invalidate our local VideoPicture bits
+ m_videobuffer.pts = DVD_NOPTS_VALUE;
+
+ m_dtsShift = DVD_NOPTS_VALUE;
+ m_indexInputBuffer = -1;
+
+ if (m_bitstream)
+ m_bitstream->ResetStartDecode();
+ }
+}
+
+bool CDVDVideoCodecAndroidMediaCodec::Reconfigure(CDVDStreamInfo &hints)
+{
+ if (m_hints.Equal(hints, CDVDStreamInfo::COMPARE_ALL &
+ ~(CDVDStreamInfo::COMPARE_ID | CDVDStreamInfo::COMPARE_EXTRADATA)))
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Reconfigure: true");
+ m_hints = hints;
+ return true;
+ }
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Reconfigure: false");
+ return false;
+}
+
+CDVDVideoCodec::VCReturn CDVDVideoCodecAndroidMediaCodec::GetPicture(VideoPicture* pVideoPicture)
+{
+ if (!m_opened)
+ return VC_NONE;
+
+ if (m_state == MEDIACODEC_STATE_ERROR || m_state == MEDIACODEC_STATE_ENDOFSTREAM)
+ return VC_EOF;
+
+ if (m_OutputDuration < m_fpsDuration || (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) != 0)
+ {
+ m_videobuffer.videoBuffer = pVideoPicture->videoBuffer;
+
+ int retgp = GetOutputPicture();
+
+ if (retgp > 0)
+ {
+ pVideoPicture->videoBuffer = nullptr;
+ pVideoPicture->SetParams(m_videobuffer);
+ pVideoPicture->videoBuffer = m_videobuffer.videoBuffer;
+
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CDVDVideoCodecAndroidMediaCodec::GetPicture index: {}, pts:{:0.4f}",
+ static_cast<CMediaCodecVideoBuffer*>(m_videobuffer.videoBuffer)->GetBufferId(),
+ pVideoPicture->pts);
+
+ m_videobuffer.videoBuffer = nullptr;
+
+ return VC_PICTURE;
+ }
+ else
+ {
+ m_videobuffer.videoBuffer = nullptr;
+ if (retgp == -1 || m_state == MEDIACODEC_STATE_WAIT_ENDOFSTREAM) // EOS
+ {
+ m_state = (retgp == -2) ? MEDIACODEC_STATE_ERROR : MEDIACODEC_STATE_ENDOFSTREAM;
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecAndroidMediaCodec::GetPicture VC_EOF");
+ return VC_EOF;
+ }
+ }
+ }
+ else
+ m_OutputDuration = 0;
+
+ if ((m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) == 0)
+ {
+ // try to fetch an input buffer
+ if (m_indexInputBuffer < 0)
+ {
+ m_indexInputBuffer = m_codec->dequeueInputBuffer(5000 /*timeout*/);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR,
+ "CDVDVideoCodecAndroidMediaCodec::GetPicture dequeueInputBuffer failed");
+ m_indexInputBuffer = -1;
+ }
+ }
+
+ if (m_indexInputBuffer >= 0)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecAndroidMediaCodec::GetPicture VC_BUFFER");
+ return VC_BUFFER;
+ }
+ }
+ else if (m_state == MEDIACODEC_STATE_RUNNING)
+ {
+ SignalEndOfStream();
+ m_state = MEDIACODEC_STATE_WAIT_ENDOFSTREAM;
+ }
+ else if (m_state == MEDIACODEC_STATE_FLUSHED)
+ return VC_EOF;
+
+ return VC_NONE;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::SetCodecControl(int flags)
+{
+ if (m_codecControlFlags != flags)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecAndroidMediaCodec::{} {:x}->{:x}", __func__,
+ m_codecControlFlags, flags);
+ m_codecControlFlags = flags;
+ }
+}
+
+unsigned CDVDVideoCodecAndroidMediaCodec::GetAllowedReferences()
+{
+ return 4;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::FlushInternal()
+{
+ SignalEndOfStream();
+
+ m_OutputDuration = 0;
+ m_lastPTS = -1;
+ m_dtsShift = DVD_NOPTS_VALUE;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::SignalEndOfStream()
+{
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::{}: state: {}", __func__, m_state);
+ if (m_codec && (m_state == MEDIACODEC_STATE_RUNNING))
+ {
+ // Release all mediaodec output buffers to allow drain if we don't get inputbuffer early
+ if (m_videoBufferPool)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::{}: ReleaseMediaCodecBuffers",
+ __func__);
+ m_videoBufferPool->ReleaseMediaCodecBuffers();
+ }
+
+ if (m_indexInputBuffer < 0)
+ {
+ m_indexInputBuffer = m_codec->dequeueInputBuffer(100000);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR,
+ "CDVDVideoCodecAndroidMediaCodec::SignalEndOfStream: dequeueInputBuffer failed");
+ }
+ }
+
+ xbmc_jnienv()->ExceptionClear();
+
+ if (m_indexInputBuffer >= 0)
+ {
+ m_codec->queueInputBuffer(m_indexInputBuffer, 0, 0, 0,
+ CJNIMediaCodec::BUFFER_FLAG_END_OF_STREAM);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::{}: queueInputBuffer failed");
+ }
+ else
+ {
+ m_indexInputBuffer = -1;
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::{}: BUFFER_FLAG_END_OF_STREAM send",
+ __func__);
+ }
+ }
+ else
+ CLog::Log(LOGWARNING, "CDVDVideoCodecAndroidMediaCodec::{}: invalid index: {}", __func__,
+ m_indexInputBuffer);
+ }
+}
+
+void CDVDVideoCodecAndroidMediaCodec::InjectExtraData(CJNIMediaFormat& mediaformat)
+{
+ if (!m_hints.extrasize)
+ return;
+
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::{}", __func__);
+ size_t size = m_hints.extrasize;
+ void* src_ptr = m_hints.extradata;
+ if (m_bitstream)
+ {
+ size = m_bitstream->GetExtraSize();
+ src_ptr = m_bitstream->GetExtraData();
+ }
+ // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer,
+ // since the latter doesn't allocate storage of its own, and we don't know how long
+ // the codec uses the buffer.
+ CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size);
+ void* dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw());
+ memcpy(dts_ptr, src_ptr, size);
+ // codec will automatically handle buffers as extradata
+ // using entries with keys "csd-0", "csd-1", etc.
+ mediaformat.setByteBuffer("csd-0", bytebuffer);
+}
+
+std::vector<uint8_t> CDVDVideoCodecAndroidMediaCodec::GetHDRStaticMetadata()
+{
+ std::vector<uint8_t> metadata;
+ if (m_hints.masteringMetadata)
+ {
+ // for more information, see CTA+861.3-A standard document
+ static const double MAX_CHROMATICITY = 50000;
+ static const double MAX_LUMINANCE = 10000;
+ metadata.resize(25);
+ metadata[0] = 0;
+ unsigned short* data = reinterpret_cast<unsigned short*>(&metadata[1]);
+ data[0] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[0][0]) * MAX_CHROMATICITY + 0.5);
+ data[1] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[0][1]) * MAX_CHROMATICITY + 0.5);
+ data[2] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[1][0]) * MAX_CHROMATICITY + 0.5);
+ data[3] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[1][1]) * MAX_CHROMATICITY + 0.5);
+ data[4] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[2][0]) * MAX_CHROMATICITY + 0.5);
+ data[5] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->display_primaries[2][1]) * MAX_CHROMATICITY + 0.5);
+ data[6] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->white_point[0]) * MAX_CHROMATICITY + 0.5);
+ data[7] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->white_point[1]) * MAX_CHROMATICITY + 0.5);
+ data[8] = static_cast<unsigned short>(av_q2d(m_hints.masteringMetadata->max_luminance) + 0.5);
+ data[9] = static_cast<unsigned short>(
+ av_q2d(m_hints.masteringMetadata->min_luminance) * MAX_LUMINANCE + 0.5);
+ // we can have HDR content that does not provide content light level metadata
+ if (m_hints.contentLightMetadata)
+ {
+ data[10] = static_cast<unsigned short>(m_hints.contentLightMetadata->MaxCLL);
+ data[11] = static_cast<unsigned short>(m_hints.contentLightMetadata->MaxFALL);
+ }
+ }
+ return metadata;
+}
+
+bool CDVDVideoCodecAndroidMediaCodec::ConfigureMediaCodec(void)
+{
+ // setup a MediaFormat to match the video content,
+ // used by codec during configure
+ CJNIMediaFormat mediaformat =
+ CJNIMediaFormat::createVideoFormat(m_mime, m_hints.width, m_hints.height);
+ mediaformat.setInteger(CJNIMediaFormat::KEY_MAX_INPUT_SIZE, 0);
+
+ if (CJNIBase::GetSDKVersion() >= 23 && m_render_surface)
+ {
+ // Handle rotation
+ mediaformat.setInteger(CJNIMediaFormat::KEY_ROTATION, m_hints.orientation);
+ mediaformat.setFeatureEnabled(CJNIMediaCodecInfoCodecCapabilities::FEATURE_TunneledPlayback,
+ false);
+ if (m_needSecureDecoder)
+ mediaformat.setFeatureEnabled(CJNIMediaCodecInfoCodecCapabilities::FEATURE_SecurePlayback,
+ true);
+ }
+
+ if (CJNIBase::GetSDKVersion() >= 24)
+ {
+ if (m_hints.colorRange != AVCOL_RANGE_UNSPECIFIED)
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_RANGE, m_hints.colorRange);
+
+ if (m_hints.colorPrimaries != AVCOL_PRI_UNSPECIFIED)
+ {
+ switch (m_hints.colorPrimaries)
+ {
+ case AVCOL_PRI_BT709:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_STANDARD,
+ CJNIMediaFormat::COLOR_STANDARD_BT709);
+ break;
+ case AVCOL_PRI_BT2020:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_STANDARD,
+ CJNIMediaFormat::COLOR_STANDARD_BT2020);
+ break;
+ default:; // do nothing
+ }
+ }
+
+ if (m_hints.colorTransferCharacteristic != AVCOL_TRC_UNSPECIFIED)
+ {
+ switch (m_hints.colorTransferCharacteristic)
+ {
+ case AVCOL_TRC_LINEAR:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_TRANSFER,
+ CJNIMediaFormat::COLOR_TRANSFER_LINEAR);
+ break;
+ case AVCOL_TRC_SMPTE170M:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_TRANSFER,
+ CJNIMediaFormat::COLOR_TRANSFER_SDR_VIDEO);
+ break;
+ case AVCOL_TRC_SMPTE2084:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_TRANSFER,
+ CJNIMediaFormat::COLOR_TRANSFER_ST2084);
+ break;
+ case AVCOL_TRC_ARIB_STD_B67:
+ mediaformat.setInteger(CJNIMediaFormat::KEY_COLOR_TRANSFER,
+ CJNIMediaFormat::COLOR_TRANSFER_HLG);
+ break;
+ default:; // do nothing
+ }
+ }
+ std::vector<uint8_t> hdr_static_data = GetHDRStaticMetadata();
+ if (!hdr_static_data.empty())
+ {
+ CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(hdr_static_data.size());
+ void* dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw());
+ memcpy(dts_ptr, hdr_static_data.data(), hdr_static_data.size());
+ mediaformat.setByteBuffer(CJNIMediaFormat::KEY_HDR_STATIC_INFO, bytebuffer);
+ }
+ }
+
+ // handle codec extradata
+ InjectExtraData(mediaformat);
+
+ if (m_render_surface)
+ {
+ m_jnivideosurface = m_jnivideoview->getSurface();
+ if (!m_jnivideosurface)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec: VideoView getSurface failed!!");
+ m_jnivideoview->release();
+ m_jnivideoview.reset();
+ return false;
+ }
+ m_formatname += "(S)";
+ }
+ else
+ InitSurfaceTexture();
+
+ // configure and start the codec.
+ // use the MediaFormat that we have setup.
+ // use a null MediaCrypto, our content is not encrypted.
+ m_codec->configure(mediaformat, m_jnivideosurface,
+ m_crypto ? *m_crypto : CJNIMediaCrypto(jni::jhobject(NULL)), 0);
+
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::ConfigureMediaCodec: configure failed");
+ return false;
+ }
+ m_state = MEDIACODEC_STATE_CONFIGURED;
+
+ m_codec->start();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ Dispose();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec:ConfigureMediaCodec: start failed");
+ return false;
+ }
+ m_state = MEDIACODEC_STATE_FLUSHED;
+
+ // There is no guarantee we'll get an INFO_OUTPUT_FORMAT_CHANGED (up to Android 4.3)
+ // Configure the output with defaults
+ ConfigureOutputFormat(mediaformat);
+
+ return true;
+}
+
+int CDVDVideoCodecAndroidMediaCodec::GetOutputPicture(void)
+{
+ int rtn = 0;
+ int64_t timeout_us = (m_state == MEDIACODEC_STATE_WAIT_ENDOFSTREAM) ? 100000 : 10000;
+ CJNIMediaCodecBufferInfo bufferInfo;
+
+ ssize_t index = m_codec->dequeueOutputBuffer(bufferInfo, timeout_us);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR,
+ "CDVDVideoCodecAndroidMediaCodec:GetOutputPicture dequeueOutputBuffer failed");
+ return -2;
+ }
+
+ if (index >= 0)
+ {
+ int64_t pts = bufferInfo.presentationTimeUs();
+ m_videobuffer.dts = DVD_NOPTS_VALUE;
+ m_videobuffer.pts = DVD_NOPTS_VALUE;
+ if (pts != AV_NOPTS_VALUE)
+ {
+ m_videobuffer.pts = pts;
+ m_videobuffer.pts += m_dtsShift;
+ if (m_lastPTS >= 0 && pts > m_lastPTS)
+ m_OutputDuration += pts - m_lastPTS;
+ m_lastPTS = pts;
+ }
+
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DROP)
+ {
+ m_codec->releaseOutputBuffer(index, false);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(
+ LOGERROR,
+ "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture: releaseOutputBuffer (drop) failed");
+ }
+ return -2;
+ }
+
+ int flags = bufferInfo.flags();
+ if (flags & CJNIMediaCodec::BUFFER_FLAG_END_OF_STREAM)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_END_OF_STREAM");
+ m_codec->releaseOutputBuffer(index, false);
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(
+ LOGERROR,
+ "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture: releaseOutputBuffer (eof) failed");
+ }
+ return -1;
+ }
+
+ if (m_videobuffer.videoBuffer)
+ m_videobuffer.videoBuffer->Release();
+
+ m_videobuffer.videoBuffer = m_videoBufferPool->Get();
+ static_cast<CMediaCodecVideoBuffer*>(m_videobuffer.videoBuffer)->Set(index, m_textureId, m_surfaceTexture, m_frameAvailable, m_jnivideoview);
+
+ rtn = 1;
+ }
+ else if (index == CJNIMediaCodec::INFO_OUTPUT_FORMAT_CHANGED)
+ {
+ CJNIMediaFormat mediaformat = m_codec->getOutputFormat();
+ if (xbmc_jnienv()->ExceptionCheck())
+ {
+ xbmc_jnienv()->ExceptionDescribe();
+ xbmc_jnienv()->ExceptionClear();
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture(INFO_OUTPUT_FORMAT_CHANGED) ExceptionCheck: getOutputBuffers");
+ }
+ else
+ ConfigureOutputFormat(mediaformat);
+ }
+ else if (index == CJNIMediaCodec::INFO_TRY_AGAIN_LATER ||
+ index == CJNIMediaCodec::INFO_OUTPUT_BUFFERS_CHANGED)
+ {
+ // ignore
+ rtn = 0;
+ }
+ else
+ {
+ // we should never get here
+ CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture unknown index({})",
+ index);
+ rtn = -2;
+ }
+
+ return rtn;
+}
+
+void CDVDVideoCodecAndroidMediaCodec::ConfigureOutputFormat(CJNIMediaFormat& mediaformat)
+{
+ int width = 0;
+ int height = 0;
+ int stride = 0;
+ int slice_height= 0;
+ int color_format= 0;
+ int crop_left = 0;
+ int crop_top = 0;
+ int crop_right = 0;
+ int crop_bottom = 0;
+
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_WIDTH))
+ width = mediaformat.getInteger(CJNIMediaFormat::KEY_WIDTH);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_HEIGHT))
+ height = mediaformat.getInteger(CJNIMediaFormat::KEY_HEIGHT);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_COLOR_FORMAT))
+ color_format = mediaformat.getInteger(CJNIMediaFormat::KEY_COLOR_FORMAT);
+
+ if (CJNIBase::GetSDKVersion() >= 23)
+ {
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_STRIDE))
+ stride = mediaformat.getInteger(CJNIMediaFormat::KEY_STRIDE);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_SLICE_HEIGHT))
+ slice_height = mediaformat.getInteger(CJNIMediaFormat::KEY_SLICE_HEIGHT);
+ }
+
+ if (CJNIBase::GetSDKVersion() >= 33)
+ {
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_CROP_LEFT))
+ crop_left = mediaformat.getInteger(CJNIMediaFormat::KEY_CROP_LEFT);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_CROP_TOP))
+ crop_top = mediaformat.getInteger(CJNIMediaFormat::KEY_CROP_TOP);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_CROP_RIGHT))
+ crop_right = mediaformat.getInteger(CJNIMediaFormat::KEY_CROP_RIGHT);
+ if (mediaformat.containsKey(CJNIMediaFormat::KEY_CROP_BOTTOM))
+ crop_bottom = mediaformat.getInteger(CJNIMediaFormat::KEY_CROP_BOTTOM);
+ }
+
+ if (!crop_right)
+ crop_right = width-1;
+ if (!crop_bottom)
+ crop_bottom = height-1;
+
+ // clear any jni exceptions
+ if (xbmc_jnienv()->ExceptionCheck())
+ xbmc_jnienv()->ExceptionClear();
+
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec:: "
+ "width({}), height({}), stride({}), slice-height({}), color-format({})",
+ width, height, stride, slice_height, color_format);
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec:: "
+ "crop-left({}), crop-top({}), crop-right({}), crop-bottom({})",
+ crop_left, crop_top, crop_right, crop_bottom);
+
+ if (m_render_surface)
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: Multi-Surface Rendering");
+ else
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: Direct Surface Rendering");
+
+ if (crop_right)
+ width = crop_right + 1 - crop_left;
+ if (crop_bottom)
+ height = crop_bottom + 1 - crop_top;
+
+ m_videobuffer.iDisplayWidth = m_videobuffer.iWidth = width;
+ m_videobuffer.iDisplayHeight = m_videobuffer.iHeight = height;
+
+ if (m_hints.aspect > 1.0 && !m_hints.forced_aspect)
+ {
+ m_videobuffer.iDisplayWidth = ((int)lrint(m_videobuffer.iHeight * m_hints.aspect)) & ~3;
+ if (m_videobuffer.iDisplayWidth > m_videobuffer.iWidth)
+ {
+ m_videobuffer.iDisplayWidth = m_videobuffer.iWidth;
+ m_videobuffer.iDisplayHeight = ((int)lrint(m_videobuffer.iWidth / m_hints.aspect)) & ~3;
+ }
+ }
+}
+
+void CDVDVideoCodecAndroidMediaCodec::CallbackInitSurfaceTexture(void *userdata)
+{
+ CDVDVideoCodecAndroidMediaCodec *ctx = static_cast<CDVDVideoCodecAndroidMediaCodec*>(userdata);
+ ctx->InitSurfaceTexture();
+}
+
+void CDVDVideoCodecAndroidMediaCodec::InitSurfaceTexture(void)
+{
+ if (m_render_surface)
+ return;
+
+ // We MUST create the GLES texture on the main thread
+ // to match where the valid GLES context is located.
+ // It would be nice to move this out of here, we would need
+ // to create/fetch/create from g_RenderManager. But g_RenderManager
+ // does not know we are using MediaCodec until Configure and we
+ // we need m_surfaceTexture valid before then. Chicken, meet Egg.
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ // localize GLuint so we do not spew gles includes in our header
+ GLuint texture_id;
+
+ glGenTextures(1, &texture_id);
+ glBindTexture( GL_TEXTURE_EXTERNAL_OES, texture_id);
+ glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glBindTexture( GL_TEXTURE_EXTERNAL_OES, 0);
+ m_textureId = texture_id;
+
+ m_surfaceTexture = std::shared_ptr<CJNISurfaceTexture>(new CJNISurfaceTexture(m_textureId));
+ // hook the surfaceTexture OnFrameAvailable callback
+ m_frameAvailable = std::shared_ptr<CDVDMediaCodecOnFrameAvailable>(new CDVDMediaCodecOnFrameAvailable(m_surfaceTexture));
+ m_jnivideosurface = CJNISurface(*m_surfaceTexture);
+ }
+ else
+ {
+ ThreadMessageCallback callbackData;
+ callbackData.callback = &CallbackInitSurfaceTexture;
+ callbackData.userptr = (void*)this;
+
+ // wait for it.
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_CALLBACK, -1, -1,
+ static_cast<void*>(&callbackData));
+ }
+}
+
+void CDVDVideoCodecAndroidMediaCodec::ReleaseSurfaceTexture(void)
+{
+ if (m_render_surface)
+ return;
+
+ // it is safe to delete here even though these items
+ // were created in the main thread instance
+ m_jnivideosurface = CJNISurface(jni::jhobject(NULL));
+ m_frameAvailable.reset();
+ m_surfaceTexture.reset();
+
+ if (m_textureId > 0)
+ {
+ GLuint texture_id = m_textureId;
+ glDeleteTextures(1, &texture_id);
+ m_textureId = 0;
+ }
+}
+
+void CDVDVideoCodecAndroidMediaCodec::UpdateFpsDuration()
+{
+ if (m_hints.fpsrate > 0 && m_hints.fpsscale > 0)
+ m_fpsDuration = static_cast<uint32_t>(static_cast<uint64_t>(DVD_TIME_BASE) * m_hints.fpsscale / m_hints.fpsrate);
+ else
+ m_fpsDuration = 1;
+
+ m_processInfo.SetVideoFps(static_cast<float>(m_hints.fpsrate) / m_hints.fpsscale);
+
+ CLog::Log(LOGDEBUG,
+ "CDVDVideoCodecAndroidMediaCodec::UpdateFpsDuration fpsRate:{} fpsscale:{}, fpsDur:{}",
+ m_hints.fpsrate, m_hints.fpsscale, m_fpsDuration);
+}
+
+void CDVDVideoCodecAndroidMediaCodec::surfaceChanged(CJNISurfaceHolder holder, int format, int width, int height)
+{
+}
+
+void CDVDVideoCodecAndroidMediaCodec::surfaceCreated(CJNISurfaceHolder holder)
+{
+ if (m_state == MEDIACODEC_STATE_STOPPED)
+ {
+ ConfigureMediaCodec();
+ }
+}
+
+void CDVDVideoCodecAndroidMediaCodec::surfaceDestroyed(CJNISurfaceHolder holder)
+{
+ if (m_state != MEDIACODEC_STATE_STOPPED && m_state != MEDIACODEC_STATE_UNINITIALIZED)
+ {
+ m_state = MEDIACODEC_STATE_STOPPED;
+ if (m_jnivideosurface)
+ m_jnivideosurface.release();
+ m_codec->stop();
+ xbmc_jnienv()->ExceptionClear();
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h
new file mode 100644
index 0000000..3deba5d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDStreamInfo.h"
+#include "DVDVideoCodec.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "threads/SingleLock.h"
+#include "threads/Thread.h"
+#include "utils/Geometry.h"
+
+#include "platform/android/activity/JNIXBMCVideoView.h"
+
+#include <atomic>
+#include <deque>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <androidjni/Surface.h>
+
+class CJNISurface;
+class CJNISurfaceTexture;
+class CJNIMediaCodec;
+class CJNIMediaCrypto;
+class CJNIMediaFormat;
+class CJNIMediaCodecBufferInfo;
+class CDVDMediaCodecOnFrameAvailable;
+class CJNIByteBuffer;
+class CBitstreamConverter;
+
+struct DemuxCryptoInfo;
+struct mpeg2_sequence;
+
+
+typedef struct amc_demux
+{
+ uint8_t* pData;
+ int iSize;
+ double dts;
+ double pts;
+} amc_demux;
+
+class CMediaCodecVideoBufferPool;
+
+class CMediaCodecVideoBuffer : public CVideoBuffer
+{
+public:
+ CMediaCodecVideoBuffer(int id) : CVideoBuffer(id) {}
+ ~CMediaCodecVideoBuffer() override = default;
+
+ void Set(int internalId,
+ int textureId,
+ std::shared_ptr<CJNISurfaceTexture> surfaceTexture,
+ std::shared_ptr<CDVDMediaCodecOnFrameAvailable> frameAvailable,
+ std::shared_ptr<CJNIXBMCVideoView> videoView);
+
+ // meat and potatoes
+ bool WaitForFrame(int millis);
+ // MediaCodec related
+ void ReleaseOutputBuffer(bool render,
+ int64_t displayTime,
+ CMediaCodecVideoBufferPool* pool = nullptr);
+ // SurfaceTexture released
+ int GetBufferId() const;
+ int GetTextureId() const;
+ void GetTransformMatrix(float* textureMatrix);
+ void UpdateTexImage();
+ void RenderUpdate(const CRect& DestRect, int64_t displayTime);
+ bool HasSurfaceTexture() const { return m_surfacetexture.operator bool(); }
+
+private:
+ int m_bufferId = -1;
+ unsigned int m_textureId = 0;
+ // shared_ptr bits, shared between
+ // CDVDVideoCodecAndroidMediaCodec and LinuxRenderGLES.
+ std::shared_ptr<CJNISurfaceTexture> m_surfacetexture;
+ std::shared_ptr<CDVDMediaCodecOnFrameAvailable> m_frameready;
+ std::shared_ptr<CJNIXBMCVideoView> m_videoview;
+};
+
+class CMediaCodecVideoBufferPool : public IVideoBufferPool
+{
+public:
+ CMediaCodecVideoBufferPool(std::shared_ptr<CJNIMediaCodec> mediaCodec)
+ : m_codec(std::move(mediaCodec))
+ {
+ }
+
+ ~CMediaCodecVideoBufferPool() override;
+
+ CVideoBuffer* Get() override;
+ void Return(int id) override;
+
+ std::shared_ptr<CJNIMediaCodec> GetMediaCodec();
+ void ResetMediaCodec();
+ void ReleaseMediaCodecBuffers();
+
+private:
+ CCriticalSection m_criticalSection;
+ std::shared_ptr<CJNIMediaCodec> m_codec;
+
+ std::vector<CMediaCodecVideoBuffer*> m_videoBuffers;
+ std::vector<int> m_freeBuffers;
+};
+
+class CDVDVideoCodecAndroidMediaCodec : public CDVDVideoCodec, public CJNISurfaceHolderCallback
+{
+public:
+ CDVDVideoCodecAndroidMediaCodec(CProcessInfo& processInfo, bool surface_render = false);
+ ~CDVDVideoCodecAndroidMediaCodec() override;
+
+ // registration
+ static std::unique_ptr<CDVDVideoCodec> Create(CProcessInfo& processInfo);
+ static bool Register();
+
+ // required overrides
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ bool AddData(const DemuxPacket& packet) override;
+ void Reset() override;
+ bool Reconfigure(CDVDStreamInfo& hints) override;
+ VCReturn GetPicture(VideoPicture* pVideoPicture) override;
+ const char* GetName() override { return m_formatname.c_str(); }
+ void SetCodecControl(int flags) override;
+ unsigned GetAllowedReferences() override;
+
+protected:
+ void Dispose();
+ void FlushInternal(void);
+ void SignalEndOfStream();
+ void InjectExtraData(CJNIMediaFormat& mediaformat);
+ std::vector<uint8_t> GetHDRStaticMetadata();
+ bool ConfigureMediaCodec(void);
+ int GetOutputPicture(void);
+ void ConfigureOutputFormat(CJNIMediaFormat& mediaformat);
+ void UpdateFpsDuration();
+
+ // surface handling functions
+ static void CallbackInitSurfaceTexture(void*);
+ void InitSurfaceTexture(void);
+ void ReleaseSurfaceTexture(void);
+
+ CDVDStreamInfo m_hints;
+ std::string m_mime;
+ std::string m_codecname;
+ int m_colorFormat;
+ std::string m_formatname;
+ bool m_opened;
+ bool m_needSecureDecoder = false;
+ int m_codecControlFlags;
+ int m_state;
+
+ std::shared_ptr<CJNIXBMCVideoView> m_jnivideoview;
+ CJNISurface m_jnivideosurface;
+ unsigned int m_textureId;
+ std::shared_ptr<CJNIMediaCodec> m_codec;
+ CJNIMediaCrypto* m_crypto = nullptr;
+ std::shared_ptr<CJNISurfaceTexture> m_surfaceTexture;
+ std::shared_ptr<CDVDMediaCodecOnFrameAvailable> m_frameAvailable;
+
+ amc_demux m_demux_pkt;
+ std::shared_ptr<CMediaCodecVideoBufferPool> m_videoBufferPool;
+
+ uint32_t m_OutputDuration, m_fpsDuration;
+ int64_t m_lastPTS;
+ int64_t m_invalidPTSValue = 0;
+ double m_dtsShift;
+
+ static std::atomic<bool> m_InstanceGuard;
+
+ std::unique_ptr<CBitstreamConverter> m_bitstream;
+ VideoPicture m_videobuffer;
+
+ int m_indexInputBuffer;
+ bool m_render_surface;
+ mpeg2_sequence* m_mpeg2_sequence;
+ int m_src_offset[4];
+ int m_src_stride[4];
+ bool m_useDTSforPTS;
+
+ // CJNISurfaceHolderCallback interface
+public:
+ void surfaceChanged(CJNISurfaceHolder holder, int format, int width, int height) override;
+ void surfaceCreated(CJNISurfaceHolder holder) override;
+ void surfaceDestroyed(CJNISurfaceHolder holder) override;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
new file mode 100644
index 0000000..80ca602
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDVideoCodecDRMPRIME.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Buffers/VideoBufferDMA.h"
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecs.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "threads/SingleLock.h"
+#include "utils/CPUInfo.h"
+#include "utils/log.h"
+
+#if defined(HAVE_GBM)
+#include "windowing/gbm/WinSystemGbm.h"
+#endif
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavutil/error.h>
+#include <libavutil/imgutils.h>
+#include <libavutil/opt.h>
+#include <libavutil/pixdesc.h>
+}
+
+namespace
+{
+
+constexpr const char* SETTING_VIDEOPLAYER_USEPRIMEDECODERFORHW{"videoplayer.useprimedecoderforhw"};
+
+static void ReleaseBuffer(void* opaque, uint8_t* data)
+{
+ CVideoBufferDMA* buffer = static_cast<CVideoBufferDMA*>(opaque);
+ buffer->Release();
+}
+
+static void AlignedSize(AVCodecContext* avctx, int& width, int& height)
+{
+ int w = width;
+ int h = height;
+ AVFrame picture;
+ int unaligned;
+ int stride_align[AV_NUM_DATA_POINTERS];
+
+ avcodec_align_dimensions2(avctx, &w, &h, stride_align);
+
+ do
+ {
+ // NOTE: do not align linesizes individually, this breaks e.g. assumptions
+ // that linesize[0] == 2*linesize[1] in the MPEG-encoder for 4:2:2
+ av_image_fill_linesizes(picture.linesize, avctx->pix_fmt, w);
+ // increase alignment of w for next try (rhs gives the lowest bit set in w)
+ w += w & ~(w - 1);
+
+ unaligned = 0;
+ for (int i = 0; i < 4; i++)
+ unaligned |= picture.linesize[i] % stride_align[i];
+ } while (unaligned);
+
+ width = w;
+ height = h;
+}
+
+} // namespace
+
+CDVDVideoCodecDRMPRIME::CDVDVideoCodecDRMPRIME(CProcessInfo& processInfo)
+ : CDVDVideoCodec(processInfo)
+{
+ m_pFrame = av_frame_alloc();
+ m_videoBufferPool = std::make_shared<CVideoBufferPoolDRMPRIMEFFmpeg>();
+}
+
+CDVDVideoCodecDRMPRIME::~CDVDVideoCodecDRMPRIME()
+{
+ av_frame_free(&m_pFrame);
+ avcodec_free_context(&m_pCodecContext);
+}
+
+std::unique_ptr<CDVDVideoCodec> CDVDVideoCodecDRMPRIME::Create(CProcessInfo& processInfo)
+{
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_VIDEOPLAYER_USEPRIMEDECODER))
+ return std::make_unique<CDVDVideoCodecDRMPRIME>(processInfo);
+
+ return nullptr;
+}
+
+void CDVDVideoCodecDRMPRIME::Register()
+{
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ auto setting = settings->GetSetting(CSettings::SETTING_VIDEOPLAYER_USEPRIMEDECODER);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}",
+ CSettings::SETTING_VIDEOPLAYER_USEPRIMEDECODER);
+ return;
+ }
+
+ setting->SetVisible(true);
+
+ setting = settings->GetSetting(SETTING_VIDEOPLAYER_USEPRIMEDECODERFORHW);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", SETTING_VIDEOPLAYER_USEPRIMEDECODERFORHW);
+ return;
+ }
+
+ setting->SetVisible(true);
+
+ CDVDFactoryCodec::RegisterHWVideoCodec("drm_prime", CDVDVideoCodecDRMPRIME::Create);
+}
+
+static bool IsSupportedHwFormat(const enum AVPixelFormat fmt)
+{
+ bool hw = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOPLAYER_USEPRIMEDECODERFORHW);
+
+ return fmt == AV_PIX_FMT_DRM_PRIME && hw;
+}
+
+static bool IsSupportedSwFormat(const enum AVPixelFormat fmt)
+{
+ return fmt == AV_PIX_FMT_YUV420P || fmt == AV_PIX_FMT_YUVJ420P;
+}
+
+static const AVCodecHWConfig* FindHWConfig(const AVCodec* codec)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOPLAYER_USEPRIMEDECODERFORHW))
+ return nullptr;
+
+ const AVCodecHWConfig* config = nullptr;
+ for (int n = 0; (config = avcodec_get_hw_config(codec, n)); n++)
+ {
+ if (!IsSupportedHwFormat(config->pix_fmt))
+ continue;
+
+ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) &&
+ config->device_type == AV_HWDEVICE_TYPE_DRM)
+ return config;
+
+ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_INTERNAL))
+ return config;
+ }
+
+ return nullptr;
+}
+
+static const AVCodec* FindDecoder(CDVDStreamInfo& hints)
+{
+ const AVCodec* codec = nullptr;
+ void* i = 0;
+
+ if (!(hints.codecOptions & CODEC_FORCE_SOFTWARE))
+ while ((codec = av_codec_iterate(&i)))
+ {
+ if (!av_codec_is_decoder(codec))
+ continue;
+ if (codec->id != hints.codec)
+ continue;
+
+ const AVCodecHWConfig* config = FindHWConfig(codec);
+ if (config)
+ return codec;
+ }
+
+ codec = avcodec_find_decoder(hints.codec);
+ if (codec && (codec->capabilities & AV_CODEC_CAP_DR1) == AV_CODEC_CAP_DR1)
+ return codec;
+
+ return nullptr;
+}
+
+enum AVPixelFormat CDVDVideoCodecDRMPRIME::GetFormat(struct AVCodecContext* avctx,
+ const enum AVPixelFormat* fmt)
+{
+ for (int n = 0; fmt[n] != AV_PIX_FMT_NONE; n++)
+ {
+ if (IsSupportedHwFormat(fmt[n]) || IsSupportedSwFormat(fmt[n]))
+ {
+ CDVDVideoCodecDRMPRIME* ctx = static_cast<CDVDVideoCodecDRMPRIME*>(avctx->opaque);
+ ctx->UpdateProcessInfo(avctx, fmt[n]);
+ return fmt[n];
+ }
+ }
+
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - unsupported pixel format", __FUNCTION__);
+ return AV_PIX_FMT_NONE;
+}
+
+int CDVDVideoCodecDRMPRIME::GetBuffer(struct AVCodecContext* avctx, AVFrame* frame, int flags)
+{
+ if (IsSupportedSwFormat(static_cast<AVPixelFormat>(frame->format)))
+ {
+ int width = frame->width;
+ int height = frame->height;
+
+ AlignedSize(avctx, width, height);
+
+ int size;
+ switch (avctx->pix_fmt)
+ {
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_YUVJ420P:
+ size = width * height * 3 / 2;
+ break;
+ default:
+ return -1;
+ }
+
+ CDVDVideoCodecDRMPRIME* ctx = static_cast<CDVDVideoCodecDRMPRIME*>(avctx->opaque);
+ auto buffer = dynamic_cast<CVideoBufferDMA*>(
+ ctx->m_processInfo.GetVideoBufferManager().Get(avctx->pix_fmt, size, nullptr));
+ if (!buffer)
+ return -1;
+
+ frame->opaque = static_cast<void*>(buffer);
+ frame->opaque_ref =
+ av_buffer_create(nullptr, 0, ReleaseBuffer, frame->opaque, AV_BUFFER_FLAG_READONLY);
+
+ buffer->Export(frame, width, height);
+ buffer->SyncStart();
+
+ return 0;
+ }
+
+ return avcodec_default_get_buffer2(avctx, frame, flags);
+}
+
+bool CDVDVideoCodecDRMPRIME::Open(CDVDStreamInfo& hints, CDVDCodecOptions& options)
+{
+ const AVCodec* pCodec = FindDecoder(hints);
+ if (!pCodec)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - unable to find decoder for codec {}",
+ __FUNCTION__, hints.codec);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDVDVideoCodecDRMPRIME::{} - using decoder {}", __FUNCTION__,
+ pCodec->long_name ? pCodec->long_name : pCodec->name);
+
+ m_pCodecContext = avcodec_alloc_context3(pCodec);
+ if (!m_pCodecContext)
+ return false;
+
+ m_hints = hints;
+
+ const AVCodecHWConfig* pConfig = FindHWConfig(pCodec);
+ if (pConfig && (pConfig->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) &&
+ pConfig->device_type == AV_HWDEVICE_TYPE_DRM)
+ {
+ const char* device = nullptr;
+
+ if (getenv("KODI_RENDER_NODE"))
+ device = getenv("KODI_RENDER_NODE");
+
+#if defined(HAVE_GBM)
+ auto winSystem = dynamic_cast<KODI::WINDOWING::GBM::CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+
+ if (winSystem)
+ {
+ auto drm = winSystem->GetDrm();
+
+ if (!drm)
+ return false;
+
+ if (!device)
+ device = drm->GetRenderDevicePath();
+ }
+#endif
+
+ //! @todo: fix with proper device when dma-hints wayland protocol works
+ if (!device)
+ device = "/dev/dri/renderD128";
+
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - using drm device for av_hwdevice_ctx: {}", __FUNCTION__, device);
+
+ if (av_hwdevice_ctx_create(&m_pCodecContext->hw_device_ctx, pConfig->device_type,
+ device, nullptr, 0) < 0)
+ {
+ CLog::Log(LOGERROR,
+ "CDVDVideoCodecDRMPRIME::{} - unable to create hwdevice context using device: {}",
+ __FUNCTION__, device);
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+ }
+
+ m_pCodecContext->pix_fmt = AV_PIX_FMT_DRM_PRIME;
+ m_pCodecContext->opaque = static_cast<void*>(this);
+ m_pCodecContext->get_format = GetFormat;
+ m_pCodecContext->get_buffer2 = GetBuffer;
+ m_pCodecContext->codec_tag = hints.codec_tag;
+ m_pCodecContext->coded_width = hints.width;
+ m_pCodecContext->coded_height = hints.height;
+ m_pCodecContext->bits_per_coded_sample = hints.bitsperpixel;
+ m_pCodecContext->time_base.num = 1;
+ m_pCodecContext->time_base.den = DVD_TIME_BASE;
+ m_pCodecContext->thread_count = CServiceBroker::GetCPUInfo()->GetCPUCount();
+
+ if (hints.extradata && hints.extrasize > 0)
+ {
+ m_pCodecContext->extradata_size = hints.extrasize;
+ m_pCodecContext->extradata =
+ static_cast<uint8_t*>(av_mallocz(hints.extrasize + AV_INPUT_BUFFER_PADDING_SIZE));
+ memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize);
+ }
+
+ for (auto&& option : options.m_keys)
+ av_opt_set(m_pCodecContext, option.m_name.c_str(), option.m_value.c_str(), 0);
+
+ if (avcodec_open2(m_pCodecContext, pCodec, nullptr) < 0)
+ {
+ CLog::Log(LOGINFO, "CDVDVideoCodecDRMPRIME::{} - unable to open codec", __FUNCTION__);
+ avcodec_free_context(&m_pCodecContext);
+ if (hints.codecOptions & CODEC_FORCE_SOFTWARE)
+ return false;
+
+ hints.codecOptions |= CODEC_FORCE_SOFTWARE;
+ return Open(hints, options);
+ }
+
+ UpdateProcessInfo(m_pCodecContext, m_pCodecContext->pix_fmt);
+ m_processInfo.SetVideoDeintMethod("none");
+ m_processInfo.SetVideoDAR(hints.aspect);
+
+ return true;
+}
+
+void CDVDVideoCodecDRMPRIME::UpdateProcessInfo(struct AVCodecContext* avctx,
+ const enum AVPixelFormat pix_fmt)
+{
+ const char* pixFmtName = av_get_pix_fmt_name(pix_fmt);
+ m_processInfo.SetVideoPixelFormat(pixFmtName ? pixFmtName : "");
+ m_processInfo.SetVideoDimensions(avctx->coded_width, avctx->coded_height);
+
+ if (avctx->codec && avctx->codec->name)
+ m_name = std::string("ff-") + avctx->codec->name;
+ else
+ m_name = "ffmpeg";
+
+ m_processInfo.SetVideoDecoderName(m_name + "-drm_prime", IsSupportedHwFormat(pix_fmt));
+}
+
+bool CDVDVideoCodecDRMPRIME::AddData(const DemuxPacket& packet)
+{
+ if (!m_pCodecContext)
+ return true;
+
+ if (!packet.pData)
+ return true;
+
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ avpkt->data = packet.pData;
+ avpkt->size = packet.iSize;
+ avpkt->dts = (packet.dts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.dts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->pts = (packet.pts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.pts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->side_data = static_cast<AVPacketSideData*>(packet.pSideData);
+ avpkt->side_data_elems = packet.iSideDataElems;
+
+ int ret = avcodec_send_packet(m_pCodecContext, avpkt);
+
+ //! @todo: properly handle avpkt side_data. this works around our improper use of the side_data
+ // as we pass pointers to ffmpeg allocated memory for the side_data. we should really be allocating
+ // and storing our own AVPacket. This will require some extensive changes.
+ av_buffer_unref(&avpkt->buf);
+ av_free(avpkt);
+
+ if (ret == AVERROR(EAGAIN))
+ return false;
+ else if (ret)
+ {
+ char err[AV_ERROR_MAX_STRING_SIZE] = {};
+ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - send packet failed: {} ({})", __FUNCTION__,
+ err, ret);
+ if (ret != AVERROR_EOF && ret != AVERROR_INVALIDDATA)
+ return false;
+ }
+
+ return true;
+}
+
+void CDVDVideoCodecDRMPRIME::Reset()
+{
+ if (!m_pCodecContext)
+ return;
+
+ Drain();
+
+ do
+ {
+ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame);
+ if (ret == AVERROR_EOF)
+ break;
+ else if (ret)
+ {
+ char err[AV_ERROR_MAX_STRING_SIZE] = {};
+ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})",
+ __FUNCTION__, err, ret);
+ break;
+ }
+ else
+ av_frame_unref(m_pFrame);
+ } while (true);
+
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - flush buffers", __FUNCTION__);
+ avcodec_flush_buffers(m_pCodecContext);
+}
+
+void CDVDVideoCodecDRMPRIME::Drain()
+{
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return;
+ }
+
+ avpkt->data = nullptr;
+ avpkt->size = 0;
+
+ int ret = avcodec_send_packet(m_pCodecContext, avpkt);
+ if (ret && ret != AVERROR_EOF)
+ {
+ char err[AV_ERROR_MAX_STRING_SIZE] = {};
+ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - send packet failed: {} ({})", __FUNCTION__,
+ err, ret);
+ }
+
+ av_packet_free(&avpkt);
+}
+
+void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture)
+{
+ pVideoPicture->iWidth = m_pFrame->width;
+ pVideoPicture->iHeight = m_pFrame->height;
+
+ double aspect_ratio = 0;
+ AVRational pixel_aspect = m_pFrame->sample_aspect_ratio;
+ if (pixel_aspect.num)
+ aspect_ratio = av_q2d(pixel_aspect) * pVideoPicture->iWidth / pVideoPicture->iHeight;
+
+ if (aspect_ratio <= 0.0)
+ aspect_ratio =
+ static_cast<double>(pVideoPicture->iWidth) / static_cast<double>(pVideoPicture->iHeight);
+
+ if (m_DAR != aspect_ratio)
+ {
+ m_DAR = aspect_ratio;
+ m_processInfo.SetVideoDAR(static_cast<float>(m_DAR));
+ }
+
+ pVideoPicture->iDisplayWidth =
+ (static_cast<int>(lrint(pVideoPicture->iHeight * aspect_ratio))) & -3;
+ pVideoPicture->iDisplayHeight = pVideoPicture->iHeight;
+ if (pVideoPicture->iDisplayWidth > pVideoPicture->iWidth)
+ {
+ pVideoPicture->iDisplayWidth = pVideoPicture->iWidth;
+ pVideoPicture->iDisplayHeight =
+ (static_cast<int>(lrint(pVideoPicture->iWidth / aspect_ratio))) & -3;
+ }
+
+ pVideoPicture->color_range = m_pFrame->color_range == AVCOL_RANGE_JPEG ||
+ m_pFrame->format == AV_PIX_FMT_YUVJ420P ||
+ m_hints.colorRange == AVCOL_RANGE_JPEG;
+ pVideoPicture->color_primaries = m_pFrame->color_primaries == AVCOL_PRI_UNSPECIFIED
+ ? m_hints.colorPrimaries
+ : m_pFrame->color_primaries;
+ pVideoPicture->color_transfer = m_pFrame->color_trc == AVCOL_TRC_UNSPECIFIED
+ ? m_hints.colorTransferCharacteristic
+ : m_pFrame->color_trc;
+ pVideoPicture->color_space =
+ m_pFrame->colorspace == AVCOL_SPC_UNSPECIFIED ? m_hints.colorSpace : m_pFrame->colorspace;
+ pVideoPicture->chroma_position = m_pFrame->chroma_location;
+
+ pVideoPicture->colorBits = 8;
+ if (m_pCodecContext->codec_id == AV_CODEC_ID_HEVC &&
+ m_pCodecContext->profile == FF_PROFILE_HEVC_MAIN_10)
+ pVideoPicture->colorBits = 10;
+ else if (m_pCodecContext->codec_id == AV_CODEC_ID_H264 &&
+ (m_pCodecContext->profile == FF_PROFILE_H264_HIGH_10 ||
+ m_pCodecContext->profile == FF_PROFILE_H264_HIGH_10_INTRA))
+ pVideoPicture->colorBits = 10;
+
+ pVideoPicture->hasDisplayMetadata = false;
+ AVFrameSideData* sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+ if (sd)
+ {
+ pVideoPicture->displayMetadata = *reinterpret_cast<AVMasteringDisplayMetadata*>(sd->data);
+ pVideoPicture->hasDisplayMetadata = true;
+ }
+ else if (m_hints.masteringMetadata)
+ {
+ pVideoPicture->displayMetadata = *m_hints.masteringMetadata.get();
+ pVideoPicture->hasDisplayMetadata = true;
+ }
+
+ pVideoPicture->hasLightMetadata = false;
+ sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+ if (sd)
+ {
+ pVideoPicture->lightMetadata = *reinterpret_cast<AVContentLightMetadata*>(sd->data);
+ pVideoPicture->hasLightMetadata = true;
+ }
+ else if (m_hints.contentLightMetadata)
+ {
+ pVideoPicture->lightMetadata = *m_hints.contentLightMetadata.get();
+ pVideoPicture->hasLightMetadata = true;
+ }
+
+ pVideoPicture->iRepeatPicture = 0;
+ pVideoPicture->iFlags = 0;
+ pVideoPicture->iFlags |= m_pFrame->interlaced_frame ? DVP_FLAG_INTERLACED : 0;
+ pVideoPicture->iFlags |= m_pFrame->top_field_first ? DVP_FLAG_TOP_FIELD_FIRST : 0;
+ pVideoPicture->iFlags |= m_pFrame->data[0] ? 0 : DVP_FLAG_DROPPED;
+
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DROP)
+ {
+ pVideoPicture->iFlags |= DVP_FLAG_DROPPED;
+ }
+
+ int64_t pts = m_pFrame->best_effort_timestamp;
+ pVideoPicture->pts = (pts == AV_NOPTS_VALUE)
+ ? DVD_NOPTS_VALUE
+ : static_cast<double>(pts) * DVD_TIME_BASE / AV_TIME_BASE;
+ pVideoPicture->dts = DVD_NOPTS_VALUE;
+}
+
+CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideoPicture)
+{
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN)
+ Drain();
+
+ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame);
+ if (ret == AVERROR(EAGAIN))
+ return VC_BUFFER;
+ else if (ret == AVERROR_EOF)
+ {
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - flush buffers", __FUNCTION__);
+ avcodec_flush_buffers(m_pCodecContext);
+ SetCodecControl(m_codecControlFlags & ~DVD_CODEC_CTRL_DRAIN);
+ }
+ return VC_EOF;
+ }
+ else if (ret)
+ {
+ char err[AV_ERROR_MAX_STRING_SIZE] = {};
+ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})", __FUNCTION__,
+ err, ret);
+ return VC_ERROR;
+ }
+
+ SetPictureParams(pVideoPicture);
+
+ if (pVideoPicture->videoBuffer)
+ {
+ pVideoPicture->videoBuffer->Release();
+ pVideoPicture->videoBuffer = nullptr;
+ }
+
+ if (IsSupportedHwFormat(static_cast<AVPixelFormat>(m_pFrame->format)))
+ {
+ CVideoBufferDRMPRIMEFFmpeg* buffer =
+ dynamic_cast<CVideoBufferDRMPRIMEFFmpeg*>(m_videoBufferPool->Get());
+ buffer->SetPictureParams(*pVideoPicture);
+ buffer->SetRef(m_pFrame);
+ pVideoPicture->videoBuffer = buffer;
+ }
+ else if (m_pFrame->opaque)
+ {
+ CVideoBufferDMA* buffer = static_cast<CVideoBufferDMA*>(m_pFrame->opaque);
+ buffer->SetPictureParams(*pVideoPicture);
+ buffer->Acquire();
+ buffer->SyncEnd();
+ buffer->SetDimensions(m_pFrame->width, m_pFrame->height);
+
+ pVideoPicture->videoBuffer = buffer;
+ av_frame_unref(m_pFrame);
+ }
+
+ if (!pVideoPicture->videoBuffer)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - videoBuffer:nullptr format:{}", __FUNCTION__,
+ av_get_pix_fmt_name(static_cast<AVPixelFormat>(m_pFrame->format)));
+ av_frame_unref(m_pFrame);
+ return VC_ERROR;
+ }
+
+ return VC_PICTURE;
+}
+
+void CDVDVideoCodecDRMPRIME::SetCodecControl(int flags)
+{
+ m_codecControlFlags = flags;
+
+ if (m_pCodecContext)
+ {
+ if ((flags & DVD_CODEC_CTRL_DROP_ANY) != 0)
+ {
+ m_pCodecContext->skip_frame = AVDISCARD_NONREF;
+ m_pCodecContext->skip_idct = AVDISCARD_NONREF;
+ m_pCodecContext->skip_loop_filter = AVDISCARD_NONREF;
+ }
+ else
+ {
+ m_pCodecContext->skip_frame = AVDISCARD_DEFAULT;
+ m_pCodecContext->skip_idct = AVDISCARD_DEFAULT;
+ m_pCodecContext->skip_loop_filter = AVDISCARD_DEFAULT;
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h
new file mode 100644
index 0000000..db49d16
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoPlayer/DVDStreamInfo.h"
+
+#include <memory>
+
+class CDVDVideoCodecDRMPRIME : public CDVDVideoCodec
+{
+public:
+ explicit CDVDVideoCodecDRMPRIME(CProcessInfo& processInfo);
+ ~CDVDVideoCodecDRMPRIME() override;
+
+ static std::unique_ptr<CDVDVideoCodec> Create(CProcessInfo& processInfo);
+ static void Register();
+
+ bool Open(CDVDStreamInfo& hints, CDVDCodecOptions& options) override;
+ bool AddData(const DemuxPacket& packet) override;
+ void Reset() override;
+ CDVDVideoCodec::VCReturn GetPicture(VideoPicture* pVideoPicture) override;
+ const char* GetName() override { return m_name.c_str(); }
+ unsigned GetAllowedReferences() override { return 5; }
+ void SetCodecControl(int flags) override;
+
+protected:
+ void Drain();
+ void SetPictureParams(VideoPicture* pVideoPicture);
+ void UpdateProcessInfo(struct AVCodecContext* avctx, const enum AVPixelFormat fmt);
+ static enum AVPixelFormat GetFormat(struct AVCodecContext* avctx, const enum AVPixelFormat* fmt);
+ static int GetBuffer(struct AVCodecContext* avctx, AVFrame* frame, int flags);
+
+ std::string m_name;
+ int m_codecControlFlags = 0;
+ CDVDStreamInfo m_hints;
+ double m_DAR = 1.0;
+ AVCodecContext* m_pCodecContext = nullptr;
+ AVFrame* m_pFrame = nullptr;
+ std::shared_ptr<IVideoBufferPool> m_videoBufferPool;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp
new file mode 100644
index 0000000..881c02e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp
@@ -0,0 +1,1380 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDVideoCodecFFmpeg.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "DVDStreamInfo.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "cores/VideoSettings.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CPUInfo.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <mutex>
+
+extern "C" {
+#include <libavutil/opt.h>
+#include <libavutil/mastering_display_metadata.h>
+#include <libavfilter/avfilter.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+#include <libavutil/pixdesc.h>
+}
+
+#ifndef TARGET_POSIX
+#define RINT(x) ((x) >= 0 ? ((int)((x) + 0.5)) : ((int)((x) - 0.5)))
+#else
+#include <math.h>
+#define RINT lrint
+#endif
+
+enum DecoderState
+{
+ STATE_NONE,
+ STATE_SW_SINGLE,
+ STATE_HW_SINGLE,
+ STATE_HW_FAILED,
+ STATE_SW_MULTI
+};
+
+enum EFilterFlags {
+ FILTER_NONE = 0x0,
+ FILTER_DEINTERLACE_YADIF = 0x1, //< use first deinterlace mode
+ FILTER_DEINTERLACE_ANY = 0xf, //< use any deinterlace mode
+ FILTER_DEINTERLACE_FLAGGED = 0x10, //< only deinterlace flagged frames
+ FILTER_DEINTERLACE_HALFED = 0x20, //< do half rate deinterlacing
+ FILTER_ROTATE = 0x40, //< rotate image according to the codec hints
+};
+
+//------------------------------------------------------------------------------
+// Video Buffers
+//------------------------------------------------------------------------------
+
+class CVideoBufferFFmpeg : public CVideoBuffer
+{
+public:
+ CVideoBufferFFmpeg(IVideoBufferPool &pool, int id);
+ ~CVideoBufferFFmpeg() override;
+ void GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES]) override;
+ void GetStrides(int(&strides)[YuvImage::MAX_PLANES]) override;
+
+ void SetRef(AVFrame *frame);
+ void Unref();
+
+protected:
+ AVFrame* m_pFrame;
+};
+
+CVideoBufferFFmpeg::CVideoBufferFFmpeg(IVideoBufferPool &pool, int id)
+: CVideoBuffer(id)
+{
+ m_pFrame = av_frame_alloc();
+}
+
+CVideoBufferFFmpeg::~CVideoBufferFFmpeg()
+{
+ av_frame_free(&m_pFrame);
+}
+
+void CVideoBufferFFmpeg::GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES])
+{
+ planes[0] = m_pFrame->data[0];
+ planes[1] = m_pFrame->data[1];
+ planes[2] = m_pFrame->data[2];
+}
+
+void CVideoBufferFFmpeg::GetStrides(int(&strides)[YuvImage::MAX_PLANES])
+{
+ strides[0] = m_pFrame->linesize[0];
+ strides[1] = m_pFrame->linesize[1];
+ strides[2] = m_pFrame->linesize[2];
+}
+
+void CVideoBufferFFmpeg::SetRef(AVFrame *frame)
+{
+ av_frame_unref(m_pFrame);
+ av_frame_move_ref(m_pFrame, frame);
+ m_pixFormat = (AVPixelFormat)m_pFrame->format;
+}
+
+void CVideoBufferFFmpeg::Unref()
+{
+ av_frame_unref(m_pFrame);
+}
+
+//------------------------------------------------------------------------------
+
+class CVideoBufferPoolFFmpeg : public IVideoBufferPool
+{
+public:
+ ~CVideoBufferPoolFFmpeg() override;
+ void Return(int id) override;
+ CVideoBuffer* Get() override;
+
+protected:
+ CCriticalSection m_critSection;
+ std::vector<CVideoBufferFFmpeg*> m_all;
+ std::deque<int> m_used;
+ std::deque<int> m_free;
+};
+
+CVideoBufferPoolFFmpeg::~CVideoBufferPoolFFmpeg()
+{
+ for (auto buf : m_all)
+ {
+ delete buf;
+ }
+}
+
+CVideoBuffer* CVideoBufferPoolFFmpeg::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CVideoBufferFFmpeg *buf = nullptr;
+ if (!m_free.empty())
+ {
+ int idx = m_free.front();
+ m_free.pop_front();
+ m_used.push_back(idx);
+ buf = m_all[idx];
+ }
+ else
+ {
+ int id = m_all.size();
+ buf = new CVideoBufferFFmpeg(*this, id);
+ m_all.push_back(buf);
+ m_used.push_back(id);
+ }
+
+ buf->Acquire(GetPtr());
+ return buf;
+}
+
+void CVideoBufferPoolFFmpeg::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_all[id]->Unref();
+ auto it = m_used.begin();
+ while (it != m_used.end())
+ {
+ if (*it == id)
+ {
+ m_used.erase(it);
+ break;
+ }
+ else
+ ++it;
+ }
+ m_free.push_back(id);
+}
+
+//------------------------------------------------------------------------------
+// main class
+//------------------------------------------------------------------------------
+
+CDVDVideoCodecFFmpeg::CDropControl::CDropControl()
+{
+ Reset(true);
+}
+
+void CDVDVideoCodecFFmpeg::CDropControl::Reset(bool init)
+{
+ m_lastPTS = AV_NOPTS_VALUE;
+
+ if (init || m_state != VALID)
+ {
+ m_count = 0;
+ m_diffPTS = 0;
+ m_state = INIT;
+ }
+}
+
+void CDVDVideoCodecFFmpeg::CDropControl::Process(int64_t pts, bool drop)
+{
+ if (m_state == INIT)
+ {
+ if (pts != AV_NOPTS_VALUE && m_lastPTS != AV_NOPTS_VALUE)
+ {
+ m_diffPTS += pts - m_lastPTS;
+ m_count++;
+ }
+ if (m_count > 10)
+ {
+ m_diffPTS = m_diffPTS / m_count;
+ if (m_diffPTS > 0)
+ {
+ CLog::Log(LOGINFO, "CDVDVideoCodecFFmpeg::CDropControl: calculated diff time: {}",
+ m_diffPTS);
+ m_state = CDropControl::VALID;
+ m_count = 0;
+ }
+ }
+ }
+ else if (m_state == VALID && !drop)
+ {
+ if (std::abs(pts - m_lastPTS - m_diffPTS) > m_diffPTS * 0.2)
+ {
+ m_count++;
+ if (m_count > 5)
+ {
+ CLog::Log(LOGINFO, "CDVDVideoCodecFFmpeg::CDropControl: lost diff");
+ Reset(true);
+ }
+ }
+ else
+ m_count = 0;
+ }
+ m_lastPTS = pts;
+}
+
+enum AVPixelFormat CDVDVideoCodecFFmpeg::GetFormat(struct AVCodecContext * avctx, const AVPixelFormat * fmt)
+{
+ ICallbackHWAccel *cb = static_cast<ICallbackHWAccel*>(avctx->opaque);
+ CDVDVideoCodecFFmpeg* ctx = dynamic_cast<CDVDVideoCodecFFmpeg*>(cb);
+
+ const char* pixFmtName = av_get_pix_fmt_name(*fmt);
+
+ ctx->m_processInfo.SetVideoDimensions(avctx->coded_width, avctx->coded_height);
+
+ // if frame threading is enabled hw accel is not allowed
+ // 2nd condition:
+ // fix an ffmpeg issue here, it calls us with an invalid profile
+ // then a 2nd call with a valid one
+ if(ctx->m_decoderState != STATE_HW_SINGLE ||
+ (avctx->codec_id == AV_CODEC_ID_VC1 && avctx->profile == FF_PROFILE_UNKNOWN))
+ {
+ AVPixelFormat defaultFmt = avcodec_default_get_format(avctx, fmt);
+ pixFmtName = av_get_pix_fmt_name(defaultFmt);
+ ctx->m_processInfo.SetVideoPixelFormat(pixFmtName ? pixFmtName : "");
+ ctx->m_processInfo.SetSwDeinterlacingMethods();
+ return defaultFmt;
+ }
+
+ // hardware decoder de-selected, restore standard ffmpeg
+ if (ctx->HasHardware())
+ {
+ ctx->SetHardware(nullptr);
+ avctx->get_buffer2 = avcodec_default_get_buffer2;
+ avctx->slice_flags = 0;
+ av_buffer_unref(&avctx->hw_frames_ctx);
+ }
+
+ const AVPixelFormat * cur = fmt;
+ while (*cur != AV_PIX_FMT_NONE)
+ {
+ pixFmtName = av_get_pix_fmt_name(*cur);
+
+ auto hwaccels = CDVDFactoryCodec::GetHWAccels();
+ for (auto &hwaccel : hwaccels)
+ {
+ IHardwareDecoder *pDecoder(CDVDFactoryCodec::CreateVideoCodecHWAccel(hwaccel, ctx->m_hints,
+ ctx->m_processInfo, *cur));
+ if (pDecoder)
+ {
+ if (pDecoder->Open(avctx, ctx->m_pCodecContext, *cur))
+ {
+ ctx->m_processInfo.SetVideoPixelFormat(pixFmtName ? pixFmtName : "");
+ ctx->SetHardware(pDecoder);
+ return *cur;
+ }
+ pDecoder->Release();
+ }
+ }
+ cur++;
+ }
+
+ ctx->m_processInfo.SetVideoPixelFormat(pixFmtName ? pixFmtName : "");
+ ctx->m_decoderState = STATE_HW_FAILED;
+ return avcodec_default_get_format(avctx, fmt);
+}
+
+CDVDVideoCodecFFmpeg::CDVDVideoCodecFFmpeg(CProcessInfo &processInfo)
+: CDVDVideoCodec(processInfo), m_postProc(processInfo)
+{
+ m_videoBufferPool = std::make_shared<CVideoBufferPoolFFmpeg>();
+
+ m_decoderState = STATE_NONE;
+}
+
+CDVDVideoCodecFFmpeg::~CDVDVideoCodecFFmpeg()
+{
+ Dispose();
+}
+
+bool CDVDVideoCodecFFmpeg::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options)
+{
+ if (hints.cryptoSession)
+ {
+ CLog::Log(LOGERROR,"CDVDVideoCodecFFmpeg::Open() CryptoSessions unsupported!");
+ return false;
+ }
+
+ m_hints = hints;
+ m_options = options;
+
+ AVCodec* pCodec = nullptr;
+
+ m_iOrientation = hints.orientation;
+
+ m_formats.clear();
+ m_formats = m_processInfo.GetPixFormats();
+ m_formats.push_back(AV_PIX_FMT_NONE); /* always add none to get a terminated list in ffmpeg world */
+ m_processInfo.SetSwDeinterlacingMethods();
+ m_processInfo.SetVideoInterlaced(false);
+
+ // libdav1d av1 sw decoding is implemented as a separate decoder
+ // in ffmpeg which is always found first when calling `avcodec_find_decoder`.
+ // To get hwaccels we look for decoders registered for `av1` (unless sw decoding is enforced).
+ // The decoder state check is needed to succesfully fallback to sw decoding if
+ // necessary (on retry).
+ if (hints.codec == AV_CODEC_ID_AV1 && m_decoderState != STATE_HW_FAILED &&
+ !(hints.codecOptions & CODEC_FORCE_SOFTWARE))
+ pCodec = avcodec_find_decoder_by_name("av1");
+
+ if (!pCodec)
+ pCodec = avcodec_find_decoder(hints.codec);
+
+ if(pCodec == NULL)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg::Open() Unable to find codec {}", hints.codec);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDVDVideoCodecFFmpeg::Open() Using codec: {}",
+ pCodec->long_name ? pCodec->long_name : pCodec->name);
+
+ m_pCodecContext = avcodec_alloc_context3(pCodec);
+ if (!m_pCodecContext)
+ return false;
+
+ m_pCodecContext->opaque = static_cast<ICallbackHWAccel*>(this);
+ m_pCodecContext->debug = 0;
+ m_pCodecContext->workaround_bugs = FF_BUG_AUTODETECT;
+ m_pCodecContext->get_format = GetFormat;
+ m_pCodecContext->codec_tag = hints.codec_tag;
+
+ // setup threading model
+ if (!(hints.codecOptions & CODEC_FORCE_SOFTWARE))
+ {
+ if (m_decoderState == STATE_NONE)
+ {
+ m_decoderState = STATE_HW_SINGLE;
+ }
+ else
+ {
+ int num_threads = CServiceBroker::GetCPUInfo()->GetCPUCount() * 3 / 2;
+ num_threads = std::max(1, std::min(num_threads, 16));
+ m_pCodecContext->thread_count = num_threads;
+ m_decoderState = STATE_SW_MULTI;
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg - open frame threaded with {} threads",
+ num_threads);
+ }
+ }
+ else
+ m_decoderState = STATE_SW_SINGLE;
+
+ // if we don't do this, then some codecs seem to fail.
+ m_pCodecContext->coded_height = hints.height;
+ m_pCodecContext->coded_width = hints.width;
+ m_pCodecContext->bits_per_coded_sample = hints.bitsperpixel;
+ m_pCodecContext->bits_per_raw_sample = hints.bitdepth;
+
+ if( hints.extradata && hints.extrasize > 0 )
+ {
+ m_pCodecContext->extradata_size = hints.extrasize;
+ m_pCodecContext->extradata = (uint8_t*)av_mallocz(hints.extrasize + AV_INPUT_BUFFER_PADDING_SIZE);
+ memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize);
+ }
+
+ // advanced setting override for skip loop filter (see avcodec.h for valid options)
+ //! @todo allow per video setting?
+ int iSkipLoopFilter = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iSkipLoopFilter;
+ if (iSkipLoopFilter != 0)
+ {
+ m_pCodecContext->skip_loop_filter = static_cast<AVDiscard>(iSkipLoopFilter);
+ }
+
+ // set any special options
+ for(std::vector<CDVDCodecOption>::iterator it = options.m_keys.begin(); it != options.m_keys.end(); ++it)
+ {
+ av_opt_set(m_pCodecContext, it->m_name.c_str(), it->m_value.c_str(), 0);
+ }
+
+ if (avcodec_open2(m_pCodecContext, pCodec, nullptr) < 0)
+ {
+ CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Unable to open codec");
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+
+ m_pFrame = av_frame_alloc();
+ if (!m_pFrame)
+ {
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+
+ m_pDecodedFrame = av_frame_alloc();
+ if (!m_pDecodedFrame)
+ {
+ av_frame_free(&m_pFrame);
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+
+ m_pFilterFrame = av_frame_alloc();
+ if (!m_pFilterFrame)
+ {
+ av_frame_free(&m_pFrame);
+ av_frame_free(&m_pDecodedFrame);
+ avcodec_free_context(&m_pCodecContext);
+ return false;
+ }
+
+ UpdateName();
+ const char* pixFmtName = av_get_pix_fmt_name(m_pCodecContext->pix_fmt);
+ m_processInfo.SetVideoDimensions(m_pCodecContext->coded_width, m_pCodecContext->coded_height);
+ m_processInfo.SetVideoPixelFormat(pixFmtName ? pixFmtName : "");
+
+ m_dropCtrl.Reset(true);
+ m_eof = false;
+ return true;
+}
+
+void CDVDVideoCodecFFmpeg::Dispose()
+{
+ av_frame_free(&m_pFrame);
+ av_frame_free(&m_pDecodedFrame);
+ av_frame_free(&m_pFilterFrame);
+ avcodec_free_context(&m_pCodecContext);
+
+ if (m_pHardware)
+ {
+ m_pHardware->Release();
+ m_pHardware = nullptr;
+ }
+
+ FilterClose();
+}
+
+void CDVDVideoCodecFFmpeg::SetFilters()
+{
+ // ask codec to do deinterlacing if possible
+ EINTERLACEMETHOD mInt = m_processInfo.GetVideoSettings().m_InterlaceMethod;
+
+ if (!m_processInfo.Supports(mInt))
+ mInt = m_processInfo.GetFallbackDeintMethod();
+
+ unsigned int filters = 0;
+
+ if (mInt != VS_INTERLACEMETHOD_NONE && m_interlaced)
+ {
+ if (mInt == VS_INTERLACEMETHOD_DEINTERLACE)
+ filters = FILTER_DEINTERLACE_ANY;
+ else if (mInt == VS_INTERLACEMETHOD_DEINTERLACE_HALF)
+ filters = FILTER_DEINTERLACE_ANY | FILTER_DEINTERLACE_HALFED;
+
+ if (filters)
+ filters |= FILTER_DEINTERLACE_FLAGGED;
+ }
+
+ if (m_codecControlFlags & DVD_CODEC_CTRL_ROTATE)
+ filters |= FILTER_ROTATE;
+
+ m_filters_next.clear();
+
+ if (filters & FILTER_ROTATE)
+ {
+ switch(m_iOrientation)
+ {
+ case 90:
+ m_filters_next += "transpose=1";
+ break;
+ case 180:
+ m_filters_next += "vflip,hflip";
+ break;
+ case 270:
+ m_filters_next += "transpose=2";
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (filters & FILTER_DEINTERLACE_YADIF)
+ {
+ if (filters & FILTER_DEINTERLACE_HALFED)
+ m_filters_next = "yadif=0:-1";
+ else
+ m_filters_next = "yadif=1:-1";
+
+ if (filters & FILTER_DEINTERLACE_FLAGGED)
+ m_filters_next += ":1";
+ }
+}
+
+void CDVDVideoCodecFFmpeg::UpdateName()
+{
+ if(m_pCodecContext->codec->name)
+ m_name = std::string("ff-") + m_pCodecContext->codec->name;
+ else
+ m_name = "ffmpeg";
+
+ if(m_pHardware)
+ m_name += "-" + m_pHardware->Name();
+
+ m_processInfo.SetVideoDecoderName(m_name, m_pHardware ? true : false);
+
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg - Updated codec: {}", m_name);
+}
+
+union pts_union
+{
+ double pts_d;
+ int64_t pts_i;
+};
+
+static int64_t pts_dtoi(double pts)
+{
+ pts_union u;
+ u.pts_d = pts;
+ return u.pts_i;
+}
+
+bool CDVDVideoCodecFFmpeg::AddData(const DemuxPacket &packet)
+{
+ if (!m_pCodecContext)
+ return true;
+
+ if (!packet.pData)
+ return true;
+
+ if (m_eof)
+ {
+ Reset();
+ }
+
+ if (packet.recoveryPoint)
+ m_started = true;
+
+ m_dts = packet.dts;
+ m_pCodecContext->reordered_opaque = pts_dtoi(packet.pts);
+
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ avpkt->data = packet.pData;
+ avpkt->size = packet.iSize;
+ avpkt->dts = (packet.dts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.dts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->pts = (packet.pts == DVD_NOPTS_VALUE)
+ ? AV_NOPTS_VALUE
+ : static_cast<int64_t>(packet.pts / DVD_TIME_BASE * AV_TIME_BASE);
+ avpkt->side_data = static_cast<AVPacketSideData*>(packet.pSideData);
+ avpkt->side_data_elems = packet.iSideDataElems;
+
+ int ret = avcodec_send_packet(m_pCodecContext, avpkt);
+
+ //! @todo: properly handle avpkt side_data. this works around our improper use of the side_data
+ // as we pass pointers to ffmpeg allocated memory for the side_data. we should really be allocating
+ // and storing our own AVPacket. This will require some extensive changes.
+ av_buffer_unref(&avpkt->buf);
+ av_free(avpkt);
+
+ // try again
+ if (ret == AVERROR(EAGAIN))
+ {
+ return false;
+ }
+ // error
+ else if (ret)
+ {
+ // handle VC_NOBUFFER error for hw accel
+ if (m_pHardware)
+ {
+ int result = m_pHardware->Check(m_pCodecContext);
+ if (result == VC_NOBUFFER)
+ {
+ return false;
+ }
+ }
+ }
+
+ m_iLastKeyframe++;
+ // put a limit on convergence count to avoid huge mem usage on streams without keyframes
+ if (m_iLastKeyframe > 300)
+ m_iLastKeyframe = 300;
+
+ m_startedInput = true;
+
+ return true;
+}
+
+CDVDVideoCodec::VCReturn CDVDVideoCodecFFmpeg::GetPicture(VideoPicture* pVideoPicture)
+{
+ if (!m_startedInput)
+ {
+ return VC_BUFFER;
+ }
+ else if (m_eof)
+ {
+ return VC_EOF;
+ }
+
+ // handle hw accelerators first, they may have frames ready
+ if (m_pHardware)
+ {
+ int flags = m_codecControlFlags;
+ flags &= ~DVD_CODEC_CTRL_DRAIN;
+ m_pHardware->SetCodecControl(flags);
+ CDVDVideoCodec::VCReturn ret = m_pHardware->Decode(m_pCodecContext, nullptr);
+ if (ret == VC_PICTURE)
+ {
+ if (m_pHardware->GetPicture(m_pCodecContext, pVideoPicture))
+ return VC_PICTURE;
+ else
+ return VC_ERROR;
+ }
+ else if (ret == VC_BUFFER)
+ ;
+ else
+ return ret;
+ }
+ else if (m_pFilterGraph && !m_filterEof)
+ {
+ CDVDVideoCodec::VCReturn ret = FilterProcess(nullptr);
+ if (ret == VC_PICTURE)
+ {
+ if (!SetPictureParams(pVideoPicture))
+ return VC_ERROR;
+ return VC_PICTURE;
+ }
+ else if (ret == VC_BUFFER)
+ ;
+ else
+ return ret;
+ }
+
+ // process ffmpeg
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN)
+ {
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return VC_ERROR;
+ }
+ avpkt->data = nullptr;
+ avpkt->size = 0;
+ avpkt->dts = AV_NOPTS_VALUE;
+ avpkt->pts = AV_NOPTS_VALUE;
+ avcodec_send_packet(m_pCodecContext, avpkt);
+
+ av_packet_free(&avpkt);
+ }
+
+ int ret = avcodec_receive_frame(m_pCodecContext, m_pDecodedFrame);
+
+ if (m_decoderState == STATE_HW_FAILED && !m_pHardware)
+ return VC_REOPEN;
+
+ if(m_iLastKeyframe < m_pCodecContext->has_b_frames + 2)
+ m_iLastKeyframe = m_pCodecContext->has_b_frames + 2;
+
+ if (ret == AVERROR_EOF)
+ {
+ // next drain hw accel or filter
+ if (m_pHardware)
+ {
+ int flags = m_codecControlFlags;
+ flags |= DVD_CODEC_CTRL_DRAIN;
+ m_pHardware->SetCodecControl(flags);
+ int ret = m_pHardware->Decode(m_pCodecContext, nullptr);
+ if (ret == VC_PICTURE)
+ {
+ if (m_pHardware->GetPicture(m_pCodecContext, pVideoPicture))
+ return VC_PICTURE;
+ else
+ return VC_ERROR;
+ }
+ else
+ {
+ m_eof = true;
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg::GetPicture - eof hw accel");
+ return VC_EOF;
+ }
+ }
+ else if (m_pFilterGraph && !m_filterEof)
+ {
+ int ret = FilterProcess(nullptr);
+ if (ret == VC_PICTURE)
+ {
+ if (!SetPictureParams(pVideoPicture))
+ return VC_ERROR;
+ else
+ return VC_PICTURE;
+ }
+ else
+ {
+ m_eof = true;
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg::GetPicture - eof filter");
+ return VC_EOF;
+ }
+ }
+ else
+ {
+ m_eof = true;
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg::GetPicture - eof");
+ return VC_EOF;
+ }
+ }
+ else if (ret == AVERROR(EAGAIN))
+ {
+ return VC_BUFFER;
+ }
+ else if (ret)
+ {
+ CLog::Log(LOGERROR, "{} - avcodec_receive_frame returned failure", __FUNCTION__);
+ return VC_ERROR;
+ }
+
+ // here we got a frame
+ int64_t framePTS = m_pDecodedFrame->best_effort_timestamp;
+
+ if (m_pCodecContext->skip_frame > AVDISCARD_DEFAULT)
+ {
+ if (m_dropCtrl.m_state == CDropControl::VALID &&
+ m_dropCtrl.m_lastPTS != AV_NOPTS_VALUE &&
+ framePTS != AV_NOPTS_VALUE &&
+ framePTS > (m_dropCtrl.m_lastPTS + m_dropCtrl.m_diffPTS * 1.5))
+ {
+ m_droppedFrames++;
+ if (m_interlaced)
+ m_droppedFrames++;
+ }
+ }
+ m_dropCtrl.Process(framePTS, m_pCodecContext->skip_frame > AVDISCARD_DEFAULT);
+
+ if (m_pDecodedFrame->key_frame)
+ {
+ m_started = true;
+ m_iLastKeyframe = m_pCodecContext->has_b_frames + 2;
+ }
+ if (m_pDecodedFrame->interlaced_frame)
+ m_interlaced = true;
+ else
+ m_interlaced = false;
+
+ if (!m_processInfo.GetVideoInterlaced() && m_interlaced)
+ m_processInfo.SetVideoInterlaced(m_interlaced);
+
+ if (!m_started)
+ {
+ int frames = 300;
+ if (m_dropCtrl.m_state == CDropControl::VALID)
+ frames = static_cast<int>(6000000 / m_dropCtrl.m_diffPTS);
+ if (m_iLastKeyframe >= frames && m_pDecodedFrame->pict_type == AV_PICTURE_TYPE_I)
+ {
+ m_started = true;
+ }
+ else
+ {
+ av_frame_unref(m_pDecodedFrame);
+ return VC_BUFFER;
+ }
+ }
+
+ // push the frame to hw decoder for further processing
+ if (m_pHardware)
+ {
+ av_frame_unref(m_pFrame);
+ av_frame_move_ref(m_pFrame, m_pDecodedFrame);
+ CDVDVideoCodec::VCReturn ret = m_pHardware->Decode(m_pCodecContext, m_pFrame);
+ if (ret == VC_FLUSHED)
+ {
+ Reset();
+ return ret;
+ }
+ else if (ret == VC_FATAL)
+ {
+ m_decoderState = STATE_HW_FAILED;
+ return VC_REOPEN;
+ }
+ else if (ret == VC_PICTURE)
+ {
+ if (m_pHardware->GetPicture(m_pCodecContext, pVideoPicture))
+ return VC_PICTURE;
+ else
+ return VC_ERROR;
+ }
+
+ return ret;
+ }
+ // process filters for sw decoding
+ else
+ {
+ SetFilters();
+
+ bool need_scale = std::find(m_formats.begin(),
+ m_formats.end(),
+ m_pCodecContext->pix_fmt) == m_formats.end();
+
+ bool need_reopen = false;
+ if (m_filters != m_filters_next)
+ need_reopen = true;
+
+ if (!m_filters_next.empty() && m_filterEof)
+ need_reopen = true;
+
+ if (m_pFilterIn)
+ {
+ if (m_pFilterIn->outputs[0]->format != m_pCodecContext->pix_fmt ||
+ m_pFilterIn->outputs[0]->w != m_pCodecContext->width ||
+ m_pFilterIn->outputs[0]->h != m_pCodecContext->height)
+ need_reopen = true;
+ }
+
+ // try to setup new filters
+ if (need_reopen || (need_scale && m_pFilterGraph == nullptr))
+ {
+ m_filters = m_filters_next;
+
+ if (FilterOpen(m_filters, need_scale) < 0)
+ FilterClose();
+ }
+
+ if (m_pFilterGraph && !m_filterEof)
+ {
+ CDVDVideoCodec::VCReturn ret = FilterProcess(m_pDecodedFrame);
+ if (ret != VC_PICTURE)
+ return VC_NONE;
+ }
+ else
+ {
+ av_frame_unref(m_pFrame);
+ av_frame_move_ref(m_pFrame, m_pDecodedFrame);
+ }
+
+ if (!SetPictureParams(pVideoPicture))
+ return VC_ERROR;
+ else
+ return VC_PICTURE;
+ }
+
+ return VC_NONE;
+}
+
+bool CDVDVideoCodecFFmpeg::SetPictureParams(VideoPicture* pVideoPicture)
+{
+ if (!GetPictureCommon(pVideoPicture))
+ return false;
+
+ pVideoPicture->iFlags |= m_pFrame->data[0] ? 0 : DVP_FLAG_DROPPED;
+
+ if (pVideoPicture->videoBuffer)
+ pVideoPicture->videoBuffer->Release();
+ pVideoPicture->videoBuffer = nullptr;
+
+ CVideoBufferFFmpeg *buffer = dynamic_cast<CVideoBufferFFmpeg*>(m_videoBufferPool->Get());
+ buffer->SetRef(m_pFrame);
+ pVideoPicture->videoBuffer = buffer;
+
+ if (m_processInfo.GetVideoSettings().m_PostProcess)
+ {
+ m_postProc.SetType(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoPPFFmpegPostProc, false);
+ m_postProc.Process(pVideoPicture);
+ }
+
+ return true;
+}
+
+void CDVDVideoCodecFFmpeg::Reset()
+{
+ m_started = false;
+ m_startedInput = false;
+ m_interlaced = false;
+ m_decoderPts = DVD_NOPTS_VALUE;
+ m_skippedDeint = 0;
+ m_droppedFrames = 0;
+ m_eof = false;
+ m_iLastKeyframe = m_pCodecContext->has_b_frames;
+ avcodec_flush_buffers(m_pCodecContext);
+ av_frame_unref(m_pFrame);
+
+ if (m_pHardware)
+ m_pHardware->Reset();
+
+ m_filters = "";
+ FilterClose();
+ m_dropCtrl.Reset(false);
+}
+
+void CDVDVideoCodecFFmpeg::Reopen()
+{
+ Dispose();
+ if (!Open(m_hints, m_options))
+ {
+ Dispose();
+ }
+}
+
+bool CDVDVideoCodecFFmpeg::GetPictureCommon(VideoPicture* pVideoPicture)
+{
+ if (!m_pFrame)
+ return false;
+
+ pVideoPicture->iWidth = m_pFrame->width;
+ pVideoPicture->iHeight = m_pFrame->height;
+
+ /* crop of 10 pixels if demuxer asked it */
+ if(m_pCodecContext->coded_width && m_pCodecContext->coded_width < (int)pVideoPicture->iWidth
+ && m_pCodecContext->coded_width > (int)pVideoPicture->iWidth - 10)
+ pVideoPicture->iWidth = m_pCodecContext->coded_width;
+
+ if(m_pCodecContext->coded_height && m_pCodecContext->coded_height < (int)pVideoPicture->iHeight
+ && m_pCodecContext->coded_height > (int)pVideoPicture->iHeight - 10)
+ pVideoPicture->iHeight = m_pCodecContext->coded_height;
+
+ double aspect_ratio;
+
+ /* use variable in the frame */
+ AVRational pixel_aspect = m_pFrame->sample_aspect_ratio;
+
+ if (pixel_aspect.num == 0)
+ aspect_ratio = 0;
+ else
+ aspect_ratio = av_q2d(pixel_aspect) * pVideoPicture->iWidth / pVideoPicture->iHeight;
+
+ if (aspect_ratio <= 0.0)
+ aspect_ratio = static_cast<double>(pVideoPicture->iWidth) / pVideoPicture->iHeight;
+
+ if (m_DAR != aspect_ratio)
+ {
+ m_DAR = aspect_ratio;
+ m_processInfo.SetVideoDAR(static_cast<float>(m_DAR));
+ }
+
+ /* XXX: we suppose the screen has a 1.0 pixel ratio */ // CDVDVideo will compensate it.
+ pVideoPicture->iDisplayHeight = pVideoPicture->iHeight;
+ pVideoPicture->iDisplayWidth = ((int)RINT(pVideoPicture->iHeight * aspect_ratio)) & -3;
+ if (pVideoPicture->iDisplayWidth > pVideoPicture->iWidth)
+ {
+ pVideoPicture->iDisplayWidth = pVideoPicture->iWidth;
+ pVideoPicture->iDisplayHeight = ((int)RINT(pVideoPicture->iWidth / aspect_ratio)) & -3;
+ }
+
+
+ pVideoPicture->pts = DVD_NOPTS_VALUE;
+
+ AVDictionaryEntry * entry = av_dict_get(m_pFrame->metadata, "stereo_mode", NULL, 0);
+ if(entry && entry->value)
+ {
+ pVideoPicture->stereoMode = (const char*)entry->value;
+ }
+ else
+ pVideoPicture->stereoMode.clear();
+
+ pVideoPicture->iRepeatPicture = 0.5 * m_pFrame->repeat_pict;
+ pVideoPicture->iFlags = 0;
+ pVideoPicture->iFlags |= m_pFrame->interlaced_frame ? DVP_FLAG_INTERLACED : 0;
+ pVideoPicture->iFlags |= m_pFrame->top_field_first ? DVP_FLAG_TOP_FIELD_FIRST: 0;
+
+ if (m_codecControlFlags & DVD_CODEC_CTRL_DROP)
+ {
+ pVideoPicture->iFlags |= DVP_FLAG_DROPPED;
+ }
+
+ pVideoPicture->pixelFormat = m_pCodecContext->sw_pix_fmt;
+
+ pVideoPicture->chroma_position = m_pCodecContext->chroma_sample_location;
+ pVideoPicture->color_primaries = m_pCodecContext->color_primaries == AVCOL_PRI_UNSPECIFIED ? m_hints.colorPrimaries : m_pCodecContext->color_primaries;
+ pVideoPicture->color_transfer = m_pCodecContext->color_trc == AVCOL_TRC_UNSPECIFIED ? m_hints.colorTransferCharacteristic : m_pCodecContext->color_trc;
+ pVideoPicture->color_space = m_pCodecContext->colorspace == AVCOL_SPC_UNSPECIFIED ? m_hints.colorSpace : m_pCodecContext->colorspace;
+ pVideoPicture->colorBits = 8;
+
+ // determine how number of bits of encoded video
+ if (m_pCodecContext->pix_fmt == AV_PIX_FMT_YUV420P12)
+ pVideoPicture->colorBits = 12;
+ else if (m_pCodecContext->pix_fmt == AV_PIX_FMT_YUV420P10)
+ pVideoPicture->colorBits = 10;
+ else if (m_pCodecContext->codec_id == AV_CODEC_ID_HEVC &&
+ m_pCodecContext->profile == FF_PROFILE_HEVC_MAIN_10)
+ pVideoPicture->colorBits = 10;
+ else if (m_pCodecContext->codec_id == AV_CODEC_ID_H264 &&
+ (m_pCodecContext->profile == FF_PROFILE_H264_HIGH_10||
+ m_pCodecContext->profile == FF_PROFILE_H264_HIGH_10_INTRA))
+ pVideoPicture->colorBits = 10;
+ else if (m_pCodecContext->codec_id == AV_CODEC_ID_VP9 &&
+ (m_pCodecContext->profile == FF_PROFILE_VP9_2 ||
+ m_pCodecContext->profile == FF_PROFILE_VP9_3))
+ pVideoPicture->colorBits = 10;
+
+ if (m_pCodecContext->color_range == AVCOL_RANGE_JPEG ||
+ m_pCodecContext->pix_fmt == AV_PIX_FMT_YUVJ420P)
+ pVideoPicture->color_range = 1;
+ else
+ pVideoPicture->color_range = m_hints.colorRange == AVCOL_RANGE_JPEG ? 1 : 0;
+
+ //! @todo: ffmpeg doesn't seem like they know how they want to handle this.
+ // av_frame_get_qp_table is deprecated but there doesn't seem to be a valid
+ // replacement. the following is basically what av_frame_get_qp_table does
+ // internally so we can avoid the deprecation warning however it may still
+ // break in the future because some definitions are guarded and may be removed.
+
+ pVideoPicture->qp_table = nullptr;
+ pVideoPicture->qstride = 0;
+ pVideoPicture->qscale_type = 0;
+
+ AVFrameSideData* sd;
+ sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_QP_TABLE_PROPERTIES);
+ if (sd)
+ {
+ struct qp_properties
+ {
+ int stride;
+ int type;
+ };
+
+ auto qp = reinterpret_cast<qp_properties*>(sd->data);
+
+ sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_QP_TABLE_DATA);
+ if (sd && sd->buf && qp)
+ {
+ // this seems wrong but it's what ffmpeg does internally
+ pVideoPicture->qp_table = reinterpret_cast<int8_t*>(sd->buf->data);
+ pVideoPicture->qstride = qp->stride;
+ pVideoPicture->qscale_type = qp->type;
+ }
+ }
+
+ pVideoPicture->pict_type = m_pFrame->pict_type;
+
+ // metadata
+ pVideoPicture->hasDisplayMetadata = false;
+ pVideoPicture->hasLightMetadata = false;
+ sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+ if (sd)
+ {
+ pVideoPicture->displayMetadata = *(AVMasteringDisplayMetadata *)sd->data;
+ pVideoPicture->hasDisplayMetadata = true;
+ }
+ else if (m_hints.masteringMetadata)
+ {
+ pVideoPicture->displayMetadata = *m_hints.masteringMetadata.get();
+ pVideoPicture->hasDisplayMetadata = true;
+ }
+ sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+ if (sd)
+ {
+ pVideoPicture->lightMetadata = *(AVContentLightMetadata *)sd->data;
+ pVideoPicture->hasLightMetadata = true;
+ }
+ else if (m_hints.contentLightMetadata)
+ {
+ pVideoPicture->lightMetadata = *m_hints.contentLightMetadata.get();
+ pVideoPicture->hasLightMetadata = true;
+ }
+
+ if (pVideoPicture->iRepeatPicture)
+ pVideoPicture->dts = DVD_NOPTS_VALUE;
+ else
+ pVideoPicture->dts = m_dts;
+
+ m_dts = DVD_NOPTS_VALUE;
+
+ int64_t bpts = m_pFrame->best_effort_timestamp;
+ if (bpts != AV_NOPTS_VALUE)
+ {
+ pVideoPicture->pts = (double)bpts * DVD_TIME_BASE / AV_TIME_BASE;
+ if (pVideoPicture->pts == m_decoderPts)
+ {
+ pVideoPicture->iRepeatPicture = -0.5;
+ pVideoPicture->pts = DVD_NOPTS_VALUE;
+ pVideoPicture->dts = DVD_NOPTS_VALUE;
+ }
+ }
+ else
+ pVideoPicture->pts = DVD_NOPTS_VALUE;
+
+ if (pVideoPicture->pts != DVD_NOPTS_VALUE)
+ m_decoderPts = pVideoPicture->pts;
+
+ if (m_requestSkipDeint)
+ {
+ pVideoPicture->iFlags |= DVD_CODEC_CTRL_SKIPDEINT;
+ m_skippedDeint++;
+ }
+
+ m_requestSkipDeint = false;
+ pVideoPicture->iFlags |= m_codecControlFlags;
+
+ return true;
+}
+
+int CDVDVideoCodecFFmpeg::FilterOpen(const std::string& filters, bool scale)
+{
+ int result;
+
+ if (m_pFilterGraph)
+ FilterClose();
+
+ if (filters.empty() && !scale)
+ return 0;
+
+ if (m_pHardware)
+ {
+ CLog::Log(LOGWARNING, "CDVDVideoCodecFFmpeg::FilterOpen - skipped opening filters on hardware decode");
+ return 0;
+ }
+
+ if (!(m_pFilterGraph = avfilter_graph_alloc()))
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - unable to alloc filter graph");
+ return -1;
+ }
+
+ const AVFilter* srcFilter = avfilter_get_by_name("buffer");
+ const AVFilter* outFilter = avfilter_get_by_name("buffersink"); // should be last filter in the graph for now
+
+ std::string args = StringUtils::Format(
+ "{}:{}:{}:{}:{}:{}:{}", m_pCodecContext->width, m_pCodecContext->height,
+ m_pCodecContext->pix_fmt, m_pCodecContext->time_base.num ? m_pCodecContext->time_base.num : 1,
+ m_pCodecContext->time_base.num ? m_pCodecContext->time_base.den : 1,
+ m_pCodecContext->sample_aspect_ratio.num != 0 ? m_pCodecContext->sample_aspect_ratio.num : 1,
+ m_pCodecContext->sample_aspect_ratio.num != 0 ? m_pCodecContext->sample_aspect_ratio.den : 1);
+
+ if ((result = avfilter_graph_create_filter(&m_pFilterIn, srcFilter, "src", args.c_str(), NULL, m_pFilterGraph)) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - avfilter_graph_create_filter: src");
+ return result;
+ }
+
+ if ((result = avfilter_graph_create_filter(&m_pFilterOut, outFilter, "out", NULL, NULL, m_pFilterGraph)) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - avfilter_graph_create_filter: out");
+ return result;
+ }
+ if ((result = av_opt_set_int_list(m_pFilterOut, "pix_fmts", &m_formats[0], AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - failed settings pix formats");
+ return result;
+ }
+
+ if (!filters.empty())
+ {
+ AVFilterInOut* outputs = avfilter_inout_alloc();
+ AVFilterInOut* inputs = avfilter_inout_alloc();
+
+ outputs->name = av_strdup("in");
+ outputs->filter_ctx = m_pFilterIn;
+ outputs->pad_idx = 0;
+ outputs->next = nullptr;
+
+ inputs->name = av_strdup("out");
+ inputs->filter_ctx = m_pFilterOut;
+ inputs->pad_idx = 0;
+ inputs->next = nullptr;
+
+ result = avfilter_graph_parse_ptr(m_pFilterGraph, m_filters.c_str(), &inputs, &outputs, NULL);
+ avfilter_inout_free(&outputs);
+ avfilter_inout_free(&inputs);
+
+ if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - avfilter_graph_parse");
+ return result;
+ }
+
+ if (filters.compare(0,5,"yadif") == 0)
+ {
+ m_processInfo.SetVideoDeintMethod(filters);
+ }
+ else
+ {
+ m_processInfo.SetVideoDeintMethod("none");
+ }
+ }
+ else
+ {
+ if ((result = avfilter_link(m_pFilterIn, 0, m_pFilterOut, 0)) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - avfilter_link");
+ return result;
+ }
+
+ m_processInfo.SetVideoDeintMethod("none");
+ }
+
+ if ((result = avfilter_graph_config(m_pFilterGraph, nullptr)) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterOpen - avfilter_graph_config");
+ return result;
+ }
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+ {
+ char* graphDump = avfilter_graph_dump(m_pFilterGraph, nullptr);
+ if (graphDump)
+ {
+ CLog::Log(LOGDEBUG, "CDVDVideoCodecFFmpeg::FilterOpen - Final filter graph:\n{}", graphDump);
+ av_freep(&graphDump);
+ }
+ }
+
+ m_filterEof = false;
+ return result;
+}
+
+void CDVDVideoCodecFFmpeg::FilterClose()
+{
+ if (m_pFilterGraph)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecFFmpeg::FilterClose - Freeing filter graph");
+ avfilter_graph_free(&m_pFilterGraph);
+
+ // Disposed by above code
+ m_pFilterIn = nullptr;
+ m_pFilterOut = nullptr;
+ }
+}
+
+CDVDVideoCodec::VCReturn CDVDVideoCodecFFmpeg::FilterProcess(AVFrame* frame)
+{
+ int result;
+
+ if (frame || (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN))
+ {
+ result = av_buffersrc_add_frame(m_pFilterIn, frame);
+ if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterProcess - av_buffersrc_add_frame");
+ return VC_ERROR;
+ }
+ }
+
+ result = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame);
+
+ if (result == AVERROR(EAGAIN))
+ return VC_BUFFER;
+ else if (result == AVERROR_EOF)
+ {
+ result = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame);
+ m_filterEof = true;
+ if (result < 0)
+ return VC_BUFFER;
+ }
+ else if (result < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDVideoCodecFFmpeg::FilterProcess - av_buffersink_get_frame");
+ return VC_ERROR;
+ }
+
+ av_frame_unref(m_pFrame);
+ av_frame_move_ref(m_pFrame, m_pFilterFrame);
+
+ return VC_PICTURE;
+}
+
+unsigned CDVDVideoCodecFFmpeg::GetConvergeCount()
+{
+ return m_iLastKeyframe;
+}
+
+unsigned CDVDVideoCodecFFmpeg::GetAllowedReferences()
+{
+ if(m_pHardware)
+ return m_pHardware->GetAllowedReferences();
+ else
+ return 0;
+}
+
+bool CDVDVideoCodecFFmpeg::GetCodecStats(double &pts, int &droppedFrames, int &skippedPics)
+{
+ if (m_decoderPts != DVD_NOPTS_VALUE)
+ pts = m_decoderPts;
+ else
+ pts = m_dts;
+
+ if (m_droppedFrames)
+ droppedFrames = m_droppedFrames;
+ else
+ droppedFrames = -1;
+ m_droppedFrames = 0;
+
+ if (m_skippedDeint)
+ skippedPics = m_skippedDeint;
+ else
+ skippedPics = -1;
+ m_skippedDeint = 0;
+
+ return true;
+}
+
+void CDVDVideoCodecFFmpeg::SetCodecControl(int flags)
+{
+ m_codecControlFlags = flags;
+
+ if (m_pCodecContext)
+ {
+ bool bDrop = (flags & DVD_CODEC_CTRL_DROP_ANY) != 0;
+ if (bDrop && m_pHardware && m_pHardware->CanSkipDeint())
+ {
+ m_requestSkipDeint = true;
+ bDrop = false;
+ }
+ else
+ m_requestSkipDeint = false;
+
+ if (bDrop)
+ {
+ m_pCodecContext->skip_frame = AVDISCARD_NONREF;
+ m_pCodecContext->skip_idct = AVDISCARD_NONREF;
+ m_pCodecContext->skip_loop_filter = AVDISCARD_NONREF;
+ }
+ else
+ {
+ m_pCodecContext->skip_frame = AVDISCARD_DEFAULT;
+ m_pCodecContext->skip_idct = AVDISCARD_DEFAULT;
+ m_pCodecContext->skip_loop_filter = AVDISCARD_DEFAULT;
+ }
+ }
+
+ if (m_pHardware)
+ m_pHardware->SetCodecControl(flags);
+}
+
+void CDVDVideoCodecFFmpeg::SetHardware(IHardwareDecoder* hardware)
+{
+ if (m_pHardware)
+ m_pHardware->Release();
+ m_pHardware = hardware;
+ UpdateName();
+}
+
+IHardwareDecoder* CDVDVideoCodecFFmpeg::GetHWAccel()
+{
+ return m_pHardware;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.h
new file mode 100644
index 0000000..86c83cd
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecs.h"
+#include "cores/VideoPlayer/DVDStreamInfo.h"
+#include "DVDVideoCodec.h"
+#include "DVDVideoPPFFmpeg.h"
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#include <libswscale/swscale.h>
+#include <libpostproc/postprocess.h>
+}
+
+class CVideoBufferPoolFFmpeg;
+
+class CDVDVideoCodecFFmpeg : public CDVDVideoCodec, public ICallbackHWAccel
+{
+public:
+ explicit CDVDVideoCodecFFmpeg(CProcessInfo &processInfo);
+ ~CDVDVideoCodecFFmpeg() override;
+ bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override;
+ bool AddData(const DemuxPacket &packet) override;
+ void Reset() override;
+ void Reopen() override;
+ CDVDVideoCodec::VCReturn GetPicture(VideoPicture* pVideoPicture) override;
+ const char* GetName() override { return m_name.c_str(); }; // m_name is never changed after open
+ unsigned GetConvergeCount() override;
+ unsigned GetAllowedReferences() override;
+ bool GetCodecStats(double &pts, int &droppedFrames, int &skippedPics) override;
+ void SetCodecControl(int flags) override;
+
+ IHardwareDecoder* GetHWAccel() override;
+ bool GetPictureCommon(VideoPicture* pVideoPicture) override;
+
+protected:
+ void Dispose();
+ static enum AVPixelFormat GetFormat(struct AVCodecContext * avctx, const AVPixelFormat * fmt);
+
+ int FilterOpen(const std::string& filters, bool scale);
+ void FilterClose();
+ CDVDVideoCodec::VCReturn FilterProcess(AVFrame* frame);
+ void SetFilters();
+ void UpdateName();
+ bool SetPictureParams(VideoPicture* pVideoPicture);
+
+ bool HasHardware() { return m_pHardware != nullptr; }
+ void SetHardware(IHardwareDecoder *hardware);
+
+ AVFrame* m_pFrame = nullptr;;
+ AVFrame* m_pDecodedFrame = nullptr;;
+ AVCodecContext* m_pCodecContext = nullptr;;
+ std::shared_ptr<CVideoBufferPoolFFmpeg> m_videoBufferPool;
+
+ std::string m_filters;
+ std::string m_filters_next;
+ AVFilterGraph* m_pFilterGraph = nullptr;
+ AVFilterContext* m_pFilterIn = nullptr;
+ AVFilterContext* m_pFilterOut = nullptr;;
+ AVFrame* m_pFilterFrame = nullptr;;
+ bool m_filterEof = false;
+ bool m_eof = false;
+
+ CDVDVideoPPFFmpeg m_postProc;
+
+ int m_iPictureWidth = 0;
+ int m_iPictureHeight = 0;
+ int m_iScreenWidth = 0;
+ int m_iScreenHeight = 0;
+ int m_iOrientation = 0;// orientation of the video in degrees counter clockwise
+
+ std::string m_name;
+ int m_decoderState;
+ IHardwareDecoder *m_pHardware = nullptr;
+ int m_iLastKeyframe = 0;
+ double m_dts = DVD_NOPTS_VALUE;
+ bool m_started = false;
+ bool m_startedInput = false;
+ std::vector<AVPixelFormat> m_formats;
+ double m_decoderPts = DVD_NOPTS_VALUE;
+ int m_skippedDeint = 0;
+ int m_droppedFrames = 0;
+ bool m_requestSkipDeint = false;
+ int m_codecControlFlags = 0;
+ bool m_interlaced = false;
+ double m_DAR = 1.0;
+ CDVDStreamInfo m_hints;
+ CDVDCodecOptions m_options;
+
+ struct CDropControl
+ {
+ CDropControl();
+ void Reset(bool init);
+ void Process(int64_t pts, bool drop);
+
+ int64_t m_lastPTS;
+ int64_t m_diffPTS;
+ int m_count;
+ enum
+ {
+ INIT,
+ VALID
+ } m_state;
+ } m_dropCtrl;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.cpp
new file mode 100644
index 0000000..a98fbb1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDVideoPPFFmpeg.h"
+#include "utils/log.h"
+#include "cores/FFmpeg.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+extern "C" {
+#include <libavutil/mem.h>
+}
+
+CDVDVideoPPFFmpeg::CDVDVideoPPFFmpeg(CProcessInfo &processInfo):
+ m_sType(""), m_processInfo(processInfo)
+{
+ m_pMode = m_pContext = NULL;
+ m_iInitWidth = m_iInitHeight = 0;
+ m_deinterlace = false;
+}
+
+CDVDVideoPPFFmpeg::~CDVDVideoPPFFmpeg()
+{
+ Dispose();
+}
+
+void CDVDVideoPPFFmpeg::Dispose()
+{
+ if (m_pMode)
+ {
+ pp_free_mode(m_pMode);
+ m_pMode = NULL;
+ }
+ if(m_pContext)
+ {
+ pp_free_context(m_pContext);
+ m_pContext = NULL;
+ }
+
+ m_iInitWidth = 0;
+ m_iInitHeight = 0;
+}
+
+bool CDVDVideoPPFFmpeg::CheckInit(int iWidth, int iHeight)
+{
+ if (m_iInitWidth != iWidth || m_iInitHeight != iHeight)
+ {
+ if (m_pContext || m_pMode)
+ {
+ Dispose();
+ }
+
+ m_pContext = pp_get_context(iWidth, iHeight, PPCPUFlags() | PP_FORMAT_420);
+
+ m_iInitWidth = iWidth;
+ m_iInitHeight = iHeight;
+
+ m_pMode = pp_get_mode_by_name_and_quality(m_sType.c_str(), PP_QUALITY_MAX);
+ }
+
+ if (m_pMode)
+ return true;
+ else
+ return false;
+}
+
+void CDVDVideoPPFFmpeg::SetType(const std::string& mType, bool deinterlace)
+{
+ m_deinterlace = deinterlace;
+
+ if (mType == m_sType)
+ return;
+
+ m_sType = mType;
+
+ if(m_pContext || m_pMode)
+ Dispose();
+}
+
+void CDVDVideoPPFFmpeg::Process(VideoPicture* pPicture)
+{
+ VideoPicture* pSource = pPicture;
+ CVideoBuffer *videoBuffer;
+
+ if (pSource->videoBuffer->GetFormat() != AV_PIX_FMT_YUV420P)
+ return;
+
+ if (!CheckInit(pSource->iWidth, pSource->iHeight))
+ {
+ CLog::Log(LOGERROR, "Initialization of ffmpeg postprocessing failed");
+ return;
+ }
+
+ uint8_t* srcPlanes[YuvImage::MAX_PLANES], *dstPlanes[YuvImage::MAX_PLANES];
+ int srcStrides[YuvImage::MAX_PLANES]{};
+ pSource->videoBuffer->GetPlanes(srcPlanes);
+ pSource->videoBuffer->GetStrides(srcStrides);
+
+ videoBuffer = m_processInfo.GetVideoBufferManager().Get(AV_PIX_FMT_YUV420P,
+ srcStrides[0] * pPicture->iHeight +
+ srcStrides[1] * pPicture->iHeight, nullptr);
+ if (!videoBuffer)
+ {
+ return;
+ }
+
+ videoBuffer->SetDimensions(pPicture->iWidth, pPicture->iHeight, srcStrides);
+ videoBuffer->GetPlanes(dstPlanes);
+ //! @bug libpostproc isn't const correct
+ pp_postprocess(const_cast<const uint8_t **>(srcPlanes), srcStrides,
+ dstPlanes, srcStrides,
+ pSource->iWidth, pSource->iHeight,
+ pSource->qp_table, pSource->qstride,
+ m_pMode, m_pContext,
+ pSource->pict_type | pSource->qscale_type ? PP_PICT_TYPE_QP2 : 0);
+
+
+ pPicture->SetParams(*pSource);
+ if (pPicture->videoBuffer)
+ pPicture->videoBuffer->Release();
+ pPicture->videoBuffer = videoBuffer;
+
+ if (m_deinterlace)
+ pPicture->iFlags &= ~DVP_FLAG_INTERLACED;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.h
new file mode 100644
index 0000000..386a4c7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoPPFFmpeg.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDVideoCodec.h"
+
+#include <string>
+
+class CProcessInfo;
+
+class CDVDVideoPPFFmpeg
+{
+public:
+
+ explicit CDVDVideoPPFFmpeg(CProcessInfo &processInfo);
+ ~CDVDVideoPPFFmpeg();
+
+ void SetType(const std::string& mType, bool deinterlace);
+ void Process(VideoPicture *pPicture);
+
+protected:
+ std::string m_sType;
+ CProcessInfo &m_processInfo;
+
+ void *m_pContext;
+ void *m_pMode;
+ bool m_deinterlace;
+
+ void Dispose();
+
+ int m_iInitWidth, m_iInitHeight;
+ bool CheckInit(int iWidth, int iHeight);
+ bool CheckFrameBuffer(const VideoPicture* pSource);
+};
+
+
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp
new file mode 100644
index 0000000..74a1eb3
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// setting that here because otherwise SampleFormat is defined to AVSampleFormat
+// which we don't use here
+#define FF_API_OLD_SAMPLE_FMT 0
+
+#define LIMIT_VIDEO_MEMORY_4K 2960ull
+
+#include "DXVA.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CPUInfo.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#include <Windows.h>
+#include <d3d11_4.h>
+#include <dxva.h>
+#include <initguid.h>
+
+using namespace DXVA;
+using namespace Microsoft::WRL;
+using namespace std::chrono_literals;
+
+DEFINE_GUID(DXVADDI_Intel_ModeH264_A, 0x604F8E64,0x4951,0x4c54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
+DEFINE_GUID(DXVADDI_Intel_ModeH264_C, 0x604F8E66,0x4951,0x4c54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
+DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4c54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
+DEFINE_GUID(DXVADDI_Intel_ModeVC1_E, 0xBCC5DB6D,0xA2B6,0x4AF0,0xAC,0xE4,0xAD,0xB1,0xF7,0x87,0xBC,0x89);
+DEFINE_GUID(DXVA_ModeH264_VLD_NoFGT_Flash, 0x4245F676,0x2BBC,0x4166,0xa0,0xBB,0x54,0xE7,0xB8,0x49,0xC3,0x80);
+DEFINE_GUID(DXVA_Intel_VC1_ClearVideo_2, 0xE07EC519,0xE651,0x4CD6,0xAC,0x84,0x13,0x70,0xCC,0xEE,0xC8,0x51);
+
+// redefine DXVA_NoEncrypt with other macro, solves unresolved external symbol linker error
+#ifndef DXVA_NoEncrypt
+DEFINE_GUID(DXVA_NoEncrypt, 0x1b81beD0, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
+#endif
+
+static const int PROFILES_MPEG2_SIMPLE[] = { FF_PROFILE_MPEG2_SIMPLE,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_MPEG2_MAIN[] = { FF_PROFILE_MPEG2_SIMPLE,
+ FF_PROFILE_MPEG2_MAIN,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_H264_HIGH[] = { FF_PROFILE_H264_BASELINE,
+ FF_PROFILE_H264_CONSTRAINED_BASELINE,
+ FF_PROFILE_H264_MAIN,
+ FF_PROFILE_H264_HIGH,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_HEVC_MAIN[] = { FF_PROFILE_HEVC_MAIN,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_HEVC_MAIN10[] = { FF_PROFILE_HEVC_MAIN,
+ FF_PROFILE_HEVC_MAIN_10,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_VP9_0[] = { FF_PROFILE_VP9_0,
+ FF_PROFILE_UNKNOWN };
+static const int PROFILES_VP9_10_2[] = { FF_PROFILE_VP9_2,
+ FF_PROFILE_UNKNOWN };
+
+typedef struct
+{
+ const char* name;
+ const GUID* guid;
+ int codec;
+ const int* profiles;
+} dxva2_mode_t;
+
+/* XXX Preferred modes must come first */
+static const std::vector<dxva2_mode_t> dxva2_modes = {
+ { "MPEG2 variable-length decoder", &D3D11_DECODER_PROFILE_MPEG2_VLD, AV_CODEC_ID_MPEG2VIDEO, PROFILES_MPEG2_MAIN },
+ { "MPEG1/2 variable-length decoder", &D3D11_DECODER_PROFILE_MPEG2and1_VLD, AV_CODEC_ID_MPEG2VIDEO, PROFILES_MPEG2_MAIN },
+ { "MPEG2 motion compensation", &D3D11_DECODER_PROFILE_MPEG2_MOCOMP, 0, nullptr },
+ { "MPEG2 inverse discrete cosine transform", &D3D11_DECODER_PROFILE_MPEG2_IDCT, 0, nullptr},
+
+ { "MPEG-1 variable-length decoder", &D3D11_DECODER_PROFILE_MPEG1_VLD, 0, nullptr },
+
+ { "H.264 variable-length decoder, film grain technology", &D3D11_DECODER_PROFILE_H264_VLD_FGT, AV_CODEC_ID_H264, PROFILES_H264_HIGH },
+ { "H.264 variable-length decoder, no film grain technology (Intel ClearVideo)", &DXVADDI_Intel_ModeH264_E, AV_CODEC_ID_H264, PROFILES_H264_HIGH },
+ { "H.264 variable-length decoder, no film grain technology", &D3D11_DECODER_PROFILE_H264_VLD_NOFGT, AV_CODEC_ID_H264, PROFILES_H264_HIGH },
+ { "H.264 variable-length decoder, no film grain technology, FMO/ASO", &D3D11_DECODER_PROFILE_H264_VLD_WITHFMOASO_NOFGT, AV_CODEC_ID_H264, PROFILES_H264_HIGH },
+ { "H.264 variable-length decoder, no film grain technology, Flash", &DXVA_ModeH264_VLD_NoFGT_Flash, AV_CODEC_ID_H264, PROFILES_H264_HIGH },
+
+ { "H.264 inverse discrete cosine transform, film grain technology", &D3D11_DECODER_PROFILE_H264_IDCT_FGT, 0, nullptr },
+ { "H.264 inverse discrete cosine transform, no film grain technology", &D3D11_DECODER_PROFILE_H264_IDCT_NOFGT, 0, nullptr },
+ { "H.264 inverse discrete cosine transform, no film grain technology (Intel)", &DXVADDI_Intel_ModeH264_C, 0, nullptr },
+
+ { "H.264 motion compensation, film grain technology", &D3D11_DECODER_PROFILE_H264_MOCOMP_FGT, 0, nullptr },
+ { "H.264 motion compensation, no film grain technology", &D3D11_DECODER_PROFILE_H264_MOCOMP_NOFGT, 0, nullptr },
+ { "H.264 motion compensation, no film grain technology (Intel)", &DXVADDI_Intel_ModeH264_A, 0, nullptr },
+
+ { "H.264 stereo high profile, mbs flag set", &D3D11_DECODER_PROFILE_H264_VLD_STEREO_PROGRESSIVE_NOFGT, 0, nullptr },
+ { "H.264 stereo high profile", &D3D11_DECODER_PROFILE_H264_VLD_STEREO_NOFGT, 0, nullptr },
+ { "H.264 multi-view high profile", &D3D11_DECODER_PROFILE_H264_VLD_MULTIVIEW_NOFGT, 0, nullptr },
+
+ { "Windows Media Video 8 motion compensation", &D3D11_DECODER_PROFILE_WMV8_MOCOMP, 0, nullptr },
+ { "Windows Media Video 8 post processing", &D3D11_DECODER_PROFILE_WMV8_POSTPROC, 0, nullptr },
+
+ { "Windows Media Video 9 inverse discrete cosine transform", &D3D11_DECODER_PROFILE_WMV9_IDCT, 0, nullptr },
+ { "Windows Media Video 9 motion compensation", &D3D11_DECODER_PROFILE_WMV9_MOCOMP, 0, nullptr },
+ { "Windows Media Video 9 post processing", &D3D11_DECODER_PROFILE_WMV9_POSTPROC, 0, nullptr },
+
+ { "VC-1 variable-length decoder", &D3D11_DECODER_PROFILE_VC1_VLD, AV_CODEC_ID_VC1, nullptr },
+ { "VC-1 variable-length decoder", &D3D11_DECODER_PROFILE_VC1_VLD, AV_CODEC_ID_WMV3, nullptr },
+ { "VC-1 variable-length decoder 2010", &D3D11_DECODER_PROFILE_VC1_D2010, AV_CODEC_ID_VC1, nullptr },
+ { "VC-1 variable-length decoder 2010", &D3D11_DECODER_PROFILE_VC1_D2010, AV_CODEC_ID_WMV3, nullptr },
+ { "VC-1 variable-length decoder 2 (Intel)", &DXVA_Intel_VC1_ClearVideo_2, 0, nullptr },
+ { "VC-1 variable-length decoder (Intel)", &DXVADDI_Intel_ModeVC1_E, 0, nullptr },
+
+ { "VC-1 inverse discrete cosine transform", &D3D11_DECODER_PROFILE_VC1_IDCT, 0, nullptr },
+ { "VC-1 motion compensation", &D3D11_DECODER_PROFILE_VC1_MOCOMP, 0, nullptr },
+ { "VC-1 post processing", &D3D11_DECODER_PROFILE_VC1_POSTPROC, 0, nullptr },
+
+ { "HEVC variable-length decoder, main", &D3D11_DECODER_PROFILE_HEVC_VLD_MAIN, AV_CODEC_ID_HEVC, PROFILES_HEVC_MAIN },
+ { "HEVC variable-length decoder, main10", &D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10, AV_CODEC_ID_HEVC, PROFILES_HEVC_MAIN10 },
+
+ { "VP9 variable-length decoder, Profile 0", &D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0, AV_CODEC_ID_VP9, PROFILES_VP9_0 },
+ { "VP9 variable-length decoder, 10bit, profile 2", &D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2, AV_CODEC_ID_VP9, PROFILES_VP9_10_2 },
+};
+
+// Preferred targets must be first
+static const DXGI_FORMAT render_targets_dxgi[] = {
+ DXGI_FORMAT_NV12,
+ DXGI_FORMAT_P010,
+ DXGI_FORMAT_P016,
+ DXGI_FORMAT_UNKNOWN
+};
+
+// List of PCI Device ID of ATI cards with UVD or UVD+ decoding block.
+static DWORD UVDDeviceID [] = {
+ 0x95C0, // ATI Radeon HD 3400 Series (and others)
+ 0x95C5, // ATI Radeon HD 3400 Series (and others)
+ 0x95C4, // ATI Radeon HD 3400 Series (and others)
+ 0x94C3, // ATI Radeon HD 3410
+ 0x9589, // ATI Radeon HD 3600 Series (and others)
+ 0x9598, // ATI Radeon HD 3600 Series (and others)
+ 0x9591, // ATI Radeon HD 3600 Series (and others)
+ 0x9501, // ATI Radeon HD 3800 Series (and others)
+ 0x9505, // ATI Radeon HD 3800 Series (and others)
+ 0x9507, // ATI Radeon HD 3830
+ 0x9513, // ATI Radeon HD 3850 X2
+ 0x950F, // ATI Radeon HD 3850 X2
+ 0x0000
+};
+
+// List of PCI Device ID of nVidia cards with the macroblock width issue. More or less the VP3 block.
+// Per NVIDIA Accelerated Linux Graphics Driver, Appendix A Supported NVIDIA GPU Products, cards with note 1.
+static DWORD VP3DeviceID [] = {
+ 0x06E0, // GeForce 9300 GE
+ 0x06E1, // GeForce 9300 GS
+ 0x06E2, // GeForce 8400
+ 0x06E4, // GeForce 8400 GS
+ 0x06E5, // GeForce 9300M GS
+ 0x06E6, // GeForce G100
+ 0x06E8, // GeForce 9200M GS
+ 0x06E9, // GeForce 9300M GS
+ 0x06EC, // GeForce G 105M
+ 0x06EF, // GeForce G 103M
+ 0x06F1, // GeForce G105M
+ 0x0844, // GeForce 9100M G
+ 0x0845, // GeForce 8200M G
+ 0x0846, // GeForce 9200
+ 0x0847, // GeForce 9100
+ 0x0848, // GeForce 8300
+ 0x0849, // GeForce 8200
+ 0x084A, // nForce 730a
+ 0x084B, // GeForce 9200
+ 0x084C, // nForce 980a/780a SLI
+ 0x084D, // nForce 750a SLI
+ 0x0860, // GeForce 9400
+ 0x0861, // GeForce 9400
+ 0x0862, // GeForce 9400M G
+ 0x0863, // GeForce 9400M
+ 0x0864, // GeForce 9300
+ 0x0865, // ION
+ 0x0866, // GeForce 9400M G
+ 0x0867, // GeForce 9400
+ 0x0868, // nForce 760i SLI
+ 0x086A, // GeForce 9400
+ 0x086C, // GeForce 9300 / nForce 730i
+ 0x086D, // GeForce 9200
+ 0x086E, // GeForce 9100M G
+ 0x086F, // GeForce 8200M G
+ 0x0870, // GeForce 9400M
+ 0x0871, // GeForce 9200
+ 0x0872, // GeForce G102M
+ 0x0873, // GeForce G102M
+ 0x0874, // ION
+ 0x0876, // ION
+ 0x087A, // GeForce 9400
+ 0x087D, // ION
+ 0x087E, // ION LE
+ 0x087F, // ION LE
+ 0x0000
+};
+
+static std::string GUIDToString(const GUID& guid)
+{
+ std::string buffer = StringUtils::Format(
+ "{:08X}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", guid.Data1,
+ guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
+ guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
+ return buffer;
+}
+
+static const dxva2_mode_t* dxva2_find_mode(const GUID* guid)
+{
+ for (const dxva2_mode_t& mode : dxva2_modes)
+ {
+ if (IsEqualGUID(*mode.guid, *guid))
+ return &mode;
+ }
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// DXVA Context
+//-----------------------------------------------------------------------------
+
+CContext::weak_ptr CContext::m_context;
+CCriticalSection CContext::m_section;
+
+CContext::~CContext()
+{
+ Close();
+}
+
+void CContext::Release(CDecoder* decoder)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ const auto it = std::find(m_decoders.begin(), m_decoders.end(), decoder);
+ if (it != m_decoders.end())
+ m_decoders.erase(it);
+}
+
+void CContext::Close()
+{
+ CLog::Log(LOGINFO, "DXVA: closing decoder context.");
+ DestroyContext();
+}
+
+CContext::shared_ptr CContext::EnsureContext(CDecoder* decoder)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ auto context = m_context.lock();
+ if (context)
+ {
+ if (!context->IsValidDecoder(decoder))
+ context->m_decoders.push_back(decoder);
+ return context;
+ }
+
+ context.reset(new CContext());
+ {
+ if (!context->CreateContext())
+ return shared_ptr();
+
+ m_context = context;
+ }
+
+ return EnsureContext(decoder);
+}
+
+bool CContext::CreateContext()
+{
+ HRESULT hr = E_FAIL;
+ ComPtr<ID3D11Device> pD3DDevice;
+ ComPtr<ID3D11DeviceContext> pD3DDeviceContext;
+
+ m_sharingAllowed = DX::DeviceResources::Get()->IsNV12SharedTexturesSupported();
+
+ if (m_sharingAllowed)
+ {
+ CLog::LogF(LOGINFO, "creating discrete d3d11va device for decoding.");
+
+ std::vector<D3D_FEATURE_LEVEL> featureLevels;
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10))
+ {
+ featureLevels.push_back(D3D_FEATURE_LEVEL_12_1);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_12_0);
+ }
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8))
+ featureLevels.push_back(D3D_FEATURE_LEVEL_11_1);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_11_0);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_10_1);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_10_0);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_3);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_2);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_1);
+
+ hr = D3D11CreateDevice(DX::DeviceResources::Get()->GetAdapter(), D3D_DRIVER_TYPE_UNKNOWN,
+ nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels.data(),
+ featureLevels.size(), D3D11_SDK_VERSION, &pD3DDevice, nullptr,
+ &pD3DDeviceContext);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGWARNING, "unable to create device for decoding, fallback to using app device.");
+ m_sharingAllowed = false;
+ }
+ }
+ else
+ {
+ CLog::LogF(LOGWARNING, "using app d3d11 device for decoding due extended NV12 shared "
+ "textures it's not supported.");
+ }
+
+ if (FAILED(hr))
+ {
+ pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ pD3DDeviceContext = DX::DeviceResources::Get()->GetImmediateContext();
+ }
+
+ if (FAILED(pD3DDevice.As(&m_pD3D11Device)) || FAILED(pD3DDeviceContext.As(&m_pD3D11Context)))
+ {
+ CLog::LogF(LOGWARNING, "failed to get Video Device and Context.");
+ return false;
+ }
+
+ if (FAILED(hr) || !m_sharingAllowed)
+ {
+ // enable multi-threaded protection only if is used same d3d11 device for rendering and decoding
+ ComPtr<ID3D11Multithread> multithread;
+ hr = pD3DDevice.As(&multithread);
+ if (SUCCEEDED(hr))
+ multithread->SetMultithreadProtected(1);
+ }
+
+ QueryCaps();
+
+ // Some older Ati devices can only open a single decoder at a given time
+ std::string renderer = DX::Windowing()->GetRenderRenderer();
+ if (renderer.find("Radeon HD 2") != std::string::npos ||
+ renderer.find("Radeon HD 3") != std::string::npos ||
+ renderer.find("Radeon HD 4") != std::string::npos ||
+ renderer.find("Radeon HD 5") != std::string::npos)
+ {
+ m_atiWorkaround = true;
+ }
+
+ // Sets high priority process for smooth playback in all circumstances
+ SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
+
+ return true;
+}
+
+void CContext::DestroyContext()
+{
+ delete[] m_input_list;
+ m_pD3D11Device = nullptr;
+ m_pD3D11Context = nullptr;
+
+ // Restores normal priority process
+ SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
+}
+
+void CContext::QueryCaps()
+{
+ m_input_count = m_pD3D11Device->GetVideoDecoderProfileCount();
+
+ m_input_list = new GUID[m_input_count];
+ for (unsigned i = 0; i < m_input_count; i++)
+ {
+ if (FAILED(m_pD3D11Device->GetVideoDecoderProfile(i, &m_input_list[i])))
+ {
+ CLog::Log(LOGINFO, "DXVA: failed getting video decoder profile");
+ return;
+ }
+ const dxva2_mode_t* mode = dxva2_find_mode(&m_input_list[i]);
+ if (mode)
+ CLog::Log(LOGDEBUG, "DXVA: supports '{}'", mode->name);
+ else
+ CLog::Log(LOGDEBUG, "DXVA: supports {}", GUIDToString(m_input_list[i]));
+ }
+}
+
+bool CContext::GetFormatAndConfig(AVCodecContext* avctx, D3D11_VIDEO_DECODER_DESC &format, D3D11_VIDEO_DECODER_CONFIG &config) const
+{
+ format.OutputFormat = DXGI_FORMAT_UNKNOWN;
+
+ // iterate through our predefined dxva modes and find the first matching for desired codec
+ // once we found a mode, get a target we support in render_targets_dxgi DXGI_FORMAT_UNKNOWN
+ for (const dxva2_mode_t& mode : dxva2_modes)
+ {
+ if (mode.codec != avctx->codec_id)
+ continue;
+
+ bool supported = false;
+ for (unsigned i = 0; i < m_input_count && !supported; i++)
+ {
+ supported = IsEqualGUID(m_input_list[i], *mode.guid) != 0;
+ }
+ if (supported)
+ {
+ // check profiles
+ supported = false;
+ if (mode.profiles == nullptr)
+ supported = true;
+ else if (avctx->profile == FF_PROFILE_UNKNOWN)
+ supported = true;
+ else
+ for (const int* pProfile = &mode.profiles[0]; *pProfile != FF_PROFILE_UNKNOWN; ++pProfile)
+ {
+ if (*pProfile == avctx->profile)
+ {
+ supported = true;
+ break;
+ }
+ }
+ if (!supported)
+ CLog::Log(LOGDEBUG, "DXVA: Unsupported profile {} for {}.", avctx->profile, mode.name);
+ }
+ if (!supported)
+ continue;
+
+ CLog::Log(LOGDEBUG, "DXVA: trying '{}'.", mode.name);
+ for (unsigned j = 0; render_targets_dxgi[j]; ++j)
+ {
+ bool bHighBits = (avctx->codec_id == AV_CODEC_ID_HEVC && (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10 || avctx->profile == FF_PROFILE_HEVC_MAIN_10))
+ || (avctx->codec_id == AV_CODEC_ID_VP9 && (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10 || avctx->profile == FF_PROFILE_VP9_2));
+ if (bHighBits && render_targets_dxgi[j] < DXGI_FORMAT_P010)
+ continue;
+
+ BOOL format_supported = FALSE;
+ HRESULT res = m_pD3D11Device->CheckVideoDecoderFormat(mode.guid, render_targets_dxgi[j], &format_supported);
+ if (FAILED(res) || !format_supported)
+ {
+ CLog::Log(LOGINFO, "DXVA: Output format {} is not supported by '{}'",
+ render_targets_dxgi[j], mode.name);
+ continue;
+ }
+
+ // check decoder config
+ D3D11_VIDEO_DECODER_DESC checkFormat = {*mode.guid, static_cast<UINT>(avctx->coded_width),
+ static_cast<UINT>(avctx->coded_height),
+ render_targets_dxgi[j]};
+ if (!GetConfig(checkFormat, config))
+ continue;
+
+ // config is found, update decoder description
+ format.Guid = *mode.guid;
+ format.OutputFormat = render_targets_dxgi[j];
+ format.SampleWidth = avctx->coded_width;
+ format.SampleHeight = avctx->coded_height;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CContext::GetConfig(const D3D11_VIDEO_DECODER_DESC &format, D3D11_VIDEO_DECODER_CONFIG &config) const
+{
+ // find what decode configs are available
+ UINT cfg_count = 0;
+ if (FAILED(m_pD3D11Device->GetVideoDecoderConfigCount(&format, &cfg_count)))
+ {
+ CLog::LogF(LOGINFO, "failed getting decoder configuration count.");
+ return false;
+ }
+ if (!cfg_count)
+ {
+ CLog::LogF(LOGINFO, "no decoder configuration possible for {}x{} ({}).", format.SampleWidth,
+ format.SampleHeight, format.OutputFormat);
+ return false;
+ }
+
+ config = {};
+ const unsigned bitstream = 2; // ConfigBitstreamRaw = 2 is required for Poulsbo and handles skipping better with nVidia
+ for (unsigned i = 0; i< cfg_count; i++)
+ {
+ D3D11_VIDEO_DECODER_CONFIG pConfig = {};
+ if (FAILED(m_pD3D11Device->GetVideoDecoderConfig(&format, i, &pConfig)))
+ {
+ CLog::LogF(LOGINFO, "failed getting decoder configuration.");
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "DXVA: config {}: bitstream type {}{}.", i, pConfig.ConfigBitstreamRaw,
+ IsEqualGUID(pConfig.guidConfigBitstreamEncryption, DXVA_NoEncrypt) ? ""
+ : ", encrypted");
+
+ // select first available
+ if (config.ConfigBitstreamRaw == 0 && pConfig.ConfigBitstreamRaw != 0)
+ config = pConfig;
+ // override with preferred if found
+ if (config.ConfigBitstreamRaw != bitstream && pConfig.ConfigBitstreamRaw == bitstream)
+ config = pConfig;
+ }
+
+ if (!config.ConfigBitstreamRaw)
+ {
+ CLog::Log(LOGDEBUG, "DXVA: failed to find a raw input bitstream.");
+ return false;
+ }
+
+ return true;
+}
+
+bool CContext::CreateSurfaces(const D3D11_VIDEO_DECODER_DESC& format, uint32_t count,
+ uint32_t alignment, ID3D11VideoDecoderOutputView** surfaces,
+ HANDLE* pHandle, bool trueShared) const
+{
+ HRESULT hr = S_OK;
+ ComPtr<ID3D11Device> pD3DDevice;
+ ComPtr<ID3D11DeviceContext> pD3DDeviceContext;
+ ComPtr<ID3D11DeviceContext1> pD3DDeviceContext1;
+
+ m_pD3D11Context->GetDevice(&pD3DDevice);
+ pD3DDevice->GetImmediateContext(&pD3DDeviceContext);
+ pD3DDeviceContext.As(&pD3DDeviceContext1);
+
+ CD3D11_TEXTURE2D_DESC texDesc(format.OutputFormat,
+ FFALIGN(format.SampleWidth, alignment),
+ FFALIGN(format.SampleHeight, alignment),
+ count, 1, D3D11_BIND_DECODER);
+ UINT supported;
+ if (SUCCEEDED(pD3DDevice->CheckFormatSupport(format.OutputFormat, &supported)) &&
+ (supported & D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
+ {
+ texDesc.BindFlags |= D3D11_BIND_SHADER_RESOURCE;
+ }
+ if (trueShared)
+ {
+ texDesc.MiscFlags |= D3D11_RESOURCE_MISC_SHARED;
+ }
+
+ CLog::Log(LOGDEBUG, "DXVA: allocating {} surfaces with format {}.", count, format.OutputFormat);
+
+ ComPtr<ID3D11Texture2D> texture;
+ if (FAILED(pD3DDevice->CreateTexture2D(&texDesc, NULL, texture.GetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "failed creating decoder texture array.");
+ return false;
+ }
+
+ // acquire shared handle once
+ if (trueShared && pHandle)
+ {
+ ComPtr<IDXGIResource> dxgiResource;
+ if (FAILED(texture.As(&dxgiResource)) || FAILED(dxgiResource->GetSharedHandle(pHandle)))
+ {
+ CLog::LogF(LOGERROR, "unable to get shared handle for texture");
+ *pHandle = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC vdovDesc = {
+ format.Guid,
+ D3D11_VDOV_DIMENSION_TEXTURE2D,
+ { 0 }
+ };
+ // For video views with YUV or YCbBr formats, ClearView doesn't
+ // convert color values but assumes UINT texture format
+ float clearColor[] = {0.f, 127.f, 127.f, 255.f}; // black color in YUV
+
+ size_t i;
+ for (i = 0; i < count; ++i)
+ {
+ vdovDesc.Texture2D.ArraySlice = D3D11CalcSubresource(0, i, texDesc.MipLevels);
+ hr = m_pD3D11Device->CreateVideoDecoderOutputView(texture.Get(), &vdovDesc, &surfaces[i]);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "failed creating surfaces.");
+ break;
+ }
+ if (pD3DDeviceContext1)
+ pD3DDeviceContext1->ClearView(surfaces[i], clearColor, nullptr, 0);
+ }
+
+ if (FAILED(hr))
+ {
+ for (size_t j = 0; j < i && surfaces[j]; ++j)
+ {
+ surfaces[j]->Release();
+ surfaces[j] = nullptr;
+ };
+ }
+
+ return SUCCEEDED(hr);
+}
+
+bool CContext::CreateDecoder(const D3D11_VIDEO_DECODER_DESC &format, const D3D11_VIDEO_DECODER_CONFIG &config
+ , ID3D11VideoDecoder **decoder, ID3D11VideoContext **context)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ int retry = 0;
+ while (retry < 2)
+ {
+ if (!m_atiWorkaround || retry > 0)
+ {
+ ComPtr<ID3D11VideoDecoder> pDecoder;
+ HRESULT res = m_pD3D11Device->CreateVideoDecoder(&format, &config, pDecoder.GetAddressOf());
+ if (!FAILED(res))
+ {
+ *decoder = pDecoder.Detach();
+ return SUCCEEDED(m_pD3D11Context.CopyTo(context));
+ }
+ }
+
+ if (retry == 0)
+ {
+ CLog::LogF(LOGINFO, "hw may not support multiple decoders, releasing existing ones.");
+ for (auto& m_decoder : m_decoders)
+ m_decoder->CloseDXVADecoder();
+ }
+ retry++;
+ }
+
+ CLog::LogF(LOGERROR, "failed creating decoder.");
+ return false;
+}
+
+bool CContext::IsValidDecoder(CDecoder* decoder)
+{
+ return std::find(m_decoders.begin(), m_decoders.end(), decoder) != m_decoders.end();
+}
+
+bool CContext::Check() const
+{
+ if (!m_sharingAllowed)
+ return true;
+
+ ComPtr<ID3D11Device> pDevice;
+ m_pD3D11Context->GetDevice(&pDevice);
+
+ return SUCCEEDED(pDevice->GetDeviceRemovedReason());
+}
+
+bool CContext::Reset()
+{
+ if (Check())
+ {
+ DXGI_ADAPTER_DESC appDesc = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&appDesc);
+
+ ComPtr<IDXGIDevice> ctxDevice;
+ ComPtr<IDXGIAdapter> ctxAdapter;
+ if (SUCCEEDED(m_pD3D11Device.As(&ctxDevice)) && SUCCEEDED(ctxDevice->GetAdapter(&ctxAdapter)))
+ {
+ DXGI_ADAPTER_DESC ctxDesc = {};
+ ctxAdapter->GetDesc(&ctxDesc);
+
+ if (appDesc.AdapterLuid.HighPart == ctxDesc.AdapterLuid.HighPart &&
+ appDesc.AdapterLuid.LowPart == ctxDesc.AdapterLuid.LowPart)
+ {
+ // 1. we have valid device
+ // 2. we are on the same adapter
+ // 3. don't reset context.
+ return true;
+ }
+ }
+ }
+ DestroyContext();
+ return CreateContext();
+}
+
+//-----------------------------------------------------------------------------
+// DXVA::CVideoBuffer
+//-----------------------------------------------------------------------------
+
+DXVA::CVideoBuffer::CVideoBuffer(int id)
+ : ::CVideoBuffer(id)
+{
+ m_pixFormat = AV_PIX_FMT_D3D11VA_VLD;
+ m_pFrame = av_frame_alloc();
+}
+
+DXVA::CVideoBuffer::~CVideoBuffer()
+{
+ av_frame_free(&m_pFrame);
+}
+
+void DXVA::CVideoBuffer::Initialize(CDecoder* decoder)
+{
+ width = FFALIGN(decoder->m_format.SampleWidth, decoder->m_surface_alignment);
+ height = FFALIGN(decoder->m_format.SampleHeight, decoder->m_surface_alignment);
+ format = decoder->m_format.OutputFormat;
+}
+
+HRESULT DXVA::CVideoBuffer::GetResource(ID3D11Resource** ppResource)
+{
+ if (!view)
+ return E_NOT_SET;
+
+ view->GetResource(ppResource);
+ return S_OK;
+}
+
+unsigned DXVA::CVideoBuffer::GetIdx()
+{
+ D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC vpivd = {};
+ ComPtr<ID3D11VideoDecoderOutputView> pView = reinterpret_cast<ID3D11VideoDecoderOutputView*>(view);
+ pView->GetDesc(&vpivd);
+
+ return vpivd.Texture2D.ArraySlice;
+}
+
+void DXVA::CVideoBuffer::SetRef(AVFrame* frame)
+{
+ av_frame_unref(m_pFrame);
+ av_frame_ref(m_pFrame, frame);
+ view = reinterpret_cast<ID3D11View*>(frame->data[3]);
+}
+
+void DXVA::CVideoBuffer::Unref()
+{
+ view = nullptr;
+ av_frame_unref(m_pFrame);
+}
+
+HRESULT CVideoBufferShared::GetResource(ID3D11Resource** ppResource)
+{
+ HRESULT hr = S_OK;
+ if (handle == INVALID_HANDLE_VALUE)
+ return E_HANDLE;
+
+ if (!m_sharedRes)
+ {
+ // open resource on app device
+ ComPtr<ID3D11Device> pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ hr = pD3DDevice->OpenSharedResource(handle, __uuidof(ID3D11Resource), &m_sharedRes);
+ }
+
+ if (SUCCEEDED(hr))
+ hr = m_sharedRes.CopyTo(ppResource);
+
+ return hr;
+}
+
+void CVideoBufferShared::Initialize(CDecoder* decoder)
+{
+ CVideoBuffer::Initialize(decoder);
+
+ if (handle == INVALID_HANDLE_VALUE)
+ handle = decoder->m_sharedHandle;
+}
+
+void CVideoBufferCopy::Initialize(CDecoder* decoder)
+{
+ CVideoBuffer::Initialize(decoder);
+
+ if (!m_copyRes)
+ {
+ ComPtr<ID3D11Device> pDevice;
+ ComPtr<ID3D11DeviceContext> pDeviceContext;
+ ComPtr<ID3D11Texture2D> pDecoderTexture;
+ ComPtr<ID3D11Texture2D> pCopyTexture;
+ ComPtr<IDXGIResource> pDXGIResource;
+ ComPtr<ID3D11Resource> pResource;
+
+ decoder->m_pD3D11Context->GetDevice(&pDevice);
+ pDevice->GetImmediateContext(&pDeviceContext);
+
+ if (FAILED(CVideoBuffer::GetResource(&pResource)))
+ {
+ CLog::LogF(LOGDEBUG, "unable to get decoder resource");
+ return;
+ }
+
+ if (FAILED(pResource.As(&pDecoderTexture)))
+ {
+ CLog::LogF(LOGDEBUG, "unable to get decoder texture");
+ return;
+ }
+
+ D3D11_TEXTURE2D_DESC desc;
+ pDecoderTexture->GetDesc(&desc);
+ desc.ArraySize = 1;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
+ desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
+
+ if (FAILED(pDevice->CreateTexture2D(&desc, nullptr, &pCopyTexture)))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create copy texture");
+ return;
+ }
+ if (FAILED(pCopyTexture.As(&pDXGIResource)))
+ {
+ CLog::LogF(LOGDEBUG, "unable to get DXGI resource for copy texture");
+ return;
+ }
+
+ HANDLE shared_handle;
+ if (FAILED(pDXGIResource->GetSharedHandle(&shared_handle)))
+ {
+ CLog::LogF(LOGDEBUG, "unable to get shared handle");
+ return;
+ }
+
+ handle = shared_handle;
+ pCopyTexture.As(&m_copyRes);
+ pResource.As(&m_pResource);
+ pDeviceContext.As(&m_pDeviceContext);
+ }
+
+ if (m_copyRes)
+ {
+ // sends commands to GPU (ensures that the last decoded image is ready)
+ m_pDeviceContext->Flush();
+
+ // copy decoder surface on decoder device
+ m_pDeviceContext->CopySubresourceRegion(m_copyRes.Get(), 0, 0, 0, 0, m_pResource.Get(),
+ CVideoBuffer::GetIdx(), nullptr);
+
+ if (decoder->m_DVDWorkaround) // DVDs menus/stills need extra Flush()
+ m_pDeviceContext->Flush();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// DXVA::CVideoBufferPool
+//-----------------------------------------------------------------------------
+
+CVideoBufferPool::CVideoBufferPool() = default;
+
+CVideoBufferPool::~CVideoBufferPool()
+{
+ CLog::LogF(LOGDEBUG, "destructing buffer pool.");
+ Reset();
+}
+
+::CVideoBuffer* CVideoBufferPool::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ CVideoBuffer* retPic;
+ if (!m_freeOut.empty())
+ {
+ const size_t idx = m_freeOut.front();
+ m_freeOut.pop_front();
+ retPic = m_out[idx];
+ }
+ else
+ {
+ const size_t idx = m_out.size();
+ retPic = CreateBuffer(idx);
+ m_out.push_back(retPic);
+ }
+
+ retPic->Acquire(GetPtr());
+ return retPic;
+}
+
+void CVideoBufferPool::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ auto buf = m_out[id];
+ buf->Unref();
+
+ m_freeOut.push_back(id);
+}
+
+void CVideoBufferPool::AddView(ID3D11View* view)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ const size_t idx = m_views.size();
+ m_views.push_back(view);
+ m_freeViews.push_back(idx);
+}
+
+bool CVideoBufferPool::IsValid(ID3D11View* view)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return std::find(m_views.begin(), m_views.end(), view) != m_views.end();
+}
+
+bool CVideoBufferPool::ReturnView(ID3D11View* view)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ const auto it = std::find(m_views.begin(), m_views.end(), view);
+ if (it == m_views.end())
+ return false;
+
+ const size_t idx = it - m_views.begin();
+ m_freeViews.push_back(idx);
+ return true;
+}
+
+ID3D11View* CVideoBufferPool::GetView()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (!m_freeViews.empty())
+ {
+ const size_t idx = m_freeViews.front();
+ m_freeViews.pop_front();
+
+ return m_views[idx];
+ }
+ return nullptr;
+}
+
+void CVideoBufferPool::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ for (auto v : m_views)
+ if (v)
+ v->Release();
+
+ for (auto buf : m_out)
+ delete buf;
+
+ m_views.clear();
+ m_freeViews.clear();
+ m_out.clear();
+ m_freeOut.clear();
+}
+
+size_t CVideoBufferPool::Size()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return m_views.size();
+}
+
+bool CVideoBufferPool::HasFree()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return !m_freeViews.empty();
+}
+
+bool CVideoBufferPool::HasRefs()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ // out buffers hold views
+ const size_t buffRefs = m_out.size() - m_freeOut.size();
+ // ffmpeg refs = total - free - out refs
+ return m_freeViews.size() != m_views.size() - buffRefs;
+}
+
+//-----------------------------------------------------------------------------
+// DXVA::CDecoder
+//-----------------------------------------------------------------------------
+
+IHardwareDecoder* CDecoder::Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt)
+{
+ if (Supports(fmt) && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_VIDEOPLAYER_USEDXVA2))
+ return new CDecoder(processInfo);
+
+ return nullptr;
+}
+
+bool CDecoder::Register()
+{
+ CDVDFactoryCodec::RegisterHWAccel("dxva", Create);
+ return true;
+}
+
+CDecoder::CDecoder(CProcessInfo& processInfo)
+ : m_processInfo(processInfo)
+{
+ m_event.Set();
+ m_avD3D11Context = av_d3d11va_alloc_context();
+ m_avD3D11Context->cfg = reinterpret_cast<D3D11_VIDEO_DECODER_CONFIG*>(av_mallocz(sizeof(D3D11_VIDEO_DECODER_CONFIG)));
+ m_avD3D11Context->surface = reinterpret_cast<ID3D11VideoDecoderOutputView**>(av_mallocz_array(32, sizeof(ID3D11VideoDecoderOutputView*)));
+ m_bufferPool.reset();
+
+ DX::Windowing()->Register(this);
+}
+
+CDecoder::~CDecoder()
+{
+ CLog::LogF(LOGDEBUG, "destructing decoder, {}.", fmt::ptr(this));
+ DX::Windowing()->Unregister(this);
+
+ Close();
+ av_freep(&m_avD3D11Context->surface);
+ av_freep(&m_avD3D11Context->cfg);
+ av_freep(&m_avD3D11Context);
+}
+
+long CDecoder::Release()
+{
+ // if ffmpeg holds any references, flush buffers
+ if (m_bufferPool && m_bufferPool->HasRefs())
+ avcodec_flush_buffers(m_avCtx);
+
+ return IHardwareDecoder::Release();
+}
+
+void CDecoder::Close()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_pD3D11Decoder = nullptr;
+ m_pD3D11Context = nullptr;
+
+ if (m_videoBuffer)
+ {
+ m_videoBuffer->Release();
+ m_videoBuffer = nullptr;
+ }
+ m_format = {};
+ m_sharedHandle = INVALID_HANDLE_VALUE;
+
+ if (m_dxvaContext)
+ {
+ auto dxva_context = m_dxvaContext;
+ CLog::LogF(LOGINFO, "closing decoder.");
+ m_dxvaContext = nullptr;
+ dxva_context->Release(this);
+ }
+}
+
+static bool CheckH264L41(AVCodecContext* avctx)
+{
+ unsigned widthmbs = (avctx->coded_width + 15) / 16; // width in macroblocks
+ unsigned heightmbs = (avctx->coded_height + 15) / 16; // height in macroblocks
+ unsigned maxdpbmbs = 32768; // Decoded Picture Buffer (DPB) capacity in macroblocks for L4.1
+
+ return avctx->refs * widthmbs * heightmbs <= maxdpbmbs;
+}
+
+static bool IsL41LimitedATI()
+{
+ DXGI_ADAPTER_DESC AIdentifier = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&AIdentifier);
+
+ if (AIdentifier.VendorId == PCIV_AMD)
+ {
+ for (unsigned idx = 0; UVDDeviceID[idx] != 0; idx++)
+ {
+ if (UVDDeviceID[idx] == AIdentifier.DeviceId)
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool HasVP3WidthBug(AVCodecContext* avctx)
+{
+ // Some nVidia VP3 hardware cannot do certain macroblock widths
+ DXGI_ADAPTER_DESC AIdentifier = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&AIdentifier);
+
+ if (AIdentifier.VendorId == PCIV_NVIDIA &&
+ !CDVDCodecUtils::IsVP3CompatibleWidth(avctx->coded_width))
+ {
+ // Find the card in a known list of problematic VP3 hardware
+ for (unsigned idx = 0; VP3DeviceID[idx] != 0; idx++)
+ if (VP3DeviceID[idx] == AIdentifier.DeviceId)
+ return true;
+ }
+ return false;
+}
+
+static bool HasATIMP2Bug(AVCodecContext* avctx)
+{
+ DXGI_ADAPTER_DESC AIdentifier = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&AIdentifier);
+ if (AIdentifier.VendorId != PCIV_AMD)
+ return false;
+
+ // AMD/ATI card doesn't like some SD MPEG2 content
+ // here are params of these videos
+ return avctx->height <= 576
+ && avctx->colorspace == AVCOL_SPC_BT470BG
+ && avctx->color_primaries == AVCOL_PRI_BT470BG
+ && avctx->color_trc == AVCOL_TRC_GAMMA28;
+}
+
+static bool HasAMDH264SDiBug(AVCodecContext* avctx)
+{
+ DXGI_ADAPTER_DESC AIdentifier = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&AIdentifier);
+
+ if (AIdentifier.VendorId != PCIV_AMD)
+ return false;
+
+ // AMD card has issues with SD H264 interlaced content
+ return (avctx->width <= 720 && avctx->height <= 576 && avctx->codec_id == AV_CODEC_ID_H264 &&
+ avctx->field_order != AV_FIELD_PROGRESSIVE);
+}
+
+static bool CheckCompatibility(AVCodecContext* avctx)
+{
+ if (avctx->codec_id == AV_CODEC_ID_MPEG2VIDEO && HasATIMP2Bug(avctx))
+ return false;
+
+ // The incompatibilities are all for H264
+ if (avctx->codec_id != AV_CODEC_ID_H264)
+ return true;
+
+ // Macroblock width incompatibility
+ if (HasVP3WidthBug(avctx))
+ {
+ CLog::Log(LOGWARNING,
+ "DXVA: width {} is not supported with nVidia VP3 hardware. DXVA will not be used.",
+ avctx->coded_width);
+ return false;
+ }
+
+ // AMD H264 SD interlaced incompatibility
+ if (HasAMDH264SDiBug(avctx))
+ {
+ CLog::Log(
+ LOGWARNING,
+ "DXVA: H264 SD interlaced has issues on AMD graphics hardware. DXVA will not be used.");
+ return false;
+ }
+
+ // Check for hardware limited to H264 L4.1 (ie Bluray).
+
+ // No advanced settings: autodetect.
+ // The advanced setting lets the user override the autodetection (in case of false positive or negative)
+
+ bool checkcompat;
+ if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_DXVACheckCompatibilityPresent)
+ checkcompat = IsL41LimitedATI(); // ATI UVD and UVD+ cards can only do L4.1 - corresponds roughly to series 3xxx
+ else
+ checkcompat = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_DXVACheckCompatibility;
+
+ if (checkcompat && !CheckH264L41(avctx))
+ {
+ CLog::Log(LOGWARNING, "DXVA: compatibility check: video exceeds L4.1. DXVA will not be used.");
+ return false;
+ }
+
+ return true;
+}
+
+bool CDecoder::Open(AVCodecContext* avctx, AVCodecContext* mainctx, enum AVPixelFormat fmt)
+{
+ if (!CheckCompatibility(avctx))
+ return false;
+
+ // DVDs menus/stills need extra Flush() after copy texture
+ if (avctx->codec_id == AV_CODEC_ID_MPEG2VIDEO && avctx->height <= 576)
+ m_DVDWorkaround = true;
+
+ std::unique_lock<CCriticalSection> lock(m_section);
+ Close();
+
+ if (m_state == DXVA_LOST)
+ {
+ CLog::Log(LOGDEBUG, "DXVA: device is in lost state, we can't start.");
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "DXVA: open decoder.");
+ m_dxvaContext = CContext::EnsureContext(this);
+ if (!m_dxvaContext)
+ return false;
+
+ if (!m_dxvaContext->GetFormatAndConfig(avctx, m_format, *m_avD3D11Context->cfg))
+ {
+ CLog::Log(LOGDEBUG, "DXVA: unable to find an input/output format combination.");
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "DXVA: selected output format: {}.", m_format.OutputFormat);
+ CLog::Log(LOGDEBUG, "DXVA: source requires {} references.", avctx->refs);
+ if (m_format.Guid == DXVADDI_Intel_ModeH264_E && avctx->refs > 11)
+ {
+ const dxva2_mode_t* mode = dxva2_find_mode(&m_format.Guid);
+ CLog::Log(LOGWARNING, "DXVA: too many references {} for selected decoder '{}'.", avctx->refs,
+ mode->name);
+ return false;
+ }
+
+ if (6 > m_shared)
+ m_shared = 6;
+
+ m_refs = 2 + m_shared; // 1 decode + 1 safety + display
+ m_surface_alignment = 16;
+
+ DXGI_ADAPTER_DESC ad = {};
+ DX::DeviceResources::Get()->GetAdapterDesc(&ad);
+
+ size_t videoMem = ad.SharedSystemMemory + ad.DedicatedVideoMemory + ad.DedicatedSystemMemory;
+ CLog::LogF(LOGINFO, "Total video memory available is {} MB (dedicated = {} MB, shared = {} MB)",
+ videoMem / MB, (ad.DedicatedVideoMemory + ad.DedicatedSystemMemory) / MB,
+ ad.SharedSystemMemory / MB);
+
+ switch (avctx->codec_id)
+ {
+ case AV_CODEC_ID_MPEG2VIDEO:
+ /* decoding MPEG-2 requires additional alignment on some Intel GPUs,
+ but it causes issues for H.264 on certain AMD GPUs..... */
+ m_surface_alignment = 32;
+ m_refs += 2;
+ break;
+ case AV_CODEC_ID_HEVC:
+ /* the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
+ all coding features have enough room to work with */
+ m_surface_alignment = 128;
+ // a driver may use multi-thread decoding internally
+ // on Xbox only add refs for <= Full HD due memory constraints (max 16 refs for 4K)
+ if (CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Xbox)
+ {
+ m_refs += CServiceBroker::GetCPUInfo()->GetCPUCount();
+ }
+ else
+ {
+ if (avctx->width <= 1920)
+ m_refs += CServiceBroker::GetCPUInfo()->GetCPUCount() / 2;
+ }
+ // by specification hevc decoder can hold up to 8 unique refs
+ // ffmpeg may report only 1 refs frame when is unknown or not present in headers
+ m_refs += (avctx->refs > 1) ? avctx->refs : 8;
+ break;
+ case AV_CODEC_ID_H264:
+ // by specification h264 decoder can hold up to 16 unique refs
+ m_refs += avctx->refs ? avctx->refs : 16;
+ break;
+ case AV_CODEC_ID_VP9:
+ m_refs += 8;
+ break;
+ default:
+ m_refs += 2;
+ }
+
+ if (avctx->active_thread_type & FF_THREAD_FRAME)
+ m_refs += avctx->thread_count;
+
+ // Limit decoder surfaces to 32 maximum in any case. Since with some 16 cores / 32 threads
+ // new CPU's (Ryzen 5950x) this number may be higher than what the graphics card can handle.
+ if (m_refs > 32)
+ {
+ CLog::LogF(LOGWARNING, "The number of decoder surfaces has been limited from {} to 32.", m_refs);
+ m_refs = 32;
+ }
+
+ // Check if available video memory is sufficient for 4K decoding (is need ~3000 MB)
+ if (avctx->width >= 3840 && m_refs > 16 && videoMem < (LIMIT_VIDEO_MEMORY_4K * MB))
+ {
+ CLog::LogF(LOGWARNING,
+ "Current available video memory ({} MB) is insufficient 4K video decoding (DXVA2) "
+ "using {} surfaces. Decoder surfaces has been limited to 16.", videoMem / MB, m_refs);
+ m_refs = 16;
+ }
+
+ if (!OpenDecoder())
+ {
+ m_bufferPool.reset();
+ return false;
+ }
+
+ avctx->get_buffer2 = FFGetBuffer;
+ avctx->hwaccel_context = m_avD3D11Context;
+ avctx->slice_flags = SLICE_FLAG_ALLOW_FIELD | SLICE_FLAG_CODED_ORDER;
+
+ mainctx->get_buffer2 = FFGetBuffer;
+ mainctx->hwaccel_context = m_avD3D11Context;
+ mainctx->slice_flags = SLICE_FLAG_ALLOW_FIELD | SLICE_FLAG_CODED_ORDER;
+
+ m_avCtx = mainctx;
+
+ if (m_format.Guid == DXVADDI_Intel_ModeH264_E)
+ {
+#ifdef FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO
+ m_avD3D11Context->workaround |= FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO;
+#else
+ CLog::Log(
+ LOGWARNING,
+ "DXVA: used Intel ClearVideo decoder, but no support workaround for it in libavcodec.");
+#endif
+ }
+ else if (ad.VendorId == PCIV_AMD && IsL41LimitedATI())
+ {
+#ifdef FF_DXVA2_WORKAROUND_SCALING_LIST_ZIGZAG
+ m_avD3D11Context->workaround |= FF_DXVA2_WORKAROUND_SCALING_LIST_ZIGZAG;
+#else
+ CLog::Log(LOGWARNING, "DXVA: video card with different scaling list zigzag order detected, but "
+ "no support in libavcodec.");
+#endif
+ }
+
+ std::list<EINTERLACEMETHOD> deintMethods;
+ deintMethods.push_back(VS_INTERLACEMETHOD_NONE);
+ m_processInfo.UpdateDeinterlacingMethods(deintMethods);
+ m_processInfo.SetDeinterlacingMethodDefault(VS_INTERLACEMETHOD_DXVA_AUTO);
+
+ m_state = DXVA_OPEN;
+ return true;
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Decode(AVCodecContext* avctx, AVFrame* frame)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ const CDVDVideoCodec::VCReturn result = Check(avctx);
+ if (result != CDVDVideoCodec::VC_NONE)
+ return result;
+
+ if (frame)
+ {
+ if (m_bufferPool->IsValid(reinterpret_cast<ID3D11View*>(frame->data[3])))
+ {
+ if (m_videoBuffer)
+ m_videoBuffer->Release();
+ m_videoBuffer = reinterpret_cast<CVideoBuffer*>(m_bufferPool->Get());
+ if (!m_videoBuffer)
+ {
+ CLog::Log(LOGERROR, "DXVA: ran out of buffers.");
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ m_videoBuffer->SetRef(frame);
+ m_videoBuffer->Initialize(this);
+ return CDVDVideoCodec::VC_PICTURE;
+ }
+ CLog::Log(LOGWARNING, "DXVA: ignoring invalid surface.");
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+
+ return CDVDVideoCodec::VC_BUFFER;
+}
+
+bool CDecoder::GetPicture(AVCodecContext* avctx, VideoPicture* picture)
+{
+ static_cast<ICallbackHWAccel*>(avctx->opaque)->GetPictureCommon(picture);
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (picture->videoBuffer)
+ picture->videoBuffer->Release();
+ picture->videoBuffer = m_videoBuffer;
+ m_videoBuffer = nullptr;
+
+ if (!m_dxvaContext->IsContextShared())
+ {
+ int queued, discard, free;
+ m_processInfo.GetRenderBuffers(queued, discard, free);
+ if (free > 1)
+ {
+ DX::Windowing()->RequestDecodingTime();
+ }
+ else
+ {
+ DX::Windowing()->ReleaseDecodingTime();
+ }
+ }
+ return true;
+}
+
+void CDecoder::Reset()
+{
+ if (m_videoBuffer)
+ {
+ m_videoBuffer->Release();
+ m_videoBuffer = nullptr;
+ }
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Check(AVCodecContext* avctx)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ // we may not have a hw decoder on systems (AMD HD2xxx, HD3xxx) which are only capable
+ // of opening a single decoder and VideoPlayer opened a new stream without having flushed
+ // current one.
+ if (!m_pD3D11Decoder)
+ return CDVDVideoCodec::VC_BUFFER;
+
+ // reset decoder if context detects an error on its device
+ if (!m_dxvaContext->Check())
+ m_state = DXVA_RESET;
+
+ // app device is lost
+ if (m_state == DXVA_LOST)
+ {
+ lock.unlock();
+ // wait app device restoration
+ m_event.Wait(2000ms);
+ lock.lock();
+
+ // still in lost state after 2sec
+ if (m_state == DXVA_LOST)
+ {
+ Close();
+ CLog::LogF(LOGERROR, "device didn't reset in reasonable time.");
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ }
+
+ if (m_state != DXVA_OPEN)
+ {
+ // reset context in case of app device reset or context device error
+ if (!m_dxvaContext->Reset())
+ {
+ CLog::LogF(LOGERROR, "context didn't reset.");
+ return CDVDVideoCodec::VC_ERROR;
+ }
+
+ if (!Open(avctx, avctx, avctx->pix_fmt))
+ {
+ CLog::LogF(LOGERROR, "decoder was not able to reset.");
+ Close();
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ // decoder re-opened
+ m_state = DXVA_OPEN;
+ return CDVDVideoCodec::VC_FLUSHED;
+ }
+
+ if (avctx->refs > m_refs)
+ {
+ CLog::LogF(LOGWARNING, "number of required reference frames increased, recreating decoder.");
+ Close();
+ return CDVDVideoCodec::VC_FLUSHED;
+ }
+
+ // Status reports are available only for the DXVA2_ModeH264 and DXVA2_ModeVC1 modes
+ if (avctx->codec_id != AV_CODEC_ID_H264 && avctx->codec_id != AV_CODEC_ID_VC1 &&
+ avctx->codec_id != AV_CODEC_ID_WMV3)
+ return CDVDVideoCodec::VC_NONE;
+
+#ifdef TARGET_WINDOWS_DESKTOP
+ D3D11_VIDEO_DECODER_EXTENSION data = {};
+ union {
+ DXVA_Status_H264 h264;
+ DXVA_Status_VC1 vc1;
+ } status = {};
+
+ /* I'm not sure, but MSDN says nothing about extensions functions in D3D11, try to using with same way as in DX9 */
+ data.Function = DXVA_STATUS_REPORTING_FUNCTION;
+ data.pPrivateOutputData = &status;
+ data.PrivateOutputDataSize = avctx->codec_id == AV_CODEC_ID_H264 ? sizeof(DXVA_Status_H264) : sizeof(DXVA_Status_VC1);
+ HRESULT hr;
+ if (FAILED(hr = m_pD3D11Context->DecoderExtension(m_pD3D11Decoder.Get(), &data)))
+ {
+ CLog::Log(LOGWARNING, "DXVA: failed to get decoder status - {:#08X}.", hr);
+ return CDVDVideoCodec::VC_ERROR;
+ }
+
+ if (avctx->codec_id == AV_CODEC_ID_H264)
+ {
+ if (status.h264.bStatus)
+ CLog::Log(LOGWARNING, "DXVA: decoder problem of status {} with {}.", status.h264.bStatus,
+ status.h264.bBufType);
+ }
+ else
+ {
+ if (status.vc1.bStatus)
+ CLog::Log(LOGWARNING, "DXVA: decoder problem of status {} with {}.", status.vc1.bStatus,
+ status.vc1.bBufType);
+ }
+#endif
+ return CDVDVideoCodec::VC_NONE;
+}
+
+bool CDecoder::OpenDecoder()
+{
+ m_pD3D11Decoder = nullptr;
+ m_pD3D11Context = nullptr;
+ m_avD3D11Context->decoder = nullptr;
+ m_avD3D11Context->video_context = nullptr;
+ m_avD3D11Context->surface_count = m_refs;
+
+ // use true shared buffers always on Intel or Nvidia/AMD with recent drivers
+ const bool trueShared = DX::DeviceResources::Get()->IsDXVA2SharedDecoderSurfaces();
+
+ if (!m_dxvaContext->CreateSurfaces(m_format, m_avD3D11Context->surface_count, m_surface_alignment,
+ m_avD3D11Context->surface, &m_sharedHandle, trueShared))
+ return false;
+
+ if (!m_dxvaContext->CreateDecoder(m_format, *m_avD3D11Context->cfg, m_pD3D11Decoder.GetAddressOf(),
+ m_pD3D11Context.GetAddressOf()))
+ return false;
+
+ if (m_dxvaContext->IsContextShared())
+ {
+ if (trueShared)
+ m_bufferPool = std::make_shared<CVideoBufferPoolTyped<CVideoBufferShared>>();
+ else
+ m_bufferPool = std::make_shared<CVideoBufferPoolTyped<CVideoBufferCopy>>();
+ }
+ else
+ m_bufferPool = std::make_shared<CVideoBufferPoolTyped<CVideoBuffer>>();
+
+ for (unsigned i = 0; i < m_avD3D11Context->surface_count; i++)
+ m_bufferPool->AddView(m_avD3D11Context->surface[i]);
+
+ m_avD3D11Context->decoder = m_pD3D11Decoder.Get();
+ m_avD3D11Context->video_context = m_pD3D11Context.Get();
+
+ return true;
+}
+
+bool CDecoder::Supports(enum AVPixelFormat fmt)
+{
+ return fmt == AV_PIX_FMT_D3D11VA_VLD;
+}
+
+void CDecoder::FFReleaseBuffer(void* opaque, uint8_t* data)
+{
+ auto decoder = static_cast<CDecoder*>(opaque);
+ decoder->ReleaseBuffer(data);
+}
+
+void CDecoder::ReleaseBuffer(uint8_t* data)
+{
+ const auto view = reinterpret_cast<ID3D11VideoDecoderOutputView*>(data);
+ if (!m_bufferPool->ReturnView(view))
+ {
+ CLog::LogF(LOGWARNING, "return of invalid surface.");
+ }
+
+ IHardwareDecoder::Release();
+}
+
+int CDecoder::FFGetBuffer(AVCodecContext* avctx, AVFrame* pic, int flags)
+{
+ auto* cb = static_cast<ICallbackHWAccel*>(avctx->opaque);
+ auto decoder = dynamic_cast<CDecoder*>(cb->GetHWAccel());
+
+ return decoder->GetBuffer(avctx, pic);
+}
+
+int CDecoder::GetBuffer(AVCodecContext* avctx, AVFrame* pic)
+{
+ if (!m_pD3D11Decoder)
+ return -1;
+
+ ID3D11View* view = m_bufferPool->GetView();
+ if (view == nullptr)
+ {
+ CLog::LogF(LOGERROR, "no surface available.");
+ m_state = DXVA_RESET;
+ return -1;
+ }
+
+ pic->reordered_opaque = avctx->reordered_opaque;
+
+ for (unsigned i = 0; i < 4; i++)
+ {
+ pic->data[i] = nullptr;
+ pic->linesize[i] = 0;
+ }
+
+ pic->data[0] = reinterpret_cast<uint8_t*>(view);
+ pic->data[3] = reinterpret_cast<uint8_t*>(view);
+ AVBufferRef* buffer = av_buffer_create(pic->data[3], 0, CDecoder::FFReleaseBuffer, this, 0);
+ if (!buffer)
+ {
+ CLog::LogF(LOGERROR, "error creating buffer.");
+ return -1;
+ }
+ pic->buf[0] = buffer;
+
+ Acquire();
+
+ return 0;
+}
+
+unsigned CDecoder::GetAllowedReferences()
+{
+ return m_shared;
+}
+
+void CDecoder::CloseDXVADecoder()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_pD3D11Decoder = nullptr;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.h
new file mode 100644
index 0000000..1883bb8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.h
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "guilib/D3DResource.h"
+#include "threads/Event.h"
+
+#include <mutex>
+#include <vector>
+
+#include <wrl/client.h>
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavcodec/d3d11va.h>
+}
+
+namespace DXVA
+{
+class CDecoder;
+
+class CVideoBuffer : public ::CVideoBuffer
+{
+ template<typename TBuffer>
+ friend class CVideoBufferPoolTyped;
+
+public:
+ virtual ~CVideoBuffer();
+
+ void SetRef(AVFrame* frame);
+ void Unref();
+
+ virtual void Initialize(CDecoder* decoder);
+ virtual HRESULT GetResource(ID3D11Resource** ppResource);
+ virtual unsigned GetIdx();
+
+ ID3D11View* view = nullptr;
+ DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
+ unsigned width = 0;
+ unsigned height = 0;
+
+protected:
+ explicit CVideoBuffer(int id);
+
+private:
+ AVFrame* m_pFrame{nullptr};
+};
+
+class CVideoBufferShared : public CVideoBuffer
+{
+ template<typename TBuffer>
+ friend class CVideoBufferPoolTyped;
+
+public:
+ HRESULT GetResource(ID3D11Resource** ppResource) override;
+ void Initialize(CDecoder* decoder) override;
+
+protected:
+ explicit CVideoBufferShared(int id)
+ : CVideoBuffer(id) {}
+
+ HANDLE handle = INVALID_HANDLE_VALUE;
+ Microsoft::WRL::ComPtr<ID3D11Resource> m_sharedRes;
+};
+
+class CVideoBufferCopy : public CVideoBufferShared
+{
+ template<typename TBuffer>
+ friend class CVideoBufferPoolTyped;
+
+public:
+ void Initialize(CDecoder* decoder) override;
+ unsigned GetIdx() override { return 0; }
+
+protected:
+ explicit CVideoBufferCopy(int id)
+ : CVideoBufferShared(id) {}
+
+ Microsoft::WRL::ComPtr<ID3D11Resource> m_copyRes;
+ Microsoft::WRL::ComPtr<ID3D11Resource> m_pResource;
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_pDeviceContext;
+};
+
+class CContext
+{
+public:
+ typedef std::shared_ptr<CContext> shared_ptr;
+ typedef std::weak_ptr<CContext> weak_ptr;
+
+ ~CContext();
+
+ static shared_ptr EnsureContext(CDecoder* decoder);
+ bool GetFormatAndConfig(AVCodecContext* avctx, D3D11_VIDEO_DECODER_DESC& format, D3D11_VIDEO_DECODER_CONFIG& config) const;
+ bool CreateSurfaces(const D3D11_VIDEO_DECODER_DESC& format, uint32_t count, uint32_t alignment,
+ ID3D11VideoDecoderOutputView** surfaces, HANDLE* pHandle, bool trueShared) const;
+ bool CreateDecoder(const D3D11_VIDEO_DECODER_DESC& format, const D3D11_VIDEO_DECODER_CONFIG& config,
+ ID3D11VideoDecoder** decoder, ID3D11VideoContext** context);
+ void Release(CDecoder* decoder);
+
+ bool Check() const;
+ bool Reset();
+ bool IsContextShared() const
+ {
+ return m_sharingAllowed;
+ }
+ bool HasAMDWorkaround() const
+ {
+ return m_atiWorkaround;
+ }
+
+private:
+ explicit CContext() = default;
+
+ void Close();
+ bool CreateContext();
+ void DestroyContext();
+ void QueryCaps();
+ bool IsValidDecoder(CDecoder* decoder);
+ bool GetConfig(const D3D11_VIDEO_DECODER_DESC& format, D3D11_VIDEO_DECODER_CONFIG& config) const;
+
+ static weak_ptr m_context;
+ static CCriticalSection m_section;
+
+ UINT m_input_count = 0;
+ GUID* m_input_list = nullptr;
+ bool m_atiWorkaround = false;
+ bool m_sharingAllowed = false;
+ Microsoft::WRL::ComPtr<ID3D11VideoContext> m_pD3D11Context;
+ Microsoft::WRL::ComPtr<ID3D11VideoDevice> m_pD3D11Device;
+ std::vector<CDecoder*> m_decoders;
+};
+
+class CVideoBufferPool : public IVideoBufferPool
+{
+public:
+ typedef std::shared_ptr<CVideoBufferPool> shared_ptr;
+
+ CVideoBufferPool();
+ virtual ~CVideoBufferPool();
+
+ // IVideoBufferPool overrides
+ ::CVideoBuffer* Get() override;
+ void Return(int id) override;
+
+ // views pool
+ void AddView(ID3D11View* view);
+ bool ReturnView(ID3D11View* view);
+ ID3D11View* GetView();
+ bool IsValid(ID3D11View* view);
+ size_t Size();
+ bool HasFree();
+ bool HasRefs();
+
+protected:
+ void Reset();
+ virtual CVideoBuffer* CreateBuffer(int idx) = 0;
+
+ CCriticalSection m_section;
+
+ std::vector<ID3D11View*> m_views;
+ std::deque<size_t> m_freeViews;
+ std::vector<CVideoBuffer*> m_out;
+ std::deque<size_t> m_freeOut;
+};
+
+template<typename TBuffer>
+class CVideoBufferPoolTyped : public CVideoBufferPool
+{
+protected:
+ CVideoBuffer* CreateBuffer(int idx) override
+ {
+ return new TBuffer(idx);
+ }
+};
+
+class CDecoder : public IHardwareDecoder, public ID3DResource
+{
+public:
+ ~CDecoder() override;
+
+ static IHardwareDecoder* Create(CDVDStreamInfo& hint, CProcessInfo& processInfo, AVPixelFormat fmt);
+ static bool Register();
+
+ // IHardwareDecoder overrides
+ bool Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat) override;
+ CDVDVideoCodec::VCReturn Decode(AVCodecContext* avctx, AVFrame* frame) override;
+ bool GetPicture(AVCodecContext* avctx, VideoPicture* picture) override;
+ CDVDVideoCodec::VCReturn Check(AVCodecContext* avctx) override;
+ const std::string Name() override { return "d3d11va"; }
+ unsigned GetAllowedReferences() override;
+ void Reset() override;
+
+ // IDVDResourceCounted overrides
+ long Release() override;
+
+ bool OpenDecoder();
+ int GetBuffer(AVCodecContext* avctx, AVFrame* pic);
+ void ReleaseBuffer(uint8_t* data);
+ void Close();
+ void CloseDXVADecoder();
+
+ //static members
+ static bool Supports(enum AVPixelFormat fmt);
+ static int FFGetBuffer(AVCodecContext* avctx, AVFrame* pic, int flags);
+ static void FFReleaseBuffer(void* opaque, uint8_t* data);
+
+protected:
+ friend CVideoBuffer;
+ friend CVideoBufferShared;
+ friend CVideoBufferCopy;
+
+ explicit CDecoder(CProcessInfo& processInfo);
+
+ enum EDeviceState
+ {
+ DXVA_OPEN,
+ DXVA_RESET,
+ DXVA_LOST
+ } m_state = DXVA_OPEN;
+
+
+ // ID3DResource overrides
+ void OnCreateDevice() override
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_state = DXVA_RESET;
+ m_event.Set();
+ }
+ void OnDestroyDevice(bool fatal) override
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_state = DXVA_LOST;
+ m_event.Reset();
+ }
+
+ CEvent m_event;
+ CCriticalSection m_section;
+ CProcessInfo& m_processInfo;
+ Microsoft::WRL::ComPtr<ID3D11VideoDecoder> m_pD3D11Decoder;
+ Microsoft::WRL::ComPtr<ID3D11VideoContext> m_pD3D11Context;
+ CVideoBufferPool::shared_ptr m_bufferPool;
+ CContext::shared_ptr m_dxvaContext;
+ CVideoBuffer* m_videoBuffer = nullptr;
+ struct AVD3D11VAContext* m_avD3D11Context = nullptr;
+ struct AVCodecContext* m_avCtx = nullptr;
+ int m_refs = 0;
+ unsigned int m_shared = 0;
+ unsigned int m_surface_alignment = 0;
+ HANDLE m_sharedHandle = INVALID_HANDLE_VALUE;
+ D3D11_VIDEO_DECODER_DESC m_format = {};
+ bool m_DVDWorkaround = false;
+};
+
+} // namespace DXVA
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp
new file mode 100644
index 0000000..0487b32
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp
@@ -0,0 +1,3242 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VAAPI.h"
+
+#include "DVDVideoCodec.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/MemUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <array>
+#include <mutex>
+
+#include <drm_fourcc.h>
+#include <va/va_drm.h>
+#include <va/va_drmcommon.h>
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+#include <libavutil/opt.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+}
+
+#include "system_egl.h"
+
+#include <EGL/eglext.h>
+#include <va/va_vpp.h>
+#include <xf86drm.h>
+
+#if VA_CHECK_VERSION(1, 0, 0)
+# include <va/va_str.h>
+#endif
+
+using namespace VAAPI;
+using namespace std::chrono_literals;
+
+#define NUM_RENDER_PICS 7
+
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPI = "videoplayer.usevaapi";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIAV1 = "videoplayer.usevaapiav1";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIHEVC = "videoplayer.usevaapihevc";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIMPEG2 = "videoplayer.usevaapimpeg2";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIMPEG4 = "videoplayer.usevaapimpeg4";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIVC1 = "videoplayer.usevaapivc1";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIVP8 = "videoplayer.usevaapivp8";
+constexpr auto SETTING_VIDEOPLAYER_USEVAAPIVP9 = "videoplayer.usevaapivp9";
+constexpr auto SETTING_VIDEOPLAYER_PREFERVAAPIRENDER = "videoplayer.prefervaapirender";
+
+void VAAPI::VaErrorCallback(void *user_context, const char *message)
+{
+ std::string str{message};
+ CLog::Log(LOGERROR, "libva error: {}", StringUtils::TrimRight(str));
+}
+
+void VAAPI::VaInfoCallback(void *user_context, const char *message)
+{
+ std::string str{message};
+ CLog::Log(LOGDEBUG, "libva info: {}", StringUtils::TrimRight(str));
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+CVAAPIContext *CVAAPIContext::m_context = 0;
+CCriticalSection CVAAPIContext::m_section;
+
+CVAAPIContext::CVAAPIContext()
+{
+ m_context = 0;
+ m_refCount = 0;
+ m_profiles = NULL;
+}
+
+void CVAAPIContext::Release(CDecoder *decoder)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ auto it = find(m_decoders.begin(), m_decoders.end(), decoder);
+ if (it != m_decoders.end())
+ m_decoders.erase(it);
+
+ m_refCount--;
+ if (m_refCount <= 0)
+ {
+ Close();
+ delete this;
+ m_context = 0;
+ }
+}
+
+void CVAAPIContext::Close()
+{
+ CLog::Log(LOGINFO, "VAAPI::Close - closing decoder context");
+ if (m_renderNodeFD >= 0)
+ {
+ close(m_renderNodeFD);
+ }
+
+ DestroyContext();
+}
+
+bool CVAAPIContext::EnsureContext(CVAAPIContext **ctx, CDecoder *decoder)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (m_context)
+ {
+ m_context->m_refCount++;
+ *ctx = m_context;
+ if (!m_context->IsValidDecoder(decoder))
+ m_context->m_decoders.push_back(decoder);
+ return true;
+ }
+
+ m_context = new CVAAPIContext();
+ *ctx = m_context;
+ {
+ std::unique_lock<CCriticalSection> gLock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ if (!m_context->CreateContext())
+ {
+ delete m_context;
+ m_context = 0;
+ *ctx = NULL;
+ return false;
+ }
+ }
+
+ m_context->m_refCount++;
+
+ if (!m_context->IsValidDecoder(decoder))
+ m_context->m_decoders.push_back(decoder);
+ *ctx = m_context;
+ return true;
+}
+
+void CVAAPIContext::SetValidDRMVaDisplayFromRenderNode()
+{
+ int const buf_size{128};
+ char name[buf_size];
+ int fd{-1};
+
+ // 128 is the start of the NUM in renderD<NUM>
+ for (int i = 128; i < (128 + 16); i++)
+ {
+ snprintf(name, buf_size, "/dev/dri/renderD%d", i);
+
+ fd = open(name, O_RDWR);
+
+ if (fd < 0)
+ {
+ continue;
+ }
+
+ auto display = vaGetDisplayDRM(fd);
+
+ if (display != nullptr)
+ {
+ m_renderNodeFD = fd;
+ m_display = display;
+ return;
+ }
+ close(fd);
+ }
+
+ CLog::Log(LOGERROR, "Failed to find any open render nodes in /dev/dri/renderD<num>");
+}
+
+void CVAAPIContext::SetVaDisplayForSystem()
+{
+ m_display = CDecoder::m_pWinSystem->GetVADisplay();
+
+ // Fallback to DRM
+ if (!m_display)
+ {
+ // Render nodes depends on kernel >= 3.15
+ SetValidDRMVaDisplayFromRenderNode();
+ }
+}
+
+bool CVAAPIContext::CreateContext()
+{
+ SetVaDisplayForSystem();
+
+ if (m_display == nullptr)
+ {
+ CLog::Log(LOGERROR, "Failed to find any VaDisplays for this system");
+ return false;
+ }
+
+#if VA_CHECK_VERSION(1, 0, 0)
+ vaSetErrorCallback(m_display, VaErrorCallback, nullptr);
+ vaSetInfoCallback(m_display, VaInfoCallback, nullptr);
+#endif
+
+ int major_version, minor_version;
+ if (!CheckSuccess(vaInitialize(m_display, &major_version, &minor_version), "vaInitialize"))
+ {
+ vaTerminate(m_display);
+ m_display = NULL;
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI - initialize version {}.{}", major_version, minor_version);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI - driver in use: {}", vaQueryVendorString(m_display));
+
+ QueryCaps();
+ if (!m_profileCount)
+ return false;
+
+ return true;
+}
+
+void CVAAPIContext::DestroyContext()
+{
+ delete[] m_profiles;
+ if (m_display)
+ {
+ if (CheckSuccess(vaTerminate(m_display), "vaTerminate"))
+ {
+ m_display = NULL;
+ }
+ else
+ {
+#if VA_CHECK_VERSION(1, 0, 0)
+ vaSetErrorCallback(m_display, nullptr, nullptr);
+ vaSetInfoCallback(m_display, nullptr, nullptr);
+#endif
+ }
+ }
+}
+
+void CVAAPIContext::QueryCaps()
+{
+ m_profileCount = 0;
+
+ int max_profiles = vaMaxNumProfiles(m_display);
+ m_profiles = new VAProfile[max_profiles];
+
+ if (!CheckSuccess(vaQueryConfigProfiles(m_display, m_profiles, &m_profileCount), "vaQueryConfigProfiles"))
+ return;
+
+ for(int i = 0; i < m_profileCount; i++)
+ {
+#if VA_CHECK_VERSION(1, 0, 0)
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI - profile {}", vaProfileStr(m_profiles[i]));
+#else
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI - profile {}", m_profiles[i]);
+#endif
+ }
+}
+
+VAConfigAttrib CVAAPIContext::GetAttrib(VAProfile profile)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ VAConfigAttrib attrib;
+ attrib.type = VAConfigAttribRTFormat;
+ CheckSuccess(vaGetConfigAttributes(m_display, profile, VAEntrypointVLD, &attrib, 1), "vaGetConfigAttributes");
+
+ return attrib;
+}
+
+bool CVAAPIContext::SupportsProfile(VAProfile profile)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ for (int i=0; i<m_profileCount; i++)
+ {
+ if (m_profiles[i] == profile)
+ return true;
+ }
+ return false;
+}
+
+VAConfigID CVAAPIContext::CreateConfig(VAProfile profile, VAConfigAttrib attrib)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ VAConfigID config = VA_INVALID_ID;
+ CheckSuccess(vaCreateConfig(m_display, profile, VAEntrypointVLD, &attrib, 1, &config), "vaCreateConfig");
+
+ return config;
+}
+
+bool CVAAPIContext::CheckSuccess(VAStatus status, const std::string& function)
+{
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI/context {} error: {} ({})", function, vaErrorStr(status), status);
+ return false;
+ }
+ return true;
+}
+
+VADisplay CVAAPIContext::GetDisplay()
+{
+ return m_display;
+}
+
+bool CVAAPIContext::IsValidDecoder(CDecoder *decoder)
+{
+ auto it = find(m_decoders.begin(), m_decoders.end(), decoder);
+ if (it != m_decoders.end())
+ return true;
+
+ return false;
+}
+
+void CVAAPIContext::FFReleaseBuffer(void *opaque, uint8_t *data)
+{
+ CDecoder *va = static_cast<CDecoder*>(opaque);
+ if (m_context && m_context->IsValidDecoder(va))
+ {
+ va->FFReleaseBuffer(data);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// VAAPI Video Surface states
+//-----------------------------------------------------------------------------
+
+#define SURFACE_USED_FOR_REFERENCE 0x01
+#define SURFACE_USED_FOR_RENDER 0x02
+
+void CVideoSurfaces::AddSurface(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_state[surf] = 0;
+ m_freeSurfaces.push_back(surf);
+}
+
+void CVideoSurfaces::ClearReference(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::ClearReference - surface invalid");
+ return;
+ }
+ m_state[surf] &= ~SURFACE_USED_FOR_REFERENCE;
+ if (m_state[surf] == 0)
+ {
+ m_freeSurfaces.push_back(surf);
+ }
+}
+
+bool CVideoSurfaces::MarkRender(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::MarkRender - surface invalid");
+ return false;
+ }
+ auto it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it != m_freeSurfaces.end())
+ {
+ m_freeSurfaces.erase(it);
+ }
+ m_state[surf] |= SURFACE_USED_FOR_RENDER;
+ return true;
+}
+
+void CVideoSurfaces::ClearRender(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::ClearRender - surface invalid");
+ return;
+ }
+ m_state[surf] &= ~SURFACE_USED_FOR_RENDER;
+ if (m_state[surf] == 0)
+ {
+ m_freeSurfaces.push_back(surf);
+ }
+}
+
+bool CVideoSurfaces::IsValid(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) != m_state.end())
+ return true;
+ else
+ return false;
+}
+
+VASurfaceID CVideoSurfaces::GetFree(VASurfaceID surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) != m_state.end())
+ {
+ auto it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it == m_freeSurfaces.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::GetFree - surface not free");
+ }
+ else
+ {
+ m_freeSurfaces.erase(it);
+ m_state[surf] = SURFACE_USED_FOR_REFERENCE;
+ return surf;
+ }
+ }
+
+ if (!m_freeSurfaces.empty())
+ {
+ VASurfaceID freeSurf = m_freeSurfaces.front();
+ m_freeSurfaces.pop_front();
+ m_state[freeSurf] = SURFACE_USED_FOR_REFERENCE;
+ return freeSurf;
+ }
+
+ return VA_INVALID_SURFACE;
+}
+
+VASurfaceID CVideoSurfaces::GetAtIndex(int idx)
+{
+ if ((size_t) idx >= m_state.size())
+ return VA_INVALID_SURFACE;
+
+ auto it = m_state.begin();
+ for(int i = 0; i < idx; i++)
+ ++it;
+ return it->first;
+}
+
+VASurfaceID CVideoSurfaces::RemoveNext(bool skiprender)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ VASurfaceID surf;
+ for(auto it = m_state.begin(); it != m_state.end(); ++it)
+ {
+ if (skiprender && it->second & SURFACE_USED_FOR_RENDER)
+ continue;
+ surf = it->first;
+ m_state.erase(surf);
+
+ auto it2 = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it2 != m_freeSurfaces.end())
+ m_freeSurfaces.erase(it2);
+ return surf;
+ }
+ return VA_INVALID_SURFACE;
+}
+
+void CVideoSurfaces::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_freeSurfaces.clear();
+ m_state.clear();
+}
+
+int CVideoSurfaces::Size()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return m_state.size();
+}
+
+bool CVideoSurfaces::HasFree()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return !m_freeSurfaces.empty();
+}
+
+int CVideoSurfaces::NumFree()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return m_freeSurfaces.size();
+}
+
+bool CVideoSurfaces::HasRefs()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (const auto &i : m_state)
+ {
+ if (i.second & SURFACE_USED_FOR_REFERENCE)
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// VAAPI
+//-----------------------------------------------------------------------------
+
+bool CDecoder::m_capGeneral = false;
+bool CDecoder::m_capDeepColor = false;
+IVaapiWinSystem* CDecoder::m_pWinSystem = nullptr;
+
+CDecoder::CDecoder(CProcessInfo& processInfo) :
+ m_vaapiOutput(*this, &m_inMsgEvent),
+ m_processInfo(processInfo)
+{
+ m_vaapiConfig.videoSurfaces = &m_videoSurfaces;
+
+ m_vaapiConfigured = false;
+ m_DisplayState = VAAPI_OPEN;
+ m_vaapiConfig.context = 0;
+ m_vaapiConfig.configId = VA_INVALID_ID;
+ m_vaapiConfig.processInfo = &m_processInfo;
+ m_avctx = nullptr;
+ m_getBufferError = 0;
+}
+
+CDecoder::~CDecoder()
+{
+ Close();
+}
+
+bool CDecoder::Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat fmt)
+{
+ if (!m_capGeneral)
+ return false;
+
+ // check if user wants to decode this format with VAAPI
+ std::map<AVCodecID, std::string> settings_map = {
+ {AV_CODEC_ID_H263, SETTING_VIDEOPLAYER_USEVAAPIMPEG4},
+ {AV_CODEC_ID_MPEG4, SETTING_VIDEOPLAYER_USEVAAPIMPEG4},
+ {AV_CODEC_ID_WMV3, SETTING_VIDEOPLAYER_USEVAAPIVC1},
+ {AV_CODEC_ID_VC1, SETTING_VIDEOPLAYER_USEVAAPIVC1},
+ {AV_CODEC_ID_MPEG2VIDEO, SETTING_VIDEOPLAYER_USEVAAPIMPEG2},
+ {AV_CODEC_ID_VP8, SETTING_VIDEOPLAYER_USEVAAPIVP8},
+ {AV_CODEC_ID_VP9, SETTING_VIDEOPLAYER_USEVAAPIVP9},
+ {AV_CODEC_ID_HEVC, SETTING_VIDEOPLAYER_USEVAAPIHEVC},
+ {AV_CODEC_ID_AV1, SETTING_VIDEOPLAYER_USEVAAPIAV1},
+ };
+
+ auto entry = settings_map.find(avctx->codec_id);
+ if (entry != settings_map.end())
+ {
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ auto setting = settings->GetSetting(entry->second);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", entry->second);
+ return false;
+ }
+
+ bool enabled = settings->GetBool(entry->second) && setting->IsVisible();
+ if (!enabled)
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI - open decoder");
+
+ if (!CVAAPIContext::EnsureContext(&m_vaapiConfig.context, this))
+ return false;
+
+ if(avctx->coded_width == 0 ||
+ avctx->coded_height == 0)
+ {
+ CLog::Log(LOGWARNING,"VAAPI::Open: no width/height available, can't init");
+ return false;
+ }
+
+ m_vaapiConfig.driverIsMesa = StringUtils::StartsWith(vaQueryVendorString(m_vaapiConfig.context->GetDisplay()), "Mesa");
+ m_vaapiConfig.vidWidth = avctx->width;
+ m_vaapiConfig.vidHeight = avctx->height;
+ m_vaapiConfig.outWidth = avctx->width;
+ m_vaapiConfig.outHeight = avctx->height;
+ m_vaapiConfig.surfaceWidth = avctx->coded_width;
+ m_vaapiConfig.surfaceHeight = avctx->coded_height;
+ m_vaapiConfig.aspect = avctx->sample_aspect_ratio;
+ m_vaapiConfig.bitDepth = avctx->bits_per_raw_sample;
+ m_DisplayState = VAAPI_OPEN;
+ m_vaapiConfigured = false;
+ m_presentPicture = nullptr;
+ m_getBufferError = 0;
+
+ VAProfile profile;
+ switch (avctx->codec_id)
+ {
+ case AV_CODEC_ID_MPEG2VIDEO:
+ profile = VAProfileMPEG2Main;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ case AV_CODEC_ID_MPEG4:
+ case AV_CODEC_ID_H263:
+ profile = VAProfileMPEG4AdvancedSimple;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ case AV_CODEC_ID_H264:
+ {
+ if (avctx->profile == FF_PROFILE_H264_CONSTRAINED_BASELINE)
+ {
+ profile = VAProfileH264ConstrainedBaseline;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ }
+ else
+ {
+ if(avctx->profile == FF_PROFILE_H264_MAIN)
+ {
+ profile = VAProfileH264Main;
+ if (m_vaapiConfig.context->SupportsProfile(profile))
+ break;
+ }
+ profile = VAProfileH264High;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ }
+ break;
+ }
+ case AV_CODEC_ID_HEVC:
+ {
+ if (avctx->profile == FF_PROFILE_HEVC_MAIN_10)
+ {
+ if (!m_capDeepColor)
+ return false;
+
+ profile = VAProfileHEVCMain10;
+ }
+ else if (avctx->profile == FF_PROFILE_HEVC_MAIN)
+ profile = VAProfileHEVCMain;
+ else
+ profile = VAProfileNone;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ }
+ case AV_CODEC_ID_VP8:
+ {
+ profile = VAProfileVP8Version0_3;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ }
+ case AV_CODEC_ID_VP9:
+ {
+ if (avctx->profile == FF_PROFILE_VP9_0)
+ profile = VAProfileVP9Profile0;
+ else if (avctx->profile == FF_PROFILE_VP9_2)
+ profile = VAProfileVP9Profile2;
+ else
+ profile = VAProfileNone;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ }
+ case AV_CODEC_ID_WMV3:
+ profile = VAProfileVC1Main;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ case AV_CODEC_ID_VC1:
+ profile = VAProfileVC1Advanced;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+#if VA_CHECK_VERSION(1, 8, 0)
+ case AV_CODEC_ID_AV1:
+ {
+ if (avctx->profile == FF_PROFILE_AV1_MAIN)
+ profile = VAProfileAV1Profile0;
+ else if (avctx->profile == FF_PROFILE_AV1_HIGH)
+ profile = VAProfileAV1Profile1;
+ else
+ profile = VAProfileNone;
+ if (!m_vaapiConfig.context->SupportsProfile(profile))
+ return false;
+ break;
+ }
+#endif
+ default:
+ return false;
+ }
+
+ m_vaapiConfig.profile = profile;
+ m_vaapiConfig.attrib = m_vaapiConfig.context->GetAttrib(profile);
+ if ((m_vaapiConfig.attrib.value & (VA_RT_FORMAT_YUV420 | VA_RT_FORMAT_YUV420_10BPP)) == 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI - invalid yuv format {:x}", m_vaapiConfig.attrib.value);
+ return false;
+ }
+
+ if (avctx->codec_id == AV_CODEC_ID_H264)
+ {
+ m_vaapiConfig.maxReferences = avctx->refs;
+ if (m_vaapiConfig.maxReferences > 16)
+ m_vaapiConfig.maxReferences = 16;
+ if (m_vaapiConfig.maxReferences < 5)
+ m_vaapiConfig.maxReferences = 5;
+ }
+ else if (avctx->codec_id == AV_CODEC_ID_HEVC)
+ m_vaapiConfig.maxReferences = 16;
+ else if (avctx->codec_id == AV_CODEC_ID_VP9)
+ m_vaapiConfig.maxReferences = 8;
+ else if (avctx->codec_id == AV_CODEC_ID_AV1)
+ m_vaapiConfig.maxReferences = 18;
+ else
+ m_vaapiConfig.maxReferences = 2;
+
+ // add an extra surface for safety, some faulty material
+ // make ffmpeg require more buffers
+ m_vaapiConfig.maxReferences += 6;
+
+ if (!ConfigVAAPI())
+ {
+ return false;
+ }
+
+ m_deviceRef = std::unique_ptr<AVBufferRef, AVBufferRefDeleter>(
+ av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI), AVBufferRefDeleter());
+
+ AVHWDeviceContext* deviceCtx = (AVHWDeviceContext*)m_deviceRef->data;
+ AVVAAPIDeviceContext *vaapiDeviceCtx = (AVVAAPIDeviceContext*)deviceCtx->hwctx;
+ AVBufferRef* framesRef = av_hwframe_ctx_alloc(m_deviceRef.get());
+ AVHWFramesContext *framesCtx = (AVHWFramesContext*)framesRef->data;
+ AVVAAPIFramesContext *vaapiFramesCtx = (AVVAAPIFramesContext*)framesCtx->hwctx;
+
+ vaapiDeviceCtx->display = m_vaapiConfig.dpy;
+ vaapiDeviceCtx->driver_quirks = AV_VAAPI_DRIVER_QUIRK_RENDER_PARAM_BUFFERS;
+ vaapiFramesCtx->nb_attributes = 0;
+ vaapiFramesCtx->nb_surfaces = m_videoSurfaces.Size();
+ VASurfaceID *surfaceIds = (VASurfaceID*)av_malloc(vaapiFramesCtx->nb_surfaces * sizeof(VASurfaceID));
+ for (int i=0; i<vaapiFramesCtx->nb_surfaces; ++i)
+ surfaceIds[i] = m_videoSurfaces.GetAtIndex(i);
+ vaapiFramesCtx->surface_ids = surfaceIds;
+ framesCtx->format = AV_PIX_FMT_VAAPI;
+ framesCtx->width = avctx->coded_width;
+ framesCtx->height = avctx->coded_height;
+
+ avctx->hw_frames_ctx = framesRef;
+ avctx->get_buffer2 = CDecoder::FFGetBuffer;
+ avctx->slice_flags = SLICE_FLAG_CODED_ORDER|SLICE_FLAG_ALLOW_FIELD;
+
+ m_avctx = mainctx;
+ return true;
+}
+
+void CDecoder::Close()
+{
+ CLog::Log(LOGINFO, "VAAPI::{}", __FUNCTION__);
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ FiniVAAPIOutput();
+
+ m_deviceRef.reset();
+
+ if (m_vaapiConfig.context)
+ m_vaapiConfig.context->Release(this);
+ m_vaapiConfig.context = 0;
+}
+
+long CDecoder::Release()
+{
+ // if ffmpeg holds any references, flush buffers
+ if (m_avctx && m_videoSurfaces.HasRefs())
+ {
+ avcodec_flush_buffers(m_avctx);
+ }
+
+ if (m_presentPicture)
+ {
+ m_presentPicture->Release();
+ m_presentPicture = nullptr;
+ }
+ // check if we should do some pre-cleanup here
+ // a second decoder might need resources
+ if (m_vaapiConfigured == true)
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI::Release pre-cleanup");
+
+ std::unique_lock<CCriticalSection> lock1(CServiceBroker::GetWinSystem()->GetGfxContext());
+ Message *reply;
+ if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::PRECLEANUP,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - pre-cleanup returned error", __FUNCTION__);
+ m_DisplayState = VAAPI_ERROR;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - pre-cleanup timed out", __FUNCTION__);
+ m_DisplayState = VAAPI_ERROR;
+ }
+
+ VASurfaceID surf;
+ while((surf = m_videoSurfaces.RemoveNext(true)) != VA_INVALID_SURFACE)
+ {
+ CheckSuccess(vaDestroySurfaces(m_vaapiConfig.dpy, &surf, 1), "vaDestroySurfaces");
+ }
+ }
+ return IHardwareDecoder::Release();
+}
+
+long CDecoder::ReleasePicReference()
+{
+ return IHardwareDecoder::Release();
+}
+
+int CDecoder::FFGetBuffer(AVCodecContext *avctx, AVFrame *pic, int flags)
+{
+ ICallbackHWAccel* cb = static_cast<ICallbackHWAccel*>(avctx->opaque);
+ CDecoder* va = static_cast<CDecoder*>(cb->GetHWAccel());
+
+ // while we are waiting to recover we can't do anything
+ std::unique_lock<CCriticalSection> lock(va->m_DecoderSection);
+
+ if(va->m_DisplayState != VAAPI_OPEN)
+ {
+ CLog::Log(LOGWARNING, "VAAPI::FFGetBuffer - returning due to awaiting recovery");
+ return -1;
+ }
+
+ VASurfaceID surf = (VASurfaceID)(uintptr_t)pic->data[3];
+ surf = va->m_videoSurfaces.GetFree(surf != 0 ? surf : VA_INVALID_SURFACE);
+
+ if (surf == VA_INVALID_SURFACE)
+ {
+ uint16_t decoded, processed, render;
+ bool vpp;
+ va->m_bufferStats.Get(decoded, processed, render, vpp);
+ CLog::Log(LOGWARNING, "VAAPI::FFGetBuffer - no surface available - dec: {}, render: {}",
+ decoded, render);
+ va->m_getBufferError++;
+ return -1;
+ }
+
+ va->m_getBufferError = 0;
+
+ pic->data[1] = pic->data[2] = NULL;
+ pic->data[0] = (uint8_t*)(uintptr_t)surf;
+ pic->data[3] = (uint8_t*)(uintptr_t)surf;
+ pic->linesize[0] = pic->linesize[1] = pic->linesize[2] = 0;
+ AVBufferRef *buffer = av_buffer_create(pic->data[3], 0, CVAAPIContext::FFReleaseBuffer, va, 0);
+ if (!buffer)
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - error creating buffer", __FUNCTION__);
+ return -1;
+ }
+ pic->buf[0] = buffer;
+
+ pic->reordered_opaque = avctx->reordered_opaque;
+ va->Acquire();
+ return 0;
+}
+
+void CDecoder::FFReleaseBuffer(uint8_t *data)
+{
+ {
+ VASurfaceID surf;
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ surf = (VASurfaceID)(uintptr_t)data;
+ m_videoSurfaces.ClearReference(surf);
+ }
+
+ IHardwareDecoder::Release();
+}
+
+void CDecoder::SetCodecControl(int flags)
+{
+ m_codecControl = flags & (DVD_CODEC_CTRL_DRAIN | DVD_CODEC_CTRL_HURRY);
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Decode(AVCodecContext* avctx, AVFrame* pFrame)
+{
+ CDVDVideoCodec::VCReturn result = Check(avctx);
+ if (result != CDVDVideoCodec::VC_NOBUFFER && result != CDVDVideoCodec::VC_NONE)
+ return result;
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (!m_vaapiConfigured)
+ return CDVDVideoCodec::VC_ERROR;
+
+ if (pFrame)
+ { // we have a new frame from decoder
+
+ VASurfaceID surf = (VASurfaceID)(uintptr_t)pFrame->data[3];
+ // ffmpeg vc-1 decoder does not flush, make sure the data buffer is still valid
+ if (!m_videoSurfaces.IsValid(surf))
+ {
+ CLog::Log(LOGWARNING, "VAAPI::Decode - ignoring invalid buffer");
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ m_videoSurfaces.MarkRender(surf);
+
+ // send frame to output for processing
+ CVaapiDecodedPicture *pic = new CVaapiDecodedPicture();
+ static_cast<ICallbackHWAccel*>(avctx->opaque)->GetPictureCommon(&(pic->DVDPic));
+ m_codecControl = pic->DVDPic.iFlags & (DVD_CODEC_CTRL_HURRY | DVD_CODEC_CTRL_NO_POSTPROC);
+ pic->videoSurface = surf;
+ m_bufferStats.IncDecoded();
+ CPayloadWrap<CVaapiDecodedPicture> *payload = new CPayloadWrap<CVaapiDecodedPicture>(pic);
+ m_vaapiOutput.m_dataPort.SendOutMessage(COutputDataProtocol::NEWFRAME, payload);
+ }
+
+ uint16_t decoded, processed, render;
+ bool vpp;
+ Message *msg;
+ while (m_vaapiOutput.m_controlPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputControlProtocol::ERROR)
+ {
+ m_DisplayState = VAAPI_ERROR;
+ msg->Release();
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ msg->Release();
+ }
+
+ bool drain = (m_codecControl & DVD_CODEC_CTRL_DRAIN);
+
+ m_bufferStats.Get(decoded, processed, render, vpp);
+ // if all pics are drained, break the loop by setting VC_EOF
+ if (drain && decoded <= 0 && processed <= 0 && render <= 0)
+ return CDVDVideoCodec::VC_EOF;
+
+ while (true)
+ {
+ // first fill the buffers to keep vaapi busy
+ if (!drain && decoded < 2 && processed < 3 && m_videoSurfaces.HasFree())
+ {
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ else if (m_vaapiOutput.m_dataPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::PICTURE)
+ {
+ if (m_presentPicture)
+ {
+ m_presentPicture->Release();
+ m_presentPicture = nullptr;
+ }
+
+ m_presentPicture = *(CVaapiRenderPicture**)msg->data;
+ m_bufferStats.DecRender();
+ m_bufferStats.SetParams(0, m_codecControl);
+ msg->Release();
+ return CDVDVideoCodec::VC_PICTURE;
+ }
+ msg->Release();
+ }
+ else if (m_vaapiOutput.m_controlPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputControlProtocol::STATS)
+ {
+ msg->Release();
+ m_bufferStats.Get(decoded, processed, render, vpp);
+ if (!drain && decoded < 2 && processed < 3)
+ {
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ }
+ else
+ {
+ msg->Release();
+ m_DisplayState = VAAPI_ERROR;
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ }
+
+ if (!m_inMsgEvent.Wait(2000ms))
+ break;
+ }
+
+ CLog::Log(LOGERROR,
+ "VAAPI::{} - timed out waiting for output message - decoded: {}, proc: {}, has free "
+ "surface: {}",
+ __FUNCTION__, decoded, processed, m_videoSurfaces.HasFree() ? "yes" : "no");
+ m_DisplayState = VAAPI_ERROR;
+
+ return CDVDVideoCodec::VC_ERROR;
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Check(AVCodecContext* avctx)
+{
+ EDisplayState state;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ state = m_DisplayState;
+ }
+
+ if (state == VAAPI_LOST)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI::Check waiting for display reset event");
+ if (!m_DisplayEvent.Wait(4000ms))
+ {
+ CLog::Log(LOGERROR, "VAAPI::Check - device didn't reset in reasonable time");
+ state = VAAPI_RESET;
+ }
+ else
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ state = m_DisplayState;
+ }
+ }
+ if (state == VAAPI_RESET || state == VAAPI_ERROR)
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ avcodec_flush_buffers(avctx);
+ FiniVAAPIOutput();
+ if (m_vaapiConfig.context)
+ m_vaapiConfig.context->Release(this);
+ m_vaapiConfig.context = 0;
+
+ if (CVAAPIContext::EnsureContext(&m_vaapiConfig.context, this) && ConfigVAAPI())
+ {
+ m_DisplayState = VAAPI_OPEN;
+ }
+
+ if (state == VAAPI_RESET)
+ return CDVDVideoCodec::VC_FLUSHED;
+ else
+ return CDVDVideoCodec::VC_ERROR;
+ }
+
+ if (m_getBufferError > 0 && m_getBufferError < 5)
+ {
+ // if there is no other error, sleep for a short while
+ // in order not to drain player's message queue
+ KODI::TIME::Sleep(10ms);
+
+ return CDVDVideoCodec::VC_NOBUFFER;
+ }
+
+ return CDVDVideoCodec::VC_NONE;
+}
+
+bool CDecoder::GetPicture(AVCodecContext* avctx, VideoPicture* picture)
+{
+ if (picture->videoBuffer)
+ {
+ picture->videoBuffer->Release();
+ picture->videoBuffer = nullptr;
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (m_DisplayState != VAAPI_OPEN)
+ return false;
+
+ picture->SetParams(m_presentPicture->DVDPic);
+ picture->videoBuffer = m_presentPicture;
+ m_presentPicture = nullptr;
+
+ return true;
+}
+
+void CDecoder::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (m_presentPicture)
+ {
+ m_presentPicture->Release();
+ m_presentPicture = nullptr;
+ }
+
+ if (!m_vaapiConfigured)
+ return;
+
+ Message *reply;
+ if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::FLUSH,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - flush returned error", __FUNCTION__);
+ m_DisplayState = VAAPI_ERROR;
+ }
+ else
+ {
+ m_bufferStats.Reset();
+ m_getBufferError = 0;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - flush timed out", __FUNCTION__);
+ m_DisplayState = VAAPI_ERROR;
+ }
+}
+
+bool CDecoder::CanSkipDeint()
+{
+ return m_bufferStats.CanSkipDeint();
+}
+
+bool CDecoder::CheckSuccess(VAStatus status, const std::string& function)
+{
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI/decoder {} error: {} ({})", function, vaErrorStr(status), status);
+ m_ErrorCount++;
+
+ if(m_DisplayState == VAAPI_OPEN)
+ {
+ if (m_ErrorCount > 2)
+ m_DisplayState = VAAPI_ERROR;
+ }
+ return false;
+ }
+ m_ErrorCount = 0;
+ return true;
+}
+
+bool CDecoder::ConfigVAAPI()
+{
+ m_vaapiConfig.dpy = m_vaapiConfig.context->GetDisplay();
+ m_vaapiConfig.attrib = m_vaapiConfig.context->GetAttrib(m_vaapiConfig.profile);
+ if ((m_vaapiConfig.attrib.value & (VA_RT_FORMAT_YUV420 | VA_RT_FORMAT_YUV420_10BPP)) == 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI - invalid yuv format {:x}", m_vaapiConfig.attrib.value);
+ return false;
+ }
+
+ m_vaapiConfig.configId = m_vaapiConfig.context->CreateConfig(m_vaapiConfig.profile,
+ m_vaapiConfig.attrib);
+ if (m_vaapiConfig.configId == VA_INVALID_ID)
+ return false;
+
+ // create surfaces
+ unsigned int format = VA_RT_FORMAT_YUV420;
+ std::int32_t pixelFormat = VA_FOURCC_NV12;
+
+ if ((m_vaapiConfig.profile == VAProfileHEVCMain10 || m_vaapiConfig.profile == VAProfileVP9Profile2
+#if VA_CHECK_VERSION(1, 8, 0)
+ || m_vaapiConfig.profile == VAProfileAV1Profile0
+#endif
+ ) &&
+ m_vaapiConfig.bitDepth == 10)
+ {
+ format = VA_RT_FORMAT_YUV420_10BPP;
+ pixelFormat = VA_FOURCC_P010;
+ }
+
+ VASurfaceAttrib attribs[1], *attrib;
+ attrib = attribs;
+ attrib->flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attrib->type = VASurfaceAttribPixelFormat;
+ attrib->value.type = VAGenericValueTypeInteger;
+ attrib->value.value.i = pixelFormat;
+
+ VASurfaceID surfaces[32];
+ int nb_surfaces = m_vaapiConfig.maxReferences;
+ if (!CheckSuccess(
+ vaCreateSurfaces(m_vaapiConfig.dpy, format, m_vaapiConfig.surfaceWidth,
+ m_vaapiConfig.surfaceHeight, surfaces,
+ nb_surfaces, attribs, 1), "vaCreateSurfaces"))
+ {
+ return false;
+ }
+ for (int i=0; i<nb_surfaces; i++)
+ {
+ m_videoSurfaces.AddSurface(surfaces[i]);
+ }
+
+ // initialize output
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_vaapiConfig.stats = &m_bufferStats;
+ m_bufferStats.Reset();
+ m_vaapiOutput.Start();
+ Message *reply;
+ if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::INIT,
+ &reply,
+ 2000,
+ &m_vaapiConfig,
+ sizeof(m_vaapiConfig)))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ if (!success)
+ {
+ reply->Release();
+ CLog::Log(LOGERROR, "VAAPI::{} - vaapi output returned error", __FUNCTION__);
+ m_vaapiOutput.Dispose();
+ return false;
+ }
+ reply->Release();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - failed to init output", __FUNCTION__);
+ m_vaapiOutput.Dispose();
+ return false;
+ }
+
+ m_inMsgEvent.Reset();
+ m_vaapiConfigured = true;
+ m_ErrorCount = 0;
+
+ return true;
+}
+
+void CDecoder::FiniVAAPIOutput()
+{
+ if (!m_vaapiConfigured)
+ return;
+
+ // uninit output
+ m_vaapiOutput.Dispose();
+ m_vaapiConfigured = false;
+
+ // destroy surfaces
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI::FiniVAAPIOutput destroying {} video surfaces",
+ m_videoSurfaces.Size());
+ VASurfaceID surf;
+ while((surf = m_videoSurfaces.RemoveNext()) != VA_INVALID_SURFACE)
+ {
+ CheckSuccess(vaDestroySurfaces(m_vaapiConfig.dpy, &surf, 1), "vaDestroySurfaces");
+ }
+ m_videoSurfaces.Reset();
+
+ // destroy vaapi config
+ if (m_vaapiConfig.configId != VA_INVALID_ID)
+ CheckSuccess(vaDestroyConfig(m_vaapiConfig.dpy, m_vaapiConfig.configId), "vaDestroyConfig");
+ m_vaapiConfig.configId = VA_INVALID_ID;
+}
+
+void CDecoder::ReturnRenderPicture(CVaapiRenderPicture *renderPic)
+{
+ m_vaapiOutput.m_dataPort.SendOutMessage(COutputDataProtocol::RETURNPIC, &renderPic, sizeof(renderPic));
+}
+
+IHardwareDecoder* CDecoder::Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt)
+{
+ if (fmt == AV_PIX_FMT_VAAPI_VLD && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(SETTING_VIDEOPLAYER_USEVAAPI))
+ return new VAAPI::CDecoder(processInfo);
+
+ return nullptr;
+}
+
+void CDecoder::Register(IVaapiWinSystem *winSystem, bool deepColor)
+{
+ m_pWinSystem = winSystem;
+
+ CVaapiConfig config;
+ if (!CVAAPIContext::EnsureContext(&config.context, nullptr))
+ return;
+
+ m_capGeneral = true;
+ m_capDeepColor = deepColor;
+ CDVDFactoryCodec::RegisterHWAccel("vaapi", CDecoder::Create);
+ config.context->Release(nullptr);
+
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ constexpr std::array<const char*, 9> vaapiSettings = {
+ SETTING_VIDEOPLAYER_USEVAAPI, SETTING_VIDEOPLAYER_USEVAAPIMPEG4,
+ SETTING_VIDEOPLAYER_USEVAAPIVC1, SETTING_VIDEOPLAYER_USEVAAPIMPEG2,
+ SETTING_VIDEOPLAYER_USEVAAPIVP8, SETTING_VIDEOPLAYER_USEVAAPIVP9,
+ SETTING_VIDEOPLAYER_USEVAAPIHEVC, SETTING_VIDEOPLAYER_PREFERVAAPIRENDER,
+ SETTING_VIDEOPLAYER_USEVAAPIAV1};
+
+ for (const auto vaapiSetting : vaapiSettings)
+ {
+ auto setting = settings->GetSetting(vaapiSetting);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", vaapiSetting);
+ continue;
+ }
+
+ setting->SetVisible(true);
+ }
+}
+
+void CDecoder::AVBufferRefDeleter::operator()(AVBufferRef* p) const
+{
+ av_buffer_unref(&p);
+}
+
+//-----------------------------------------------------------------------------
+// Buffer Pool
+//-----------------------------------------------------------------------------
+
+/**
+ * Buffer pool holds allocated vaapi and gl resources
+ * Embedded in COutput
+ */
+class VAAPI::CVaapiBufferPool : public IVideoBufferPool
+{
+public:
+ explicit CVaapiBufferPool(CDecoder &decoder);
+ ~CVaapiBufferPool() override;
+ CVideoBuffer* Get() override;
+ void Return(int id) override;
+ CVaapiRenderPicture* GetVaapi();
+ bool HasFree();
+ void QueueReturnPicture(CVaapiRenderPicture *pic);
+ CVaapiRenderPicture* ProcessSyncPicture();
+ void Init();
+ void DeleteTextures(bool precleanup);
+
+ std::deque<CVaapiProcessedPicture> processedPics;
+ std::deque<CVaapiProcessedPicture> processedPicsAway;
+ std::deque<CVaapiDecodedPicture> decodedPics;
+ int procPicId;
+
+protected:
+ std::vector<CVaapiRenderPicture*> allRenderPics;
+ std::deque<int> usedRenderPics;
+ std::deque<int> freeRenderPics;
+ std::deque<int> syncRenderPics;
+
+ CDecoder &m_vaapi;
+};
+
+CVaapiBufferPool::CVaapiBufferPool(CDecoder &decoder)
+ : m_vaapi(decoder)
+{
+ CVaapiRenderPicture *pic;
+ for (unsigned int i = 0; i < NUM_RENDER_PICS; i++)
+ {
+ pic = new CVaapiRenderPicture(i);
+ allRenderPics.push_back(pic);
+ freeRenderPics.push_back(i);
+ }
+}
+
+CVaapiBufferPool::~CVaapiBufferPool()
+{
+ CVaapiRenderPicture *pic;
+ for (unsigned int i = 0; i < NUM_RENDER_PICS; i++)
+ {
+ pic = allRenderPics[i];
+ delete pic;
+ }
+ allRenderPics.clear();
+}
+
+CVideoBuffer* CVaapiBufferPool::Get()
+{
+ if (freeRenderPics.empty())
+ return nullptr;
+
+ int idx = freeRenderPics.front();
+ freeRenderPics.pop_front();
+ usedRenderPics.push_back(idx);
+
+ CVideoBuffer *retPic = allRenderPics[idx];
+ retPic->Acquire(GetPtr());
+
+ m_vaapi.Acquire();
+
+ return retPic;
+}
+
+void CVaapiBufferPool::Return(int id)
+{
+ CVaapiRenderPicture *pic = allRenderPics[id];
+
+ m_vaapi.ReturnRenderPicture(pic);
+ m_vaapi.ReleasePicReference();
+}
+
+CVaapiRenderPicture* CVaapiBufferPool::GetVaapi()
+{
+ return dynamic_cast<CVaapiRenderPicture*>(Get());
+}
+
+bool CVaapiBufferPool::HasFree()
+{
+ return !freeRenderPics.empty();
+}
+
+void CVaapiBufferPool::QueueReturnPicture(CVaapiRenderPicture *pic)
+{
+ std::deque<int>::iterator it;
+ for (it = usedRenderPics.begin(); it != usedRenderPics.end(); ++it)
+ {
+ if (allRenderPics[*it] == pic)
+ {
+ break;
+ }
+ }
+
+ if (it == usedRenderPics.end())
+ {
+ CLog::Log(LOGWARNING, "CVaapiRenderPicture::QueueReturnPicture - pic not found");
+ return;
+ }
+
+ // check if already queued
+ auto it2 = find(syncRenderPics.begin(), syncRenderPics.end(), *it);
+ if (it2 == syncRenderPics.end())
+ {
+ syncRenderPics.push_back(*it);
+ }
+}
+
+CVaapiRenderPicture* CVaapiBufferPool::ProcessSyncPicture()
+{
+ CVaapiRenderPicture *retPic = nullptr;
+
+ for (auto it = syncRenderPics.begin(); it != syncRenderPics.end(); ++it)
+ {
+ retPic = allRenderPics[*it];
+
+ freeRenderPics.push_back(*it);
+
+ auto it2 = find(usedRenderPics.begin(), usedRenderPics.end(),*it);
+ if (it2 == usedRenderPics.end())
+ {
+ CLog::Log(LOGERROR, "CVaapiRenderPicture::ProcessSyncPicture - pic not found in queue");
+ }
+ else
+ {
+ usedRenderPics.erase(it2);
+ }
+ it = syncRenderPics.erase(it);
+
+ if (!retPic->valid)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVaapiRenderPicture::{} - return of invalid render pic",
+ __FUNCTION__);
+ retPic = nullptr;
+ }
+ break;
+ }
+ return retPic;
+}
+
+void CVaapiBufferPool::Init()
+{
+ for (auto &pic : allRenderPics)
+ {
+ pic->avFrame = av_frame_alloc();
+ pic->valid = false;
+ }
+ procPicId = 0;
+}
+
+void CVaapiBufferPool::DeleteTextures(bool precleanup)
+{
+ for (auto &pic : allRenderPics)
+ {
+ if (precleanup && pic->valid)
+ continue;
+
+ av_frame_free(&pic->avFrame);
+ pic->valid = false;
+ }
+}
+
+void CVaapiRenderPicture::GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES])
+{
+ planes[0] = avFrame->data[0];
+ planes[1] = avFrame->data[1];
+ planes[2] = avFrame->data[2];
+}
+
+void CVaapiRenderPicture::GetStrides(int(&strides)[YuvImage::MAX_PLANES])
+{
+ strides[0] = avFrame->linesize[0];
+ strides[1] = avFrame->linesize[1];
+ strides[2] = avFrame->linesize[2];
+}
+
+//-----------------------------------------------------------------------------
+// Output
+//-----------------------------------------------------------------------------
+COutput::COutput(CDecoder &decoder, CEvent *inMsgEvent) :
+ CThread("Vaapi-Output"),
+ m_controlPort("OutputControlPort", inMsgEvent, &m_outMsgEvent),
+ m_dataPort("OutputDataPort", inMsgEvent, &m_outMsgEvent),
+ m_vaapi(decoder)
+{
+ m_inMsgEvent = inMsgEvent;
+ m_bufferPool = std::make_shared<CVaapiBufferPool>(decoder);
+}
+
+void COutput::Start()
+{
+ Create();
+}
+
+COutput::~COutput()
+{
+ Dispose();
+}
+
+void COutput::Dispose()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_bStop = true;
+ m_outMsgEvent.Set();
+ StopThread();
+ m_controlPort.Purge();
+ m_dataPort.Purge();
+}
+
+void COutput::OnStartup()
+{
+ CLog::Log(LOGINFO, "COutput::OnStartup: Output Thread created");
+}
+
+void COutput::OnExit()
+{
+ CLog::Log(LOGINFO, "COutput::OnExit: Output Thread terminated");
+}
+
+enum OUTPUT_STATES
+{
+ O_TOP = 0, // 0
+ O_TOP_ERROR, // 1
+ O_TOP_UNCONFIGURED, // 2
+ O_TOP_CONFIGURED, // 3
+ O_TOP_CONFIGURED_IDLE, // 4
+ O_TOP_CONFIGURED_WORK, // 5
+ O_TOP_CONFIGURED_STEP1, // 6
+ O_TOP_CONFIGURED_STEP2, // 7
+ O_TOP_CONFIGURED_OUTPUT, // 8
+};
+
+int VAAPI_OUTPUT_parentStates[] = {
+ -1,
+ 0, //TOP_ERROR
+ 0, //TOP_UNCONFIGURED
+ 0, //TOP_CONFIGURED
+ 3, //TOP_CONFIGURED_IDLE
+ 3, //TOP_CONFIGURED_WORK
+ 3, //TOP_CONFIGURED_STEP1
+ 3, //TOP_CONFIGURED_STEP2
+ 3, //TOP_CONFIGURED_OUTPUT
+};
+
+void COutput::StateMachine(int signal, Protocol *port, Message *msg)
+{
+ for (int state = m_state; ; state = VAAPI_OUTPUT_parentStates[state])
+ {
+ switch (state)
+ {
+ case O_TOP: // TOP
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::FLUSH:
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ case COutputControlProtocol::PRECLEANUP:
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case COutputDataProtocol::RETURNPIC:
+ CVaapiRenderPicture *pic;
+ pic = *((CVaapiRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ return;
+ case COutputDataProtocol::RETURNPROCPIC:
+ int id;
+ id = *((int*)msg->data);
+ ProcessReturnProcPicture(id);
+ return;
+ default:
+ break;
+ }
+ }
+ {
+ std::string portName = port == NULL ? "timer" : port->portName;
+ CLog::Log(LOGWARNING, "COutput::{} - signal: {} form port: {} not handled for state: {}",
+ __FUNCTION__, signal, portName, m_state);
+ }
+ return;
+
+ case O_TOP_ERROR:
+ break;
+
+ case O_TOP_UNCONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::INIT:
+ CVaapiConfig *data;
+ data = reinterpret_cast<CVaapiConfig*>(msg->data);
+ if (data)
+ {
+ m_config = *data;
+ }
+ Init();
+
+ // set initial number of
+ EnsureBufferPool();
+ m_state = O_TOP_CONFIGURED_IDLE;
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::FLUSH:
+ Flush();
+ msg->Reply(COutputControlProtocol::ACC);
+ m_state = O_TOP_CONFIGURED_IDLE;
+ return;
+ case COutputControlProtocol::PRECLEANUP:
+ Flush();
+ ReleaseBufferPool(true);
+ msg->Reply(COutputControlProtocol::ACC);
+ m_state = O_TOP_UNCONFIGURED;
+ m_extTimeout = 10000;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case COutputDataProtocol::NEWFRAME:
+ CPayloadWrap<CVaapiDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVaapiDecodedPicture>*>(msg->payloadObj.get());
+ if (payload)
+ {
+ m_bufferPool->decodedPics.push_back(*(payload->GetPlayload()));
+ m_extTimeout = 0;
+ }
+ return;
+ case COutputDataProtocol::RETURNPIC:
+ CVaapiRenderPicture *pic;
+ pic = *((CVaapiRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ m_controlPort.SendInMessage(COutputControlProtocol::STATS);
+ m_extTimeout = 0;
+ return;
+ case COutputDataProtocol::RETURNPROCPIC:
+ int id;
+ id = *((int*)msg->data);
+ ProcessReturnProcPicture(id);
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_IDLE:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ ProcessSyncPicture();
+ m_extTimeout = 100;
+ if (HasWork())
+ {
+ m_state = O_TOP_CONFIGURED_WORK;
+ m_extTimeout = 0;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_WORK:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ if (PreferPP())
+ {
+ m_currentPicture = m_bufferPool->decodedPics.front();
+ m_bufferPool->decodedPics.pop_front();
+ InitCycle();
+ m_state = O_TOP_CONFIGURED_STEP1;
+ m_extTimeout = 0;
+ return;
+ }
+ else if (m_bufferPool->HasFree() &&
+ !m_bufferPool->processedPics.empty())
+ {
+ m_state = O_TOP_CONFIGURED_OUTPUT;
+ m_extTimeout = 0;
+ return;
+ }
+ else
+ m_state = O_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 100;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_STEP1:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ {
+ if (!m_pp->AddPicture(m_currentPicture))
+ {
+ m_state = O_TOP_ERROR;
+ return;
+ }
+ CVaapiProcessedPicture outPic;
+ if (m_pp->Filter(outPic))
+ {
+ m_config.stats->IncProcessed();
+ m_bufferPool->processedPics.push_back(outPic);
+ m_state = O_TOP_CONFIGURED_STEP2;
+ }
+ else
+ {
+ m_state = O_TOP_CONFIGURED_IDLE;
+ }
+ m_config.stats->DecDecoded();
+ m_controlPort.SendInMessage(COutputControlProtocol::STATS);
+ m_extTimeout = 0;
+ return;
+ }
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_STEP2:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ {
+ CVaapiProcessedPicture outPic;
+ if (m_pp->Filter(outPic))
+ {
+ m_bufferPool->processedPics.push_back(outPic);
+ m_config.stats->IncProcessed();
+ m_extTimeout = 0;
+ return;
+ }
+ m_state = O_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 0;
+ return;
+ }
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_OUTPUT:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ if (!m_bufferPool->processedPics.empty())
+ {
+ CVaapiRenderPicture *outPic;
+ CVaapiProcessedPicture procPic;
+ procPic = m_bufferPool->processedPics.front();
+ m_bufferPool->processedPics.pop_front();
+ outPic = ProcessPicture(procPic);
+ if (outPic)
+ {
+ m_config.stats->IncRender();
+ m_dataPort.SendInMessage(COutputDataProtocol::PICTURE, &outPic, sizeof(outPic));
+ }
+ m_config.stats->DecProcessed();
+ }
+ m_state = O_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default: // we are in no state, should not happen
+ CLog::Log(LOGERROR, "COutput::{} - no valid state: {}", __FUNCTION__, m_state);
+ return;
+ }
+ } // for
+}
+
+void COutput::Process()
+{
+ Message *msg = NULL;
+ Protocol *port = NULL;
+ bool gotMsg;
+
+ m_state = O_TOP_UNCONFIGURED;
+ m_extTimeout = 1000;
+ m_bStateMachineSelfTrigger = false;
+
+ while (!m_bStop)
+ {
+ gotMsg = false;
+
+ if (m_bStateMachineSelfTrigger)
+ {
+ m_bStateMachineSelfTrigger = false;
+ // self trigger state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+ // check control port
+ else if (m_controlPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_controlPort;
+ }
+ // check data port
+ else if (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ }
+ if (gotMsg)
+ {
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+
+ // wait for message
+ else if (m_outMsgEvent.Wait(std::chrono::milliseconds(m_extTimeout)))
+ {
+ continue;
+ }
+ // time out
+ else
+ {
+ msg = m_controlPort.GetMessage();
+ msg->signal = COutputControlProtocol::TIMEOUT;
+ port = 0;
+ // signal timeout to state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ }
+ }
+ Flush();
+ Uninit();
+}
+
+bool COutput::Init()
+{
+ m_diMethods.numDiMethods = 0;
+
+ m_pp = new CFFmpegPostproc();
+ m_pp->PreInit(m_config, &m_diMethods);
+ delete m_pp;
+
+ m_pp = new CVppPostproc();
+ m_pp->PreInit(m_config, &m_diMethods);
+ delete m_pp;
+
+ m_pp = nullptr;
+
+ std::list<EINTERLACEMETHOD> deintMethods;
+ deintMethods.assign(m_diMethods.diMethods, m_diMethods.diMethods + m_diMethods.numDiMethods);
+ m_config.processInfo->UpdateDeinterlacingMethods(deintMethods);
+ m_config.processInfo->SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_VAAPI_BOB);
+
+ m_seenInterlaced = false;
+
+ return true;
+}
+
+bool COutput::Uninit()
+{
+ ProcessSyncPicture();
+ ReleaseBufferPool();
+ if (m_pp)
+ {
+ std::shared_ptr<CPostproc> pp(m_pp);
+ m_discardedPostprocs.push_back(pp);
+ m_pp->Discard(this, &COutput::ReadyForDisposal);
+ m_pp = nullptr;
+ }
+
+ if (!m_discardedPostprocs.empty())
+ {
+ CLog::Log(LOGERROR, "VAAPI::COutput::Uninit - not all CPostprcs released");
+ }
+ return true;
+}
+
+void COutput::Flush()
+{
+ Message *msg;
+ while (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::NEWFRAME)
+ {
+ CPayloadWrap<CVaapiDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVaapiDecodedPicture>*>(msg->payloadObj.get());
+ if (payload)
+ {
+ CVaapiDecodedPicture pic = *(payload->GetPlayload());
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ }
+ }
+ else if (msg->signal == COutputDataProtocol::RETURNPIC)
+ {
+ CVaapiRenderPicture *pic;
+ pic = *((CVaapiRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ }
+ msg->Release();
+ }
+
+ while (m_dataPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::PICTURE)
+ {
+ CVaapiRenderPicture *pic;
+ pic = *((CVaapiRenderPicture**)msg->data);
+ pic->Release();
+ }
+ msg->Release();
+ }
+
+ for (unsigned int i = 0; i < m_bufferPool->decodedPics.size(); i++)
+ {
+ m_config.videoSurfaces->ClearRender(m_bufferPool->decodedPics[i].videoSurface);
+ }
+ m_bufferPool->decodedPics.clear();
+
+ for (unsigned int i = 0; i < m_bufferPool->processedPics.size(); i++)
+ {
+ ReleaseProcessedPicture(m_bufferPool->processedPics[i]);
+ }
+ m_bufferPool->processedPics.clear();
+
+ if (m_pp)
+ m_pp->Flush();
+}
+
+bool COutput::HasWork()
+{
+ // send a pic to renderer
+ if (m_bufferPool->HasFree() && !m_bufferPool->processedPics.empty())
+ return true;
+
+ bool ppWantsPic = true;
+ if (m_pp)
+ ppWantsPic = m_pp->WantsPic();
+
+ if (!m_bufferPool->decodedPics.empty() && m_bufferPool->processedPics.size() < 4 && ppWantsPic)
+ return true;
+
+ return false;
+}
+
+bool COutput::PreferPP()
+{
+ if (!m_bufferPool->decodedPics.empty())
+ {
+ if (!m_pp)
+ return true;
+
+ if (!m_pp->WantsPic())
+ return false;
+
+ if (!m_pp->DoesSync() && m_bufferPool->processedPics.size() < 4)
+ return true;
+
+ if (!m_bufferPool->HasFree() || m_bufferPool->processedPics.empty())
+ return true;
+ }
+
+ return false;
+}
+
+void COutput::InitCycle()
+{
+ uint64_t latency;
+ int flags;
+ m_config.stats->GetParams(latency, flags);
+
+ m_config.stats->SetCanSkipDeint(false);
+
+ EINTERLACEMETHOD method = m_config.processInfo->GetVideoSettings().m_InterlaceMethod;
+ bool interlaced = m_currentPicture.DVDPic.iFlags & DVP_FLAG_INTERLACED;
+ // Remember whether any interlaced frames were encountered already.
+ // If this is the case, the deinterlace method will never automatically be switched to NONE again in
+ // order to not change deint methods every few frames in PAFF streams.
+ m_seenInterlaced = m_seenInterlaced || interlaced;
+
+ if (!(flags & DVD_CODEC_CTRL_NO_POSTPROC) &&
+ m_seenInterlaced &&
+ method != VS_INTERLACEMETHOD_NONE)
+ {
+ if (!m_config.processInfo->Supports(method))
+ method = VS_INTERLACEMETHOD_VAAPI_BOB;
+
+ if (m_pp && !m_pp->UpdateDeintMethod(method))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Current postproc does not want new deinterlace mode, removing");
+ std::shared_ptr<CPostproc> pp(m_pp);
+ m_discardedPostprocs.push_back(pp);
+ m_pp->Discard(this, &COutput::ReadyForDisposal);
+ m_pp = nullptr;
+ m_config.processInfo->SetVideoDeintMethod("unknown");
+ }
+ if (!m_pp)
+ {
+ if (method == VS_INTERLACEMETHOD_DEINTERLACE ||
+ method == VS_INTERLACEMETHOD_RENDER_BOB)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing ffmpeg postproc");
+ m_pp = new CFFmpegPostproc();
+ m_config.stats->SetVpp(false);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing vaapi postproc");
+ m_pp = new CVppPostproc();
+ m_config.stats->SetVpp(true);
+ }
+ if (m_pp->PreInit(m_config))
+ {
+ m_pp->Init(method);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VAAPI output: Postproc preinit failed");
+ delete m_pp;
+ m_pp = nullptr;
+ }
+ }
+ }
+ // progressive
+ else
+ {
+ method = VS_INTERLACEMETHOD_NONE;
+
+ if (m_pp && !m_pp->UpdateDeintMethod(method))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Current postproc does not want new deinterlace mode, removing");
+ std::shared_ptr<CPostproc> pp(m_pp);
+ m_discardedPostprocs.push_back(pp);
+ m_pp->Discard(this, &COutput::ReadyForDisposal);
+ m_pp = nullptr;
+ m_config.processInfo->SetVideoDeintMethod("unknown");
+ }
+ if (!m_pp)
+ {
+ const bool preferVaapiRender = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(SETTING_VIDEOPLAYER_PREFERVAAPIRENDER);
+ // For 1080p/i or below, always use CVppPostproc even when not deinterlacing
+ // Reason is: mesa cannot dynamically switch surfaces between use for VAAPI post-processing
+ // and use for direct export, so we run into trouble if we or the user want to switch
+ // deinterlacing on/off mid-stream.
+ // See also: https://bugs.freedesktop.org/show_bug.cgi?id=105145
+ const bool alwaysInsertVpp = m_config.driverIsMesa &&
+ ((m_config.vidWidth * m_config.vidHeight) <= (1920 * 1080)) &&
+ interlaced;
+
+ m_config.stats->SetVpp(false);
+ if (!preferVaapiRender)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing ffmpeg postproc");
+ m_pp = new CFFmpegPostproc();
+ }
+ else if (alwaysInsertVpp)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing vaapi postproc");
+ m_pp = new CVppPostproc();
+ m_config.stats->SetVpp(true);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing skip postproc");
+ m_pp = new CSkipPostproc();
+ }
+ if (m_pp->PreInit(m_config))
+ {
+ m_pp->Init(method);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VAAPI output: Postproc preinit failed");
+ delete m_pp;
+ m_pp = nullptr;
+ }
+ }
+ }
+ if (!m_pp) // fallback
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI output: Initializing skip postproc as fallback");
+ m_pp = new CSkipPostproc();
+ m_config.stats->SetVpp(false);
+ if (m_pp->PreInit(m_config))
+ m_pp->Init(method);
+ }
+}
+
+CVaapiRenderPicture* COutput::ProcessPicture(CVaapiProcessedPicture &pic)
+{
+ CVaapiRenderPicture *retPic;
+ retPic = m_bufferPool->GetVaapi();
+ retPic->DVDPic.SetParams(pic.DVDPic);
+
+ if (!pic.source)
+ {
+ CLog::Log(LOGERROR, "VAAPI::ProcessPicture - pic has no source");
+ retPic->Release();
+ return nullptr;
+ }
+
+ if (pic.source->UseVideoSurface())
+ {
+ vaSyncSurface(m_config.dpy, pic.videoSurface);
+ pic.id = m_bufferPool->procPicId++;
+ m_bufferPool->processedPicsAway.push_back(pic);
+ retPic->procPic = pic;
+ retPic->vadsp = m_config.dpy;
+ }
+ else
+ {
+ av_frame_move_ref(retPic->avFrame, pic.frame);
+ pic.source->ClearRef(pic);
+ retPic->procPic.videoSurface = VA_INVALID_ID;
+ }
+
+ retPic->DVDPic.dts = DVD_NOPTS_VALUE;
+ retPic->DVDPic.iWidth = m_config.vidWidth;
+ retPic->DVDPic.iHeight = m_config.vidHeight;
+
+ retPic->valid = true;
+
+ return retPic;
+}
+
+void COutput::ReleaseProcessedPicture(CVaapiProcessedPicture &pic)
+{
+ if (!pic.source)
+ {
+ return;
+ }
+ pic.source->ClearRef(pic);
+ pic.source = nullptr;
+}
+
+void COutput::QueueReturnPicture(CVaapiRenderPicture *pic)
+{
+ m_bufferPool->QueueReturnPicture(pic);
+ ProcessSyncPicture();
+}
+
+void COutput::ProcessSyncPicture()
+{
+ CVaapiRenderPicture *pic;
+
+ pic = m_bufferPool->ProcessSyncPicture();
+
+ if (pic)
+ {
+ ProcessReturnPicture(pic);
+ }
+}
+
+void COutput::ProcessReturnPicture(CVaapiRenderPicture *pic)
+{
+ if (pic->avFrame)
+ av_frame_unref(pic->avFrame);
+
+ ProcessReturnProcPicture(pic->procPic.id);
+ pic->valid = false;
+}
+
+void COutput::ProcessReturnProcPicture(int id)
+{
+ for (auto it=m_bufferPool->processedPicsAway.begin(); it!=m_bufferPool->processedPicsAway.end(); ++it)
+ {
+ if (it->id == id)
+ {
+ ReleaseProcessedPicture(*it);
+ m_bufferPool->processedPicsAway.erase(it);
+ break;
+ }
+ }
+}
+
+void COutput::EnsureBufferPool()
+{
+ m_bufferPool->Init();
+}
+
+void COutput::ReleaseBufferPool(bool precleanup)
+{
+ ProcessSyncPicture();
+
+ m_bufferPool->DeleteTextures(precleanup);
+
+ for (unsigned int i = 0; i < m_bufferPool->decodedPics.size(); i++)
+ {
+ m_config.videoSurfaces->ClearRender(m_bufferPool->decodedPics[i].videoSurface);
+ }
+ m_bufferPool->decodedPics.clear();
+
+ for (unsigned int i = 0; i < m_bufferPool->processedPics.size(); i++)
+ {
+ ReleaseProcessedPicture(m_bufferPool->processedPics[i]);
+ }
+ m_bufferPool->processedPics.clear();
+}
+
+void COutput::ReadyForDisposal(CPostproc *pp)
+{
+ for (auto it = m_discardedPostprocs.begin(); it != m_discardedPostprocs.end(); ++it)
+ {
+ if ((*it).get() == pp)
+ {
+ m_discardedPostprocs.erase(it);
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Postprocessing
+//-----------------------------------------------------------------------------
+
+bool CSkipPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods)
+{
+ m_config = config;
+ return true;
+}
+
+bool CSkipPostproc::Init(EINTERLACEMETHOD method)
+{
+ m_config.processInfo->SetVideoDeintMethod("none");
+ return true;
+}
+
+bool CSkipPostproc::AddPicture(CVaapiDecodedPicture &inPic)
+{
+ m_pic = inPic;
+ m_step = 0;
+ return true;
+}
+
+bool CSkipPostproc::Filter(CVaapiProcessedPicture &outPic)
+{
+ if (m_step > 0)
+ return false;
+ outPic.DVDPic.SetParams(m_pic.DVDPic);
+ outPic.videoSurface = m_pic.videoSurface;
+ m_refsToSurfaces++;
+ outPic.source = this;
+ outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST |
+ DVP_FLAG_REPEAT_TOP_FIELD |
+ DVP_FLAG_INTERLACED);
+ m_step++;
+ return true;
+}
+
+void CSkipPostproc::ClearRef(CVaapiProcessedPicture &pic)
+{
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ m_refsToSurfaces--;
+
+ if (m_pOut && m_refsToSurfaces <= 0)
+ (m_pOut->*m_cbDispose)(this);
+}
+
+void CSkipPostproc::Flush()
+{
+
+}
+
+bool CSkipPostproc::UpdateDeintMethod(EINTERLACEMETHOD method)
+{
+ if (method == VS_INTERLACEMETHOD_NONE)
+ return true;
+
+ return false;
+}
+
+bool CSkipPostproc::DoesSync()
+{
+ return false;
+}
+
+bool CSkipPostproc::UseVideoSurface()
+{
+ return true;
+}
+
+void CSkipPostproc::Discard(COutput *output, ReadyToDispose cb)
+{
+ m_pOut = output;
+ m_cbDispose = cb;
+ if (m_refsToSurfaces <= 0)
+ (m_pOut->*m_cbDispose)(this);
+}
+
+//-----------------------------------------------------------------------------
+// VPP Postprocessing
+//-----------------------------------------------------------------------------
+
+CVppPostproc::CVppPostproc()
+{
+}
+
+CVppPostproc::~CVppPostproc()
+{
+ Dispose();
+}
+
+bool CVppPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods)
+{
+ m_config = config;
+
+ // create config
+ if (!CheckSuccess(
+ vaCreateConfig(m_config.dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &m_configId),
+ "vaCreateConfig"))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVppPostproc::PreInit - VPP init failed in vaCreateConfig");
+
+ return false;
+ }
+
+ VASurfaceAttrib attribs[1], *attrib;
+ attrib = attribs;
+ attrib->flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attrib->type = VASurfaceAttribPixelFormat;
+ attrib->value.type = VAGenericValueTypeInteger;
+ attrib->value.value.i = VA_FOURCC_NV12;
+
+ // create surfaces
+ VASurfaceID surfaces[32];
+ unsigned int format = VA_RT_FORMAT_YUV420;
+ if (m_config.profile == VAProfileHEVCMain10)
+ {
+ format = VA_RT_FORMAT_YUV420_10BPP;
+ attrib->value.value.i = VA_FOURCC_P010;
+ }
+ int nb_surfaces = NUM_RENDER_PICS;
+ if (!CheckSuccess(
+ vaCreateSurfaces(m_config.dpy, format, m_config.surfaceWidth, m_config.surfaceHeight,
+ surfaces, nb_surfaces,
+ attribs, 1), "vaCreateSurfaces"))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVppPostproc::PreInit - VPP init failed in vaCreateSurfaces");
+
+ return false;
+ }
+ for (int i=0; i<nb_surfaces; i++)
+ {
+ m_videoSurfaces.AddSurface(surfaces[i]);
+ }
+
+ // create vaapi decoder context
+ if (!CheckSuccess(
+ vaCreateContext(m_config.dpy, m_configId, m_config.surfaceWidth, m_config.surfaceHeight, 0,
+ surfaces,
+ nb_surfaces, &m_contextId), "vaCreateContext"))
+ {
+ m_contextId = VA_INVALID_ID;
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVppPostproc::PreInit - VPP init failed in vaCreateContext");
+
+ return false;
+ }
+
+ VAProcFilterType filters[VAProcFilterCount];
+ unsigned int numFilters = VAProcFilterCount;
+ VAProcFilterCapDeinterlacing deinterlacingCaps[VAProcDeinterlacingCount];
+ unsigned int numDeinterlacingCaps = VAProcDeinterlacingCount;
+
+ if (!CheckSuccess(vaQueryVideoProcFilters(m_config.dpy, m_contextId, filters, &numFilters),
+ "vaQueryVideoProcFilters"))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVppPostproc::PreInit - VPP init failed in vaQueryVideoProcFilters");
+
+ return false;
+ }
+
+ if (!CheckSuccess(vaQueryVideoProcFilterCaps(m_config.dpy, m_contextId, VAProcFilterDeinterlacing,
+ deinterlacingCaps,
+ &numDeinterlacingCaps), "vaQueryVideoProcFilterCaps"))
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVppPostproc::PreInit - VPP init failed in vaQueryVideoProcFilterCaps");
+
+ return false;
+ }
+
+ if (methods)
+ {
+ for (unsigned int i = 0; i < numFilters; i++)
+ {
+ if (filters[i] == VAProcFilterDeinterlacing)
+ {
+ for (unsigned int j = 0; j < numDeinterlacingCaps; j++)
+ {
+ if (deinterlacingCaps[j].type == VAProcDeinterlacingBob)
+ {
+ methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_BOB;
+ }
+ else if (deinterlacingCaps[j].type == VAProcDeinterlacingMotionAdaptive)
+ {
+ methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_MADI;
+ }
+ else if (deinterlacingCaps[j].type == VAProcDeinterlacingMotionCompensated)
+ {
+ methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_MACI;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool CVppPostproc::Init(EINTERLACEMETHOD method)
+{
+ m_forwardRefs = 0;
+ m_backwardRefs = 0;
+ m_currentIdx = 0;
+ m_frameCount = 0;
+ m_vppMethod = VS_INTERLACEMETHOD_AUTO;
+
+ return UpdateDeintMethod(method);
+}
+
+
+bool CVppPostproc::UpdateDeintMethod(EINTERLACEMETHOD method)
+{
+ if (method == m_vppMethod)
+ {
+ return true;
+ }
+
+ m_vppMethod = method;
+ m_forwardRefs = 0;
+ m_backwardRefs = 0;
+
+ if (m_filter != VA_INVALID_ID)
+ {
+ CheckSuccess(vaDestroyBuffer(m_config.dpy, m_filter), "vaDestroyBuffer");
+ m_filter = VA_INVALID_ID;
+ }
+
+ VAProcDeinterlacingType vppMethod;
+ switch (method)
+ {
+ case VS_INTERLACEMETHOD_VAAPI_BOB:
+ vppMethod = VAProcDeinterlacingBob;
+ m_config.processInfo->SetVideoDeintMethod("vaapi-bob");
+ break;
+ case VS_INTERLACEMETHOD_VAAPI_MADI:
+ vppMethod = VAProcDeinterlacingMotionAdaptive;
+ m_config.processInfo->SetVideoDeintMethod("vaapi-madi");
+ break;
+ case VS_INTERLACEMETHOD_VAAPI_MACI:
+ vppMethod = VAProcDeinterlacingMotionCompensated;
+ m_config.processInfo->SetVideoDeintMethod("vaapi-mcdi");
+ break;
+ case VS_INTERLACEMETHOD_NONE:
+ // Early exit, filter parameter buffer not needed then
+ m_config.processInfo->SetVideoDeintMethod("vaapi-none");
+ return true;
+ default:
+ m_config.processInfo->SetVideoDeintMethod("unknown");
+ return false;
+ }
+
+ VAProcFilterParameterBufferDeinterlacing filterparams;
+ filterparams.type = VAProcFilterDeinterlacing;
+ filterparams.algorithm = vppMethod;
+ filterparams.flags = 0;
+
+ if (!CheckSuccess(vaCreateBuffer(m_config.dpy, m_contextId, VAProcFilterParameterBufferType,
+ sizeof(filterparams), 1,
+ &filterparams, &m_filter), "vaCreateBuffer"))
+ {
+ m_filter = VA_INVALID_ID;
+ return false;
+ }
+
+ VAProcPipelineCaps pplCaps;
+ if (!CheckSuccess(vaQueryVideoProcPipelineCaps(m_config.dpy, m_contextId, &m_filter, 1, &pplCaps),
+ "vaQueryVideoProcPipelineCaps"))
+ {
+ return false;
+ }
+
+ m_forwardRefs = pplCaps.num_forward_references;
+ m_backwardRefs = pplCaps.num_backward_references;
+
+ return true;
+}
+
+void CVppPostproc::Dispose()
+{
+ // make sure surfaces are idle
+ for (int i=0; i<m_videoSurfaces.Size(); i++)
+ {
+ CheckSuccess(vaSyncSurface(m_config.dpy, m_videoSurfaces.GetAtIndex(i)), "vaSyncSurface");
+ }
+
+ if (m_filter != VA_INVALID_ID)
+ {
+ CheckSuccess(vaDestroyBuffer(m_config.dpy, m_filter), "vaDestroyBuffer");
+ m_filter = VA_INVALID_ID;
+ }
+ if (m_contextId != VA_INVALID_ID)
+ {
+ CheckSuccess(vaDestroyContext(m_config.dpy, m_contextId), "vaDestroyContext");
+ m_contextId = VA_INVALID_ID;
+ }
+ VASurfaceID surf;
+ while((surf = m_videoSurfaces.RemoveNext()) != VA_INVALID_SURFACE)
+ {
+ CheckSuccess(vaDestroySurfaces(m_config.dpy, &surf, 1), "vaDestroySurface");
+ }
+ m_videoSurfaces.Reset();
+
+ if (m_configId != VA_INVALID_ID)
+ {
+ CheckSuccess(vaDestroyConfig(m_config.dpy, m_configId), "vaDestroyConfig");
+ m_configId = VA_INVALID_ID;
+ }
+
+ // release all decoded pictures
+ Flush();
+}
+
+bool CVppPostproc::AddPicture(CVaapiDecodedPicture &pic)
+{
+ pic.index = m_frameCount;
+ m_decodedPics.push_front(pic);
+ m_frameCount++;
+ m_step = 0;
+ m_config.stats->SetCanSkipDeint(true);
+ return true;
+}
+
+bool CVppPostproc::Filter(CVaapiProcessedPicture &outPic)
+{
+ if (m_step>1)
+ {
+ Advance();
+ return false;
+ }
+
+ // we need a free render target
+ VASurfaceID surf = m_videoSurfaces.GetFree(VA_INVALID_SURFACE);
+ if (surf == VA_INVALID_SURFACE)
+ {
+ CLog::Log(LOGERROR, "VAAPI - VPP - no free render target");
+ return false;
+ }
+ // clear reference in case we return false
+ m_videoSurfaces.ClearReference(surf);
+
+ // move window of frames we are looking at to account for backward (=future) refs
+ const auto currentIdx = m_currentIdx - m_backwardRefs;
+
+ // make sure we have all needed forward refs
+ if ((currentIdx - m_forwardRefs) < m_decodedPics.back().index)
+ {
+ Advance();
+ return false;
+ }
+
+ auto it = std::find_if(m_decodedPics.begin(), m_decodedPics.end(),
+ [currentIdx](const CVaapiDecodedPicture &picture){
+ return picture.index == currentIdx;
+ });
+ if (it==m_decodedPics.end())
+ {
+ return false;
+ }
+ outPic.DVDPic.SetParams(it->DVDPic);
+
+ // skip deinterlacing cycle if requested
+ if ((m_step == 1) &&
+ ((outPic.DVDPic.iFlags & DVD_CODEC_CTRL_SKIPDEINT) || !(outPic.DVDPic.iFlags & DVP_FLAG_INTERLACED) || (m_vppMethod == VS_INTERLACEMETHOD_NONE)))
+ {
+ Advance();
+ return false;
+ }
+
+ // vpp deinterlacing
+ VAProcFilterParameterBufferDeinterlacing *filterParams;
+ VABufferID pipelineBuf;
+ VAProcPipelineParameterBuffer *pipelineParams;
+ VARectangle inputRegion;
+ VARectangle outputRegion;
+
+ if (!CheckSuccess(vaBeginPicture(m_config.dpy, m_contextId, surf), "vaBeginPicture"))
+ {
+ return false;
+ }
+
+ if (!CheckSuccess(vaCreateBuffer(m_config.dpy, m_contextId, VAProcPipelineParameterBufferType,
+ sizeof(VAProcPipelineParameterBuffer), 1, NULL, &pipelineBuf), "vaCreateBuffer"))
+ {
+ return false;
+ }
+ if (!CheckSuccess(vaMapBuffer(m_config.dpy, pipelineBuf, (void**) &pipelineParams),
+ "vaMapBuffer"))
+ {
+ return false;
+ }
+ memset(pipelineParams, 0, sizeof(VAProcPipelineParameterBuffer));
+
+ inputRegion.x = outputRegion.x = 0;
+ inputRegion.y = outputRegion.y = 0;
+ inputRegion.width = outputRegion.width = m_config.surfaceWidth;
+ inputRegion.height = outputRegion.height = m_config.surfaceHeight;
+
+ pipelineParams->output_region = &outputRegion;
+ pipelineParams->surface_region = &inputRegion;
+ pipelineParams->output_background_color = 0xff000000;
+ pipelineParams->filter_flags = 0;
+
+ VASurfaceID forwardRefs[32];
+ VASurfaceID backwardRefs[32];
+ pipelineParams->forward_references = forwardRefs;
+ pipelineParams->backward_references = backwardRefs;
+ pipelineParams->num_forward_references = 0;
+ pipelineParams->num_backward_references = 0;
+
+ int maxPic = currentIdx + m_backwardRefs;
+ int minPic = currentIdx - m_forwardRefs;
+ int curPic = currentIdx;
+
+ // deinterlace flag
+ if (m_vppMethod != VS_INTERLACEMETHOD_NONE)
+ {
+ unsigned int flags = 0;
+
+ if (it->DVDPic.iFlags & DVP_FLAG_INTERLACED)
+ {
+ if (it->DVDPic.iFlags & DVP_FLAG_TOP_FIELD_FIRST)
+ flags = 0;
+ else
+ flags = VA_DEINTERLACING_BOTTOM_FIELD_FIRST | VA_DEINTERLACING_BOTTOM_FIELD;
+
+ if (m_step)
+ {
+ if (flags & VA_DEINTERLACING_BOTTOM_FIELD)
+ flags &= ~VA_DEINTERLACING_BOTTOM_FIELD;
+ else
+ flags |= VA_DEINTERLACING_BOTTOM_FIELD;
+ }
+ }
+ if (!CheckSuccess(vaMapBuffer(m_config.dpy, m_filter, (void**) &filterParams), "vaMapBuffer"))
+ {
+ return false;
+ }
+ filterParams->flags = flags;
+ if (!CheckSuccess(vaUnmapBuffer(m_config.dpy, m_filter), "vaUnmapBuffer"))
+ {
+ return false;
+ }
+
+ pipelineParams->filters = &m_filter;
+ pipelineParams->num_filters = 1;
+ }
+ else
+ {
+ pipelineParams->num_filters = 0;
+ }
+
+ // references
+ double ptsLast = DVD_NOPTS_VALUE;
+ double pts = DVD_NOPTS_VALUE;
+
+ pipelineParams->surface = VA_INVALID_SURFACE;
+ for (const auto &picture : m_decodedPics)
+ {
+ if (picture.index >= minPic && picture.index <= maxPic)
+ {
+ if (picture.index > curPic)
+ {
+ backwardRefs[(picture.index - curPic) - 1] = picture.videoSurface;
+ pipelineParams->num_backward_references++;
+ }
+ else if (picture.index == curPic)
+ {
+ pipelineParams->surface = picture.videoSurface;
+ pts = picture.DVDPic.pts;
+ }
+ if (picture.index < curPic)
+ {
+ forwardRefs[(curPic - picture.index) - 1] = picture.videoSurface;
+ pipelineParams->num_forward_references++;
+ if (picture.index == curPic - 1)
+ ptsLast = picture.DVDPic.pts;
+ }
+ }
+ }
+
+ // set pts for 2nd frame
+ if (m_step && pts != DVD_NOPTS_VALUE && ptsLast != DVD_NOPTS_VALUE)
+ outPic.DVDPic.pts += (pts-ptsLast)/2;
+
+ if (pipelineParams->surface == VA_INVALID_SURFACE)
+ return false;
+
+ if (!CheckSuccess(vaUnmapBuffer(m_config.dpy, pipelineBuf), "vaUnmmapBuffer"))
+ {
+ return false;
+ }
+
+ if (!CheckSuccess(vaRenderPicture(m_config.dpy, m_contextId, &pipelineBuf, 1), "vaRenderPicture"))
+ {
+ return false;
+ }
+
+ if (!CheckSuccess(vaEndPicture(m_config.dpy, m_contextId), "vaEndPicture"))
+ {
+ return false;
+ }
+
+ if (!CheckSuccess(vaDestroyBuffer(m_config.dpy, pipelineBuf), "vaDestroyBuffer"))
+ {
+ return false;
+ }
+
+ m_step++;
+ outPic.videoSurface = m_videoSurfaces.GetFree(surf);
+ outPic.source = this;
+ outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST |
+ DVP_FLAG_REPEAT_TOP_FIELD |
+ DVP_FLAG_INTERLACED);
+
+ return true;
+}
+
+void CVppPostproc::Advance()
+{
+ m_currentIdx++;
+
+ // release all unneeded refs
+ auto it = m_decodedPics.begin();
+ while (it != m_decodedPics.end())
+ {
+ if (it->index < m_currentIdx - m_forwardRefs - m_backwardRefs)
+ {
+ m_config.videoSurfaces->ClearRender(it->videoSurface);
+ it = m_decodedPics.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void CVppPostproc::ClearRef(CVaapiProcessedPicture &pic)
+{
+ m_videoSurfaces.ClearReference(pic.videoSurface);
+
+ if (m_pOut && !m_videoSurfaces.HasRefs())
+ (m_pOut->*m_cbDispose)(this);
+}
+
+void CVppPostproc::Flush()
+{
+ // release all decoded pictures
+ auto it = m_decodedPics.begin();
+ while (it != m_decodedPics.end())
+ {
+ m_config.videoSurfaces->ClearRender(it->videoSurface);
+ it = m_decodedPics.erase(it);
+ }
+}
+
+bool CVppPostproc::DoesSync()
+{
+ return false;
+}
+
+bool CVppPostproc::WantsPic()
+{
+ // need at least 2 for deinterlacing
+ if (m_videoSurfaces.NumFree() > 1)
+ return true;
+
+ return false;
+}
+
+bool CVppPostproc::UseVideoSurface()
+{
+ return true;
+}
+
+void CVppPostproc::Discard(COutput *output, ReadyToDispose cb)
+{
+ m_pOut = output;
+ m_cbDispose = cb;
+ if (!m_videoSurfaces.HasRefs())
+ (m_pOut->*m_cbDispose)(this);
+}
+
+bool CVppPostproc::CheckSuccess(VAStatus status, const std::string& function)
+{
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI/vpp {} error: {} ({})", function, vaErrorStr(status), status);
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// FFmpeg Postprocessing
+//-----------------------------------------------------------------------------
+
+#define CACHED_BUFFER_SIZE 4096
+
+CFFmpegPostproc::CFFmpegPostproc()
+{
+ m_cache = NULL;
+ m_pFilterFrameIn = NULL;
+ m_pFilterFrameOut = NULL;
+ m_pFilterGraph = NULL;
+ m_DVDPic.pts = DVD_NOPTS_VALUE;
+ m_frametime = 0;
+ m_lastOutPts = DVD_NOPTS_VALUE;
+}
+
+CFFmpegPostproc::~CFFmpegPostproc()
+{
+ Close();
+ KODI::MEMORY::AlignedFree(m_cache);
+ m_dllSSE4.Unload();
+ av_frame_free(&m_pFilterFrameIn);
+ av_frame_free(&m_pFilterFrameOut);
+}
+
+bool CFFmpegPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods)
+{
+ m_config = config;
+ bool use_filter = true;
+
+ // copying large surfaces via sse4 is a bit slow
+ // we just return false here as the primary use case the
+ // sse4 copy method is deinterlacing of max 1080i content
+ if (m_config.vidWidth > 1920 || m_config.vidHeight > 1088)
+ return false;
+
+ VAImage image;
+ image.image_id = VA_INVALID_ID;
+ VASurfaceID surface = config.videoSurfaces->GetAtIndex(0);
+ VAStatus status = vaDeriveImage(config.dpy, surface, &image);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGINFO, "VAAPI::SupportsFilter vaDeriveImage not supported by driver - ffmpeg postprocessing and CPU-copy rendering will not be available");
+ use_filter = false;
+ }
+ if (use_filter && (image.format.fourcc != VA_FOURCC_NV12))
+ {
+ CLog::Log(LOGWARNING,"VAAPI::SupportsFilter image format not NV12");
+ use_filter = false;
+ }
+ if (use_filter && ((image.pitches[0] % 64) || (image.pitches[1] % 64)))
+ {
+ CLog::Log(LOGWARNING,"VAAPI::SupportsFilter patches no multiple of 64");
+ use_filter = false;
+ }
+ if (image.image_id != VA_INVALID_ID)
+ CheckSuccess(vaDestroyImage(config.dpy, image.image_id), "vaDestroyImage");
+
+ if (use_filter && !m_dllSSE4.Load())
+ {
+ CLog::Log(LOGERROR,"VAAPI::SupportsFilter failed loading sse4 lib");
+ use_filter = false;
+ }
+
+ if (use_filter)
+ {
+ m_cache = static_cast<uint8_t*>(KODI::MEMORY::AlignedMalloc(CACHED_BUFFER_SIZE, 64));
+ if (methods)
+ {
+ methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_DEINTERLACE;
+ methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_RENDER_BOB;
+ }
+ }
+ return use_filter;
+}
+
+bool CFFmpegPostproc::Init(EINTERLACEMETHOD method)
+{
+ if (!(m_pFilterGraph = avfilter_graph_alloc()))
+ {
+ CLog::Log(LOGERROR, "VAAPI::CFFmpegPostproc::Init - unable to alloc filter graph");
+ return false;
+ }
+
+ const AVFilter* srcFilter = avfilter_get_by_name("buffer");
+ const AVFilter* outFilter = avfilter_get_by_name("buffersink");
+
+ std::string args = StringUtils::Format("{}:{}:{}:{}:{}:{}:{}", m_config.vidWidth,
+ m_config.vidHeight, AV_PIX_FMT_NV12, 1, 1,
+ (m_config.aspect.num != 0) ? m_config.aspect.num : 1,
+ (m_config.aspect.num != 0) ? m_config.aspect.den : 1);
+
+ if (avfilter_graph_create_filter(&m_pFilterIn, srcFilter, "src", args.c_str(), NULL, m_pFilterGraph) < 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI::CFFmpegPostproc::Init - avfilter_graph_create_filter: src");
+ return false;
+ }
+
+ if (avfilter_graph_create_filter(&m_pFilterOut, outFilter, "out", NULL, NULL, m_pFilterGraph) < 0)
+ {
+ CLog::Log(LOGERROR, "CFFmpegPostproc::Init - avfilter_graph_create_filter: out");
+ return false;
+ }
+
+ enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_NV12, AV_PIX_FMT_NONE };
+ if (av_opt_set_int_list(m_pFilterOut, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI::CFFmpegPostproc::Init - failed settings pix formats");
+ return false;
+ }
+
+ AVFilterInOut* outputs = avfilter_inout_alloc();
+ AVFilterInOut* inputs = avfilter_inout_alloc();
+
+ outputs->name = av_strdup("in");
+ outputs->filter_ctx = m_pFilterIn;
+ outputs->pad_idx = 0;
+ outputs->next = NULL;
+
+ inputs->name = av_strdup("out");
+ inputs->filter_ctx = m_pFilterOut;
+ inputs->pad_idx = 0;
+ inputs->next = NULL;
+
+ if (method == VS_INTERLACEMETHOD_DEINTERLACE)
+ {
+ std::string filter;
+
+ filter = "yadif=1:-1";
+
+ if (avfilter_graph_parse_ptr(m_pFilterGraph, filter.c_str(), &inputs, &outputs, NULL) < 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI::CFFmpegPostproc::Init - avfilter_graph_parse");
+ avfilter_inout_free(&outputs);
+ avfilter_inout_free(&inputs);
+ return false;
+ }
+
+ avfilter_inout_free(&outputs);
+ avfilter_inout_free(&inputs);
+
+ if (avfilter_graph_config(m_pFilterGraph, NULL) < 0)
+ {
+ CLog::Log(LOGERROR, "VAAPI::CFFmpegPostproc::Init - avfilter_graph_config");
+ return false;
+ }
+
+ m_config.processInfo->SetVideoDeintMethod("yadif");
+ }
+ else if (method == VS_INTERLACEMETHOD_RENDER_BOB ||
+ method == VS_INTERLACEMETHOD_NONE)
+ {
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VAAPI::CFFmpegPostproc::Init - skip deinterlacing");
+ avfilter_inout_free(&outputs);
+ avfilter_inout_free(&inputs);
+ m_config.processInfo->SetVideoDeintMethod("none");
+ }
+ else
+ {
+ avfilter_inout_free(&outputs);
+ avfilter_inout_free(&inputs);
+ m_config.processInfo->SetVideoDeintMethod("unknown");
+ return false;
+ }
+ m_diMethod = method;
+
+ m_pFilterFrameIn = av_frame_alloc();
+ m_pFilterFrameOut = av_frame_alloc();
+ return true;
+}
+
+bool CFFmpegPostproc::AddPicture(CVaapiDecodedPicture &inPic)
+{
+ VASurfaceID surf = inPic.videoSurface;
+ VAImage image;
+ uint8_t *buf;
+ if (m_DVDPic.pts != DVD_NOPTS_VALUE && inPic.DVDPic.pts != DVD_NOPTS_VALUE)
+ {
+ m_frametime = inPic.DVDPic.pts - m_DVDPic.pts;
+ }
+ m_DVDPic.SetParams(inPic.DVDPic);
+ bool result = false;
+
+ if (!CheckSuccess(vaSyncSurface(m_config.dpy, surf), "vaSyncSurface"))
+ goto error;
+
+ if (!CheckSuccess(vaDeriveImage(m_config.dpy, surf, &image), "vaDeriveImage"))
+ goto error;
+
+ if (!CheckSuccess(vaMapBuffer(m_config.dpy, image.buf, (void**) &buf), "vaMapBuffer"))
+ goto error;
+
+ m_pFilterFrameIn->format = AV_PIX_FMT_NV12;
+ m_pFilterFrameIn->width = m_config.vidWidth;
+ m_pFilterFrameIn->height = m_config.vidHeight;
+ m_pFilterFrameIn->linesize[0] = image.pitches[0];
+ m_pFilterFrameIn->linesize[1] = image.pitches[1];
+ m_pFilterFrameIn->interlaced_frame = (inPic.DVDPic.iFlags & DVP_FLAG_INTERLACED) ? 1 : 0;
+ m_pFilterFrameIn->top_field_first = (inPic.DVDPic.iFlags & DVP_FLAG_TOP_FIELD_FIRST) ? 1 : 0;
+
+ if (inPic.DVDPic.pts == DVD_NOPTS_VALUE)
+ m_pFilterFrameIn->pts = AV_NOPTS_VALUE;
+ else
+ m_pFilterFrameIn->pts = (inPic.DVDPic.pts / DVD_TIME_BASE) * AV_TIME_BASE;
+
+ m_pFilterFrameIn->pkt_dts = m_pFilterFrameIn->pts;
+ m_pFilterFrameIn->best_effort_timestamp = m_pFilterFrameIn->pts;
+
+ av_frame_get_buffer(m_pFilterFrameIn, 64);
+
+ uint8_t *src, *dst;
+ src = buf + image.offsets[0];
+ dst = m_pFilterFrameIn->data[0];
+ m_dllSSE4.copy_frame(src, dst, m_cache, m_config.vidWidth, m_config.vidHeight, image.pitches[0]);
+
+ src = buf + image.offsets[1];
+ dst = m_pFilterFrameIn->data[1];
+ m_dllSSE4.copy_frame(src, dst, m_cache, image.width, image.height/2, image.pitches[1]);
+
+ m_pFilterFrameIn->linesize[0] = image.pitches[0];
+ m_pFilterFrameIn->linesize[1] = image.pitches[1];
+ m_pFilterFrameIn->data[2] = NULL;
+ m_pFilterFrameIn->data[3] = NULL;
+ m_pFilterFrameIn->pkt_size = image.data_size;
+
+ CheckSuccess(vaUnmapBuffer(m_config.dpy, image.buf), "vaUnmapBuffer");
+ CheckSuccess(vaDestroyImage(m_config.dpy, image.image_id), "vaDestroyImage");
+
+ if (m_diMethod == VS_INTERLACEMETHOD_DEINTERLACE)
+ {
+ if (av_buffersrc_add_frame(m_pFilterIn, m_pFilterFrameIn) < 0)
+ {
+ CLog::Log(LOGERROR, "CFFmpegPostproc::AddPicture - av_buffersrc_add_frame");
+ goto error;
+ }
+ }
+ else if (m_diMethod == VS_INTERLACEMETHOD_RENDER_BOB ||
+ m_diMethod == VS_INTERLACEMETHOD_NONE)
+ {
+ av_frame_move_ref(m_pFilterFrameOut, m_pFilterFrameIn);
+ m_step = 0;
+ }
+ av_frame_unref(m_pFilterFrameIn);
+
+ result = true;
+
+error:
+ m_config.videoSurfaces->ClearRender(surf);
+ return result;
+}
+
+bool CFFmpegPostproc::Filter(CVaapiProcessedPicture &outPic)
+{
+ outPic.DVDPic.SetParams(m_DVDPic);
+ if (m_diMethod == VS_INTERLACEMETHOD_DEINTERLACE)
+ {
+ int result;
+ result = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrameOut);
+
+ if(result == AVERROR(EAGAIN) || result == AVERROR_EOF)
+ return false;
+ else if(result < 0)
+ {
+ CLog::Log(LOGERROR, "CFFmpegPostproc::Filter - av_buffersink_get_frame");
+ return false;
+ }
+ outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST |
+ DVP_FLAG_REPEAT_TOP_FIELD |
+ DVP_FLAG_INTERLACED);
+ }
+ else if (m_diMethod == VS_INTERLACEMETHOD_RENDER_BOB ||
+ m_diMethod == VS_INTERLACEMETHOD_NONE)
+ {
+ if (m_step > 0)
+ return false;
+ }
+
+ m_step++;
+ outPic.frame = av_frame_clone(m_pFilterFrameOut);
+ av_frame_unref(m_pFilterFrameOut);
+
+ outPic.source = this;
+ m_refsToPics++;
+
+ int64_t bpts = outPic.frame->best_effort_timestamp;
+ if(bpts != AV_NOPTS_VALUE)
+ {
+ outPic.DVDPic.pts = (double)bpts * DVD_TIME_BASE / AV_TIME_BASE;
+ }
+ else
+ outPic.DVDPic.pts = DVD_NOPTS_VALUE;
+
+ double pts = outPic.DVDPic.pts;
+ if (m_lastOutPts != DVD_NOPTS_VALUE && m_lastOutPts == pts)
+ {
+ outPic.DVDPic.pts += m_frametime/2;
+ }
+ m_lastOutPts = pts;
+
+ return true;
+}
+
+void CFFmpegPostproc::ClearRef(CVaapiProcessedPicture &pic)
+{
+ av_frame_free(&pic.frame);
+ m_refsToPics--;
+
+ if (m_pOut && m_refsToPics <= 0 && m_cbDispose)
+ (m_pOut->*m_cbDispose)(this);
+}
+
+void CFFmpegPostproc::Close()
+{
+ if (m_pFilterGraph)
+ {
+ avfilter_graph_free(&m_pFilterGraph);
+ }
+}
+
+void CFFmpegPostproc::Flush()
+{
+ Close();
+ Init(m_diMethod);
+ m_DVDPic.pts = DVD_NOPTS_VALUE;
+ m_frametime = 0;
+ m_lastOutPts = DVD_NOPTS_VALUE;
+}
+
+bool CFFmpegPostproc::UpdateDeintMethod(EINTERLACEMETHOD method)
+{
+ /// \todo switching between certain methods could be done without deinit/init
+ return (m_diMethod == method);
+}
+
+bool CFFmpegPostproc::DoesSync()
+{
+ return true;
+}
+
+bool CFFmpegPostproc::UseVideoSurface()
+{
+ return false;
+}
+
+void CFFmpegPostproc::Discard(COutput *output, ReadyToDispose cb)
+{
+ m_pOut = output;
+ m_cbDispose = cb;
+ if (m_refsToPics <= 0)
+ (m_pOut->*m_cbDispose)(this);
+}
+
+bool CFFmpegPostproc::CheckSuccess(VAStatus status, const std::string& function)
+{
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI/ffpp error: {} ({})", function, vaErrorStr(status), status);
+ return false;
+ }
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.h
new file mode 100644
index 0000000..1202199
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.h
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDVideoCodec.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoSettings.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/SharedSection.h"
+#include "threads/Thread.h"
+#include "utils/ActorProtocol.h"
+#include "utils/Geometry.h"
+
+#include "platform/linux/sse4/DllLibSSE4.h"
+
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+#include <va/va.h>
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavfilter/avfilter.h>
+}
+
+using namespace Actor;
+
+class CProcessInfo;
+
+#define FULLHD_WIDTH 1920
+
+namespace VAAPI
+{
+
+void VaErrorCallback(void *user_context, const char *message);
+void VaInfoCallback(void *user_context, const char *message);
+
+//-----------------------------------------------------------------------------
+// VAAPI data structs
+//-----------------------------------------------------------------------------
+
+class CDecoder;
+
+/**
+ * Buffer statistics used to control number of frames in queue
+ */
+
+class CVaapiBufferStats
+{
+public:
+ uint16_t decodedPics;
+ uint16_t processedPics;
+ uint16_t renderPics;
+ uint64_t latency; // time decoder has waited for a frame, ideally there is no latency
+ int codecFlags;
+ bool canSkipDeint;
+ int processCmd;
+ bool isVpp;
+
+ void IncDecoded()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics++;
+ }
+ void DecDecoded()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics--;
+ }
+ void IncProcessed()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ processedPics++;
+ }
+ void DecProcessed()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ processedPics--;
+ }
+ void IncRender()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ renderPics++;
+ }
+ void DecRender()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ renderPics--;
+ }
+ void Reset()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics = 0;
+ processedPics = 0;
+ renderPics = 0;
+ latency = 0;
+ isVpp = false;
+ }
+ void Get(uint16_t& decoded, uint16_t& processed, uint16_t& render, bool& vpp)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decoded = decodedPics, processed = processedPics, render = renderPics;
+ vpp = isVpp;
+ }
+ void SetParams(uint64_t time, int flags)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ latency = time;
+ codecFlags = flags;
+ }
+ void GetParams(uint64_t& lat, int& flags)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ lat = latency;
+ flags = codecFlags;
+ }
+ void SetCmd(int cmd)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ processCmd = cmd;
+ }
+ void GetCmd(int& cmd)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ cmd = processCmd;
+ processCmd = 0;
+ }
+ void SetCanSkipDeint(bool canSkip)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ canSkipDeint = canSkip;
+ }
+ bool CanSkipDeint()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ if (canSkipDeint)
+ return true;
+ else
+ return false;
+ }
+ void SetVpp(bool vpp)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ isVpp = vpp;
+ }
+
+private:
+ CCriticalSection m_sec;
+};
+
+/**
+ * CVaapiConfig holds all configuration parameters needed by vaapi
+ * The structure is sent to the internal classes CMixer and COutput
+ * for init.
+ */
+
+class CVideoSurfaces;
+class CVAAPIContext;
+
+struct CVaapiConfig
+{
+ int surfaceWidth;
+ int surfaceHeight;
+ int vidWidth;
+ int vidHeight;
+ int outWidth;
+ int outHeight;
+ AVRational aspect;
+ VAConfigID configId;
+ CVaapiBufferStats *stats;
+ int upscale;
+ CVideoSurfaces *videoSurfaces;
+ uint32_t maxReferences;
+ CVAAPIContext *context;
+ VADisplay dpy;
+ VAProfile profile;
+ VAConfigAttrib attrib;
+ CProcessInfo *processInfo;
+ bool driverIsMesa;
+ int bitDepth;
+};
+
+/**
+ * Holds a decoded frame
+ * Input to COutput for further processing
+ */
+struct CVaapiDecodedPicture
+{
+ CVaapiDecodedPicture() = default;
+ CVaapiDecodedPicture(const CVaapiDecodedPicture &rhs)
+ {
+ *this = rhs;
+ }
+ CVaapiDecodedPicture& operator=(const CVaapiDecodedPicture& rhs)
+ {
+ DVDPic.SetParams(rhs.DVDPic);
+ videoSurface = rhs.videoSurface;
+ index = rhs.index;
+ return *this;
+ };
+ VideoPicture DVDPic;
+ VASurfaceID videoSurface;
+ int index;
+};
+
+/**
+ * Frame after having been processed by vpp
+ */
+class CPostproc;
+struct CVaapiProcessedPicture
+{
+ CVaapiProcessedPicture() = default;
+ CVaapiProcessedPicture(const CVaapiProcessedPicture &rhs)
+ {
+ *this = rhs;
+ }
+ CVaapiProcessedPicture& operator=(const CVaapiProcessedPicture& rhs)
+ {
+ DVDPic.SetParams(rhs.DVDPic);
+ videoSurface = rhs.videoSurface;
+ frame = rhs.frame;
+ id = rhs.id;
+ source = rhs.source;
+ crop = rhs.crop;
+ return *this;
+ };
+
+ VideoPicture DVDPic;
+ VASurfaceID videoSurface;
+ AVFrame *frame;
+ int id;
+ CPostproc *source = nullptr;
+ bool crop;
+};
+
+class CVaapiRenderPicture : public CVideoBuffer
+{
+public:
+ explicit CVaapiRenderPicture(int id) : CVideoBuffer(id) { }
+ void GetPlanes(uint8_t*(&planes)[YuvImage::MAX_PLANES]) override;
+ void GetStrides(int(&strides)[YuvImage::MAX_PLANES]) override;
+ VideoPicture DVDPic;
+ CVaapiProcessedPicture procPic;
+ AVFrame *avFrame = nullptr;
+
+ bool valid = false;
+ VADisplay vadsp;
+};
+
+//-----------------------------------------------------------------------------
+// Output
+//-----------------------------------------------------------------------------
+
+class COutputControlProtocol : public Protocol
+{
+public:
+ COutputControlProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ INIT,
+ FLUSH,
+ PRECLEANUP,
+ TIMEOUT,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERROR,
+ STATS,
+ };
+};
+
+class COutputDataProtocol : public Protocol
+{
+public:
+ COutputDataProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ NEWFRAME = 0,
+ RETURNPIC,
+ RETURNPROCPIC,
+ };
+ enum InSignal
+ {
+ PICTURE,
+ };
+};
+
+struct SDiMethods
+{
+ EINTERLACEMETHOD diMethods[8];
+ int numDiMethods;
+};
+
+/**
+ * COutput is embedded in CDecoder and embeds vpp
+ * The class has its own OpenGl context which is shared with render thread
+ * COutput generated ready to render textures and passes them back to
+ * CDecoder
+ */
+
+class CDecoder;
+class CPostproc;
+class CVaapiBufferPool;
+
+class COutput : private CThread
+{
+public:
+ COutput(CDecoder &decoder, CEvent *inMsgEvent);
+ ~COutput() override;
+ void Start();
+ void Dispose();
+ COutputControlProtocol m_controlPort;
+ COutputDataProtocol m_dataPort;
+protected:
+ void OnStartup() override;
+ void OnExit() override;
+ void Process() override;
+ void StateMachine(int signal, Protocol *port, Message *msg);
+ bool HasWork();
+ bool PreferPP();
+ void InitCycle();
+ CVaapiRenderPicture* ProcessPicture(CVaapiProcessedPicture &pic);
+ void QueueReturnPicture(CVaapiRenderPicture *pic);
+ void ProcessReturnPicture(CVaapiRenderPicture *pic);
+ void ProcessReturnProcPicture(int id);
+ void ProcessSyncPicture();
+ void ReleaseProcessedPicture(CVaapiProcessedPicture &pic);
+ bool Init();
+ bool Uninit();
+ void Flush();
+ void EnsureBufferPool();
+ void ReleaseBufferPool(bool precleanup = false);
+ void ReadyForDisposal(CPostproc *pp);
+ CEvent m_outMsgEvent;
+ CEvent *m_inMsgEvent;
+ int m_state;
+ bool m_bStateMachineSelfTrigger;
+ CDecoder &m_vaapi;
+
+ // extended state variables for state machine
+ int m_extTimeout;
+ /// \brief Whether at least one interlaced frame was encountered in the video stream (indicating that more interlaced frames could potentially follow)
+ bool m_seenInterlaced;
+ CVaapiConfig m_config;
+ std::shared_ptr<CVaapiBufferPool> m_bufferPool;
+ CVaapiDecodedPicture m_currentPicture;
+ CPostproc *m_pp;
+ std::list<std::shared_ptr<CPostproc>> m_discardedPostprocs;
+ SDiMethods m_diMethods;
+};
+
+//-----------------------------------------------------------------------------
+// VAAPI Video Surface states
+//-----------------------------------------------------------------------------
+
+class CVideoSurfaces
+{
+public:
+ void AddSurface(VASurfaceID surf);
+ void ClearReference(VASurfaceID surf);
+ bool MarkRender(VASurfaceID surf);
+ void ClearRender(VASurfaceID surf);
+ bool IsValid(VASurfaceID surf);
+ VASurfaceID GetFree(VASurfaceID surf);
+ VASurfaceID GetAtIndex(int idx);
+ VASurfaceID RemoveNext(bool skiprender = false);
+ void Reset();
+ int Size();
+ bool HasFree();
+ bool HasRefs();
+ int NumFree();
+protected:
+ std::map<VASurfaceID, int> m_state;
+ std::list<VASurfaceID> m_freeSurfaces;
+ CCriticalSection m_section;
+};
+
+//-----------------------------------------------------------------------------
+// VAAPI decoder
+//-----------------------------------------------------------------------------
+
+class CVAAPIContext
+{
+public:
+ static bool EnsureContext(CVAAPIContext **ctx, CDecoder *decoder);
+ void Release(CDecoder *decoder);
+ VADisplay GetDisplay();
+ bool SupportsProfile(VAProfile profile);
+ VAConfigAttrib GetAttrib(VAProfile profile);
+ VAConfigID CreateConfig(VAProfile profile, VAConfigAttrib attrib);
+ static void FFReleaseBuffer(void *opaque, uint8_t *data);
+private:
+ CVAAPIContext();
+ void Close();
+ void SetVaDisplayForSystem();
+ bool CreateContext();
+ void DestroyContext();
+ void QueryCaps();
+ bool CheckSuccess(VAStatus status, const std::string& function);
+ bool IsValidDecoder(CDecoder *decoder);
+ void SetValidDRMVaDisplayFromRenderNode();
+ static CVAAPIContext *m_context;
+ static CCriticalSection m_section;
+ VADisplay m_display = NULL;
+ int m_refCount;
+ int m_profileCount;
+ VAProfile *m_profiles;
+ std::vector<CDecoder*> m_decoders;
+ int m_renderNodeFD{-1};
+};
+
+//-----------------------------------------------------------------------------
+// Interface into windowing
+//-----------------------------------------------------------------------------
+
+class IVaapiWinSystem
+{
+public:
+ virtual ~IVaapiWinSystem() = default;
+
+ virtual VADisplay GetVADisplay() = 0;
+ virtual void* GetEGLDisplay() { return nullptr; }
+};
+
+//-----------------------------------------------------------------------------
+// VAAPI main class
+//-----------------------------------------------------------------------------
+
+class CDecoder
+ : public IHardwareDecoder
+{
+ friend class CVaapiBufferPool;
+
+public:
+
+ explicit CDecoder(CProcessInfo& processInfo);
+ ~CDecoder() override;
+
+ bool Open (AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat) override;
+ CDVDVideoCodec::VCReturn Decode (AVCodecContext* avctx, AVFrame* frame) override;
+ bool GetPicture(AVCodecContext* avctx, VideoPicture* picture) override;
+ void Reset() override;
+ virtual void Close();
+ long Release() override;
+ bool CanSkipDeint() override;
+ unsigned GetAllowedReferences() override { return 4; }
+
+ CDVDVideoCodec::VCReturn Check(AVCodecContext* avctx) override;
+ const std::string Name() override { return "vaapi"; }
+ void SetCodecControl(int flags) override;
+
+ void FFReleaseBuffer(uint8_t *data);
+ static int FFGetBuffer(AVCodecContext *avctx, AVFrame *pic, int flags);
+
+ static IHardwareDecoder* Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt);
+ static void Register(IVaapiWinSystem *winSystem, bool deepColor);
+
+ static IVaapiWinSystem* m_pWinSystem;
+
+protected:
+ void SetWidthHeight(int width, int height);
+ bool ConfigVAAPI();
+ bool CheckStatus(VAStatus vdp_st, int line);
+ void FiniVAAPIOutput();
+ void ReturnRenderPicture(CVaapiRenderPicture *renderPic);
+ long ReleasePicReference();
+ bool CheckSuccess(VAStatus status, const std::string& function);
+
+ enum EDisplayState
+ { VAAPI_OPEN
+ , VAAPI_RESET
+ , VAAPI_LOST
+ , VAAPI_ERROR
+ } m_DisplayState;
+ CCriticalSection m_DecoderSection;
+ CEvent m_DisplayEvent;
+ int m_ErrorCount;
+
+ bool m_vaapiConfigured;
+ CVaapiConfig m_vaapiConfig;
+ CVideoSurfaces m_videoSurfaces;
+ AVCodecContext* m_avctx;
+ int m_getBufferError;
+
+ COutput m_vaapiOutput;
+ CVaapiBufferStats m_bufferStats;
+ CEvent m_inMsgEvent;
+ CVaapiRenderPicture *m_presentPicture = nullptr;
+
+ int m_codecControl;
+ CProcessInfo& m_processInfo;
+
+ static bool m_capGeneral;
+ static bool m_capDeepColor;
+
+private:
+ struct AVBufferRefDeleter
+ {
+ void operator()(AVBufferRef* p) const;
+ };
+
+ std::unique_ptr<AVBufferRef, AVBufferRefDeleter> m_deviceRef;
+};
+
+//-----------------------------------------------------------------------------
+// Postprocessing
+//-----------------------------------------------------------------------------
+
+/**
+ * Base class
+ */
+typedef void (COutput::*ReadyToDispose)(CPostproc *pool);
+class CPostproc
+{
+public:
+ virtual ~CPostproc() = default;
+ virtual bool PreInit(CVaapiConfig &config, SDiMethods *methods = NULL) = 0;
+ virtual bool Init(EINTERLACEMETHOD method) = 0;
+ virtual bool AddPicture(CVaapiDecodedPicture &inPic) = 0;
+ virtual bool Filter(CVaapiProcessedPicture &outPic) = 0;
+ virtual void ClearRef(CVaapiProcessedPicture &pic) = 0;
+ virtual void Flush() = 0;
+ virtual bool UpdateDeintMethod(EINTERLACEMETHOD method) = 0;
+ virtual bool DoesSync() = 0;
+ virtual bool WantsPic() {return true;}
+ virtual bool UseVideoSurface() = 0;
+ virtual void Discard(COutput* output, ReadyToDispose cb) { (output->*cb)(this); }
+
+protected:
+ CVaapiConfig m_config;
+ int m_step;
+};
+
+/**
+ * skip post processing
+ */
+class CSkipPostproc : public CPostproc
+{
+public:
+ bool PreInit(CVaapiConfig &config, SDiMethods *methods = NULL) override;
+ bool Init(EINTERLACEMETHOD method) override;
+ bool AddPicture(CVaapiDecodedPicture &inPic) override;
+ bool Filter(CVaapiProcessedPicture &outPic) override;
+ void ClearRef(CVaapiProcessedPicture &pic) override;
+ void Flush() override;
+ bool UpdateDeintMethod(EINTERLACEMETHOD method) override;
+ bool DoesSync() override;
+ bool UseVideoSurface() override;
+ void Discard(COutput *output, ReadyToDispose cb) override;
+protected:
+ CVaapiDecodedPicture m_pic;
+ ReadyToDispose m_cbDispose = nullptr;
+ COutput *m_pOut;
+ int m_refsToSurfaces = 0;
+};
+
+/**
+ * VAAPI post processing
+ */
+class CVppPostproc : public CPostproc
+{
+public:
+ CVppPostproc();
+ ~CVppPostproc() override;
+ bool PreInit(CVaapiConfig &config, SDiMethods *methods = NULL) override;
+ bool Init(EINTERLACEMETHOD method) override;
+ bool AddPicture(CVaapiDecodedPicture &inPic) override;
+ bool Filter(CVaapiProcessedPicture &outPic) override;
+ void ClearRef(CVaapiProcessedPicture &pic) override;
+ void Flush() override;
+ bool UpdateDeintMethod(EINTERLACEMETHOD method) override;
+ bool DoesSync() override;
+ bool WantsPic() override;
+ bool UseVideoSurface() override;
+ void Discard(COutput *output, ReadyToDispose cb) override;
+protected:
+ bool CheckSuccess(VAStatus status, const std::string& function);
+ void Dispose();
+ void Advance();
+ VAConfigID m_configId = VA_INVALID_ID;
+ VAContextID m_contextId = VA_INVALID_ID;
+ CVideoSurfaces m_videoSurfaces;
+ std::deque<CVaapiDecodedPicture> m_decodedPics;
+ VABufferID m_filter = VA_INVALID_ID;
+ int m_forwardRefs, m_backwardRefs;
+ int m_currentIdx;
+ int m_frameCount;
+ EINTERLACEMETHOD m_vppMethod;
+ ReadyToDispose m_cbDispose = nullptr;
+ COutput *m_pOut = nullptr;
+};
+
+/**
+ * ffmpeg filter
+ */
+class CFFmpegPostproc : public CPostproc
+{
+public:
+ CFFmpegPostproc();
+ ~CFFmpegPostproc() override;
+ bool PreInit(CVaapiConfig &config, SDiMethods *methods = NULL) override;
+ bool Init(EINTERLACEMETHOD method) override;
+ bool AddPicture(CVaapiDecodedPicture &inPic) override;
+ bool Filter(CVaapiProcessedPicture &outPic) override;
+ void ClearRef(CVaapiProcessedPicture &pic) override;
+ void Flush() override;
+ bool UpdateDeintMethod(EINTERLACEMETHOD method) override;
+ bool DoesSync() override;
+ bool UseVideoSurface() override;
+ void Discard(COutput *output, ReadyToDispose cb) override;
+protected:
+ bool CheckSuccess(VAStatus status, const std::string& function);
+ void Close();
+ DllLibSSE4 m_dllSSE4;
+ uint8_t *m_cache;
+ AVFilterGraph* m_pFilterGraph;
+ AVFilterContext* m_pFilterIn;
+ AVFilterContext* m_pFilterOut;
+ AVFrame *m_pFilterFrameIn;
+ AVFrame *m_pFilterFrameOut;
+ EINTERLACEMETHOD m_diMethod;
+ VideoPicture m_DVDPic;
+ double m_frametime;
+ double m_lastOutPts;
+ ReadyToDispose m_cbDispose = nullptr;
+ COutput *m_pOut;
+ int m_refsToPics = 0;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.cpp
new file mode 100644
index 0000000..fc7f6b9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.cpp
@@ -0,0 +1,3464 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VDPAU.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "guilib/TextureManager.h"
+#include "rendering/RenderSystem.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/X11/WinSystemX11.h"
+
+#include <array>
+#include <mutex>
+
+#include <dlfcn.h>
+
+using namespace Actor;
+using namespace VDPAU;
+using namespace std::chrono_literals;
+
+#define NUM_RENDER_PICS 7
+#define NUM_CROP_PIX 3
+
+#define ARSIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+CDecoder::Desc decoder_profiles[] = {
+ {"MPEG1", VDP_DECODER_PROFILE_MPEG1, 0},
+ {"MPEG2_SIMPLE", VDP_DECODER_PROFILE_MPEG2_SIMPLE, 0},
+ {"MPEG2_MAIN", VDP_DECODER_PROFILE_MPEG2_MAIN, 0},
+ {"H264_BASELINE", VDP_DECODER_PROFILE_H264_BASELINE, 0},
+ {"H264_MAIN", VDP_DECODER_PROFILE_H264_MAIN, 0},
+ {"H264_HIGH", VDP_DECODER_PROFILE_H264_HIGH, 0},
+ {"VC1_SIMPLE", VDP_DECODER_PROFILE_VC1_SIMPLE, 0},
+ {"VC1_MAIN", VDP_DECODER_PROFILE_VC1_MAIN, 0},
+ {"VC1_ADVANCED", VDP_DECODER_PROFILE_VC1_ADVANCED, 0},
+ {"MPEG4_PART2_ASP", VDP_DECODER_PROFILE_MPEG4_PART2_ASP, 0},
+#ifdef VDP_DECODER_PROFILE_HEVC_MAIN
+ {"HEVC_MAIN", VDP_DECODER_PROFILE_HEVC_MAIN, 0},
+#endif
+#ifdef VDP_DECODER_PROFILE_VP9_PROFILE_0
+ {"VP9_PROFILE_0", VDP_DECODER_PROFILE_VP9_PROFILE_0, 0},
+#endif
+};
+
+static struct SInterlaceMapping
+{
+ const EINTERLACEMETHOD method;
+ const VdpVideoMixerFeature feature;
+} g_interlace_mapping[] =
+{ {VS_INTERLACEMETHOD_VDPAU_TEMPORAL , VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL}
+, {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF , VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL}
+, {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL , VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL}
+, {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF, VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL}
+, {VS_INTERLACEMETHOD_VDPAU_INVERSE_TELECINE , VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE}
+, {VS_INTERLACEMETHOD_NONE , (VdpVideoMixerFeature)-1}
+};
+
+static float studioCSCKCoeffs601[3] = {0.299, 0.587, 0.114}; //BT601 {Kr, Kg, Kb}
+static float studioCSCKCoeffs709[3] = {0.2126, 0.7152, 0.0722}; //BT709 {Kr, Kg, Kb}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+CVDPAUContext *CVDPAUContext::m_context = 0;
+CCriticalSection CVDPAUContext::m_section;
+Display *CVDPAUContext::m_display = 0;
+void *CVDPAUContext::m_dlHandle = 0;
+
+CVDPAUContext::CVDPAUContext()
+{
+ m_context = 0;
+ m_refCount = 0;
+}
+
+void CVDPAUContext::Release()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ m_refCount--;
+ if (m_refCount <= 0)
+ {
+ Close();
+ delete this;
+ m_context = 0;
+ }
+}
+
+void CVDPAUContext::Close()
+{
+ CLog::Log(LOGINFO, "VDPAU::Close - closing decoder context");
+ DestroyContext();
+}
+
+bool CVDPAUContext::EnsureContext(CVDPAUContext **ctx)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (m_context)
+ {
+ m_context->m_refCount++;
+ *ctx = m_context;
+ return true;
+ }
+
+ m_context = new CVDPAUContext();
+ *ctx = m_context;
+ {
+ std::unique_lock<CCriticalSection> gLock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ if (!m_context->LoadSymbols() || !m_context->CreateContext())
+ {
+ delete m_context;
+ m_context = 0;
+ *ctx = NULL;
+ return false;
+ }
+ }
+
+ m_context->m_refCount++;
+
+ *ctx = m_context;
+ return true;
+}
+
+VDPAU_procs& CVDPAUContext::GetProcs()
+{
+ return m_vdpProcs;
+}
+
+VdpVideoMixerFeature* CVDPAUContext::GetFeatures()
+{
+ return m_vdpFeatures;
+}
+
+int CVDPAUContext::GetFeatureCount()
+{
+ return m_featureCount;
+}
+
+bool CVDPAUContext::LoadSymbols()
+{
+ if (!m_dlHandle)
+ {
+ m_dlHandle = dlopen("libvdpau.so.1", RTLD_LAZY);
+ if (!m_dlHandle)
+ {
+ const char* error = dlerror();
+ if (!error)
+ error = "dlerror() returned NULL";
+
+ CLog::Log(LOGERROR, "VDPAU::LoadSymbols: Unable to get handle to lib: {}", error);
+ return false;
+ }
+ }
+
+ char* error;
+ (void)dlerror();
+ dl_vdp_device_create_x11 = (VdpStatus (*)(Display*, int, VdpDevice*, VdpStatus (**)(VdpDevice, VdpFuncId, void**)))dlsym(m_dlHandle, (const char*)"vdp_device_create_x11");
+ error = dlerror();
+ if (error)
+ {
+ CLog::Log(LOGERROR, "(VDPAU) - {} in {}", error, __FUNCTION__);
+ m_vdpDevice = VDP_INVALID_HANDLE;
+ return false;
+ }
+ return true;
+}
+
+bool CVDPAUContext::CreateContext()
+{
+ CLog::Log(LOGINFO, "VDPAU::CreateContext - creating decoder context");
+
+ int screen;
+ {
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ if (!m_display)
+ m_display = XOpenDisplay(NULL);
+
+ if (!m_display)
+ return false;
+
+ screen = static_cast<KODI::WINDOWING::X11::CWinSystemX11*>(CServiceBroker::GetWinSystem())->GetScreen();
+ }
+
+ VdpStatus vdp_st;
+ // Create Device
+ vdp_st = dl_vdp_device_create_x11(m_display,
+ screen,
+ &m_vdpDevice,
+ &m_vdpProcs.vdp_get_proc_address);
+
+ CLog::Log(LOGINFO, "vdp_device = {:#08x} vdp_st = {:#08x}", m_vdpDevice, vdp_st);
+ if (vdp_st != VDP_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "(VDPAU) unable to init VDPAU - vdp_st = 0x{:x}. Falling back.", vdp_st);
+ m_vdpDevice = VDP_INVALID_HANDLE;
+ return false;
+ }
+
+ QueryProcs();
+ SpewHardwareAvailable();
+ return true;
+}
+
+void CVDPAUContext::QueryProcs()
+{
+ VdpStatus vdp_st;
+
+#define VDP_PROC(id, proc) \
+ do { \
+ vdp_st = m_vdpProcs.vdp_get_proc_address(m_vdpDevice, id, (void**)&proc); \
+ if (vdp_st != VDP_STATUS_OK) \
+ { \
+ CLog::Log(LOGERROR, "CVDPAUContext::GetProcs - failed to get proc id"); \
+ } \
+ } while(0);
+
+ VDP_PROC(VDP_FUNC_ID_GET_ERROR_STRING , m_vdpProcs.vdp_get_error_string);
+ VDP_PROC(VDP_FUNC_ID_DEVICE_DESTROY , m_vdpProcs.vdp_device_destroy);
+ VDP_PROC(VDP_FUNC_ID_GENERATE_CSC_MATRIX , m_vdpProcs.vdp_generate_csc_matrix);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_SURFACE_CREATE , m_vdpProcs.vdp_video_surface_create);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY , m_vdpProcs.vdp_video_surface_destroy);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR , m_vdpProcs.vdp_video_surface_put_bits_y_cb_cr);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR , m_vdpProcs.vdp_video_surface_get_bits_y_cb_cr);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_Y_CB_CR , m_vdpProcs.vdp_output_surface_put_bits_y_cb_cr);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_NATIVE , m_vdpProcs.vdp_output_surface_put_bits_native);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE , m_vdpProcs.vdp_output_surface_create);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY , m_vdpProcs.vdp_output_surface_destroy);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_GET_BITS_NATIVE , m_vdpProcs.vdp_output_surface_get_bits_native);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_OUTPUT_SURFACE, m_vdpProcs.vdp_output_surface_render_output_surface);
+ VDP_PROC(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_INDEXED , m_vdpProcs.vdp_output_surface_put_bits_indexed);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_CREATE , m_vdpProcs.vdp_video_mixer_create);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_SET_FEATURE_ENABLES , m_vdpProcs.vdp_video_mixer_set_feature_enables);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_DESTROY , m_vdpProcs.vdp_video_mixer_destroy);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_RENDER , m_vdpProcs.vdp_video_mixer_render);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_SET_ATTRIBUTE_VALUES , m_vdpProcs.vdp_video_mixer_set_attribute_values);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_QUERY_PARAMETER_SUPPORT , m_vdpProcs.vdp_video_mixer_query_parameter_support);
+ VDP_PROC(VDP_FUNC_ID_VIDEO_MIXER_QUERY_FEATURE_SUPPORT , m_vdpProcs.vdp_video_mixer_query_feature_support);
+ VDP_PROC(VDP_FUNC_ID_DECODER_CREATE , m_vdpProcs.vdp_decoder_create);
+ VDP_PROC(VDP_FUNC_ID_DECODER_DESTROY , m_vdpProcs.vdp_decoder_destroy);
+ VDP_PROC(VDP_FUNC_ID_DECODER_RENDER , m_vdpProcs.vdp_decoder_render);
+ VDP_PROC(VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES , m_vdpProcs.vdp_decoder_query_caps);
+#undef VDP_PROC
+}
+
+VdpDevice CVDPAUContext::GetDevice()
+{
+ return m_vdpDevice;
+}
+
+void CVDPAUContext::DestroyContext()
+{
+ if (!m_vdpProcs.vdp_device_destroy)
+ return;
+
+ m_vdpProcs.vdp_device_destroy(m_vdpDevice);
+ m_vdpDevice = VDP_INVALID_HANDLE;
+}
+
+void CVDPAUContext::SpewHardwareAvailable() //Copyright (c) 2008 Wladimir J. van der Laan -- VDPInfo
+{
+ VdpStatus rv;
+ CLog::Log(LOGINFO, "VDPAU Decoder capabilities:");
+ CLog::Log(LOGINFO, "name level macbs width height");
+ CLog::Log(LOGINFO, "------------------------------------");
+ for(const CDecoder::Desc& decoder_profile : decoder_profiles)
+ {
+ VdpBool is_supported = false;
+ uint32_t max_level, max_macroblocks, max_width, max_height;
+ rv = m_vdpProcs.vdp_decoder_query_caps(m_vdpDevice, decoder_profile.id,
+ &is_supported, &max_level, &max_macroblocks, &max_width, &max_height);
+ if(rv == VDP_STATUS_OK && is_supported)
+ {
+ CLog::Log(LOGINFO, "{:<16} {:2} {:5} {:5} {:5}", decoder_profile.name, max_level,
+ max_macroblocks, max_width, max_height);
+ }
+ }
+ CLog::Log(LOGINFO, "------------------------------------");
+ m_featureCount = 0;
+#define CHECK_SUPPORT(feature) \
+ do \
+ { \
+ VdpBool supported; \
+ if (m_vdpProcs.vdp_video_mixer_query_feature_support(m_vdpDevice, feature, &supported) == \
+ VDP_STATUS_OK && \
+ supported) \
+ { \
+ CLog::Log(LOGINFO, "Mixer feature: " #feature); \
+ m_vdpFeatures[m_featureCount++] = feature; \
+ } \
+ } while (false)
+
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_SHARPNESS);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE);
+#ifdef VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L2);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L3);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L4);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L5);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L6);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L7);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L8);
+ CHECK_SUPPORT(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L9);
+#endif
+#undef CHECK_SUPPORT
+}
+
+bool CVDPAUContext::Supports(VdpVideoMixerFeature feature)
+{
+ for(int i = 0; i < m_featureCount; i++)
+ {
+ if(m_vdpFeatures[i] == feature)
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// VDPAU Video Surface states
+//-----------------------------------------------------------------------------
+
+#define SURFACE_USED_FOR_REFERENCE 0x01
+#define SURFACE_USED_FOR_RENDER 0x02
+
+void CVideoSurfaces::AddSurface(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_state[surf] = SURFACE_USED_FOR_REFERENCE;
+}
+
+void CVideoSurfaces::ClearReference(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::ClearReference - surface invalid");
+ return;
+ }
+ m_state[surf] &= ~SURFACE_USED_FOR_REFERENCE;
+ if (m_state[surf] == 0)
+ {
+ m_freeSurfaces.push_back(surf);
+ }
+}
+
+bool CVideoSurfaces::MarkRender(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::MarkRender - surface invalid");
+ return false;
+ }
+ std::list<VdpVideoSurface>::iterator it;
+ it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it != m_freeSurfaces.end())
+ {
+ m_freeSurfaces.erase(it);
+ }
+ m_state[surf] |= SURFACE_USED_FOR_RENDER;
+ return true;
+}
+
+void CVideoSurfaces::ClearRender(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) == m_state.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::ClearRender - surface invalid");
+ return;
+ }
+ m_state[surf] &= ~SURFACE_USED_FOR_RENDER;
+ if (m_state[surf] == 0)
+ {
+ m_freeSurfaces.push_back(surf);
+ }
+}
+
+bool CVideoSurfaces::IsValid(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) != m_state.end())
+ return true;
+ else
+ return false;
+}
+
+VdpVideoSurface CVideoSurfaces::GetFree(VdpVideoSurface surf)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_state.find(surf) != m_state.end())
+ {
+ std::list<VdpVideoSurface>::iterator it;
+ it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it == m_freeSurfaces.end())
+ {
+ CLog::Log(LOGWARNING, "CVideoSurfaces::GetFree - surface not free");
+ }
+ else
+ {
+ m_freeSurfaces.erase(it);
+ m_state[surf] = SURFACE_USED_FOR_REFERENCE;
+ return surf;
+ }
+ }
+
+ if (!m_freeSurfaces.empty())
+ {
+ VdpVideoSurface freeSurf = m_freeSurfaces.front();
+ m_freeSurfaces.pop_front();
+ m_state[freeSurf] = SURFACE_USED_FOR_REFERENCE;
+ return freeSurf;
+ }
+
+ return VDP_INVALID_HANDLE;
+}
+
+VdpVideoSurface CVideoSurfaces::RemoveNext(bool skiprender)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ VdpVideoSurface surf;
+ std::map<VdpVideoSurface, int>::iterator it;
+ for(it = m_state.begin(); it != m_state.end(); ++it)
+ {
+ if (skiprender && it->second & SURFACE_USED_FOR_RENDER)
+ continue;
+ surf = it->first;
+ m_state.erase(surf);
+
+ std::list<VdpVideoSurface>::iterator it2;
+ it2 = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf);
+ if (it2 != m_freeSurfaces.end())
+ m_freeSurfaces.erase(it2);
+ return surf;
+ }
+ return VDP_INVALID_HANDLE;
+}
+
+void CVideoSurfaces::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_freeSurfaces.clear();
+ m_state.clear();
+}
+
+int CVideoSurfaces::Size()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ return m_state.size();
+}
+
+bool CVideoSurfaces::HasRefs()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (const auto &i : m_state)
+ {
+ if (i.second & SURFACE_USED_FOR_REFERENCE)
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// CVDPAU
+//-----------------------------------------------------------------------------
+
+bool CDecoder::m_capGeneral = false;
+
+CDecoder::CDecoder(CProcessInfo& processInfo) :
+ m_vdpauOutput(*this, &m_inMsgEvent), m_processInfo(processInfo)
+{
+ m_vdpauConfig.videoSurfaces = &m_videoSurfaces;
+
+ m_vdpauConfigured = false;
+ m_DisplayState = VDPAU_OPEN;
+ m_vdpauConfig.context = 0;
+ m_vdpauConfig.processInfo = &m_processInfo;
+ m_vdpauConfig.resetCounter = 0;
+}
+
+bool CDecoder::Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat fmt)
+{
+ // this could be done better by querying actual hw capabilities
+ // but since vdpau will be dropped anyway in v19, this should do
+ if (avctx->sw_pix_fmt != AV_PIX_FMT_YUV420P &&
+ avctx->sw_pix_fmt != AV_PIX_FMT_YUVJ420P)
+ return false;
+
+ // check if user wants to decode this format with VDPAU
+ std::string gpuvendor = CServiceBroker::GetRenderSystem()->GetRenderVendor();
+ std::transform(gpuvendor.begin(), gpuvendor.end(), gpuvendor.begin(), ::tolower);
+ // nvidia is whitelisted despite for mpeg-4 we need to query user settings
+ if ((gpuvendor.compare(0, 6, "nvidia") != 0) || (avctx->codec_id == AV_CODEC_ID_MPEG4) || (avctx->codec_id == AV_CODEC_ID_H263))
+ {
+ std::map<AVCodecID, std::string> settings_map = {
+ { AV_CODEC_ID_H263, CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG4 },
+ { AV_CODEC_ID_MPEG4, CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG4 },
+ { AV_CODEC_ID_WMV3, CSettings::SETTING_VIDEOPLAYER_USEVDPAUVC1 },
+ { AV_CODEC_ID_VC1, CSettings::SETTING_VIDEOPLAYER_USEVDPAUVC1 },
+ { AV_CODEC_ID_MPEG2VIDEO, CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG2 },
+ };
+
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ auto entry = settings_map.find(avctx->codec_id);
+ if (entry != settings_map.end())
+ {
+ auto setting = settings->GetSetting(entry->second);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", entry->second);
+ return false;
+ }
+
+ bool enabled = settings->GetBool(entry->second) && setting->IsVisible();
+ if (!enabled)
+ return false;
+ }
+ }
+
+ if (!CServiceBroker::GetRenderSystem()->IsExtSupported("GL_NV_vdpau_interop"))
+ {
+ CLog::Log(LOGINFO, "VDPAU::Open: required extension GL_NV_vdpau_interop not found");
+ return false;
+ }
+
+ if (avctx->coded_width == 0 ||
+ avctx->coded_height == 0)
+ {
+ CLog::Log(LOGWARNING,"VDPAU::Open: no width/height available, can't init");
+ return false;
+ }
+ m_vdpauConfig.numRenderBuffers = 5;
+ m_vdpauConfig.timeOpened = CurrentHostCounter();
+
+ if (!CVDPAUContext::EnsureContext(&m_vdpauConfig.context))
+ return false;
+
+ m_DisplayState = VDPAU_OPEN;
+ m_vdpauConfigured = false;
+
+ m_presentPicture = 0;
+
+ {
+ VdpDecoderProfile profile = 0;
+
+ // convert FFMPEG codec ID to VDPAU profile.
+ ReadFormatOf(avctx->codec_id, profile, m_vdpauConfig.vdpChromaType);
+ if(profile)
+ {
+ VdpStatus vdp_st;
+ VdpBool is_supported = false;
+ uint32_t max_level, max_macroblocks, max_width, max_height;
+
+ // query device capabilities to ensure that VDPAU can handle the requested codec
+ vdp_st = m_vdpauConfig.context->GetProcs().vdp_decoder_query_caps(m_vdpauConfig.context->GetDevice(),
+ profile, &is_supported, &max_level, &max_macroblocks, &max_width, &max_height);
+
+ // test to make sure there is a possibility the codec will work
+ if (CheckStatus(vdp_st, __LINE__))
+ {
+ CLog::Log(LOGERROR, "VDPAU::Open: error {}({}) checking for decoder support",
+ m_vdpauConfig.context->GetProcs().vdp_get_error_string(vdp_st), vdp_st);
+ return false;
+ }
+
+ if (max_width < (uint32_t) avctx->coded_width || max_height < (uint32_t) avctx->coded_height)
+ {
+ CLog::Log(LOGWARNING,
+ "VDPAU::Open: requested picture dimensions ({}, {}) exceed hardware capabilities "
+ "( {}, {}).",
+ avctx->coded_width, avctx->coded_height, max_width, max_height);
+ return false;
+ }
+
+ if (!CDVDCodecUtils::IsVP3CompatibleWidth(avctx->coded_width))
+ CLog::Log(LOGWARNING, "VDPAU::Open width {} might not be supported because of hardware bug",
+ avctx->width);
+
+ // attempt to create a decoder with this width/height, some sizes are not supported by hw
+ vdp_st = m_vdpauConfig.context->GetProcs().vdp_decoder_create(m_vdpauConfig.context->GetDevice(), profile, avctx->coded_width, avctx->coded_height, 5, &m_vdpauConfig.vdpDecoder);
+
+ if (CheckStatus(vdp_st, __LINE__))
+ {
+ CLog::Log(LOGERROR, "VDPAU::Open: error: {}({}) checking for decoder support",
+ m_vdpauConfig.context->GetProcs().vdp_get_error_string(vdp_st), vdp_st);
+ return false;
+ }
+
+ m_vdpauConfig.context->GetProcs().vdp_decoder_destroy(m_vdpauConfig.vdpDecoder);
+ CheckStatus(vdp_st, __LINE__);
+
+ // finally setup ffmpeg
+ memset(&m_hwContext, 0, sizeof(AVVDPAUContext));
+ m_hwContext.render2 = CDecoder::Render;
+ avctx->get_buffer2 = CDecoder::FFGetBuffer;
+ avctx->slice_flags = SLICE_FLAG_CODED_ORDER|SLICE_FLAG_ALLOW_FIELD;
+ avctx->hwaccel_context = &m_hwContext;
+
+ CServiceBroker::GetWinSystem()->Register(this);
+ m_avctx = mainctx;
+ return true;
+ }
+ }
+ return false;
+}
+
+CDecoder::~CDecoder()
+{
+ Close();
+}
+
+void CDecoder::Close()
+{
+ CLog::Log(LOGINFO, " (VDPAU) {}", __FUNCTION__);
+
+ CServiceBroker::GetWinSystem()->Unregister(this);
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ FiniVDPAUOutput();
+ m_vdpauOutput.Dispose();
+
+ if (m_vdpauConfig.context)
+ m_vdpauConfig.context->Release();
+ m_vdpauConfig.context = 0;
+}
+
+long CDecoder::Release()
+{
+ // if ffmpeg holds any references, flush buffers
+ if (m_avctx && m_videoSurfaces.HasRefs())
+ {
+ avcodec_flush_buffers(m_avctx);
+ }
+
+ // check if we should do some pre-cleanup here
+ // a second decoder might need resources
+ if (m_vdpauConfigured == true)
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ CLog::Log(LOGINFO, "CVDPAU::Release pre-cleanup");
+
+ Message *reply;
+ if (m_vdpauOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::PRECLEANUP,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - pre-cleanup returned error", __FUNCTION__);
+ m_DisplayState = VDPAU_ERROR;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - pre-cleanup timed out", __FUNCTION__);
+ m_DisplayState = VDPAU_ERROR;
+ }
+
+ VdpVideoSurface surf;
+ while((surf = m_videoSurfaces.RemoveNext(true)) != VDP_INVALID_HANDLE)
+ {
+ m_vdpauConfig.context->GetProcs().vdp_video_surface_destroy(surf);
+ }
+ }
+ return IHardwareDecoder::Release();
+}
+
+long CDecoder::ReleasePicReference()
+{
+ return IHardwareDecoder::Release();
+}
+
+void CDecoder::SetWidthHeight(int width, int height)
+{
+ m_vdpauConfig.upscale = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoVDPAUScaling;
+
+ //pick the smallest dimensions, so we downscale with vdpau and upscale with opengl when appropriate
+ //this requires the least amount of gpu memory bandwidth
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() < width || CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() < height || m_vdpauConfig.upscale >= 0)
+ {
+ //scale width to desktop size if the aspect ratio is the same or bigger than the desktop
+ if ((double)height * CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() / width <= (double)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())
+ {
+ m_vdpauConfig.outWidth = CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth();
+ m_vdpauConfig.outHeight = MathUtils::round_int((double)height * CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() / width);
+ }
+ else //scale height to the desktop size if the aspect ratio is smaller than the desktop
+ {
+ m_vdpauConfig.outHeight = CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight();
+ m_vdpauConfig.outWidth = MathUtils::round_int((double)width * CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() / height);
+ }
+ }
+ else
+ { //let opengl scale
+ m_vdpauConfig.outWidth = width;
+ m_vdpauConfig.outHeight = height;
+ }
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVDPAU::SetWidthHeight Setting OutWidth: {} OutHeight: {}",
+ m_vdpauConfig.outWidth, m_vdpauConfig.outHeight);
+}
+
+void CDecoder::OnLostDisplay()
+{
+ CLog::Log(LOGINFO, "CVDPAU::OnLostDevice event");
+
+ int count = CServiceBroker::GetWinSystem()->GetGfxContext().exit();
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ FiniVDPAUOutput();
+ if (m_vdpauConfig.context)
+ m_vdpauConfig.context->Release();
+ m_vdpauConfig.context = 0;
+
+ m_DisplayState = VDPAU_LOST;
+ lock.unlock();
+ m_DisplayEvent.Reset();
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().restore(count);
+}
+
+void CDecoder::OnResetDisplay()
+{
+ CLog::Log(LOGINFO, "CVDPAU::OnResetDevice event");
+
+ int count = CServiceBroker::GetWinSystem()->GetGfxContext().exit();
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ if (m_DisplayState == VDPAU_LOST)
+ {
+ m_DisplayState = VDPAU_RESET;
+ lock.unlock();
+ m_DisplayEvent.Set();
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().restore(count);
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Check(AVCodecContext* avctx)
+{
+ EDisplayState state;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ state = m_DisplayState;
+ }
+
+ if (state == VDPAU_LOST)
+ {
+ CLog::Log(LOGINFO, "CVDPAU::Check waiting for display reset event");
+ if (!m_DisplayEvent.Wait(4000ms))
+ {
+ CLog::Log(LOGERROR, "CVDPAU::Check - device didn't reset in reasonable time");
+ state = VDPAU_RESET;
+ }
+ else
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+ state = m_DisplayState;
+ }
+ }
+ if (state == VDPAU_RESET || state == VDPAU_ERROR)
+ {
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ avcodec_flush_buffers(avctx);
+ FiniVDPAUOutput();
+ if (m_vdpauConfig.context)
+ m_vdpauConfig.context->Release();
+ m_vdpauConfig.context = 0;
+
+ if (CVDPAUContext::EnsureContext(&m_vdpauConfig.context))
+ {
+ m_DisplayState = VDPAU_OPEN;
+ m_vdpauConfigured = false;
+ }
+
+ if (state == VDPAU_RESET)
+ return CDVDVideoCodec::VC_FLUSHED;
+ else
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ return CDVDVideoCodec::VC_NONE;
+}
+
+bool CDecoder::IsVDPAUFormat(AVPixelFormat format)
+{
+ if (format == AV_PIX_FMT_VDPAU)
+ return true;
+ else
+ return false;
+}
+
+bool CDecoder::Supports(VdpVideoMixerFeature feature)
+{
+ return m_vdpauConfig.context->Supports(feature);
+}
+
+void CDecoder::FiniVDPAUOutput()
+{
+ if (!m_vdpauConfigured)
+ return;
+
+ CLog::Log(LOGINFO, " (VDPAU) {}", __FUNCTION__);
+
+ // uninit output
+ m_vdpauOutput.Dispose();
+ m_vdpauConfigured = false;
+
+ VdpStatus vdp_st;
+
+ vdp_st = m_vdpauConfig.context->GetProcs().vdp_decoder_destroy(m_vdpauConfig.vdpDecoder);
+ if (CheckStatus(vdp_st, __LINE__))
+ return;
+ m_vdpauConfig.vdpDecoder = VDP_INVALID_HANDLE;
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVDPAU::FiniVDPAUOutput destroying {} video surfaces",
+ m_videoSurfaces.Size());
+
+ VdpVideoSurface surf;
+ while((surf = m_videoSurfaces.RemoveNext()) != VDP_INVALID_HANDLE)
+ {
+ m_vdpauConfig.context->GetProcs().vdp_video_surface_destroy(surf);
+ if (CheckStatus(vdp_st, __LINE__))
+ return;
+ }
+ m_videoSurfaces.Reset();
+}
+
+void CDecoder::ReadFormatOf( AVCodecID codec
+ , VdpDecoderProfile &vdp_decoder_profile
+ , VdpChromaType &vdp_chroma_type)
+{
+ switch (codec)
+ {
+ case AV_CODEC_ID_MPEG1VIDEO:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+ case AV_CODEC_ID_MPEG2VIDEO:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+ case AV_CODEC_ID_H264:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+#ifdef VDP_DECODER_PROFILE_HEVC_MAIN
+ case AV_CODEC_ID_HEVC:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_HEVC_MAIN;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+#endif
+#ifdef VDP_DECODER_PROFILE_VP9_PROFILE_0
+ case AV_CODEC_ID_VP9:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_VP9_PROFILE_0;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+#endif
+ case AV_CODEC_ID_WMV3:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+ case AV_CODEC_ID_VC1:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+ case AV_CODEC_ID_MPEG4:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP;
+ vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ break;
+ default:
+ vdp_decoder_profile = 0;
+ vdp_chroma_type = 0;
+ break;
+ }
+}
+
+bool CDecoder::ConfigVDPAU(AVCodecContext* avctx, int ref_frames)
+{
+ FiniVDPAUOutput();
+
+ VdpStatus vdp_st;
+ VdpDecoderProfile vdp_decoder_profile;
+
+ m_vdpauConfig.vidWidth = avctx->width;
+ m_vdpauConfig.vidHeight = avctx->height;
+ m_vdpauConfig.surfaceWidth = avctx->coded_width;
+ m_vdpauConfig.surfaceHeight = avctx->coded_height;
+
+ SetWidthHeight(avctx->width,avctx->height);
+
+ CLog::Log(LOGINFO, " (VDPAU) screenWidth:{} vidWidth:{} surfaceWidth:{}", m_vdpauConfig.outWidth,
+ m_vdpauConfig.vidWidth, m_vdpauConfig.surfaceWidth);
+ CLog::Log(LOGINFO, " (VDPAU) screenHeight:{} vidHeight:{} surfaceHeight:{}",
+ m_vdpauConfig.outHeight, m_vdpauConfig.vidHeight, m_vdpauConfig.surfaceHeight);
+
+ ReadFormatOf(avctx->codec_id, vdp_decoder_profile, m_vdpauConfig.vdpChromaType);
+
+ if (avctx->codec_id == AV_CODEC_ID_H264)
+ {
+ m_vdpauConfig.maxReferences = ref_frames;
+ if (m_vdpauConfig.maxReferences > 16) m_vdpauConfig.maxReferences = 16;
+ if (m_vdpauConfig.maxReferences < 5) m_vdpauConfig.maxReferences = 5;
+ }
+ else if (avctx->codec_id == AV_CODEC_ID_HEVC)
+ {
+ // The DPB works quite differently in hevc and there isn't a per-file max
+ // reference number, so we force the maximum number (source: upstream ffmpeg)
+ m_vdpauConfig.maxReferences = 16;
+ }
+ else if (avctx->codec_id == AV_CODEC_ID_VP9)
+ {
+ if (avctx->profile != FF_PROFILE_VP9_0)
+ return false;
+
+ m_vdpauConfig.maxReferences = 8;
+ }
+ else
+ m_vdpauConfig.maxReferences = 2;
+
+ vdp_st = m_vdpauConfig.context->GetProcs().vdp_decoder_create(m_vdpauConfig.context->GetDevice(),
+ vdp_decoder_profile,
+ m_vdpauConfig.surfaceWidth,
+ m_vdpauConfig.surfaceHeight,
+ m_vdpauConfig.maxReferences,
+ &m_vdpauConfig.vdpDecoder);
+ if (CheckStatus(vdp_st, __LINE__))
+ return false;
+
+ // initialize output
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_vdpauConfig.stats = &m_bufferStats;
+ m_vdpauConfig.vdpau = this;
+ m_bufferStats.Reset();
+ m_vdpauOutput.Start();
+ Message *reply;
+ if (m_vdpauOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::INIT,
+ &reply,
+ 2000,
+ &m_vdpauConfig,
+ sizeof(m_vdpauConfig)))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - vdpau output returned error", __FUNCTION__);
+ m_vdpauOutput.Dispose();
+ return false;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - failed to init output", __FUNCTION__);
+ m_vdpauOutput.Dispose();
+ return false;
+ }
+
+ m_inMsgEvent.Reset();
+ m_vdpauConfigured = true;
+ m_ErrorCount = 0;
+ m_vdpauConfig.resetCounter++;
+ return true;
+}
+
+int CDecoder::FFGetBuffer(AVCodecContext *avctx, AVFrame *pic, int flags)
+{
+ ICallbackHWAccel* cb = static_cast<ICallbackHWAccel*>(avctx->opaque);
+ CDecoder* vdp = static_cast<CDecoder*>(cb->GetHWAccel());
+
+ // while we are waiting to recover we can't do anything
+ std::unique_lock<CCriticalSection> lock(vdp->m_DecoderSection);
+
+ if(vdp->m_DisplayState != VDPAU_OPEN)
+ {
+ CLog::Log(LOGWARNING, "CVDPAU::FFGetBuffer - returning due to awaiting recovery");
+ return -1;
+ }
+
+ VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)pic->data[3];
+ surf = vdp->m_videoSurfaces.GetFree(surf != 0 ? surf : VDP_INVALID_HANDLE);
+
+ VdpStatus vdp_st = VDP_STATUS_ERROR;
+ if (surf == VDP_INVALID_HANDLE)
+ {
+ // create a new surface
+ VdpDecoderProfile profile;
+ ReadFormatOf(avctx->codec_id, profile, vdp->m_vdpauConfig.vdpChromaType);
+
+ vdp_st = vdp->m_vdpauConfig.context->GetProcs().vdp_video_surface_create(vdp->m_vdpauConfig.context->GetDevice(),
+ vdp->m_vdpauConfig.vdpChromaType,
+ avctx->coded_width,
+ avctx->coded_height,
+ &surf);
+ vdp->CheckStatus(vdp_st, __LINE__);
+ if (vdp_st != VDP_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "CVDPAU::FFGetBuffer - No Video surface available could be created");
+ return -1;
+ }
+ vdp->m_videoSurfaces.AddSurface(surf);
+ }
+
+ pic->data[1] = pic->data[2] = NULL;
+ pic->data[0] = (uint8_t*)(uintptr_t)surf;
+ pic->data[3] = (uint8_t*)(uintptr_t)surf;
+ pic->linesize[0] = pic->linesize[1] = pic->linesize[2] = 0;
+ AVBufferRef *buffer = av_buffer_create(pic->data[3], 0, FFReleaseBuffer, vdp, 0);
+ if (!buffer)
+ {
+ CLog::Log(LOGERROR, "CVDPAU::{} - error creating buffer", __FUNCTION__);
+ return -1;
+ }
+ pic->buf[0] = buffer;
+
+ pic->reordered_opaque= avctx->reordered_opaque;
+ return 0;
+}
+
+void CDecoder::FFReleaseBuffer(void *opaque, uint8_t *data)
+{
+ CDecoder *vdp = static_cast<CDecoder*>(opaque);
+
+ VdpVideoSurface surf;
+
+ std::unique_lock<CCriticalSection> lock(vdp->m_DecoderSection);
+
+ surf = (VdpVideoSurface)(uintptr_t)data;
+
+ vdp->m_videoSurfaces.ClearReference(surf);
+}
+
+int CDecoder::Render(struct AVCodecContext *s, struct AVFrame *src,
+ const VdpPictureInfo *info, uint32_t buffers_used,
+ const VdpBitstreamBuffer *buffers)
+{
+ ICallbackHWAccel* ctx = static_cast<ICallbackHWAccel*>(s->opaque);
+ CDecoder* vdp = static_cast<CDecoder*>(ctx->GetHWAccel());
+
+ // while we are waiting to recover we can't do anything
+ std::unique_lock<CCriticalSection> lock(vdp->m_DecoderSection);
+
+ if(vdp->m_DisplayState != VDPAU_OPEN)
+ return -1;
+
+ if(src->linesize[0] || src->linesize[1] || src->linesize[2])
+ {
+ CLog::Log(LOGERROR, "CVDPAU::FFDrawSlice - invalid linesizes or offsets provided");
+ return -1;
+ }
+
+ VdpStatus vdp_st;
+ VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)src->data[3];
+
+ // ffmpeg vc-1 decoder does not flush, make sure the data buffer is still valid
+ if (!vdp->m_videoSurfaces.IsValid(surf))
+ {
+ CLog::Log(LOGWARNING, "CVDPAU::FFDrawSlice - ignoring invalid buffer");
+ return -1;
+ }
+
+ uint32_t max_refs = 0;
+ if(s->codec_id == AV_CODEC_ID_H264)
+ max_refs = s->refs;
+
+ if(vdp->m_vdpauConfig.vdpDecoder == VDP_INVALID_HANDLE
+ || vdp->m_vdpauConfigured == false
+ || vdp->m_vdpauConfig.maxReferences < max_refs)
+ {
+ if(!vdp->ConfigVDPAU(s, max_refs))
+ return -1;
+ }
+
+ uint64_t startTime = CurrentHostCounter();
+ uint16_t decoded, processed, rend;
+ vdp->m_bufferStats.Get(decoded, processed, rend);
+ vdp_st = vdp->m_vdpauConfig.context->GetProcs().vdp_decoder_render(vdp->m_vdpauConfig.vdpDecoder,
+ surf, info, buffers_used, buffers);
+ if (vdp->CheckStatus(vdp_st, __LINE__))
+ return -1;
+
+ uint64_t diff = CurrentHostCounter() - startTime;
+ if (diff*1000/CurrentHostFrequency() > 30)
+ CLog::Log(
+ LOGDEBUG, LOGVIDEO,
+ "CVDPAU::DrawSlice - VdpDecoderRender long decoding: {} ms, dec: {}, proc: {}, rend: {}",
+ (int)((diff * 1000) / CurrentHostFrequency()), decoded, processed, rend);
+
+ return 0;
+}
+
+void CDecoder::SetCodecControl(int flags)
+{
+ m_codecControl = flags & (DVD_CODEC_CTRL_DRAIN | DVD_CODEC_CTRL_HURRY);
+ if (m_codecControl & DVD_CODEC_CTRL_DRAIN)
+ m_bufferStats.SetDraining(true);
+ else
+ m_bufferStats.SetDraining(false);
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Decode(AVCodecContext *avctx, AVFrame *pFrame)
+{
+ CDVDVideoCodec::VCReturn result = Check(avctx);
+ if (result != CDVDVideoCodec::VC_NONE)
+ return result;
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (!m_vdpauConfigured)
+ return CDVDVideoCodec::VC_ERROR;
+
+ if(pFrame)
+ { // we have a new frame from decoder
+
+ VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)pFrame->data[3];
+ // ffmpeg vc-1 decoder does not flush, make sure the data buffer is still valid
+ if (!m_videoSurfaces.IsValid(surf))
+ {
+ CLog::Log(LOGWARNING, "CVDPAU::Decode - ignoring invalid buffer");
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ m_videoSurfaces.MarkRender(surf);
+
+ // send frame to output for processing
+ CVdpauDecodedPicture *pic = new CVdpauDecodedPicture();
+ static_cast<ICallbackHWAccel*>(avctx->opaque)->GetPictureCommon(&(pic->DVDPic));
+ m_codecControl = pic->DVDPic.iFlags & (DVD_CODEC_CTRL_HURRY | DVD_CODEC_CTRL_NO_POSTPROC);
+ pic->videoSurface = surf;
+ pic->DVDPic.color_space = avctx->colorspace;
+ m_bufferStats.IncDecoded();
+ CPayloadWrap<CVdpauDecodedPicture> *payload = new CPayloadWrap<CVdpauDecodedPicture>(pic);
+ m_vdpauOutput.m_dataPort.SendOutMessage(COutputDataProtocol::NEWFRAME, payload);
+ }
+
+ uint16_t decoded, processed, render;
+ Message *msg;
+ while (m_vdpauOutput.m_controlPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputControlProtocol::ERROR)
+ {
+ m_DisplayState = VDPAU_ERROR;
+ msg->Release();
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ msg->Release();
+ }
+
+ bool drain = (m_codecControl & DVD_CODEC_CTRL_DRAIN);
+
+ m_bufferStats.Get(decoded, processed, render);
+ // if all pics are drained, break the loop by setting VC_EOF
+ if (drain && decoded <= 0 && processed <= 0 && render <= 0)
+ return CDVDVideoCodec::VC_EOF;
+
+ uint64_t startTime = CurrentHostCounter();
+ while (true)
+ {
+ // first fill the buffers to keep vdpau busy
+ // mixer will run with decoded >= 2. output is limited by number of output surfaces
+ // In case mixer is bypassed we limit by looking at processed
+ if (!drain && decoded < 3 && processed < 3)
+ {
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ else if (m_vdpauOutput.m_dataPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::PICTURE)
+ {
+ if (m_presentPicture)
+ {
+ m_presentPicture->Release();
+ m_presentPicture = nullptr;
+ }
+
+ m_presentPicture = *(CVdpauRenderPicture**)msg->data;
+ m_bufferStats.DecRender();
+ msg->Release();
+ uint64_t diff = CurrentHostCounter() - startTime;
+ m_bufferStats.SetParams(diff, m_codecControl);
+ return CDVDVideoCodec::VC_PICTURE;
+ }
+ msg->Release();
+ }
+ else if (m_vdpauOutput.m_controlPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputControlProtocol::STATS)
+ {
+ msg->Release();
+ m_bufferStats.Get(decoded, processed, render);
+ if (!drain && decoded < 3 && processed < 3)
+ {
+ return CDVDVideoCodec::VC_BUFFER;
+ }
+ }
+ else
+ {
+ m_DisplayState = VDPAU_ERROR;
+ msg->Release();
+ return CDVDVideoCodec::VC_ERROR;
+ }
+ }
+
+ if (!m_inMsgEvent.Wait(2000ms))
+ break;
+ }
+
+ CLog::Log(LOGERROR, "VDPAU::{} - timed out waiting for output message", __FUNCTION__);
+ m_DisplayState = VDPAU_ERROR;
+
+ return CDVDVideoCodec::VC_ERROR;
+}
+
+bool CDecoder::GetPicture(AVCodecContext* avctx, VideoPicture* picture)
+{
+ if (picture->videoBuffer)
+ {
+ picture->videoBuffer->Release();
+ picture->videoBuffer = nullptr;
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (m_DisplayState != VDPAU_OPEN)
+ return false;
+
+ picture->SetParams(m_presentPicture->DVDPic);
+ picture->videoBuffer = m_presentPicture;
+ m_presentPicture = nullptr;
+
+ return true;
+}
+
+void CDecoder::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_DecoderSection);
+
+ if (m_presentPicture)
+ {
+ m_presentPicture->Release();
+ m_presentPicture = nullptr;
+ }
+
+ if (!m_vdpauConfigured)
+ return;
+
+ Message *reply;
+ if (m_vdpauOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::FLUSH,
+ &reply,
+ 2000))
+ {
+ bool success = reply->signal == COutputControlProtocol::ACC ? true : false;
+ reply->Release();
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - flush returned error", __FUNCTION__);
+ m_DisplayState = VDPAU_ERROR;
+ }
+ else
+ m_bufferStats.Reset();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "VDPAU::{} - flush timed out", __FUNCTION__);
+ m_DisplayState = VDPAU_ERROR;
+ }
+}
+
+bool CDecoder::CanSkipDeint()
+{
+ return m_bufferStats.CanSkipDeint();
+}
+
+void CDecoder::ReturnRenderPicture(CVdpauRenderPicture *renderPic)
+{
+ m_vdpauOutput.m_dataPort.SendOutMessage(COutputDataProtocol::RETURNPIC, &renderPic, sizeof(renderPic));
+}
+
+bool CDecoder::CheckStatus(VdpStatus vdp_st, int line)
+{
+ if (vdp_st != VDP_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, " (VDPAU) Error: {}({}) at {}:{}",
+ m_vdpauConfig.context->GetProcs().vdp_get_error_string(vdp_st), vdp_st, __FILE__,
+ line);
+
+ m_ErrorCount++;
+
+ if(m_DisplayState == VDPAU_OPEN)
+ {
+ if (vdp_st == VDP_STATUS_DISPLAY_PREEMPTED)
+ {
+ m_DisplayEvent.Reset();
+ m_DisplayState = VDPAU_LOST;
+ }
+ else if (m_ErrorCount > 2)
+ m_DisplayState = VDPAU_ERROR;
+ }
+
+ return true;
+ }
+ m_ErrorCount = 0;
+ return false;
+}
+
+IHardwareDecoder* CDecoder::Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt)
+ {
+ if (CDecoder::IsVDPAUFormat(fmt) && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEVDPAU))
+ return new VDPAU::CDecoder(processInfo);
+
+ return nullptr;
+ }
+
+void CDecoder::Register()
+{
+ CVDPAUContext *context;
+ if (!CVDPAUContext::EnsureContext(&context))
+ return;
+
+ context->Release();
+
+ m_capGeneral = true;
+
+ CDVDFactoryCodec::RegisterHWAccel("vdpau", CDecoder::Create);
+
+ std::string gpuvendor = CServiceBroker::GetRenderSystem()->GetRenderVendor();
+ std::transform(gpuvendor.begin(), gpuvendor.end(), gpuvendor.begin(), ::tolower);
+ bool isNvidia = (gpuvendor.compare(0, 6, "nvidia") == 0);
+
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ auto setting = settings->GetSetting(CSettings::SETTING_VIDEOPLAYER_USEVDPAU);
+ if (!setting)
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", CSettings::SETTING_VIDEOPLAYER_USEVDPAU);
+ else
+ setting->SetVisible(true);
+
+ if (!isNvidia)
+ {
+ constexpr std::array<const char*, 4> vdpauSettings = {
+ CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG4, CSettings::SETTING_VIDEOPLAYER_USEVDPAUVC1,
+ CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG2, CSettings::SETTING_VIDEOPLAYER_USEVDPAUMIXER};
+
+ for (const auto& vdpauSetting : vdpauSettings)
+ {
+ setting = settings->GetSetting(vdpauSetting);
+ if (!setting)
+ {
+ CLog::Log(LOGERROR, "Failed to load setting for: {}", vdpauSetting);
+ continue;
+ }
+
+ setting->SetVisible(true);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// BufferPool
+//-----------------------------------------------------------------------------
+
+class VDPAU::CVdpauBufferPool : public IVideoBufferPool
+{
+public:
+ explicit CVdpauBufferPool(CDecoder &decoder);
+ ~CVdpauBufferPool() override;
+ CVideoBuffer* Get() override;
+ void Return(int id) override;
+ CVdpauRenderPicture* GetVdpau();
+ bool HasFree();
+ void QueueReturnPicture(CVdpauRenderPicture *pic);
+ CVdpauRenderPicture* ProcessSyncPicture();
+ void InvalidateUsed();
+
+ unsigned short numOutputSurfaces;
+ std::vector<VdpOutputSurface> outputSurfaces;
+ std::queue<CVdpauProcessedPicture> processedPics;
+ std::deque<CVdpauProcessedPicture> processedPicsAway;
+
+ int procPicId = 0;
+
+protected:
+ std::vector<CVdpauRenderPicture*> allRenderPics;
+ std::deque<int> usedRenderPics;
+ std::deque<int> freeRenderPics;
+ std::deque<int> syncRenderPics;
+
+ CDecoder &m_vdpau;
+};
+
+CVdpauBufferPool::CVdpauBufferPool(CDecoder &decoder)
+ : m_vdpau(decoder)
+{
+ CVdpauRenderPicture *pic;
+ for (unsigned int i = 0; i < NUM_RENDER_PICS; i++)
+ {
+ pic = new CVdpauRenderPicture(i);
+ allRenderPics.push_back(pic);
+ freeRenderPics.push_back(i);
+ }
+}
+
+CVdpauBufferPool::~CVdpauBufferPool()
+{
+ CVdpauRenderPicture *pic;
+ for (unsigned int i = 0; i < NUM_RENDER_PICS; i++)
+ {
+ pic = allRenderPics[i];
+ delete pic;
+ }
+ allRenderPics.clear();
+}
+
+CVideoBuffer* CVdpauBufferPool::Get()
+{
+ if (freeRenderPics.empty())
+ return nullptr;
+
+ int idx = freeRenderPics.front();
+ freeRenderPics.pop_front();
+ usedRenderPics.push_back(idx);
+
+ CVideoBuffer *retPic = allRenderPics[idx];
+ retPic->Acquire(GetPtr());
+
+ m_vdpau.Acquire();
+
+ return retPic;
+}
+
+void CVdpauBufferPool::Return(int id)
+{
+ CVdpauRenderPicture *pic = allRenderPics[id];
+
+ m_vdpau.ReturnRenderPicture(pic);
+ m_vdpau.ReleasePicReference();
+}
+
+CVdpauRenderPicture* CVdpauBufferPool::GetVdpau()
+{
+ return dynamic_cast<CVdpauRenderPicture*>(Get());
+}
+
+bool CVdpauBufferPool::HasFree()
+{
+ return !freeRenderPics.empty();
+}
+
+void CVdpauBufferPool::QueueReturnPicture(CVdpauRenderPicture *pic)
+{
+ std::deque<int>::iterator it;
+ for (it = usedRenderPics.begin(); it != usedRenderPics.end(); ++it)
+ {
+ if (allRenderPics[*it] == pic)
+ {
+ break;
+ }
+ }
+
+ if (it == usedRenderPics.end())
+ {
+ CLog::Log(LOGWARNING, "COutput::QueueReturnPicture - pic not found");
+ return;
+ }
+
+ // check if already queued
+ std::deque<int>::iterator it2 = find(syncRenderPics.begin(),
+ syncRenderPics.end(),
+ *it);
+ if (it2 == syncRenderPics.end())
+ {
+ syncRenderPics.push_back(*it);
+ }
+}
+
+CVdpauRenderPicture* CVdpauBufferPool::ProcessSyncPicture()
+{
+ CVdpauRenderPicture *retPic = nullptr;
+
+ std::deque<int>::iterator it;
+ for (it = syncRenderPics.begin(); it != syncRenderPics.end(); )
+ {
+ retPic = allRenderPics[*it];
+
+ freeRenderPics.push_back(*it);
+
+ std::deque<int>::iterator it2 = find(usedRenderPics.begin(),
+ usedRenderPics.end(),
+ *it);
+ if (it2 == usedRenderPics.end())
+ {
+ CLog::Log(LOGERROR, "COutput::ProcessSyncPicture - pic not found in queue");
+ }
+ else
+ {
+ usedRenderPics.erase(it2);
+ }
+ it = syncRenderPics.erase(it);
+
+ break;
+ }
+ return retPic;
+}
+
+void CVdpauBufferPool::InvalidateUsed()
+{
+ std::deque<int>::iterator it;
+ for (it = usedRenderPics.begin(); it != usedRenderPics.end(); ++it)
+ {
+ allRenderPics[*it]->procPic.outputSurface = VDP_INVALID_HANDLE;
+ allRenderPics[*it]->procPic.videoSurface = VDP_INVALID_HANDLE;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Mixer
+//-----------------------------------------------------------------------------
+CMixer::CMixer(CEvent *inMsgEvent) :
+ CThread("Vdpau Mixer"),
+ m_controlPort("ControlPort", inMsgEvent, &m_outMsgEvent),
+ m_dataPort("DataPort", inMsgEvent, &m_outMsgEvent)
+{
+ m_inMsgEvent = inMsgEvent;
+}
+
+CMixer::~CMixer()
+{
+ Dispose();
+}
+
+void CMixer::Start()
+{
+ Create();
+}
+
+void CMixer::Dispose()
+{
+ m_bStop = true;
+ m_outMsgEvent.Set();
+ StopThread();
+
+ m_controlPort.Purge();
+ m_dataPort.Purge();
+}
+
+bool CMixer::IsActive()
+{
+ return IsRunning();
+}
+
+void CMixer::OnStartup()
+{
+ CLog::Log(LOGINFO, "CMixer::OnStartup: Output Thread created");
+}
+
+void CMixer::OnExit()
+{
+ CLog::Log(LOGINFO, "CMixer::OnExit: Output Thread terminated");
+}
+
+enum MIXER_STATES
+{
+ M_TOP = 0, // 0
+ M_TOP_ERROR, // 1
+ M_TOP_UNCONFIGURED, // 2
+ M_TOP_CONFIGURED, // 3
+ M_TOP_CONFIGURED_WAIT1, // 4
+ M_TOP_CONFIGURED_STEP1, // 5
+ M_TOP_CONFIGURED_WAIT2, // 6
+ M_TOP_CONFIGURED_STEP2, // 7
+};
+
+int MIXER_parentStates[] = {
+ -1,
+ 0, //TOP_ERROR
+ 0, //TOP_UNCONFIGURED
+ 0, //TOP_CONFIGURED
+ 3, //TOP_CONFIGURED_WAIT1
+ 3, //TOP_CONFIGURED_STEP1
+ 3, //TOP_CONFIGURED_WAIT2
+ 3, //TOP_CONFIGURED_STEP2
+};
+
+void CMixer::StateMachine(int signal, Protocol *port, Message *msg)
+{
+ for (int state = m_state; ; state = MIXER_parentStates[state])
+ {
+ switch (state)
+ {
+ case M_TOP: // TOP
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::FLUSH:
+ Flush();
+ msg->Reply(CMixerControlProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ {
+ std::string portName = port == NULL ? "timer" : port->portName;
+ CLog::Log(LOGWARNING, "CMixer::{} - signal: {} form port: {} not handled for state: {}",
+ __FUNCTION__, signal, portName, m_state);
+ }
+ return;
+
+ case M_TOP_ERROR: // TOP
+ break;
+
+ case M_TOP_UNCONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::INIT:
+ CVdpauConfig *data;
+ data = (CVdpauConfig*)msg->data;
+ if (data)
+ {
+ m_config = *data;
+ }
+ Init();
+ if (!m_vdpError)
+ {
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ msg->Reply(CMixerControlProtocol::ACC);
+ }
+ else
+ {
+ msg->Reply(CMixerControlProtocol::ERROR);
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case M_TOP_CONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::FLUSH:
+ Flush();
+ msg->Reply(CMixerControlProtocol::ACC);
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case CMixerDataProtocol::FRAME:
+ CPayloadWrap<CVdpauDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVdpauDecodedPicture>*>(msg->payloadObj.get());
+ if (payload)
+ {
+ m_decodedPics.push(*(payload->GetPlayload()));
+ }
+ m_extTimeout = 0;
+ return;
+ case CMixerDataProtocol::BUFFER:
+ VdpOutputSurface *surf;
+ surf = (VdpOutputSurface*)msg->data;
+ if (surf)
+ {
+ m_outputSurfaces.push(*surf);
+ }
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case M_TOP_CONFIGURED_WAIT1:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::TIMEOUT:
+ if (!m_decodedPics.empty() && !m_outputSurfaces.empty())
+ {
+ m_state = M_TOP_CONFIGURED_STEP1;
+ m_bStateMachineSelfTrigger = true;
+ }
+ else if (!m_outputSurfaces.empty() &&
+ m_config.stats->IsDraining() &&
+ m_mixerInput.size() >= 1)
+ {
+ CVdpauDecodedPicture pic;
+ pic.DVDPic.SetParams(m_mixerInput[0].DVDPic);
+ pic.videoSurface = VDP_INVALID_HANDLE;
+ m_decodedPics.push(pic);
+ m_state = M_TOP_CONFIGURED_STEP1;
+ m_bStateMachineSelfTrigger = true;
+ }
+ else
+ {
+ m_extTimeout = 100;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case M_TOP_CONFIGURED_STEP1:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::TIMEOUT:
+ m_mixerInput.push_front(m_decodedPics.front());
+ m_decodedPics.pop();
+ if (m_mixerInput.size() < 2)
+ {
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ m_extTimeout = 0;
+ return;
+ }
+ InitCycle();
+ ProcessPicture();
+ if (m_vdpError)
+ {
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ m_extTimeout = 1000;
+ return;
+ }
+ if (!m_processPicture.isYuv)
+ m_outputSurfaces.pop();
+ m_config.stats->IncProcessed();
+ m_config.stats->DecDecoded();
+ m_dataPort.SendInMessage(CMixerDataProtocol::PICTURE,&m_processPicture,sizeof(m_processPicture));
+ if (m_mixersteps > 1)
+ {
+ m_state = M_TOP_CONFIGURED_WAIT2;
+ m_extTimeout = 0;
+ }
+ else
+ {
+ FiniCycle();
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ m_extTimeout = 0;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case M_TOP_CONFIGURED_WAIT2:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::TIMEOUT:
+ if (!m_outputSurfaces.empty())
+ {
+ m_state = M_TOP_CONFIGURED_STEP2;
+ m_bStateMachineSelfTrigger = true;
+ }
+ else
+ {
+ m_extTimeout = 100;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case M_TOP_CONFIGURED_STEP2:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case CMixerControlProtocol::TIMEOUT:
+ m_processPicture.outputSurface = m_outputSurfaces.front();
+ m_mixerstep = 1;
+ ProcessPicture();
+ if (m_vdpError)
+ {
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ m_extTimeout = 1000;
+ return;
+ }
+ if (!m_processPicture.isYuv)
+ m_outputSurfaces.pop();
+ m_config.stats->IncProcessed();
+ m_dataPort.SendInMessage(CMixerDataProtocol::PICTURE,&m_processPicture,sizeof(m_processPicture));
+ FiniCycle();
+ m_state = M_TOP_CONFIGURED_WAIT1;
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default: // we are in no state, should not happen
+ CLog::Log(LOGERROR, "CMixer::{} - no valid state: {}", __FUNCTION__, m_state);
+ return;
+ }
+ } // for
+}
+
+void CMixer::Process()
+{
+ Message *msg = NULL;
+ Protocol *port = NULL;
+ bool gotMsg;
+
+ m_state = M_TOP_UNCONFIGURED;
+ m_extTimeout = 1000;
+ m_bStateMachineSelfTrigger = false;
+
+ while (!m_bStop)
+ {
+ gotMsg = false;
+
+ if (m_bStateMachineSelfTrigger)
+ {
+ m_bStateMachineSelfTrigger = false;
+ // self trigger state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+ // check control port
+ else if (m_controlPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_controlPort;
+ }
+ // check data port
+ else if (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ }
+
+ if (gotMsg)
+ {
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+
+ // wait for message
+ else if (m_outMsgEvent.Wait(std::chrono::milliseconds(m_extTimeout)))
+ {
+ continue;
+ }
+ // time out
+ else
+ {
+ msg = m_controlPort.GetMessage();
+ msg->signal = CMixerControlProtocol::TIMEOUT;
+ port = 0;
+ // signal timeout to state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ }
+ }
+ Uninit();
+}
+
+void CMixer::CreateVdpauMixer()
+{
+ CLog::Log(LOGINFO, " (VDPAU) Creating the video mixer");
+
+ InitCSCMatrix(m_config.vidWidth);
+
+ VdpVideoMixerParameter parameters[] = {
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
+ VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE};
+
+ void const * parameter_values[] = {
+ &m_config.surfaceWidth,
+ &m_config.surfaceHeight,
+ &m_config.vdpChromaType};
+
+ VdpStatus vdp_st = m_config.context->GetProcs().vdp_video_mixer_create(m_config.context->GetDevice(),
+ m_config.context->GetFeatureCount(),
+ m_config.context->GetFeatures(),
+ ARSIZE(parameters),
+ parameters,
+ parameter_values,
+ &m_videoMixer);
+ CheckStatus(vdp_st, __LINE__);
+
+}
+
+void CMixer::InitCSCMatrix(int Width)
+{
+ m_Procamp.struct_version = VDP_PROCAMP_VERSION;
+ m_Procamp.brightness = 0.0;
+ m_Procamp.contrast = 1.0;
+ m_Procamp.saturation = 1.0;
+ m_Procamp.hue = 0;
+}
+
+void CMixer::CheckFeatures()
+{
+ if (m_Upscale != m_config.upscale)
+ {
+ SetHWUpscaling();
+ m_Upscale = m_config.upscale;
+ }
+ if (m_Brightness != m_config.processInfo->GetVideoSettings().m_Brightness ||
+ m_Contrast != m_config.processInfo->GetVideoSettings().m_Contrast ||
+ m_ColorMatrix != m_mixerInput[1].DVDPic.color_space)
+ {
+ SetColor();
+ m_Brightness = m_config.processInfo->GetVideoSettings().m_Brightness;
+ m_Contrast = m_config.processInfo->GetVideoSettings().m_Contrast;
+ m_ColorMatrix = m_mixerInput[1].DVDPic.color_space;
+ }
+ if (m_NoiseReduction != m_config.processInfo->GetVideoSettings().m_NoiseReduction)
+ {
+ m_NoiseReduction = m_config.processInfo->GetVideoSettings().m_NoiseReduction;
+ SetNoiseReduction();
+ }
+ if (m_Sharpness != m_config.processInfo->GetVideoSettings().m_Sharpness)
+ {
+ m_Sharpness = m_config.processInfo->GetVideoSettings().m_Sharpness;
+ SetSharpness();
+ }
+ if (m_Deint != m_config.processInfo->GetVideoSettings().m_InterlaceMethod)
+ {
+ m_Deint = m_config.processInfo->GetVideoSettings().m_InterlaceMethod;
+ SetDeinterlacing();
+ }
+}
+
+void CMixer::SetPostProcFeatures(bool postProcEnabled)
+{
+ if (m_PostProc != postProcEnabled)
+ {
+ if (postProcEnabled)
+ {
+ SetNoiseReduction();
+ SetSharpness();
+ SetDeinterlacing();
+ SetHWUpscaling();
+ }
+ else
+ PostProcOff();
+ m_PostProc = postProcEnabled;
+ }
+}
+
+void CMixer::PostProcOff()
+{
+ VdpStatus vdp_st;
+
+ if (m_videoMixer == VDP_INVALID_HANDLE)
+ return;
+
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
+ VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE};
+
+ VdpBool enabled[]={0,0,0};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION};
+
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_SHARPNESS))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_SHARPNESS};
+
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ DisableHQScaling();
+}
+
+bool CMixer::GenerateStudioCSCMatrix(VdpColorStandard colorStandard, VdpCSCMatrix &studioCSCMatrix)
+{
+ // instead use studioCSCKCoeffs601[3], studioCSCKCoeffs709[3] to generate float[3][4] matrix (float studioCSC[3][4])
+ // m00 = mRY = red: luma factor (contrast factor) (1.0)
+ // m10 = mGY = green: luma factor (contrast factor) (1.0)
+ // m20 = mBY = blue: luma factor (contrast factor) (1.0)
+ //
+ // m01 = mRB = red: blue color diff coeff (0.0)
+ // m11 = mGB = green: blue color diff coeff (-2Kb(1-Kb)/(Kg))
+ // m21 = mBB = blue: blue color diff coeff ((1-Kb)/0.5)
+ //
+ // m02 = mRR = red: red color diff coeff ((1-Kr)/0.5)
+ // m12 = mGR = green: red color diff coeff (-2Kr(1-Kr)/(Kg))
+ // m22 = mBR = blue: red color diff coeff (0.0)
+ //
+ // m03 = mRC = red: colour zero offset (brightness factor) (-(1-Kr)/0.5 * (128/255))
+ // m13 = mGC = green: colour zero offset (brightness factor) ((256/255) * (Kb(1-Kb) + Kr(1-Kr)) / Kg)
+ // m23 = mBC = blue: colour zero offset (brightness factor) (-(1-Kb)/0.5 * (128/255))
+
+ // columns
+ int Y = 0;
+ int Cb = 1;
+ int Cr = 2;
+ int C = 3;
+ // rows
+ int R = 0;
+ int G = 1;
+ int B = 2;
+ // colour standard coefficients for red, geen, blue
+ float Kr, Kg, Kb;
+ // colour diff zero position (use standard 8-bit coding precision)
+ float CDZ = 128; //256*0.5
+ // range excursion (use standard 8-bit coding precision)
+ float EXC = 255; //256-1
+
+ if (colorStandard == VDP_COLOR_STANDARD_ITUR_BT_601)
+ {
+ Kr = studioCSCKCoeffs601[0];
+ Kg = studioCSCKCoeffs601[1];
+ Kb = studioCSCKCoeffs601[2];
+ }
+ else // assume VDP_COLOR_STANDARD_ITUR_BT_709
+ {
+ Kr = studioCSCKCoeffs709[0];
+ Kg = studioCSCKCoeffs709[1];
+ Kb = studioCSCKCoeffs709[2];
+ }
+ // we keep luma unscaled to retain the levels present in source so that 16-235 luma is converted to RGB 16-235
+ studioCSCMatrix[R][Y] = 1.0f;
+ studioCSCMatrix[G][Y] = 1.0f;
+ studioCSCMatrix[B][Y] = 1.0f;
+
+ studioCSCMatrix[R][Cb] = 0.0f;
+ studioCSCMatrix[G][Cb] = -2 * Kb * (1 - Kb) / Kg;
+ studioCSCMatrix[B][Cb] = (1 - Kb) / 0.5f;
+
+ studioCSCMatrix[R][Cr] = (1 - Kr) / 0.5f;
+ studioCSCMatrix[G][Cr] = -2 * Kr * (1 - Kr) / Kg;
+ studioCSCMatrix[B][Cr] = 0.0f;
+
+ studioCSCMatrix[R][C] = -1 * studioCSCMatrix[R][Cr] * CDZ / EXC;
+ studioCSCMatrix[G][C] = -1 * (studioCSCMatrix[G][Cb] + studioCSCMatrix[G][Cr]) * CDZ / EXC;
+ studioCSCMatrix[B][C] = -1 * studioCSCMatrix[B][Cb] * CDZ / EXC;
+
+ return true;
+}
+
+void CMixer::SetColor()
+{
+ VdpStatus vdp_st;
+
+ if (m_Brightness != m_config.processInfo->GetVideoSettings().m_Brightness)
+ m_Procamp.brightness = (float)((m_config.processInfo->GetVideoSettings().m_Brightness)-50) / 100;
+ if (m_Contrast != m_config.processInfo->GetVideoSettings().m_Contrast)
+ m_Procamp.contrast = (float)((m_config.processInfo->GetVideoSettings().m_Contrast)+50) / 100;
+
+ VdpColorStandard colorStandard;
+ switch(m_mixerInput[1].DVDPic.color_space)
+ {
+ case AVCOL_SPC_BT709:
+ colorStandard = VDP_COLOR_STANDARD_ITUR_BT_709;
+ break;
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_SMPTE170M:
+ colorStandard = VDP_COLOR_STANDARD_ITUR_BT_601;
+ break;
+ case AVCOL_SPC_SMPTE240M:
+ colorStandard = VDP_COLOR_STANDARD_SMPTE_240M;
+ break;
+ case AVCOL_SPC_FCC:
+ case AVCOL_SPC_UNSPECIFIED:
+ case AVCOL_SPC_RGB:
+ default:
+ if(m_config.surfaceWidth > 1000)
+ colorStandard = VDP_COLOR_STANDARD_ITUR_BT_709;
+ else
+ colorStandard = VDP_COLOR_STANDARD_ITUR_BT_601;
+ }
+
+ VdpVideoMixerAttribute attributes[] = { VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX };
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE))
+ {
+ float studioCSC[3][4];
+ GenerateStudioCSCMatrix(colorStandard, studioCSC);
+ void const * pm_CSCMatrix[] = { &studioCSC };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_attribute_values(m_videoMixer, ARSIZE(attributes), attributes, pm_CSCMatrix);
+ }
+ else
+ {
+ vdp_st = m_config.context->GetProcs().vdp_generate_csc_matrix(&m_Procamp, colorStandard, &m_CSCMatrix);
+ if(vdp_st != VDP_STATUS_ERROR)
+ {
+ void const * pm_CSCMatrix[] = { &m_CSCMatrix };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_attribute_values(m_videoMixer, ARSIZE(attributes), attributes, pm_CSCMatrix);
+ }
+ }
+
+ CheckStatus(vdp_st, __LINE__);
+}
+
+void CMixer::SetNoiseReduction()
+{
+ if(!m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION))
+ return;
+
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION };
+ VdpVideoMixerAttribute attributes[] = { VDP_VIDEO_MIXER_ATTRIBUTE_NOISE_REDUCTION_LEVEL };
+ VdpStatus vdp_st;
+
+ if (!m_config.processInfo->GetVideoSettings().m_NoiseReduction)
+ {
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ return;
+ }
+ VdpBool enabled[]={1};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ float noiseReduction = m_config.processInfo->GetVideoSettings().m_NoiseReduction;
+ void* nr[] = { &noiseReduction };
+ CLog::Log(LOGINFO, "Setting Noise Reduction to {:f}",
+ m_config.processInfo->GetVideoSettings().m_NoiseReduction);
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_attribute_values(m_videoMixer, ARSIZE(attributes), attributes, nr);
+ CheckStatus(vdp_st, __LINE__);
+}
+
+void CMixer::SetSharpness()
+{
+ if(!m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_SHARPNESS))
+ return;
+
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_SHARPNESS };
+ VdpVideoMixerAttribute attributes[] = { VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL };
+ VdpStatus vdp_st;
+
+ if (!m_config.processInfo->GetVideoSettings().m_Sharpness)
+ {
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ return;
+ }
+ VdpBool enabled[]={1};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ float sharpness = m_config.processInfo->GetVideoSettings().m_Sharpness;
+ void* sh[] = { &sharpness };
+ CLog::Log(LOGINFO, "Setting Sharpness to {:f}",
+ m_config.processInfo->GetVideoSettings().m_Sharpness);
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_attribute_values(m_videoMixer, ARSIZE(attributes), attributes, sh);
+ CheckStatus(vdp_st, __LINE__);
+}
+
+void CMixer::SetDeinterlacing()
+{
+ VdpStatus vdp_st;
+
+ if (m_videoMixer == VDP_INVALID_HANDLE)
+ return;
+
+ EINTERLACEMETHOD method = m_config.processInfo->GetVideoSettings().m_InterlaceMethod;
+
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
+ VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE };
+
+ if (method == VS_INTERLACEMETHOD_NONE)
+ {
+ VdpBool enabled[] = {0,0,0};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ }
+ else
+ {
+ // fall back path if called with non supported method
+ if (!m_config.processInfo->Supports(method))
+ {
+ method = VS_INTERLACEMETHOD_VDPAU_TEMPORAL;
+ m_Deint = VS_INTERLACEMETHOD_VDPAU_TEMPORAL;
+ }
+
+ if (method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL
+ || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF)
+ {
+ VdpBool enabled[] = {1,0,0};
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoVDPAUtelecine)
+ enabled[2] = 1;
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ }
+ else if (method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL
+ || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF)
+ {
+ VdpBool enabled[] = {1,1,0};
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoVDPAUtelecine)
+ enabled[2] = 1;
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ }
+ else
+ {
+ VdpBool enabled[]={0,0,0};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ }
+ }
+ CheckStatus(vdp_st, __LINE__);
+
+ SetDeintSkipChroma();
+
+ m_config.useInteropYuv = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEVDPAUMIXER);
+
+ std::string deintStr = GetDeintStrFromInterlaceMethod(method);
+ // update deinterlacing method used in processInfo (none if progressive)
+ m_config.processInfo->SetVideoDeintMethod(deintStr);
+}
+
+void CMixer::SetDeintSkipChroma()
+{
+ VdpVideoMixerAttribute attribute[] = { VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE};
+ VdpStatus vdp_st;
+
+ uint8_t val;
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoVDPAUdeintSkipChromaHD && m_config.outHeight >= 720)
+ val = 1;
+ else
+ val = 0;
+
+ void const *values[]={&val};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_attribute_values(m_videoMixer, ARSIZE(attribute), attribute, values);
+
+ CheckStatus(vdp_st, __LINE__);
+}
+
+void CMixer::SetHWUpscaling()
+{
+#ifdef VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1
+
+ VdpStatus vdp_st;
+ VdpBool enabled[]={1};
+ switch (m_config.upscale)
+ {
+ case 9:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L9))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L9 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 8:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L8))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L8 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 7:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L7))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L7 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 6:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L6))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L6 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 5:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L5))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L5 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 4:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L4))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L4 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 3:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L3))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L3 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 2:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L2))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L2 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ case 1:
+ if (m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 };
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ break;
+ }
+ [[fallthrough]];
+ default:
+ DisableHQScaling();
+ return;
+ }
+ CheckStatus(vdp_st, __LINE__);
+#endif
+}
+
+void CMixer::DisableHQScaling()
+{
+ VdpStatus vdp_st;
+
+ if (m_videoMixer == VDP_INVALID_HANDLE)
+ return;
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L2))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L2 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L3))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L3 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L4))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L4 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L5))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L5 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L6))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L6 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L7))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L7 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L8))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L8 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+
+ if(m_config.vdpau->Supports(VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L9))
+ {
+ VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L9 };
+ VdpBool enabled[] = {};
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_set_feature_enables(m_videoMixer, ARSIZE(feature), feature, enabled);
+ CheckStatus(vdp_st, __LINE__);
+ }
+}
+
+void CMixer::Init()
+{
+ m_Brightness = 0.0;
+ m_Contrast = 0.0;
+ m_NoiseReduction = 0.0;
+ m_Sharpness = 0.0;
+ m_Deint = 0;
+ m_Upscale = 0;
+ m_SeenInterlaceFlag = false;
+ m_ColorMatrix = 0;
+ m_PostProc = false;
+ m_vdpError = false;
+
+ m_config.upscale = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoVDPAUScaling;
+ m_config.useInteropYuv = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEVDPAUMIXER);
+
+ CreateVdpauMixer();
+
+ // update deinterlacing methods in processInfo
+ std::list<EINTERLACEMETHOD> deintMethods;
+ deintMethods.push_back(VS_INTERLACEMETHOD_NONE);
+ for(SInterlaceMapping* p = g_interlace_mapping; p->method != VS_INTERLACEMETHOD_NONE; p++)
+ {
+ if (p->method == VS_INTERLACEMETHOD_VDPAU_INVERSE_TELECINE)
+ continue;
+
+ if (m_config.vdpau->Supports(p->feature))
+ deintMethods.push_back(p->method);
+ }
+ deintMethods.push_back(VS_INTERLACEMETHOD_VDPAU_BOB);
+ deintMethods.push_back(VS_INTERLACEMETHOD_RENDER_BOB);
+ m_config.processInfo->UpdateDeinterlacingMethods(deintMethods);
+ m_config.processInfo->SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_VDPAU_TEMPORAL);
+}
+
+void CMixer::Uninit()
+{
+ Flush();
+ while (!m_outputSurfaces.empty())
+ {
+ m_outputSurfaces.pop();
+ }
+ m_config.context->GetProcs().vdp_video_mixer_destroy(m_videoMixer);
+}
+
+void CMixer::Flush()
+{
+ while (!m_mixerInput.empty())
+ {
+ CVdpauDecodedPicture pic = m_mixerInput.back();
+ m_mixerInput.pop_back();
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ }
+ while (!m_decodedPics.empty())
+ {
+ CVdpauDecodedPicture pic = m_decodedPics.front();
+ m_decodedPics.pop();
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ }
+ Message *msg;
+ while (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ if (msg->signal == CMixerDataProtocol::FRAME)
+ {
+ CPayloadWrap<CVdpauDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVdpauDecodedPicture>*>(msg->payloadObj.get());
+ if (payload)
+ {
+ CVdpauDecodedPicture pic = *(payload->GetPlayload());
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ }
+ }
+ else if (msg->signal == CMixerDataProtocol::BUFFER)
+ {
+ VdpOutputSurface *surf;
+ surf = (VdpOutputSurface*)msg->data;
+ m_outputSurfaces.push(*surf);
+ }
+ msg->Release();
+ }
+}
+
+std::string CMixer::GetDeintStrFromInterlaceMethod(EINTERLACEMETHOD method)
+{
+ switch (method)
+ {
+ case VS_INTERLACEMETHOD_NONE:
+ return "none";
+ case VS_INTERLACEMETHOD_VDPAU_BOB:
+ return "vdpau-bob";
+ case VS_INTERLACEMETHOD_VDPAU_TEMPORAL:
+ return "vdpau-temp";
+ case VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF:
+ return "vdpau-temp-half";
+ case VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL:
+ return "vdpau-temp-spat";
+ case VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF:
+ return "vdpau-temp-spat-half";
+ case VS_INTERLACEMETHOD_RENDER_BOB:
+ return "bob";
+ default:
+ return "unknown";
+ }
+}
+
+void CMixer::InitCycle()
+{
+ CheckFeatures();
+ int flags;
+ uint64_t latency;
+ m_config.stats->GetParams(latency, flags);
+ if (flags & DVD_CODEC_CTRL_NO_POSTPROC)
+ SetPostProcFeatures(false);
+ else
+ SetPostProcFeatures(true);
+
+ m_config.stats->SetCanSkipDeint(false);
+
+ EINTERLACEMETHOD method = m_config.processInfo->GetVideoSettings().m_InterlaceMethod;
+ bool interlaced = m_mixerInput[1].DVDPic.iFlags & DVP_FLAG_INTERLACED;
+ m_SeenInterlaceFlag |= interlaced;
+
+ if (!(flags & DVD_CODEC_CTRL_NO_POSTPROC) &&
+ interlaced &&
+ method != VS_INTERLACEMETHOD_NONE)
+ {
+ if (!m_config.processInfo->Supports(method))
+ method = VS_INTERLACEMETHOD_VDPAU_TEMPORAL;
+
+ if (method == VS_INTERLACEMETHOD_VDPAU_BOB ||
+ method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL ||
+ method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF ||
+ method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL ||
+ method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF)
+ {
+ if(method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF ||
+ method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF ||
+ !CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo())
+ m_mixersteps = 1;
+ else
+ {
+ m_mixersteps = 2;
+ m_config.stats->SetCanSkipDeint(true);
+ }
+
+ if (m_mixerInput[1].DVDPic.iFlags & DVD_CODEC_CTRL_SKIPDEINT)
+ {
+ m_mixersteps = 1;
+ }
+
+ if(m_mixerInput[1].DVDPic.iFlags & DVP_FLAG_TOP_FIELD_FIRST)
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD;
+ else
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD;
+
+ m_mixerInput[1].DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST |
+ DVP_FLAG_REPEAT_TOP_FIELD |
+ DVP_FLAG_INTERLACED);
+ m_mixerInput[1].isYuv = false;
+ m_config.useInteropYuv = false;
+ }
+ else if (method == VS_INTERLACEMETHOD_RENDER_BOB)
+ {
+ m_mixersteps = 1;
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
+ m_mixerInput[1].isYuv = true;
+ m_config.useInteropYuv = true;
+ }
+ }
+ else
+ {
+ m_mixersteps = 1;
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
+
+ if (m_config.useInteropYuv)
+ m_mixerInput[1].isYuv = true;
+ else
+ {
+ m_mixerInput[1].DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST |
+ DVP_FLAG_REPEAT_TOP_FIELD |
+ DVP_FLAG_INTERLACED);
+ m_mixerInput[1].isYuv = false;
+ }
+ }
+ m_mixerstep = 0;
+ //m_mixerInput[1].DVDPic.format = RENDER_FMT_VDPAU;
+
+ m_processPicture.crop = false;
+ if (!m_mixerInput[1].isYuv)
+ {
+ m_processPicture.outputSurface = m_outputSurfaces.front();
+ m_mixerInput[1].DVDPic.iWidth = m_config.outWidth;
+ m_mixerInput[1].DVDPic.iHeight = m_config.outHeight;
+ if (m_SeenInterlaceFlag)
+ {
+ double ratio = (double)m_mixerInput[1].DVDPic.iDisplayHeight / m_mixerInput[1].DVDPic.iHeight;
+ m_mixerInput[1].DVDPic.iDisplayHeight = lrint(ratio*(m_mixerInput[1].DVDPic.iHeight-NUM_CROP_PIX*2));
+ m_processPicture.crop = true;
+ }
+ }
+ else
+ {
+ m_mixerInput[1].DVDPic.iWidth = m_config.vidWidth;
+ m_mixerInput[1].DVDPic.iHeight = m_config.vidHeight;
+ }
+
+ m_processPicture.isYuv = m_mixerInput[1].isYuv;
+ m_processPicture.DVDPic.SetParams(m_mixerInput[1].DVDPic);
+ m_processPicture.videoSurface = m_mixerInput[1].videoSurface;
+}
+
+void CMixer::FiniCycle()
+{
+ // Keep video surfaces for one 2 cycles longer than used
+ // by mixer. This avoids blocking in decoder.
+ // NVidia recommends num_ref + 5
+ size_t surfToKeep = 5;
+
+ if (m_mixerInput.size() > 0 &&
+ (m_mixerInput[0].videoSurface == VDP_INVALID_HANDLE))
+ surfToKeep = 1;
+
+ while (m_mixerInput.size() > surfToKeep)
+ {
+ CVdpauDecodedPicture &tmp = m_mixerInput.back();
+ if (!m_processPicture.isYuv)
+ {
+ m_config.videoSurfaces->ClearRender(tmp.videoSurface);
+ }
+ m_mixerInput.pop_back();
+ }
+
+ if (surfToKeep == 1)
+ m_mixerInput.clear();
+}
+
+void CMixer::ProcessPicture()
+{
+ if (m_processPicture.isYuv)
+ return;
+
+ VdpStatus vdp_st;
+
+ if (m_mixerstep == 1)
+ {
+ if(m_mixerfield == VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD)
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD;
+ else
+ m_mixerfield = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD;
+ }
+
+ VdpVideoSurface past_surfaces[4] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE, VDP_INVALID_HANDLE, VDP_INVALID_HANDLE };
+ VdpVideoSurface futu_surfaces[2] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE };
+ uint32_t pastCount = 4;
+ uint32_t futuCount = 2;
+
+ if(m_mixerfield == VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME)
+ {
+ // use only 2 past 1 future for progressive/weave
+ // (only used for postproc anyway eg noise reduction)
+ if (m_mixerInput.size() > 3)
+ past_surfaces[1] = m_mixerInput[3].videoSurface;
+ if (m_mixerInput.size() > 2)
+ past_surfaces[0] = m_mixerInput[2].videoSurface;
+ if (m_mixerInput.size() > 1)
+ futu_surfaces[0] = m_mixerInput[0].videoSurface;
+ pastCount = 2;
+ futuCount = 1;
+ }
+ else
+ {
+ if(m_mixerstep == 0)
+ { // first field
+ if (m_mixerInput.size() > 3)
+ {
+ past_surfaces[3] = m_mixerInput[3].videoSurface;
+ past_surfaces[2] = m_mixerInput[3].videoSurface;
+ }
+ if (m_mixerInput.size() > 2)
+ {
+ past_surfaces[1] = m_mixerInput[2].videoSurface;
+ past_surfaces[0] = m_mixerInput[2].videoSurface;
+ }
+ futu_surfaces[0] = m_mixerInput[1].videoSurface;
+ futu_surfaces[1] = m_mixerInput[0].videoSurface;
+ }
+ else
+ { // second field
+ if (m_mixerInput.size() > 3)
+ {
+ past_surfaces[3] = m_mixerInput[3].videoSurface;
+ }
+ if (m_mixerInput.size() > 2)
+ {
+ past_surfaces[2] = m_mixerInput[2].videoSurface;
+ past_surfaces[1] = m_mixerInput[2].videoSurface;
+ }
+ past_surfaces[0] = m_mixerInput[1].videoSurface;
+ futu_surfaces[0] = m_mixerInput[0].videoSurface;
+ futu_surfaces[1] = m_mixerInput[0].videoSurface;
+
+ if (m_mixerInput[0].DVDPic.pts != DVD_NOPTS_VALUE &&
+ m_mixerInput[1].DVDPic.pts != DVD_NOPTS_VALUE)
+ {
+ m_processPicture.DVDPic.pts = m_mixerInput[1].DVDPic.pts +
+ (m_mixerInput[0].DVDPic.pts -
+ m_mixerInput[1].DVDPic.pts) / 2;
+ }
+ else
+ m_processPicture.DVDPic.pts = DVD_NOPTS_VALUE;
+ m_processPicture.DVDPic.dts = DVD_NOPTS_VALUE;
+ }
+ m_processPicture.DVDPic.iRepeatPicture = 0.0;
+ } // interlaced
+
+ VdpRect sourceRect;
+ sourceRect.x0 = 0;
+ sourceRect.y0 = 0;
+ sourceRect.x1 = m_config.vidWidth;
+ sourceRect.y1 = m_config.vidHeight;
+
+ VdpRect destRect;
+ destRect.x0 = 0;
+ destRect.y0 = 0;
+ destRect.x1 = m_config.outWidth;
+ destRect.y1 = m_config.outHeight;
+
+ // start vdpau video mixer
+ vdp_st = m_config.context->GetProcs().vdp_video_mixer_render(m_videoMixer,
+ VDP_INVALID_HANDLE,
+ 0,
+ m_mixerfield,
+ pastCount,
+ past_surfaces,
+ m_mixerInput[1].videoSurface,
+ futuCount,
+ futu_surfaces,
+ &sourceRect,
+ m_processPicture.outputSurface,
+ &destRect,
+ &destRect,
+ 0,
+ NULL);
+ CheckStatus(vdp_st, __LINE__);
+}
+
+
+bool CMixer::CheckStatus(VdpStatus vdp_st, int line)
+{
+ if (vdp_st != VDP_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, " (VDPAU) Error: {}({}) at {}:{}",
+ m_config.context->GetProcs().vdp_get_error_string(vdp_st), vdp_st, __FILE__, line);
+ m_vdpError = true;
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Output
+//-----------------------------------------------------------------------------
+COutput::COutput(CDecoder &decoder, CEvent *inMsgEvent) :
+ CThread("Vdpau Output"),
+ m_controlPort("OutputControlPort", inMsgEvent, &m_outMsgEvent),
+ m_dataPort("OutputDataPort", inMsgEvent, &m_outMsgEvent),
+ m_vdpau(decoder),
+ m_mixer(&m_outMsgEvent)
+{
+ m_inMsgEvent = inMsgEvent;
+ m_bufferPool = std::make_shared<CVdpauBufferPool>(decoder);
+}
+
+void COutput::Start()
+{
+ Create();
+}
+
+COutput::~COutput()
+{
+ Dispose();
+}
+
+void COutput::Dispose()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_bStop = true;
+ m_outMsgEvent.Set();
+ StopThread();
+ m_controlPort.Purge();
+ m_dataPort.Purge();
+}
+
+void COutput::OnStartup()
+{
+ CLog::Log(LOGINFO, "COutput::OnStartup: Output Thread created");
+}
+
+void COutput::OnExit()
+{
+ CLog::Log(LOGINFO, "COutput::OnExit: Output Thread terminated");
+}
+
+enum OUTPUT_STATES
+{
+ O_TOP = 0, // 0
+ O_TOP_ERROR, // 1
+ O_TOP_UNCONFIGURED, // 2
+ O_TOP_CONFIGURED, // 3
+ O_TOP_CONFIGURED_IDLE, // 4
+ O_TOP_CONFIGURED_WORK, // 5
+};
+
+int VDPAU_OUTPUT_parentStates[] = {
+ -1,
+ 0, //TOP_ERROR
+ 0, //TOP_UNCONFIGURED
+ 0, //TOP_CONFIGURED
+ 3, //TOP_CONFIGURED_IDLE
+ 3, //TOP_CONFIGURED_WORK
+};
+
+void COutput::StateMachine(int signal, Protocol *port, Message *msg)
+{
+ for (int state = m_state; ; state = VDPAU_OUTPUT_parentStates[state])
+ {
+ switch (state)
+ {
+ case O_TOP: // TOP
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::FLUSH:
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ case COutputControlProtocol::PRECLEANUP:
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case COutputDataProtocol::RETURNPIC:
+ CVdpauRenderPicture *pic;
+ pic = *((CVdpauRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ return;
+ default:
+ break;
+ }
+ }
+ {
+ std::string portName = port == NULL ? "timer" : port->portName;
+ CLog::Log(LOGWARNING, "COutput::{} - signal: {} form port: {} not handled for state: {}",
+ __FUNCTION__, signal, portName, m_state);
+ }
+ return;
+
+ case O_TOP_ERROR:
+ break;
+
+ case O_TOP_UNCONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::INIT:
+ CVdpauConfig *data;
+ data = (CVdpauConfig*)msg->data;
+ if (data)
+ {
+ m_config = *data;
+ }
+ Init();
+ Message *reply;
+ if (m_mixer.m_controlPort.SendOutMessageSync(CMixerControlProtocol::INIT,
+ &reply, 1000, &m_config, sizeof(m_config)))
+ {
+ if (reply->signal != CMixerControlProtocol::ACC)
+ m_vdpError = true;
+ reply->Release();
+ }
+
+ // set initial number of
+ m_bufferPool->numOutputSurfaces = 4;
+ EnsureBufferPool();
+ if (!m_vdpError)
+ {
+ m_state = O_TOP_CONFIGURED_IDLE;
+ msg->Reply(COutputControlProtocol::ACC, &m_config, sizeof(m_config));
+ }
+ else
+ {
+ m_state = O_TOP_ERROR;
+ msg->Reply(COutputControlProtocol::ERROR);
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED:
+ if (port == &m_controlPort)
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::FLUSH:
+ Flush();
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ case COutputControlProtocol::PRECLEANUP:
+ Flush();
+ PreCleanup();
+ msg->Reply(COutputControlProtocol::ACC);
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_dataPort)
+ {
+ switch (signal)
+ {
+ case COutputDataProtocol::NEWFRAME:
+ CPayloadWrap<CVdpauDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVdpauDecodedPicture>*>(msg->payloadObj.release());
+ if (payload)
+ {
+ m_mixer.m_dataPort.SendOutMessage(CMixerDataProtocol::FRAME, payload);
+ }
+ return;
+ case COutputDataProtocol::RETURNPIC:
+ CVdpauRenderPicture *pic;
+ pic = *((CVdpauRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ m_controlPort.SendInMessage(COutputControlProtocol::STATS);
+ m_state = O_TOP_CONFIGURED_WORK;
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ else if (port == &m_mixer.m_dataPort)
+ {
+ switch (signal)
+ {
+ case CMixerDataProtocol::PICTURE:
+ CVdpauProcessedPicture *pic;
+ pic = (CVdpauProcessedPicture*)msg->data;
+ m_bufferPool->processedPics.push(*pic);
+ m_state = O_TOP_CONFIGURED_WORK;
+ m_extTimeout = 0;
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_IDLE:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ m_extTimeout = 100;
+ if (HasWork())
+ {
+ m_state = O_TOP_CONFIGURED_WORK;
+ m_extTimeout = 0;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case O_TOP_CONFIGURED_WORK:
+ if (port == NULL) // timeout
+ {
+ switch (signal)
+ {
+ case COutputControlProtocol::TIMEOUT:
+ if (HasWork())
+ {
+ CVdpauRenderPicture *pic;
+ pic = ProcessMixerPicture();
+ if (pic)
+ {
+ m_config.stats->DecProcessed();
+ m_config.stats->IncRender();
+ m_dataPort.SendInMessage(COutputDataProtocol::PICTURE, &pic, sizeof(pic));
+ }
+ m_extTimeout = 1;
+ }
+ else
+ {
+ m_state = O_TOP_CONFIGURED_IDLE;
+ m_extTimeout = 0;
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default: // we are in no state, should not happen
+ CLog::Log(LOGERROR, "COutput::{} - no valid state: {}", __FUNCTION__, m_state);
+ return;
+ }
+ } // for
+}
+
+void COutput::Process()
+{
+ Message *msg = NULL;
+ Protocol *port = NULL;
+ bool gotMsg;
+
+ m_state = O_TOP_UNCONFIGURED;
+ m_extTimeout = 1000;
+ m_bStateMachineSelfTrigger = false;
+
+ while (!m_bStop)
+ {
+ gotMsg = false;
+
+ if (m_bStateMachineSelfTrigger)
+ {
+ m_bStateMachineSelfTrigger = false;
+ // self trigger state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+ // check control port
+ else if (m_controlPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_controlPort;
+ }
+ // check data port
+ else if (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_dataPort;
+ }
+ // check mixer data port
+ else if (m_mixer.m_dataPort.ReceiveInMessage(&msg))
+ {
+ gotMsg = true;
+ port = &m_mixer.m_dataPort;
+ }
+ if (gotMsg)
+ {
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ continue;
+ }
+
+ // wait for message
+ else if (m_outMsgEvent.Wait(std::chrono::milliseconds(m_extTimeout)))
+ {
+ continue;
+ }
+ // time out
+ else
+ {
+ msg = m_controlPort.GetMessage();
+ msg->signal = COutputControlProtocol::TIMEOUT;
+ port = 0;
+ // signal timeout to state machine
+ StateMachine(msg->signal, port, msg);
+ if (!m_bStateMachineSelfTrigger)
+ {
+ msg->Release();
+ msg = NULL;
+ }
+ }
+ }
+ Flush();
+ Uninit();
+}
+
+bool COutput::Init()
+{
+ m_mixer.Start();
+ m_vdpError = false;
+
+ return true;
+}
+
+bool COutput::Uninit()
+{
+ m_mixer.Dispose();
+ ProcessSyncPicture();
+ ReleaseBufferPool();
+ return true;
+}
+
+void COutput::Flush()
+{
+ if (m_mixer.IsActive())
+ {
+ Message *reply;
+ if (m_mixer.m_controlPort.SendOutMessageSync(CMixerControlProtocol::FLUSH,
+ &reply,
+ 2000))
+ {
+ reply->Release();
+ }
+ else
+ CLog::Log(LOGERROR, "Coutput::{} - failed to flush mixer", __FUNCTION__);
+ }
+
+ Message *msg;
+
+ while (m_dataPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::PICTURE)
+ {
+ CVdpauRenderPicture *pic;
+ pic = *((CVdpauRenderPicture**)msg->data);
+ pic->Release();
+ }
+ msg->Release();
+ }
+
+ while (m_dataPort.ReceiveOutMessage(&msg))
+ {
+ if (msg->signal == COutputDataProtocol::NEWFRAME)
+ {
+ CPayloadWrap<CVdpauDecodedPicture> *payload;
+ payload = dynamic_cast<CPayloadWrap<CVdpauDecodedPicture>*>(msg->payloadObj.get());
+ if (payload)
+ {
+ CVdpauDecodedPicture pic = *(payload->GetPlayload());
+ m_config.videoSurfaces->ClearRender(pic.videoSurface);
+ }
+ }
+ else if (msg->signal == COutputDataProtocol::RETURNPIC)
+ {
+ CVdpauRenderPicture *pic;
+ pic = *((CVdpauRenderPicture**)msg->data);
+ QueueReturnPicture(pic);
+ }
+ msg->Release();
+ }
+
+ while (m_mixer.m_dataPort.ReceiveInMessage(&msg))
+ {
+ if (msg->signal == CMixerDataProtocol::PICTURE)
+ {
+ CVdpauProcessedPicture pic = *reinterpret_cast<CVdpauProcessedPicture*>(msg->data);
+ m_bufferPool->processedPics.push(pic);
+ }
+ msg->Release();
+ }
+
+ // reset used render flag which was cleared on mixer flush
+ for (auto &awayPic : m_bufferPool->processedPicsAway)
+ {
+ if (awayPic.isYuv)
+ {
+ m_config.videoSurfaces->MarkRender(awayPic.videoSurface);
+ }
+ }
+
+ // clear processed pics
+ while(!m_bufferPool->processedPics.empty())
+ {
+ CVdpauProcessedPicture procPic = m_bufferPool->processedPics.front();
+ if (!procPic.isYuv)
+ {
+ m_mixer.m_dataPort.SendOutMessage(CMixerDataProtocol::BUFFER, &procPic.outputSurface, sizeof(procPic.outputSurface));
+ }
+ else
+ {
+ m_config.videoSurfaces->ClearRender(procPic.videoSurface);
+ }
+ m_bufferPool->processedPics.pop();
+ }
+}
+
+bool COutput::HasWork()
+{
+ if (!m_bufferPool->processedPics.empty() && m_bufferPool->HasFree())
+ return true;
+ return false;
+}
+
+CVdpauRenderPicture* COutput::ProcessMixerPicture()
+{
+ CVdpauRenderPicture *retPic = NULL;
+
+ if (!m_bufferPool->processedPics.empty() && m_bufferPool->HasFree())
+ {
+ retPic = m_bufferPool->GetVdpau();
+ CVdpauProcessedPicture procPic = m_bufferPool->processedPics.front();
+ procPic.id = m_bufferPool->procPicId++;
+ m_bufferPool->processedPics.pop();
+ m_bufferPool->processedPicsAway.push_back(procPic);
+ retPic->procPic = procPic;
+ retPic->device = reinterpret_cast<void*>(m_config.context->GetDevice());
+ retPic->procFunc = reinterpret_cast<void*>(m_config.context->GetProcs().vdp_get_proc_address);
+ retPic->ident = m_config.timeOpened + m_config.resetCounter;
+
+ retPic->DVDPic.SetParams(procPic.DVDPic);
+ if (!procPic.isYuv)
+ {
+ m_config.useInteropYuv = false;
+ m_bufferPool->numOutputSurfaces = NUM_RENDER_PICS;
+ EnsureBufferPool();
+ retPic->width = m_config.outWidth;
+ retPic->height = m_config.outHeight;
+ retPic->crop.x1 = 0;
+ retPic->crop.y1 = procPic.crop ? NUM_CROP_PIX : 0;
+ retPic->crop.x2 = m_config.outWidth;
+ retPic->crop.y2 = m_config.outHeight - retPic->crop.y1;
+ }
+ else
+ {
+ m_config.useInteropYuv = true;
+ retPic->width = m_config.surfaceWidth;
+ retPic->height = m_config.surfaceHeight;
+ retPic->crop.x1 = 0;
+ retPic->crop.y1 = 0;
+ retPic->crop.x2 = m_config.surfaceWidth - m_config.vidWidth;
+ retPic->crop.y2 = m_config.surfaceHeight - m_config.vidHeight;
+ }
+ }
+ return retPic;
+}
+
+void COutput::QueueReturnPicture(CVdpauRenderPicture *pic)
+{
+ m_bufferPool->QueueReturnPicture(pic);
+ ProcessSyncPicture();
+}
+
+void COutput::ProcessSyncPicture()
+{
+ CVdpauRenderPicture *pic;
+
+ pic = m_bufferPool->ProcessSyncPicture();
+
+ while (pic != nullptr)
+ {
+ ProcessReturnPicture(pic);
+ pic = m_bufferPool->ProcessSyncPicture();
+ }
+}
+
+void COutput::ProcessReturnPicture(CVdpauRenderPicture *pic)
+{
+ for (auto it=m_bufferPool->processedPicsAway.begin(); it!=m_bufferPool->processedPicsAway.end(); ++it)
+ {
+ if (it->id == pic->procPic.id)
+ {
+ if (pic->procPic.isYuv)
+ {
+ VdpVideoSurface surf = pic->procPic.videoSurface;
+ if (surf != VDP_INVALID_HANDLE)
+ m_config.videoSurfaces->ClearRender(surf);
+ }
+ else
+ {
+ VdpOutputSurface outSurf = pic->procPic.outputSurface;
+ if (outSurf != VDP_INVALID_HANDLE)
+ m_mixer.m_dataPort.SendOutMessage(CMixerDataProtocol::BUFFER, &outSurf, sizeof(outSurf));
+ }
+ m_bufferPool->processedPicsAway.erase(it);
+ break;
+ }
+ }
+}
+
+bool COutput::EnsureBufferPool()
+{
+ VdpStatus vdp_st;
+
+ // Creation of outputSurfaces
+ VdpOutputSurface outputSurface;
+ for (int i = m_bufferPool->outputSurfaces.size(); i < m_bufferPool->numOutputSurfaces; i++)
+ {
+ vdp_st = m_config.context->GetProcs().vdp_output_surface_create(m_config.context->GetDevice(),
+ VDP_RGBA_FORMAT_B8G8R8A8,
+ m_config.outWidth,
+ m_config.outHeight,
+ &outputSurface);
+ if (CheckStatus(vdp_st, __LINE__))
+ return false;
+ m_bufferPool->outputSurfaces.push_back(outputSurface);
+
+ m_mixer.m_dataPort.SendOutMessage(CMixerDataProtocol::BUFFER,
+ &outputSurface,
+ sizeof(VdpOutputSurface));
+ CLog::Log(LOGINFO, "VDPAU::COutput::InitBufferPool - Output Surface created");
+ }
+ return true;
+}
+
+void COutput::ReleaseBufferPool()
+{
+ VdpStatus vdp_st;
+
+ // release all output surfaces
+ m_bufferPool->InvalidateUsed();
+ for (unsigned int i = 0; i < m_bufferPool->outputSurfaces.size(); ++i)
+ {
+ if (m_bufferPool->outputSurfaces[i] == VDP_INVALID_HANDLE)
+ continue;
+ vdp_st = m_config.context->GetProcs().vdp_output_surface_destroy(m_bufferPool->outputSurfaces[i]);
+ CheckStatus(vdp_st, __LINE__);
+ }
+ m_bufferPool->outputSurfaces.clear();
+
+ ProcessSyncPicture();
+}
+
+void COutput::PreCleanup()
+{
+
+ VdpStatus vdp_st;
+
+ m_mixer.Dispose();
+ ProcessSyncPicture();
+
+ for (unsigned int i = 0; i < m_bufferPool->outputSurfaces.size(); ++i)
+ {
+ if (m_bufferPool->outputSurfaces[i] == VDP_INVALID_HANDLE)
+ continue;
+
+ // check if output surface is in use
+ bool used = false;
+ for (auto &picAway : m_bufferPool->processedPicsAway)
+ {
+ if (picAway.outputSurface == m_bufferPool->outputSurfaces[i])
+ {
+ used = true;
+ break;
+ }
+ }
+ if (used)
+ continue;
+
+ vdp_st = m_config.context->GetProcs().vdp_output_surface_destroy(m_bufferPool->outputSurfaces[i]);
+ CheckStatus(vdp_st, __LINE__);
+
+ m_bufferPool->outputSurfaces[i] = VDP_INVALID_HANDLE;
+ CLog::Log(LOGDEBUG, LOGVIDEO, "VDPAU::PreCleanup - released output surface");
+ }
+
+}
+
+void COutput::InitMixer()
+{
+ for (unsigned int i = 0; i < m_bufferPool->outputSurfaces.size(); ++i)
+ {
+ m_mixer.m_dataPort.SendOutMessage(CMixerDataProtocol::BUFFER,
+ &m_bufferPool->outputSurfaces[i],
+ sizeof(VdpOutputSurface));
+ }
+}
+
+bool COutput::CheckStatus(VdpStatus vdp_st, int line)
+{
+ if (vdp_st != VDP_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, " (VDPAU) Error: {}({}) at {}:{}",
+ m_config.context->GetProcs().vdp_get_error_string(vdp_st), vdp_st, __FILE__, line);
+ m_vdpError = true;
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.h
new file mode 100644
index 0000000..d92416e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VDPAU.h
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/**
+ * design goals:
+ * - improve performance
+ * max out hw resources: e.g. make 1080p60 play on ION2
+ * allow advanced de-interlacing on ION
+ *
+ * - add vdpau/opengl interop
+ *
+ * - remove tight dependency to render thread
+ * prior design needed to hijack render thread in order to do
+ * gl interop functions. In particular this was a problem for
+ * init and clear down. Introduction of GL_NV_vdpau_interop
+ * increased the need to be independent from render thread
+ *
+ * - move to an actor based design in order to reduce the number
+ * of locks needed.
+ */
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoSettings.h"
+#include "guilib/DispResource.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/SharedSection.h"
+#include "threads/Thread.h"
+#include "utils/ActorProtocol.h"
+#include "utils/Geometry.h"
+
+#include <deque>
+#include <list>
+#include <map>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavcodec/vdpau.h>
+}
+
+class CProcessInfo;
+
+namespace VDPAU
+{
+
+/**
+ * VDPAU interface to driver
+ */
+
+struct VDPAU_procs
+{
+ VdpGetProcAddress*vdp_get_proc_address;
+ VdpDeviceDestroy* vdp_device_destroy;
+
+ VdpVideoSurfaceCreate* vdp_video_surface_create;
+ VdpVideoSurfaceDestroy* vdp_video_surface_destroy;
+ VdpVideoSurfacePutBitsYCbCr* vdp_video_surface_put_bits_y_cb_cr;
+ VdpVideoSurfaceGetBitsYCbCr* vdp_video_surface_get_bits_y_cb_cr;
+
+ VdpOutputSurfacePutBitsYCbCr* vdp_output_surface_put_bits_y_cb_cr;
+ VdpOutputSurfacePutBitsNative* vdp_output_surface_put_bits_native;
+ VdpOutputSurfaceCreate* vdp_output_surface_create;
+ VdpOutputSurfaceDestroy* vdp_output_surface_destroy;
+ VdpOutputSurfaceGetBitsNative* vdp_output_surface_get_bits_native;
+ VdpOutputSurfaceRenderOutputSurface* vdp_output_surface_render_output_surface;
+ VdpOutputSurfacePutBitsIndexed* vdp_output_surface_put_bits_indexed;
+
+ VdpVideoMixerCreate* vdp_video_mixer_create;
+ VdpVideoMixerSetFeatureEnables* vdp_video_mixer_set_feature_enables;
+ VdpVideoMixerQueryParameterSupport* vdp_video_mixer_query_parameter_support;
+ VdpVideoMixerQueryFeatureSupport* vdp_video_mixer_query_feature_support;
+ VdpVideoMixerDestroy* vdp_video_mixer_destroy;
+ VdpVideoMixerRender* vdp_video_mixer_render;
+ VdpVideoMixerSetAttributeValues* vdp_video_mixer_set_attribute_values;
+
+ VdpGenerateCSCMatrix* vdp_generate_csc_matrix;
+
+ VdpGetErrorString* vdp_get_error_string;
+
+ VdpDecoderCreate* vdp_decoder_create;
+ VdpDecoderDestroy* vdp_decoder_destroy;
+ VdpDecoderRender* vdp_decoder_render;
+ VdpDecoderQueryCapabilities* vdp_decoder_query_caps;
+
+ VdpPreemptionCallbackRegister* vdp_preemption_callback_register;
+};
+
+//-----------------------------------------------------------------------------
+// VDPAU data structs
+//-----------------------------------------------------------------------------
+
+class CDecoder;
+
+/**
+ * Buffer statistics used to control number of frames in queue
+ */
+
+class CVdpauBufferStats
+{
+public:
+ uint16_t decodedPics;
+ uint16_t processedPics;
+ uint16_t renderPics;
+ uint64_t latency; // time decoder has waited for a frame, ideally there is no latency
+ int codecFlags;
+ bool canSkipDeint;
+ bool draining;
+
+ void IncDecoded()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics++;
+ }
+ void DecDecoded()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics--;
+ }
+ void IncProcessed()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ processedPics++;
+ }
+ void DecProcessed()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ processedPics--;
+ }
+ void IncRender()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ renderPics++;
+ }
+ void DecRender()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ renderPics--;
+ }
+ void Reset()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decodedPics = 0;
+ processedPics = 0;
+ renderPics = 0;
+ latency = 0;
+ }
+ void Get(uint16_t& decoded, uint16_t& processed, uint16_t& render)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ decoded = decodedPics, processed = processedPics, render = renderPics;
+ }
+ void SetParams(uint64_t time, int flags)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ latency = time;
+ codecFlags = flags;
+ }
+ void GetParams(uint64_t& lat, int& flags)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ lat = latency;
+ flags = codecFlags;
+ }
+ void SetCanSkipDeint(bool canSkip)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ canSkipDeint = canSkip;
+ }
+ bool CanSkipDeint()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ if (canSkipDeint)
+ return true;
+ else
+ return false;
+ }
+ void SetDraining(bool drain)
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ draining = drain;
+ }
+ bool IsDraining()
+ {
+ std::unique_lock<CCriticalSection> l(m_sec);
+ if (draining)
+ return true;
+ else
+ return false;
+ }
+
+private:
+ CCriticalSection m_sec;
+};
+
+/**
+ * CVdpauConfig holds all configuration parameters needed by vdpau
+ * The structure is sent to the internal classes CMixer and COutput
+ * for init.
+ */
+
+class CVideoSurfaces;
+class CVDPAUContext;
+
+struct CVdpauConfig
+{
+ int surfaceWidth;
+ int surfaceHeight;
+ int vidWidth;
+ int vidHeight;
+ int outWidth;
+ int outHeight;
+ VdpDecoder vdpDecoder;
+ VdpChromaType vdpChromaType;
+ CVdpauBufferStats *stats;
+ CDecoder *vdpau;
+ int upscale;
+ CVideoSurfaces *videoSurfaces;
+ int numRenderBuffers;
+ uint32_t maxReferences;
+ bool useInteropYuv;
+ CVDPAUContext *context;
+ CProcessInfo *processInfo;
+ int resetCounter;
+ uint64_t timeOpened;
+};
+
+/**
+ * Holds a decoded frame
+ * Input to COutput for further processing
+ */
+struct CVdpauDecodedPicture
+{
+ CVdpauDecodedPicture() = default;
+ CVdpauDecodedPicture(const CVdpauDecodedPicture &rhs)
+ {
+ *this = rhs;
+ }
+ CVdpauDecodedPicture& operator=(const CVdpauDecodedPicture& rhs)
+ {
+ DVDPic.SetParams(rhs.DVDPic);
+ videoSurface = rhs.videoSurface;
+ isYuv = rhs.isYuv;
+ return *this;
+ };
+ VideoPicture DVDPic;
+ VdpVideoSurface videoSurface;
+ bool isYuv;
+};
+
+/**
+ * Frame after having been processed by vdpau mixer
+ */
+struct CVdpauProcessedPicture
+{
+ CVdpauProcessedPicture() = default;
+ CVdpauProcessedPicture(const CVdpauProcessedPicture& rhs)
+ {
+ *this = rhs;
+ }
+ CVdpauProcessedPicture& operator=(const CVdpauProcessedPicture& rhs)
+ {
+ DVDPic.SetParams(rhs.DVDPic);
+ videoSurface = rhs.videoSurface;
+ outputSurface = rhs.outputSurface;
+ crop = rhs.crop;
+ isYuv = rhs.isYuv;
+ id = rhs.id;
+ return *this;
+ };
+
+ VideoPicture DVDPic;
+ VdpVideoSurface videoSurface = VDP_INVALID_HANDLE;
+ VdpOutputSurface outputSurface = VDP_INVALID_HANDLE;
+ bool crop;
+ bool isYuv;
+ int id = 0;
+};
+
+class CVdpauRenderPicture : public CVideoBuffer
+{
+public:
+ explicit CVdpauRenderPicture(int id) : CVideoBuffer(id) { }
+ VideoPicture DVDPic;
+ CVdpauProcessedPicture procPic;
+ int width;
+ int height;
+ CRect crop;
+ void *device;
+ void *procFunc;
+ int64_t ident;
+};
+
+//-----------------------------------------------------------------------------
+// Mixer
+//-----------------------------------------------------------------------------
+
+class CMixerControlProtocol : public Actor::Protocol
+{
+public:
+ CMixerControlProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ INIT = 0,
+ FLUSH,
+ TIMEOUT,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERROR,
+ };
+};
+
+class CMixerDataProtocol : public Actor::Protocol
+{
+public:
+ CMixerDataProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ FRAME,
+ BUFFER,
+ };
+ enum InSignal
+ {
+ PICTURE,
+ };
+};
+
+/**
+ * Embeds the vdpau video mixer
+ * Embedded by COutput class, gets decoded frames from COutput, processes
+ * them in mixer ands sends processed frames back to COutput
+ */
+class CMixer : private CThread
+{
+public:
+ explicit CMixer(CEvent *inMsgEvent);
+ ~CMixer() override;
+ void Start();
+ void Dispose();
+ bool IsActive();
+ CMixerControlProtocol m_controlPort;
+ CMixerDataProtocol m_dataPort;
+protected:
+ void OnStartup() override;
+ void OnExit() override;
+ void Process() override;
+ void StateMachine(int signal, Actor::Protocol *port, Actor::Message *msg);
+ void Init();
+ void Uninit();
+ void Flush();
+ void CreateVdpauMixer();
+ void ProcessPicture();
+ void InitCycle();
+ void FiniCycle();
+ void CheckFeatures();
+ void SetPostProcFeatures(bool postProcEnabled);
+ void PostProcOff();
+ void InitCSCMatrix(int Width);
+ bool GenerateStudioCSCMatrix(VdpColorStandard colorStandard, VdpCSCMatrix &studioCSCMatrix);
+ void SetColor();
+ void SetNoiseReduction();
+ void SetSharpness();
+ void SetDeintSkipChroma();
+ void SetDeinterlacing();
+ void SetHWUpscaling();
+ void DisableHQScaling();
+ std::string GetDeintStrFromInterlaceMethod(EINTERLACEMETHOD method);
+ bool CheckStatus(VdpStatus vdp_st, int line);
+ CEvent m_outMsgEvent;
+ CEvent *m_inMsgEvent;
+ int m_state;
+ bool m_bStateMachineSelfTrigger;
+
+ // extended state variables for state machine
+ int m_extTimeout;
+ bool m_vdpError;
+ CVdpauConfig m_config;
+ VdpVideoMixer m_videoMixer;
+ VdpProcamp m_Procamp;
+ VdpCSCMatrix m_CSCMatrix;
+ bool m_PostProc;
+ float m_Brightness;
+ float m_Contrast;
+ float m_NoiseReduction;
+ float m_Sharpness;
+ int m_Deint;
+ int m_Upscale;
+ bool m_SeenInterlaceFlag;
+ unsigned int m_ColorMatrix : 4;
+ VdpVideoMixerPictureStructure m_mixerfield;
+ int m_mixerstep;
+ int m_mixersteps;
+ CVdpauProcessedPicture m_processPicture;
+ std::queue<VdpOutputSurface> m_outputSurfaces;
+ std::queue<CVdpauDecodedPicture> m_decodedPics;
+ std::deque<CVdpauDecodedPicture> m_mixerInput;
+};
+
+//-----------------------------------------------------------------------------
+// Output
+//-----------------------------------------------------------------------------
+
+class COutputControlProtocol : public Actor::Protocol
+{
+public:
+ COutputControlProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Actor::Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ INIT,
+ FLUSH,
+ PRECLEANUP,
+ TIMEOUT,
+ };
+ enum InSignal
+ {
+ ACC,
+ ERROR,
+ STATS,
+ };
+};
+
+class COutputDataProtocol : public Actor::Protocol
+{
+public:
+ COutputDataProtocol(std::string name, CEvent* inEvent, CEvent* outEvent)
+ : Actor::Protocol(std::move(name), inEvent, outEvent)
+ {
+ }
+ enum OutSignal
+ {
+ NEWFRAME = 0,
+ RETURNPIC,
+ };
+ enum InSignal
+ {
+ PICTURE,
+ };
+};
+
+/**
+ * COutput is embedded in CDecoder and embeds CMixer
+ * The class has its own OpenGl context which is shared with render thread
+ * COutput generated ready to render textures and passes them back to
+ * CDecoder
+ */
+class CVdpauBufferPool;
+
+class COutput : private CThread
+{
+public:
+ COutput(CDecoder &decoder, CEvent *inMsgEvent);
+ ~COutput() override;
+ void Start();
+ void Dispose();
+ COutputControlProtocol m_controlPort;
+ COutputDataProtocol m_dataPort;
+protected:
+ void OnStartup() override;
+ void OnExit() override;
+ void Process() override;
+ void StateMachine(int signal, Actor::Protocol *port, Actor::Message *msg);
+ bool HasWork();
+ CVdpauRenderPicture *ProcessMixerPicture();
+ void QueueReturnPicture(CVdpauRenderPicture *pic);
+ void ProcessReturnPicture(CVdpauRenderPicture *pic);
+ void ProcessSyncPicture();
+ bool Init();
+ bool Uninit();
+ void Flush();
+ bool EnsureBufferPool();
+ void ReleaseBufferPool();
+ void PreCleanup();
+ void InitMixer();
+ bool CheckStatus(VdpStatus vdp_st, int line);
+ CEvent m_outMsgEvent;
+ CEvent *m_inMsgEvent;
+ int m_state;
+ bool m_bStateMachineSelfTrigger;
+ CDecoder &m_vdpau;
+
+ // extended state variables for state machine
+ int m_extTimeout;
+ bool m_vdpError;
+ CVdpauConfig m_config;
+ std::shared_ptr<CVdpauBufferPool> m_bufferPool;
+ CMixer m_mixer;
+};
+
+//-----------------------------------------------------------------------------
+// VDPAU Video Surface states
+//-----------------------------------------------------------------------------
+
+class CVideoSurfaces
+{
+public:
+ void AddSurface(VdpVideoSurface surf);
+ void ClearReference(VdpVideoSurface surf);
+ bool MarkRender(VdpVideoSurface surf);
+ void ClearRender(VdpVideoSurface surf);
+ bool IsValid(VdpVideoSurface surf);
+ VdpVideoSurface GetFree(VdpVideoSurface surf);
+ VdpVideoSurface RemoveNext(bool skiprender = false);
+ void Reset();
+ int Size();
+ bool HasRefs();
+protected:
+ std::map<VdpVideoSurface, int> m_state;
+ std::list<VdpVideoSurface> m_freeSurfaces;
+ CCriticalSection m_section;
+};
+
+//-----------------------------------------------------------------------------
+// VDPAU decoder
+//-----------------------------------------------------------------------------
+
+class CVDPAUContext
+{
+public:
+ static bool EnsureContext(CVDPAUContext **ctx);
+ void Release();
+ VDPAU_procs& GetProcs();
+ VdpDevice GetDevice();
+ bool Supports(VdpVideoMixerFeature feature);
+ VdpVideoMixerFeature* GetFeatures();
+ int GetFeatureCount();
+private:
+ CVDPAUContext();
+ void Close();
+ bool LoadSymbols();
+ bool CreateContext();
+ void DestroyContext();
+ void QueryProcs();
+ void SpewHardwareAvailable();
+ static CVDPAUContext *m_context;
+ static CCriticalSection m_section;
+ static Display *m_display;
+ int m_refCount;
+ VdpVideoMixerFeature m_vdpFeatures[14];
+ int m_featureCount;
+ static void *m_dlHandle;
+ VdpDevice m_vdpDevice;
+ VDPAU_procs m_vdpProcs;
+ VdpStatus (*dl_vdp_device_create_x11)(Display* display, int screen, VdpDevice* device, VdpGetProcAddress **get_proc_address);
+};
+
+/**
+ * VDPAU main class
+ */
+class CDecoder
+ : public IHardwareDecoder
+ , public IDispResource
+{
+ friend class CVdpauBufferPool;
+
+public:
+
+ struct Desc
+ {
+ const char *name;
+ uint32_t id;
+ uint32_t aux; /* optional extra parameter... */
+ };
+
+ explicit CDecoder(CProcessInfo& processInfo);
+ ~CDecoder() override;
+
+ bool Open (AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat) override;
+ CDVDVideoCodec::VCReturn Decode (AVCodecContext* avctx, AVFrame* frame) override;
+ bool GetPicture(AVCodecContext* avctx, VideoPicture* picture) override;
+ void Reset() override;
+ virtual void Close();
+ long Release() override;
+ bool CanSkipDeint() override;
+ unsigned GetAllowedReferences() override { return 5; }
+
+ CDVDVideoCodec::VCReturn Check(AVCodecContext* avctx) override;
+ const std::string Name() override { return "vdpau"; }
+ void SetCodecControl(int flags) override;
+
+ bool Supports(VdpVideoMixerFeature feature);
+ static bool IsVDPAUFormat(AVPixelFormat fmt);
+
+ static void FFReleaseBuffer(void *opaque, uint8_t *data);
+ static int FFGetBuffer(AVCodecContext *avctx, AVFrame *pic, int flags);
+ static int Render(struct AVCodecContext *s, struct AVFrame *src,
+ const VdpPictureInfo *info, uint32_t buffers_used,
+ const VdpBitstreamBuffer *buffers);
+
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+
+ static IHardwareDecoder* Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt);
+ static void Register();
+
+protected:
+ void SetWidthHeight(int width, int height);
+ bool ConfigVDPAU(AVCodecContext *avctx, int ref_frames);
+ bool CheckStatus(VdpStatus vdp_st, int line);
+ void FiniVDPAUOutput();
+ void ReturnRenderPicture(CVdpauRenderPicture *renderPic);
+ long ReleasePicReference();
+
+ static void ReadFormatOf( AVCodecID codec
+ , VdpDecoderProfile &decoder_profile
+ , VdpChromaType &chroma_type);
+
+ // OnLostDevice triggers transition from all states to LOST
+ // internal errors trigger transition from OPEN to RESET
+ // OnResetDevice triggers transition from LOST to RESET
+ enum EDisplayState
+ { VDPAU_OPEN
+ , VDPAU_RESET
+ , VDPAU_LOST
+ , VDPAU_ERROR
+ } m_DisplayState;
+ CCriticalSection m_DecoderSection;
+ CEvent m_DisplayEvent;
+ int m_ErrorCount;
+
+ bool m_vdpauConfigured;
+ CVdpauConfig m_vdpauConfig;
+ CVideoSurfaces m_videoSurfaces;
+ AVVDPAUContext m_hwContext;
+ AVCodecContext* m_avctx = nullptr;
+
+ COutput m_vdpauOutput;
+ CVdpauBufferStats m_bufferStats;
+ CEvent m_inMsgEvent;
+ CVdpauRenderPicture *m_presentPicture = nullptr;
+
+ int m_codecControl;
+ CProcessInfo& m_processInfo;
+
+ static bool m_capGeneral;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.cpp
new file mode 100644
index 0000000..00ad20c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VTB.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "DVDVideoCodec.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+
+#include <mutex>
+
+extern "C" {
+#include <libavcodec/videotoolbox.h>
+}
+
+using namespace VTB;
+
+//------------------------------------------------------------------------------
+// Video Buffers
+//------------------------------------------------------------------------------
+
+CVideoBufferVTB::CVideoBufferVTB(IVideoBufferPool &pool, int id)
+: CVideoBuffer(id)
+{
+ m_pFrame = av_frame_alloc();
+}
+
+CVideoBufferVTB::~CVideoBufferVTB()
+{
+ av_frame_free(&m_pFrame);
+}
+
+void CVideoBufferVTB::SetRef(AVFrame *frame)
+{
+ av_frame_unref(m_pFrame);
+ av_frame_ref(m_pFrame, frame);
+ m_pbRef = (CVPixelBufferRef)m_pFrame->data[3];
+}
+
+void CVideoBufferVTB::Unref()
+{
+ av_frame_unref(m_pFrame);
+}
+
+CVPixelBufferRef CVideoBufferVTB::GetPB()
+{
+ return m_pbRef;
+}
+
+//------------------------------------------------------------------------------
+
+class VTB::CVideoBufferPoolVTB : public IVideoBufferPool
+{
+public:
+ ~CVideoBufferPoolVTB() override;
+ void Return(int id) override;
+ CVideoBuffer* Get() override;
+
+protected:
+ CCriticalSection m_critSection;
+ std::vector<CVideoBufferVTB*> m_all;
+ std::deque<int> m_used;
+ std::deque<int> m_free;
+};
+
+CVideoBufferPoolVTB::~CVideoBufferPoolVTB()
+{
+ for (auto buf : m_all)
+ {
+ delete buf;
+ }
+}
+
+CVideoBuffer* CVideoBufferPoolVTB::Get()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CVideoBufferVTB *buf = nullptr;
+ if (!m_free.empty())
+ {
+ int idx = m_free.front();
+ m_free.pop_front();
+ m_used.push_back(idx);
+ buf = m_all[idx];
+ }
+ else
+ {
+ int id = m_all.size();
+ buf = new CVideoBufferVTB(*this, id);
+ m_all.push_back(buf);
+ m_used.push_back(id);
+ }
+
+ buf->Acquire(GetPtr());
+ return buf;
+}
+
+void CVideoBufferPoolVTB::Return(int id)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_all[id]->Unref();
+ auto it = m_used.begin();
+ while (it != m_used.end())
+ {
+ if (*it == id)
+ {
+ m_used.erase(it);
+ break;
+ }
+ else
+ ++it;
+ }
+ m_free.push_back(id);
+}
+
+//------------------------------------------------------------------------------
+// main class
+//------------------------------------------------------------------------------
+
+IHardwareDecoder* CDecoder::Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt)
+{
+#if defined(TARGET_DARWIN_EMBEDDED)
+ // force disable HW acceleration for live streams
+ // to avoid absent image issue on interlaced videos
+ if (processInfo.IsRealtimeStream())
+ return nullptr;
+#endif
+
+ if (fmt == AV_PIX_FMT_VIDEOTOOLBOX && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEVTB))
+ return new VTB::CDecoder(processInfo);
+
+ return nullptr;
+}
+
+bool CDecoder::Register()
+{
+ CDVDFactoryCodec::RegisterHWAccel("vtb", CDecoder::Create);
+ return true;
+}
+
+CDecoder::CDecoder(CProcessInfo& processInfo) : m_processInfo(processInfo)
+{
+ m_avctx = nullptr;
+ m_videoBufferPool = std::make_shared<CVideoBufferPoolVTB>();
+}
+
+CDecoder::~CDecoder()
+{
+ if (m_renderBuffer)
+ m_renderBuffer->Release();
+ Close();
+}
+
+void CDecoder::Close()
+{
+
+}
+
+bool CDecoder::Open(AVCodecContext *avctx, AVCodecContext* mainctx, enum AVPixelFormat fmt)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEVTB))
+ return false;
+
+ AVBufferRef *deviceRef = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
+ AVBufferRef *framesRef = av_hwframe_ctx_alloc(deviceRef);
+ AVHWFramesContext *framesCtx = (AVHWFramesContext*)framesRef->data;
+ framesCtx->format = AV_PIX_FMT_VIDEOTOOLBOX;
+ framesCtx->sw_format = AV_PIX_FMT_NV12;
+ avctx->hw_frames_ctx = framesRef;
+ m_avctx = avctx;
+
+ m_processInfo.SetVideoDeintMethod("none");
+
+ std::list<EINTERLACEMETHOD> deintMethods;
+ deintMethods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE);
+ m_processInfo.UpdateDeinterlacingMethods(deintMethods);
+
+ return true;
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Decode(AVCodecContext* avctx, AVFrame* frame)
+{
+ CDVDVideoCodec::VCReturn status = Check(avctx);
+ if(status)
+ return status;
+
+ if(frame)
+ {
+ if (frame->interlaced_frame)
+ return CDVDVideoCodec::VC_FATAL;
+
+ if (m_renderBuffer)
+ m_renderBuffer->Release();
+ m_renderBuffer = dynamic_cast<CVideoBufferVTB*>(m_videoBufferPool->Get());
+ m_renderBuffer->SetRef(frame);
+ return CDVDVideoCodec::VC_PICTURE;
+ }
+ else
+ return CDVDVideoCodec::VC_BUFFER;
+}
+
+bool CDecoder::GetPicture(AVCodecContext* avctx, VideoPicture* picture)
+{
+ ((ICallbackHWAccel*)avctx->opaque)->GetPictureCommon(picture);
+
+ if (picture->videoBuffer)
+ picture->videoBuffer->Release();
+
+ picture->videoBuffer = m_renderBuffer;
+ picture->videoBuffer->Acquire();
+ return true;
+}
+
+CDVDVideoCodec::VCReturn CDecoder::Check(AVCodecContext* avctx)
+{
+ return CDVDVideoCodec::VC_NONE;
+}
+
+unsigned CDecoder::GetAllowedReferences()
+{
+ return 5;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.h
new file mode 100644
index 0000000..726d680
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VTB.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#ifdef HAS_GL
+#include <OpenGL/gl.h>
+#else
+#include <OpenGLES/ES2/gl.h>
+#endif
+
+#include "DVDVideoCodecFFmpeg.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+
+#include <CoreVideo/CVPixelBuffer.h>
+
+class CProcessInfo;
+
+namespace VTB
+{
+class CVideoBufferVTB;
+class CVideoBufferPoolVTB;
+
+class CVideoBufferVTB: public CVideoBuffer
+{
+public:
+ CVideoBufferVTB(IVideoBufferPool &pool, int id);
+ ~CVideoBufferVTB() override;
+ void SetRef(AVFrame *frame);
+ void Unref();
+ CVPixelBufferRef GetPB();
+
+ GLuint m_fence = 0;
+protected:
+ CVPixelBufferRef m_pbRef = nullptr;
+ AVFrame *m_pFrame;
+};
+
+class CDecoder: public IHardwareDecoder
+{
+public:
+ CDecoder(CProcessInfo& processInfo);
+ ~CDecoder() override;
+ static IHardwareDecoder* Create(CDVDStreamInfo &hint, CProcessInfo &processInfo, AVPixelFormat fmt);
+ static bool Register();
+ bool Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat) override;
+ CDVDVideoCodec::VCReturn Decode(AVCodecContext* avctx, AVFrame* frame) override;
+ bool GetPicture(AVCodecContext* avctx, VideoPicture* picture) override;
+ CDVDVideoCodec::VCReturn Check(AVCodecContext* avctx) override;
+ const std::string Name() override { return "vtb"; }
+ unsigned GetAllowedReferences() override;
+
+ void Close();
+
+protected:
+ unsigned m_renderbuffers_count;
+ AVCodecContext *m_avctx;
+ CProcessInfo& m_processInfo;
+ CVideoBufferVTB *m_renderBuffer = nullptr;
+ std::shared_ptr<CVideoBufferPoolVTB> m_videoBufferPool;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxSPU.cpp b/xbmc/cores/VideoPlayer/DVDDemuxSPU.cpp
new file mode 100644
index 0000000..082d858
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxSPU.cpp
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxSPU.h"
+
+#include "DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+#include <locale.h>
+#include <stdlib.h>
+
+#undef ALIGN
+#define ALIGN(value, alignment) (((value)+((alignment)-1))&~((alignment)-1))
+
+// #define SPU_DEBUG
+
+void DebugLog(const char *format, ...)
+{
+#ifdef SPU_DEBUG
+ static char temp_spubuffer[1024];
+ va_list va;
+
+ va_start(va, format);
+ _vsnprintf(temp_spubuffer, 1024, format, va);
+ va_end(va);
+
+ CLog::Log(LOGDEBUG,temp_spubuffer);
+#endif
+}
+
+CDVDDemuxSPU::CDVDDemuxSPU()
+{
+ memset(&m_spuData, 0, sizeof(m_spuData));
+ memset(m_clut, 0, sizeof(m_clut));
+ m_bHasClut = false;
+}
+
+CDVDDemuxSPU::~CDVDDemuxSPU()
+{
+ free(m_spuData.data);
+}
+
+void CDVDDemuxSPU::Reset()
+{
+ FlushCurrentPacket();
+
+ // We can't reset this during playback, cause we don't always
+ // get a new clut from libdvdnav leading to invalid colors
+ // so let's just never reset it. It will only be reset
+ // when VideoPlayer is destructed and constructed
+ // m_bHasClut = false;
+ // memset(m_clut, 0, sizeof(m_clut));
+}
+
+void CDVDDemuxSPU::FlushCurrentPacket()
+{
+ free(m_spuData.data);
+ memset(&m_spuData, 0, sizeof(m_spuData));
+}
+
+CDVDOverlaySpu* CDVDDemuxSPU::AddData(uint8_t* data, int iSize, double pts)
+{
+ SPUData* pSPUData = &m_spuData;
+
+ if (pSPUData->iNeededSize > 0 &&
+ (pSPUData->iSize != pSPUData->iNeededSize) &&
+ ((pSPUData->iSize + iSize) > pSPUData->iNeededSize))
+ {
+ DebugLog("corrupt spu data: packet does not fit");
+ m_spuData.iNeededSize = 0;
+ m_spuData.iSize = 0;
+ return NULL;
+ }
+
+ // check if we are about to start a new packet
+ if (pSPUData->iSize == pSPUData->iNeededSize)
+ {
+ // for now we don't delete the memory associated with m_spuData.data
+ pSPUData->iSize = 0;
+
+ // check spu data length, only needed / possible in the first spu packet
+ uint16_t length = data[0] << 8 | data[1];
+ if (length == 0)
+ {
+ DebugLog("corrupt spu data: zero packet");
+ m_spuData.iNeededSize = 0;
+ m_spuData.iSize = 0;
+ return NULL;
+ }
+ if (length > iSize) pSPUData->iNeededSize = length;
+ else pSPUData->iNeededSize = iSize;
+
+ // set presentation time stamp
+ pSPUData->pts = pts;
+ }
+
+ // allocate data if not already done ( done in blocks off 16384 bytes )
+ // or allocate some more if 16384 bytes is not enough
+ if ((pSPUData->iSize + iSize) > pSPUData->iAllocatedSize)
+ {
+ uint8_t* tmpptr = (uint8_t*)realloc(pSPUData->data, ALIGN(pSPUData->iSize + iSize, 0x4000));
+ if (!tmpptr)
+ {
+ free(pSPUData->data);
+ return NULL;
+ }
+ pSPUData->data = tmpptr;
+ }
+
+ if(!pSPUData->data)
+ return NULL; // crap realloc failed, this will have leaked some memory due to odd realloc
+
+ // add new data
+ memcpy(pSPUData->data + pSPUData->iSize, data, iSize);
+ pSPUData->iSize += iSize;
+
+ if (pSPUData->iNeededSize - pSPUData->iSize == 1) // to make it even
+ {
+ DebugLog("missing 1 byte to complete packet, adding 0xff");
+
+ pSPUData->data[pSPUData->iSize] = 0xff;
+ pSPUData->iSize++;
+ }
+
+ if (pSPUData->iSize == pSPUData->iNeededSize)
+ {
+ DebugLog("got complete spu packet\n length: %i bytes\n stream: %i\n", pSPUData->iSize);
+
+ return ParsePacket(pSPUData);
+ }
+
+ return NULL;
+}
+
+#define CMD_END 0xFF
+#define FSTA_DSP 0x00
+#define STA_DSP 0x01
+#define STP_DSP 0x02
+#define SET_COLOR 0x03
+#define SET_CONTR 0x04
+#define SET_DAREA 0x05
+#define SET_DSPXA 0x06
+#define CHG_COLCON 0x07
+
+CDVDOverlaySpu* CDVDDemuxSPU::ParsePacket(SPUData* pSPUData)
+{
+ unsigned int alpha[4];
+ uint8_t* pUnparsedData = NULL;
+
+ if (pSPUData->iNeededSize != pSPUData->iSize)
+ {
+ DebugLog("GetPacket, packet is incomplete, missing: %i bytes", (pSPUData->iNeededSize - pSPUData->iSize));
+ }
+
+ if (pSPUData->data[pSPUData->iSize - 1] != 0xff)
+ {
+ DebugLog("GetPacket, missing end of data 0xff");
+ }
+
+ CDVDOverlaySpu* pSPUInfo = new CDVDOverlaySpu();
+ uint8_t* p = pSPUData->data; // pointer to walk through all data
+
+ // get data length
+ uint16_t datalength = p[2] << 8 | p[3]; // datalength + 4 control bytes
+
+ pUnparsedData = pSPUData->data + 4;
+
+ // if it is set to 0 it means it's a menu overlay by default
+ // this is not what we want too, cause you get strange results on a parse error
+ pSPUInfo->iPTSStartTime = -1;
+
+ //skip data packet and goto control sequence
+ p += datalength;
+
+ bool bHasNewDCSQ = true;
+ while (bHasNewDCSQ)
+ {
+ DebugLog(" starting new SP_DCSQT");
+ // p is beginning of first SP_DCSQT now
+ uint16_t delay = p[0] << 8 | p[1];
+ uint16_t next_DCSQ = p[2] << 8 | p[3];
+
+ //offset within the Sub-Picture Unit to the next SP_DCSQ. If this is the last SP_DCSQ, it points to itself.
+ bHasNewDCSQ = ((pSPUData->data + next_DCSQ) != p);
+ // skip 4 bytes
+ p += 4;
+
+ while (*p != CMD_END && (unsigned int)(p - pSPUData->data) <= pSPUData->iSize)
+ {
+ switch (*p)
+ {
+ case FSTA_DSP:
+ p++;
+ DebugLog(" GetPacket, FSTA_DSP: Forced Start Display, no arguments");
+ pSPUInfo->iPTSStartTime = pSPUData->pts;
+ pSPUInfo->iPTSStopTime = 0x9000000000000LL;
+ pSPUInfo->bForced = true;
+ // delay is always 0, the VideoPlayer should decide when to display the packet (menu highlight)
+ break;
+ case STA_DSP:
+ {
+ p++;
+ pSPUInfo->iPTSStartTime = pSPUData->pts;
+ pSPUInfo->iPTSStartTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
+ DebugLog(" GetPacket, STA_DSP: Start Display, delay: %i", ((delay * 1024) / 90000));
+ }
+ break;
+ case STP_DSP:
+ {
+ p++;
+ pSPUInfo->iPTSStopTime = pSPUData->pts;
+ pSPUInfo->iPTSStopTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
+ DebugLog(" GetPacket, STP_DSP: Stop Display, delay: %i", ((delay * 1024) / 90000));
+ }
+ break;
+ case SET_COLOR:
+ {
+ p++;
+
+ if (m_bHasClut)
+ {
+ pSPUInfo->bHasColor = true;
+
+ unsigned int idx[4];
+ // 0, 1, 2, 3
+ idx[0] = (p[0] >> 4) & 0x0f;
+ idx[1] = (p[0]) & 0x0f;
+ idx[2] = (p[1] >> 4) & 0x0f;
+ idx[3] = (p[1]) & 0x0f;
+
+ for (int i = 0; i < 4 ; i++) // emphasis 1, emphasis 2, pattern, back ground
+ {
+ uint8_t* iColor = m_clut[idx[i]];
+
+ pSPUInfo->color[3 - i][0] = iColor[0]; // Y
+ pSPUInfo->color[3 - i][1] = iColor[1]; // Cr
+ pSPUInfo->color[3 - i][2] = iColor[2]; // Cb
+ }
+ }
+
+ DebugLog(" GetPacket, SET_COLOR:");
+ p += 2;
+ }
+ break;
+ case SET_CONTR: // alpha
+ {
+ p++;
+ // 3, 2, 1, 0
+ alpha[0] = (p[0] >> 4) & 0x0f;
+ alpha[1] = (p[0]) & 0x0f;
+ alpha[2] = (p[1] >> 4) & 0x0f;
+ alpha[3] = (p[1]) & 0x0f;
+
+ // Ignore blank alpha palette.
+ if (alpha[0] | alpha[1] | alpha[2] | alpha[3])
+ {
+ pSPUInfo->bHasAlpha = true;
+
+ // 0, 1, 2, 3
+ pSPUInfo->alpha[0] = alpha[3]; //0 // background, should be hidden
+ pSPUInfo->alpha[1] = alpha[2]; //1
+ pSPUInfo->alpha[2] = alpha[1]; //2 // wm button overlay
+ pSPUInfo->alpha[3] = alpha[0]; //3
+ }
+
+ DebugLog(" GetPacket, SET_CONTR:");
+ p += 2;
+ }
+ break;
+ case SET_DAREA:
+ {
+ p++;
+ pSPUInfo->x = (p[0] << 4) | (p[1] >> 4);
+ pSPUInfo->y = (p[3] << 4) | (p[4] >> 4);
+ pSPUInfo->width = (((p[1] & 0x0f) << 8) | p[2]) - pSPUInfo->x + 1;
+ pSPUInfo->height = (((p[4] & 0x0f) << 8) | p[5]) - pSPUInfo->y + 1;
+ DebugLog(" GetPacket, SET_DAREA: x,y:%i,%i width,height:%i,%i",
+ pSPUInfo->x, pSPUInfo->y, pSPUInfo->width, pSPUInfo->height);
+ p += 6;
+ }
+ break;
+ case SET_DSPXA:
+ {
+ p++;
+ uint16_t tfaddr = (p[0] << 8 | p[1]); // offset in packet
+ uint16_t bfaddr = (p[2] << 8 | p[3]); // offset in packet
+ pSPUInfo->pTFData = (tfaddr - 4); //pSPUInfo->pData + (tfaddr - 4); // pSPUData->data = packet startaddr - 4
+ pSPUInfo->pBFData = (bfaddr - 4); //pSPUInfo->pData + (bfaddr - 4); // pSPUData->data = packet startaddr - 4
+ p += 4;
+ DebugLog(" GetPacket, SET_DSPXA: tf: %i bf: %i ", tfaddr, bfaddr);
+ }
+ break;
+ case CHG_COLCON:
+ {
+ p++;
+ uint16_t paramlength = p[0] << 8 | p[1];
+ DebugLog("GetPacket, CHG_COLCON, skippin %i bytes", paramlength);
+ p += paramlength;
+ }
+ break;
+
+ default:
+ DebugLog("GetPacket, error parsing control sequence");
+ delete pSPUInfo;
+ return NULL;
+ break;
+ }
+ }
+ DebugLog(" end off SP_DCSQT");
+ if (*p == CMD_END) p++;
+ else
+ {
+ DebugLog("GetPacket, end off SP_DCSQT, but did not found 0xff (CMD_END)");
+ }
+ }
+
+ // parse the rle.
+ // this should be changed so it get's converted to a yuv overlay
+ return ParseRLE(pSPUInfo, pUnparsedData);
+}
+
+/*****************************************************************************
+ * AddNibble: read a nibble from a source packet and add it to our integer.
+ *****************************************************************************/
+inline unsigned int AddNibble(unsigned int i_code, const uint8_t* p_src, unsigned int* pi_index)
+{
+ if ( *pi_index & 0x1 )
+ {
+ return ( i_code << 4 | ( p_src[(*pi_index)++ >> 1] & 0xf ) );
+ }
+ else
+ {
+ return ( i_code << 4 | p_src[(*pi_index)++ >> 1] >> 4 );
+ }
+}
+
+/*****************************************************************************
+ * ParseRLE: parse the RLE part of the subtitle
+ *****************************************************************************
+ * This part parses the subtitle graphical data and stores it in a more
+ * convenient structure for later decoding. For more information on the
+ * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html
+ *****************************************************************************/
+CDVDOverlaySpu* CDVDDemuxSPU::ParseRLE(CDVDOverlaySpu* pSPU, uint8_t* pUnparsedData)
+{
+ uint8_t* p_src = pUnparsedData;
+
+ unsigned int i_code = 0;
+
+ unsigned int i_width = pSPU->width;
+ unsigned int i_height = pSPU->height;
+ unsigned int i_x, i_y;
+
+ // allocate a buffer for the result
+ uint16_t* p_dest = (uint16_t*)pSPU->result;
+
+ /* The subtitles are interlaced, we need two offsets */
+ unsigned int i_id = 0; /* Start on the even SPU layer */
+ unsigned int pi_table[2];
+
+ /* Colormap statistics */
+ int i_border = -1;
+ int stats[4]; stats[0] = stats[1] = stats[2] = stats[3] = 0;
+
+ pi_table[ 0 ] = pSPU->pTFData << 1;
+ pi_table[ 1 ] = pSPU->pBFData << 1;
+
+ for ( i_y = 0 ; i_y < i_height ; i_y++ )
+ {
+ unsigned int *pi_offset = pi_table + i_id;
+
+ for ( i_x = 0 ; i_x < i_width ; i_x += i_code >> 2 )
+ {
+ i_code = AddNibble( 0, p_src, pi_offset );
+
+ if ( i_code < 0x04 )
+ {
+ i_code = AddNibble( i_code, p_src, pi_offset );
+
+ if ( i_code < 0x10 )
+ {
+ i_code = AddNibble( i_code, p_src, pi_offset );
+
+ if ( i_code < 0x040 )
+ {
+ i_code = AddNibble( i_code, p_src, pi_offset );
+
+ if ( i_code < 0x0100 )
+ {
+ /* If the 14 first bits are set to 0, then it's a
+ * new line. We emulate it. */
+ if ( i_code < 0x0004 )
+ {
+ i_code |= ( i_width - i_x ) << 2;
+ }
+ else
+ {
+ /* We have a boo boo ! */
+ CLog::Log(LOGERROR, "ParseRLE: unknown RLE code {:#4x}", i_code);
+ pSPU->Release();
+ return NULL;
+ }
+ }
+ }
+ }
+ }
+
+ if ( ( (i_code >> 2) + i_x + i_y * i_width ) > i_height * i_width )
+ {
+ CLog::Log(LOGERROR, "ParseRLE: out of bounds, {} at ({},{}) is out of {}x{}", i_code >> 2,
+ i_x, i_y, i_width, i_height);
+ pSPU->Release();
+ return NULL;
+ }
+
+ // keep trace of all occurring pixels, even keeping the background in mind
+ stats[i_code & 0x3] += i_code >> 2;
+
+ // count the number of pixels for every occurring parts, without background
+ if (pSPU->alpha[i_code & 0x3] != 0x00)
+ {
+ // the last non background pixel is probably the border color
+ i_border = i_code & 0x3;
+ stats[i_border] += i_code >> 2;
+ }
+
+ /* Check we aren't overwriting our data range
+ This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
+ where we use around 96k rather than 64k + 20bytes */
+ if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
+ {
+ CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range. Need {} bytes",
+ (long)((uint8_t*)p_dest - pSPU->result));
+ pSPU->Release();
+ return NULL;
+ }
+ *p_dest++ = i_code;
+ }
+
+ /* Check that we didn't go too far */
+ if ( i_x > i_width )
+ {
+ CLog::Log(LOGERROR, "ParseRLE: i_x overflowed, {} > {}", i_x, i_width);
+ pSPU->Release();
+ return NULL;
+ }
+
+ /* Byte-align the stream */
+ if ( *pi_offset & 0x1 )
+ {
+ (*pi_offset)++;
+ }
+
+ /* Swap fields */
+ i_id = ~i_id & 0x1;
+ }
+
+ /* We shouldn't get any padding bytes */
+ if ( i_y < i_height )
+ {
+ DebugLog("ParseRLE: padding bytes found in RLE sequence" );
+ DebugLog("ParseRLE: send mail to <sam@zoy.org> if you want to help debugging this" );
+
+ /* Skip them just in case */
+ while ( i_y < i_height )
+ {
+ /* Check we aren't overwriting our data range
+ This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
+ where we use around 96k rather than 64k + 20bytes */
+ if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
+ {
+ CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range. Need {} bytes",
+ (long)((uint8_t*)p_dest - pSPU->result));
+ pSPU->Release();
+ return NULL;
+ }
+ *p_dest++ = i_width << 2;
+ i_y++;
+ }
+
+ pSPU->Release();
+ return NULL;
+ }
+
+ DebugLog("ParseRLE: valid subtitle, size: %ix%i, position: %i,%i",
+ pSPU->width, pSPU->height, pSPU->x, pSPU->y );
+
+ // forced spu's (menu overlays) retrieve their alpha/color information from InputStreamNavigator::GetCurrentButtonInfo
+ // also they may contain completely covering data which is supposed to be hidden normally
+ // since whole spu is drawn, if this is done for forced, that may be displayed
+ // so we must trust what is given
+ if( !pSPU->bForced )
+ {
+ // Handle color if no palette was found.
+ // we only set it if there is a valid i_border color
+ if (!pSPU->bHasColor)
+ {
+ CLog::Log(LOGINFO, "{} - no color palette found, using default", __FUNCTION__);
+ FindSubtitleColor(i_border, stats, pSPU);
+ }
+
+ // check alpha values, for non forced spu's we use a default value
+ if (pSPU->bHasAlpha)
+ {
+ // check alpha values
+ // the array stats represents the nr of pixels for each color channel
+ // thus if there are no pixels to display, we assume the alphas are incorrect.
+ if (!CanDisplayWithAlphas(pSPU->alpha, stats))
+ {
+ CLog::Log(LOGINFO, "{} - no matching color and alpha found, resetting alpha",
+ __FUNCTION__);
+
+ pSPU->alpha[0] = 0x00; // back ground
+ pSPU->alpha[1] = 0x0f;
+ pSPU->alpha[2] = 0x0f;
+ pSPU->alpha[3] = 0x0f;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "{} - ignoring blank alpha palette, using default", __FUNCTION__);
+
+ pSPU->alpha[0] = 0x00; // back ground
+ pSPU->alpha[1] = 0x0f;
+ pSPU->alpha[2] = 0x0f;
+ pSPU->alpha[3] = 0x0f;
+ }
+
+ }
+
+ return pSPU;
+}
+
+void CDVDDemuxSPU::FindSubtitleColor(int last_color, int stats[4], CDVDOverlaySpu* pSPU)
+{
+ const int COLOR_INNER = 0;
+ const int COLOR_SHADE = 1;
+ const int COLOR_BORDER = 2;
+
+ //uint8_t custom_subtitle_color[4][3] = { // blue, yellow and something else (xine)
+ // { 0x80, 0x90, 0x80 }, // inner color
+ // { 0x00, 0x90, 0x00 }, // shade color
+ // { 0x00, 0x90, 0xff } // border color
+ //};
+
+ uint8_t custom_subtitle_color[4][3] = { // inner color white, gray shading and a black border
+ { 0xff, 0x80, 0x80 }, // inner color, white
+ { 0x80, 0x80, 0x80 }, // shade color, gray
+ { 0x00, 0x80, 0x80 } // border color, black
+ };
+
+ //uint8_t custom_subtitle_color[4][3] = { // completely white and a black border
+ // { 0xff, 0x80, 0x80 }, // inner color, white
+ // { 0xff, 0x80, 0x80 }, // shade color, white
+ // { 0x00, 0x80, 0x80 } // border color, black
+ //};
+
+
+ int nrOfUsedColors = 0;
+ for (int alpha : pSPU->alpha)
+ {
+ if (alpha > 0) nrOfUsedColors++;
+ }
+
+ if (nrOfUsedColors == 0)
+ {
+ // nothing todo
+ DebugLog("FindSubtitleColor: all 4 alpha channels are 0, nothing todo");
+ }
+ else if (nrOfUsedColors == 1)
+ {
+ // only one color is used, probably the inner color
+ for (int i = 0; i < 4; i++) // find the position that is used
+ {
+ if (pSPU->alpha[i] > 0)
+ {
+ pSPU->color[i][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
+ pSPU->color[i][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
+ pSPU->color[i][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
+ return;
+ }
+ }
+
+ }
+ else
+ {
+ // old code
+
+ if (last_color >= 0 && last_color < 4)
+ {
+ int i, i_inner = -1, i_shade = -1;
+ // Set the border color, the last color is probably the border color
+ pSPU->color[last_color][0] = custom_subtitle_color[COLOR_BORDER][0];
+ pSPU->color[last_color][1] = custom_subtitle_color[COLOR_BORDER][1];
+ pSPU->color[last_color][2] = custom_subtitle_color[COLOR_BORDER][2];
+ stats[last_color] = 0;
+
+ // find the inner colors
+ for ( i = 0 ; i < 4 && i_inner == -1 ; i++ )
+ {
+ if ( stats[i] )
+ {
+ i_inner = i;
+ }
+ }
+
+ // try to find the shade color
+ for ( ; i < 4 && i_shade == -1 ; i++)
+ {
+ if ( stats[i] )
+ {
+ if ( stats[i] > stats[i_inner] )
+ {
+ i_shade = i_inner;
+ i_inner = i;
+ }
+ else
+ {
+ i_shade = i;
+ }
+ }
+ }
+
+ /* Set the inner color */
+ if ( i_inner != -1 )
+ {
+ // white color
+ pSPU->color[i_inner][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
+ pSPU->color[i_inner][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
+ pSPU->color[i_inner][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
+ }
+
+ /* Set the anti-aliasing color */
+ if ( i_shade != -1 )
+ {
+ // gray
+ pSPU->color[i_shade][0] = custom_subtitle_color[COLOR_SHADE][0];
+ pSPU->color[i_shade][1] = custom_subtitle_color[COLOR_SHADE][1];
+ pSPU->color[i_shade][2] = custom_subtitle_color[COLOR_SHADE][2];
+ }
+
+ DebugLog("ParseRLE: using custom palette (border %i, inner %i, shade %i)", last_color, i_inner, i_shade);
+ }
+ }
+}
+
+bool CDVDDemuxSPU::CanDisplayWithAlphas(const int a[4], const int stats[4])
+{
+ return(
+ a[0] * stats[0] > 0 ||
+ a[1] * stats[1] > 0 ||
+ a[2] * stats[2] > 0 ||
+ a[3] * stats[3] > 0);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxSPU.h b/xbmc/cores/VideoPlayer/DVDDemuxSPU.h
new file mode 100644
index 0000000..8f1bcc1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxSPU.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct AVFrame;
+class CDVDOverlaySpu;
+
+typedef struct SPUData
+{
+ uint8_t* data;
+ unsigned int iSize; // current data size
+ unsigned int iNeededSize; // wanted packet size
+ unsigned int iAllocatedSize;
+ double pts;
+}
+SPUData;
+
+// upto 32 streams can exist
+#define DVD_MAX_SPUSTREAMS 32
+
+class CDVDDemuxSPU final
+{
+public:
+ CDVDDemuxSPU();
+ ~CDVDDemuxSPU();
+
+ CDVDOverlaySpu* AddData(uint8_t* data, int iSize, double pts); // returns a packet from ParsePacket if possible
+
+ CDVDOverlaySpu* ParseRLE(CDVDOverlaySpu* pSPU, uint8_t* pUnparsedData);
+ static void FindSubtitleColor(int last_color, int stats[4], CDVDOverlaySpu* pSPU);
+ static bool CanDisplayWithAlphas(const int a[4], const int stats[4]);
+
+ void Reset();
+ void FlushCurrentPacket(); // flushes current unparsed data
+
+ // m_clut set by libdvdnav once in a time
+ // color lookup table is representing 16 different yuv colors
+ // [][0] = Y, [][1] = Cr, [][2] = Cb
+ uint8_t m_clut[16][3];
+ bool m_bHasClut;
+
+protected:
+ CDVDOverlaySpu* ParsePacket(SPUData* pSPUData);
+
+ SPUData m_spuData;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDDemuxers/CMakeLists.txt
new file mode 100644
index 0000000..48710f8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(SOURCES DemuxMultiSource.cpp
+ DVDDemux.cpp
+ DVDDemuxBXA.cpp
+ DVDDemuxCC.cpp
+ DVDDemuxCDDA.cpp
+ DVDDemuxClient.cpp
+ DVDDemuxFFmpeg.cpp
+ DVDDemuxUtils.cpp
+ DVDDemuxVobsub.cpp
+ DVDFactoryDemuxer.cpp)
+
+set(HEADERS DemuxMultiSource.h
+ DVDDemux.h
+ DVDDemuxBXA.h
+ DVDDemuxCC.h
+ DVDDemuxCDDA.h
+ DVDDemuxClient.h
+ DVDDemuxFFmpeg.h
+ DVDDemuxUtils.h
+ DVDDemuxVobsub.h
+ DVDFactoryDemuxer.h)
+
+core_add_library(dvddemuxers)
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp
new file mode 100644
index 0000000..c2ac33f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemux.h"
+
+std::string CDemuxStreamAudio::GetStreamType()
+{
+ std::string strInfo;
+ switch (codec)
+ {
+ case AV_CODEC_ID_AC3:
+ strInfo = "AC3 ";
+ break;
+ case AV_CODEC_ID_EAC3:
+ strInfo = "DD+ ";
+ break;
+ case AV_CODEC_ID_DTS:
+ {
+ switch (profile)
+ {
+ case FF_PROFILE_DTS_HD_MA:
+ strInfo = "DTS-HD MA ";
+ break;
+ case FF_PROFILE_DTS_HD_HRA:
+ strInfo = "DTS-HD HRA ";
+ break;
+ default:
+ strInfo = "DTS ";
+ break;
+ }
+ break;
+ }
+ case AV_CODEC_ID_MP2:
+ strInfo = "MP2 ";
+ break;
+ case AV_CODEC_ID_MP3:
+ strInfo = "MP3 ";
+ break;
+ case AV_CODEC_ID_TRUEHD:
+ strInfo = "TrueHD ";
+ break;
+ case AV_CODEC_ID_AAC:
+ strInfo = "AAC ";
+ break;
+ case AV_CODEC_ID_ALAC:
+ strInfo = "ALAC ";
+ break;
+ case AV_CODEC_ID_FLAC:
+ strInfo = "FLAC ";
+ break;
+ case AV_CODEC_ID_OPUS:
+ strInfo = "Opus ";
+ break;
+ case AV_CODEC_ID_VORBIS:
+ strInfo = "Vorbis ";
+ break;
+ case AV_CODEC_ID_PCM_BLURAY:
+ case AV_CODEC_ID_PCM_DVD:
+ strInfo = "PCM ";
+ break;
+ default:
+ strInfo = "";
+ break;
+ }
+
+ strInfo += m_channelLayoutName;
+
+ return strInfo;
+}
+
+int CDVDDemux::GetNrOfStreams(StreamType streamType)
+{
+ int iCounter = 0;
+
+ for (auto pStream : GetStreams())
+ {
+ if (pStream && pStream->type == streamType) iCounter++;
+ }
+
+ return iCounter;
+}
+
+int CDVDDemux::GetNrOfSubtitleStreams()
+{
+ return GetNrOfStreams(STREAM_SUBTITLE);
+}
+
+std::string CDemuxStream::GetStreamName()
+{
+ return name;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.h
new file mode 100644
index 0000000..f4f7161
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.h
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Interface/StreamInfo.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+struct DemuxPacket;
+struct DemuxCryptoSession;
+
+class CDVDInputStream;
+
+namespace ADDON
+{
+class IAddonProvider;
+}
+
+#ifndef __GNUC__
+#pragma warning(push)
+#pragma warning(disable : 4244)
+#endif
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavutil/mastering_display_metadata.h>
+}
+
+#ifndef __GNUC__
+#pragma warning(pop)
+#endif
+
+enum StreamType
+{
+ STREAM_NONE = 0, // if unknown
+ STREAM_AUDIO, // audio stream
+ STREAM_VIDEO, // video stream
+ STREAM_DATA, // data stream
+ STREAM_SUBTITLE, // subtitle stream
+ STREAM_TELETEXT, // Teletext data stream
+ STREAM_RADIO_RDS, // Radio RDS data stream
+ STREAM_AUDIO_ID3 // Audio ID3 data stream
+};
+
+enum StreamSource
+{
+ STREAM_SOURCE_NONE = 0x000,
+ STREAM_SOURCE_DEMUX = 0x100,
+ STREAM_SOURCE_NAV = 0x200,
+ STREAM_SOURCE_DEMUX_SUB = 0x300,
+ STREAM_SOURCE_TEXT = 0x400,
+ STREAM_SOURCE_VIDEOMUX = 0x500
+};
+
+#define STREAM_SOURCE_MASK(a) ((a)&0xf00)
+
+/*
+ * CDemuxStream
+ * Base class for all demuxer streams
+ */
+class CDemuxStream
+{
+public:
+ CDemuxStream()
+ {
+ uniqueId = 0;
+ dvdNavId = 0;
+ demuxerId = -1;
+ codec_fourcc = 0;
+ profile = FF_PROFILE_UNKNOWN;
+ level = FF_LEVEL_UNKNOWN;
+ type = STREAM_NONE;
+ source = STREAM_SOURCE_NONE;
+ iDuration = 0;
+ pPrivate = NULL;
+ ExtraSize = 0;
+ disabled = false;
+ changes = 0;
+ flags = StreamFlags::FLAG_NONE;
+ }
+
+ virtual ~CDemuxStream() = default;
+ CDemuxStream(CDemuxStream&&) = default;
+
+ virtual std::string GetStreamName();
+
+ int uniqueId; // unique stream id
+ int dvdNavId;
+ int64_t demuxerId; // id of the associated demuxer
+ AVCodecID codec = AV_CODEC_ID_NONE;
+ unsigned int codec_fourcc; // if available
+ int profile; // encoder profile of the stream reported by the decoder. used to qualify hw decoders.
+ int level; // encoder level of the stream reported by the decoder. used to qualify hw decoders.
+ StreamType type;
+ int source;
+
+ int iDuration; // in mseconds
+ void* pPrivate; // private pointer for the demuxer
+ std::unique_ptr<uint8_t[]> ExtraData; // extra data for codec to use
+ unsigned int ExtraSize; // size of extra data
+
+ StreamFlags flags;
+ std::string language; // RFC 5646 language code (empty string if undefined)
+ bool disabled; // set when stream is disabled. (when no decoder exists)
+
+ std::string name;
+ std::string codecName;
+
+ int changes; // increment on change which player may need to know about
+
+ std::shared_ptr<DemuxCryptoSession> cryptoSession;
+ std::shared_ptr<ADDON::IAddonProvider> externalInterfaces;
+};
+
+class CDemuxStreamVideo : public CDemuxStream
+{
+public:
+ CDemuxStreamVideo() { type = STREAM_VIDEO; }
+
+ ~CDemuxStreamVideo() override = default;
+ int iFpsScale = 0; // scale of 1000 and a rate of 29970 will result in 29.97 fps
+ int iFpsRate = 0;
+ int iHeight = 0; // height of the stream reported by the demuxer
+ int iWidth = 0; // width of the stream reported by the demuxer
+ double fAspect = 0; // display aspect of stream
+ bool bVFR = false; // variable framerate
+ bool bPTSInvalid = false; // pts cannot be trusted (avi's).
+ bool bForcedAspect = false; // aspect is forced from container
+ int iOrientation = 0; // orientation of the video in degrees counter clockwise
+ int iBitsPerPixel = 0;
+ int iBitRate = 0;
+ int bitDepth = 0;
+
+ AVColorSpace colorSpace = AVCOL_SPC_UNSPECIFIED;
+ AVColorRange colorRange = AVCOL_RANGE_UNSPECIFIED;
+ AVColorPrimaries colorPrimaries = AVCOL_PRI_UNSPECIFIED;
+ AVColorTransferCharacteristic colorTransferCharacteristic = AVCOL_TRC_UNSPECIFIED;
+
+ std::shared_ptr<AVMasteringDisplayMetadata> masteringMetaData;
+ std::shared_ptr<AVContentLightMetadata> contentLightMetaData;
+
+ std::string stereo_mode; // expected stereo mode
+ StreamHdrType hdr_type = StreamHdrType::HDR_TYPE_NONE; // type of HDR for this stream (hdr10, etc)
+};
+
+class CDemuxStreamAudio : public CDemuxStream
+{
+public:
+ CDemuxStreamAudio()
+ : CDemuxStream()
+ {
+ iChannels = 0;
+ iSampleRate = 0;
+ iBlockAlign = 0;
+ iBitRate = 0;
+ iBitsPerSample = 0;
+ iChannelLayout = 0;
+ type = STREAM_AUDIO;
+ }
+
+ ~CDemuxStreamAudio() override = default;
+
+ std::string GetStreamType();
+
+ int iChannels;
+ int iSampleRate;
+ int iBlockAlign;
+ int iBitRate;
+ int iBitsPerSample;
+ uint64_t iChannelLayout;
+ std::string m_channelLayoutName;
+};
+
+class CDemuxStreamSubtitle : public CDemuxStream
+{
+public:
+ CDemuxStreamSubtitle()
+ : CDemuxStream()
+ {
+ type = STREAM_SUBTITLE;
+ }
+};
+
+class CDemuxStreamTeletext : public CDemuxStream
+{
+public:
+ CDemuxStreamTeletext()
+ : CDemuxStream()
+ {
+ type = STREAM_TELETEXT;
+ }
+};
+
+class CDemuxStreamAudioID3 : public CDemuxStream
+{
+public:
+ CDemuxStreamAudioID3() : CDemuxStream() { type = STREAM_AUDIO_ID3; }
+};
+
+class CDemuxStreamRadioRDS : public CDemuxStream
+{
+public:
+ CDemuxStreamRadioRDS()
+ : CDemuxStream()
+ {
+ type = STREAM_RADIO_RDS;
+ }
+};
+
+class CDVDDemux
+{
+public:
+ CDVDDemux()
+ : m_demuxerId(NewGuid())
+ {
+ }
+ virtual ~CDVDDemux() = default;
+
+
+ /*
+ * Reset the entire demuxer (same result as closing and opening it)
+ */
+ virtual bool Reset() = 0;
+
+ /*
+ * Aborts any internal reading that might be stalling main thread
+ * NOTICE - this can be called from another thread
+ */
+ virtual void Abort() {}
+
+ /*
+ * Flush the demuxer, if any data is kept in buffers, this should be freed now
+ */
+ virtual void Flush() = 0;
+
+ /*
+ * Read a packet, returns NULL on error
+ *
+ */
+ virtual DemuxPacket* Read() = 0;
+
+ /*
+ * Seek, time in msec calculated from stream start
+ */
+ virtual bool SeekTime(double time, bool backwards = false, double* startpts = NULL) = 0;
+
+ /*
+ * Seek to a specified chapter.
+ * startpts can be updated to the point where display should start
+ */
+ virtual bool SeekChapter(int chapter, double* startpts = NULL) { return false; }
+
+ /*
+ * Get the number of chapters available
+ */
+ virtual int GetChapterCount() { return 0; }
+
+ /*
+ * Get current chapter
+ */
+ virtual int GetChapter() { return 0; }
+
+ /*
+ * Get the name of a chapter
+ * \param strChapterName[out] Name of chapter
+ * \param chapterIdx -1 for current chapter, else a chapter index
+ */
+ virtual void GetChapterName(std::string& strChapterName, int chapterIdx = -1) {}
+
+ /*
+ * Get the position of a chapter
+ * \param chapterIdx -1 for current chapter, else a chapter index
+ */
+ virtual int64_t GetChapterPos(int chapterIdx = -1) { return 0; }
+
+ /*
+ * Set the playspeed, if demuxer can handle different
+ * speeds of playback
+ */
+ virtual void SetSpeed(int iSpeed) {}
+
+ /*
+ * Let demuxer know if we want to fill demux queue
+ */
+ virtual void FillBuffer(bool mode) {}
+
+ /*
+ * returns the total time in msec
+ */
+ virtual int GetStreamLength() { return 0; }
+
+ /*
+ * returns the stream or NULL on error
+ */
+ virtual CDemuxStream* GetStream(int64_t demuxerId, int iStreamId) const
+ {
+ return GetStream(iStreamId);
+ };
+
+ virtual std::vector<CDemuxStream*> GetStreams() const = 0;
+
+ /*
+ * return nr of streams, 0 if none
+ */
+ virtual int GetNrOfStreams() const = 0;
+
+ /*
+ * get a list of available programs
+ */
+ virtual int GetPrograms(std::vector<ProgramInfo>& programs) { return 0; }
+
+ /*
+ * select programs
+ */
+ virtual void SetProgram(int progId) {}
+
+ /*
+ * returns opened filename
+ */
+ virtual std::string GetFileName() { return ""; }
+
+ /*
+ * return nr of subtitle streams, 0 if none
+ */
+ int GetNrOfSubtitleStreams();
+
+ /*
+ * return a user-presentable codec name of the given stream
+ */
+ virtual std::string GetStreamCodecName(int64_t demuxerId, int iStreamId)
+ {
+ return GetStreamCodecName(iStreamId);
+ };
+
+ /*
+ * enable / disable demux stream
+ */
+ virtual void EnableStream(int64_t demuxerId, int id, bool enable) { EnableStream(id, enable); }
+
+ /*
+ * implicitly enable and open a demux stream for playback
+ */
+ virtual void OpenStream(int64_t demuxerId, int id) { OpenStream(id); }
+
+ /*
+ * sets desired width / height for video stream
+ * adaptive demuxers like DASH can use this to choose best fitting video stream
+ */
+ virtual void SetVideoResolution(unsigned int width, unsigned int height) {}
+
+ /*
+ * return the id of the demuxer
+ */
+ int64_t GetDemuxerId() { return m_demuxerId; }
+
+protected:
+ virtual void EnableStream(int id, bool enable) {}
+ virtual void OpenStream(int id) {}
+ virtual CDemuxStream* GetStream(int iStreamId) const = 0;
+ virtual std::string GetStreamCodecName(int iStreamId) { return ""; }
+
+ int GetNrOfStreams(StreamType streamType);
+
+ int64_t m_demuxerId;
+
+private:
+ int64_t NewGuid()
+ {
+ static int64_t guid = 0;
+ return guid++;
+ }
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.cpp
new file mode 100644
index 0000000..d37f07b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxBXA.h"
+
+#include "DVDDemuxUtils.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/StringUtils.h"
+
+// AirTunes audio Demuxer.
+
+class CDemuxStreamAudioBXA
+ : public CDemuxStreamAudio
+{
+ std::string m_codec;
+public:
+ CDemuxStreamAudioBXA(CDVDDemuxBXA *parent, const std::string& codec)
+ : m_codec(codec)
+
+ {}
+};
+
+CDVDDemuxBXA::CDVDDemuxBXA() : CDVDDemux()
+{
+ m_stream = NULL;
+ m_bytes = 0;
+ memset(&m_header, 0x0, sizeof(Demux_BXA_FmtHeader));
+}
+
+CDVDDemuxBXA::~CDVDDemuxBXA()
+{
+ Dispose();
+}
+
+bool CDVDDemuxBXA::Open(const std::shared_ptr<CDVDInputStream>& pInput)
+{
+ Abort();
+
+ Dispose();
+
+ if(!pInput || !pInput->IsStreamType(DVDSTREAM_TYPE_FILE))
+ return false;
+
+ if(pInput->Read((uint8_t *)&m_header, sizeof(Demux_BXA_FmtHeader)) < 1)
+ return false;
+
+ // file valid?
+ if (strncmp(m_header.fourcc, "BXA ", 4) != 0 || m_header.type != BXA_PACKET_TYPE_FMT_DEMUX)
+ {
+ pInput->Seek(0, SEEK_SET);
+ return false;
+ }
+
+ m_pInput = pInput;
+
+ m_stream = new CDemuxStreamAudioBXA(this, "BXA");
+
+ if(!m_stream)
+ return false;
+
+ m_stream->iSampleRate = m_header.sampleRate;
+ m_stream->iBitsPerSample = m_header.bitsPerSample;
+ m_stream->iBitRate = m_header.sampleRate * m_header.channels * m_header.bitsPerSample;
+ m_stream->iChannels = m_header.channels;
+ m_stream->type = STREAM_AUDIO;
+ m_stream->codec = AV_CODEC_ID_PCM_S16LE;
+
+ return true;
+}
+
+void CDVDDemuxBXA::Dispose()
+{
+ delete m_stream;
+ m_stream = NULL;
+
+ m_pInput = NULL;
+ m_bytes = 0;
+
+ memset(&m_header, 0x0, sizeof(Demux_BXA_FmtHeader));
+}
+
+bool CDVDDemuxBXA::Reset()
+{
+ std::shared_ptr<CDVDInputStream> pInputStream = m_pInput;
+ Dispose();
+ return Open(pInputStream);
+}
+
+void CDVDDemuxBXA::Abort()
+{
+ if(m_pInput)
+ return m_pInput->Abort();
+}
+
+void CDVDDemuxBXA::Flush()
+{
+}
+
+#define BXA_READ_SIZE 4096
+DemuxPacket* CDVDDemuxBXA::Read()
+{
+ if(!m_pInput)
+ return NULL;
+
+ DemuxPacket* pPacket = CDVDDemuxUtils::AllocateDemuxPacket(BXA_READ_SIZE);
+
+ if (!pPacket)
+ {
+ if (m_pInput)
+ m_pInput->Close();
+ return NULL;
+ }
+
+ pPacket->iSize = m_pInput->Read(pPacket->pData, BXA_READ_SIZE);
+ pPacket->iStreamId = 0;
+
+ if(pPacket->iSize < 1)
+ {
+ delete pPacket;
+ pPacket = NULL;
+ }
+ else
+ {
+ int n = (m_header.channels * m_header.bitsPerSample * m_header.sampleRate)>>3;
+ if (n > 0)
+ {
+ m_bytes += pPacket->iSize;
+ pPacket->dts = (double)m_bytes * DVD_TIME_BASE / n;
+ pPacket->pts = pPacket->dts;
+ }
+ else
+ {
+ pPacket->dts = DVD_NOPTS_VALUE;
+ pPacket->pts = DVD_NOPTS_VALUE;
+ }
+ }
+
+ return pPacket;
+}
+
+CDemuxStream* CDVDDemuxBXA::GetStream(int iStreamId) const
+{
+ if(iStreamId != 0)
+ return NULL;
+
+ return m_stream;
+}
+
+std::vector<CDemuxStream*> CDVDDemuxBXA::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ if (m_stream != nullptr)
+ {
+ streams.push_back(m_stream);
+ }
+
+ return streams;
+}
+
+int CDVDDemuxBXA::GetNrOfStreams() const
+{
+ return (m_stream == NULL ? 0 : 1);
+}
+
+std::string CDVDDemuxBXA::GetFileName()
+{
+ if(m_pInput)
+ return m_pInput->GetFileName();
+ else
+ return "";
+}
+
+std::string CDVDDemuxBXA::GetStreamCodecName(int iStreamId)
+{
+ if (m_stream && iStreamId == 0)
+ return "BXA";
+ else
+ return "";
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.h
new file mode 100644
index 0000000..94fa8c9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+
+#ifdef TARGET_WINDOWS
+#define __attribute__(dummy_val)
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+typedef struct
+{
+ char fourcc[4];
+ uint32_t type;
+ uint32_t channels;
+ uint32_t sampleRate;
+ uint32_t bitsPerSample;
+ uint64_t durationMs;
+} __attribute__((__packed__)) Demux_BXA_FmtHeader;
+
+#ifdef TARGET_WINDOWS
+#pragma pack(pop)
+#endif
+
+#include <vector>
+
+#define BXA_PACKET_TYPE_FMT_DEMUX 1
+
+class CDemuxStreamAudioBXA;
+
+class CDVDDemuxBXA : public CDVDDemux
+{
+public:
+
+ CDVDDemuxBXA();
+ ~CDVDDemuxBXA() override;
+
+ bool Open(const std::shared_ptr<CDVDInputStream>& pInput);
+ void Dispose();
+ bool Reset() override;
+ void Abort() override;
+ void Flush() override;
+ DemuxPacket* Read() override;
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override { return false; }
+ int GetStreamLength() override { return (int)m_header.durationMs; }
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+ std::string GetFileName() override;
+ std::string GetStreamCodecName(int iStreamId) override;
+
+protected:
+ friend class CDemuxStreamAudioBXA;
+ std::shared_ptr<CDVDInputStream> m_pInput;
+ int64_t m_bytes;
+
+ CDemuxStreamAudioBXA *m_stream;
+
+ Demux_BXA_FmtHeader m_header;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp
new file mode 100644
index 0000000..b91e8e6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxCC.h"
+
+#include "DVDDemuxUtils.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/contrib/cc_decoder708.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+
+#include <algorithm>
+
+class CBitstream
+{
+public:
+ CBitstream(uint8_t *data, int bits)
+ {
+ m_data = data;
+ m_offset = 0;
+ m_len = bits;
+ m_error = false;
+ }
+ unsigned int readBits(int num)
+ {
+ int r = 0;
+ while (num > 0)
+ {
+ if (m_offset >= m_len)
+ {
+ m_error = true;
+ return 0;
+ }
+ num--;
+ if (m_data[m_offset / 8] & (1 << (7 - (m_offset & 7))))
+ r |= 1 << num;
+ m_offset++;
+ }
+ return r;
+ }
+ unsigned int readGolombUE(int maxbits = 32)
+ {
+ int lzb = -1;
+ int bits = 0;
+ for (int b = 0; !b; lzb++, bits++)
+ {
+ if (bits > maxbits)
+ return 0;
+ b = readBits(1);
+ }
+ return (1 << lzb) - 1 + readBits(lzb);
+ }
+
+private:
+ uint8_t *m_data;
+ int m_offset;
+ int m_len;
+ bool m_error;
+};
+
+class CCaptionBlock
+{
+ CCaptionBlock(const CCaptionBlock&) = delete;
+ CCaptionBlock& operator=(const CCaptionBlock&) = delete;
+public:
+ explicit CCaptionBlock(int size)
+ {
+ m_data = (uint8_t*)malloc(size);
+ m_size = size;
+ m_pts = 0.0; //silence coverity uninitialized warning, is set elsewhere
+ }
+ virtual ~CCaptionBlock()
+ {
+ free(m_data);
+ }
+ double m_pts;
+ uint8_t *m_data;
+ int m_size;
+};
+
+bool reorder_sort (CCaptionBlock *lhs, CCaptionBlock *rhs)
+{
+ return (lhs->m_pts > rhs->m_pts);
+}
+
+CDVDDemuxCC::CDVDDemuxCC(AVCodecID codec) : m_codec(codec)
+{
+ m_hasData = false;
+ m_curPts = 0.0;
+ m_ccDecoder = NULL;
+}
+
+CDVDDemuxCC::~CDVDDemuxCC()
+{
+ Dispose();
+}
+
+CDemuxStream* CDVDDemuxCC::GetStream(int iStreamId) const
+{
+ for (int i=0; i<GetNrOfStreams(); i++)
+ {
+ if (m_streams[i].uniqueId == iStreamId)
+ return const_cast<CDemuxStreamSubtitle*>(&m_streams[i]);
+ }
+ return nullptr;
+}
+
+std::vector<CDemuxStream*> CDVDDemuxCC::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ int num = GetNrOfStreams();
+ streams.reserve(num);
+ for (int i = 0; i < num; ++i)
+ {
+ streams.push_back(const_cast<CDemuxStreamSubtitle*>(&m_streams[i]));
+ }
+
+ return streams;
+}
+
+int CDVDDemuxCC::GetNrOfStreams() const
+{
+ return m_streams.size();
+}
+
+DemuxPacket* CDVDDemuxCC::Read(DemuxPacket *pSrcPacket)
+{
+ DemuxPacket *pPacket = NULL;
+ uint32_t startcode = 0xffffffff;
+ int picType = 0;
+ int p = 0;
+ int len;
+
+ if (!pSrcPacket)
+ {
+ pPacket = Decode();
+ return pPacket;
+ }
+ if (pSrcPacket->pts == DVD_NOPTS_VALUE)
+ {
+ return pPacket;
+ }
+
+ while (!m_ccTempBuffer.empty())
+ {
+ m_ccReorderBuffer.push_back(m_ccTempBuffer.back());
+ m_ccTempBuffer.pop_back();
+ }
+
+ while ((len = pSrcPacket->iSize - p) > 3)
+ {
+ if ((startcode & 0xffffff00) == 0x00000100)
+ {
+ if (m_codec == AV_CODEC_ID_MPEG2VIDEO)
+ {
+ int scode = startcode & 0xFF;
+ if (scode == 0x00)
+ {
+ if (len > 4)
+ {
+ uint8_t *buf = pSrcPacket->pData + p;
+ picType = (buf[1] & 0x38) >> 3;
+ }
+ }
+ else if (scode == 0xb2) // user data
+ {
+ uint8_t *buf = pSrcPacket->pData + p;
+ if (len >= 6 &&
+ buf[0] == 'G' && buf[1] == 'A' && buf[2] == '9' && buf[3] == '4' &&
+ buf[4] == 3 && (buf[5] & 0x40))
+ {
+ int cc_count = buf[5] & 0x1f;
+ if (cc_count > 0 && len >= 7 + cc_count * 3)
+ {
+ CCaptionBlock *cc = new CCaptionBlock(cc_count * 3);
+ memcpy(cc->m_data, buf + 7, cc_count * 3);
+ cc->m_pts = pSrcPacket->pts;
+ if (picType == 1 || picType == 2)
+ m_ccTempBuffer.push_back(cc);
+ else
+ m_ccReorderBuffer.push_back(cc);
+ }
+ }
+ else if (len >= 6 &&
+ buf[0] == 'C' && buf[1] == 'C' && buf[2] == 1)
+ {
+ int oddidx = (buf[4] & 0x80) ? 0 : 1;
+ int cc_count = (buf[4] & 0x3e) >> 1;
+ int extrafield = buf[4] & 0x01;
+ if (extrafield)
+ cc_count++;
+
+ if (cc_count > 0 && len >= 5 + cc_count * 3 * 2)
+ {
+ CCaptionBlock *cc = new CCaptionBlock(cc_count * 3);
+ uint8_t *src = buf + 5;
+ uint8_t *dst = cc->m_data;
+
+ for (int i = 0; i < cc_count; i++)
+ {
+ for (int j = 0; j < 2; j++)
+ {
+ if (i == cc_count - 1 && extrafield && j == 1)
+ break;
+
+ if ((oddidx == j) && (src[0] == 0xFF))
+ {
+ dst[0] = 0x04;
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst += 3;
+ }
+ src += 3;
+ }
+ }
+ cc->m_pts = pSrcPacket->pts;
+ m_ccReorderBuffer.push_back(cc);
+ picType = 1;
+ }
+ }
+ }
+ }
+ else if (m_codec == AV_CODEC_ID_H264)
+ {
+ int scode = startcode & 0x9F;
+ // slice data comes after SEI
+ if (scode >= 1 && scode <= 5)
+ {
+ uint8_t *buf = pSrcPacket->pData + p;
+ CBitstream bs(buf, len * 8);
+ bs.readGolombUE();
+ int sliceType = bs.readGolombUE();
+ if (sliceType == 2 || sliceType == 7) // I slice
+ picType = 1;
+ else if (sliceType == 0 || sliceType == 5) // P slice
+ picType = 2;
+ if (picType == 0)
+ {
+ while (!m_ccTempBuffer.empty())
+ {
+ m_ccReorderBuffer.push_back(m_ccTempBuffer.back());
+ m_ccTempBuffer.pop_back();
+ }
+ }
+ }
+ if (scode == 0x06) // SEI
+ {
+ uint8_t *buf = pSrcPacket->pData + p;
+ if (len >= 12 &&
+ buf[3] == 0 && buf[4] == 49 &&
+ buf[5] == 'G' && buf[6] == 'A' && buf[7] == '9' && buf[8] == '4' && buf[9] == 3)
+ {
+ uint8_t *userdata = buf + 10;
+ int cc_count = userdata[0] & 0x1f;
+ if (len >= cc_count * 3 + 10)
+ {
+ CCaptionBlock *cc = new CCaptionBlock(cc_count * 3);
+ memcpy(cc->m_data, userdata + 2, cc_count * 3);
+ cc->m_pts = pSrcPacket->pts;
+ m_ccTempBuffer.push_back(cc);
+ }
+ }
+ }
+ }
+ }
+ startcode = startcode << 8 | pSrcPacket->pData[p++];
+ }
+
+ if ((picType == 1 || picType == 2) && !m_ccReorderBuffer.empty())
+ {
+ if (!m_ccDecoder)
+ {
+ if (!OpenDecoder())
+ return NULL;
+ }
+ std::sort(m_ccReorderBuffer.begin(), m_ccReorderBuffer.end(), reorder_sort);
+ pPacket = Decode();
+ }
+ return pPacket;
+}
+
+void CDVDDemuxCC::Handler(int service, void *userdata)
+{
+ CDVDDemuxCC *ctx = static_cast<CDVDDemuxCC*>(userdata);
+
+ unsigned int idx;
+
+ // switch back from 608 fallback if we got 708
+ if (ctx->m_ccDecoder->m_seen608 && ctx->m_ccDecoder->m_seen708)
+ {
+ for (idx = 0; idx < ctx->m_streamdata.size(); idx++)
+ {
+ if (ctx->m_streamdata[idx].service == 0)
+ break;
+ }
+ if (idx < ctx->m_streamdata.size())
+ {
+ ctx->m_streamdata.erase(ctx->m_streamdata.begin() + idx);
+ ctx->m_ccDecoder->m_seen608 = false;
+ }
+ if (service == 0)
+ return;
+ }
+
+ for (idx = 0; idx < ctx->m_streamdata.size(); idx++)
+ {
+ if (ctx->m_streamdata[idx].service == service)
+ break;
+ }
+ if (idx >= ctx->m_streamdata.size())
+ {
+ CDemuxStreamSubtitle stream;
+ stream.source = STREAM_SOURCE_VIDEOMUX;
+ stream.language = "cc";
+ stream.flags = FLAG_HEARING_IMPAIRED;
+ stream.codec = AV_CODEC_ID_TEXT;
+ stream.uniqueId = service;
+ ctx->m_streams.push_back(std::move(stream));
+
+ streamdata data;
+ data.streamIdx = idx;
+ data.service = service;
+ ctx->m_streamdata.push_back(data);
+
+ if (service == 0)
+ ctx->m_ccDecoder->m_seen608 = true;
+ else
+ ctx->m_ccDecoder->m_seen708 = true;
+ }
+
+ ctx->m_streamdata[idx].pts = ctx->m_curPts;
+ ctx->m_streamdata[idx].hasData = true;
+ ctx->m_hasData = true;
+}
+
+bool CDVDDemuxCC::OpenDecoder()
+{
+ m_ccDecoder = new CDecoderCC708();
+ m_ccDecoder->Init(Handler, this);
+ return true;
+}
+
+void CDVDDemuxCC::Dispose()
+{
+ m_streams.clear();
+ m_streamdata.clear();
+ delete m_ccDecoder;
+ m_ccDecoder = NULL;
+
+ while (!m_ccReorderBuffer.empty())
+ {
+ delete m_ccReorderBuffer.back();
+ m_ccReorderBuffer.pop_back();
+ }
+ while (!m_ccTempBuffer.empty())
+ {
+ delete m_ccTempBuffer.back();
+ m_ccTempBuffer.pop_back();
+ }
+}
+
+DemuxPacket* CDVDDemuxCC::Decode()
+{
+ DemuxPacket *pPacket = NULL;
+
+ while(!m_hasData && !m_ccReorderBuffer.empty())
+ {
+ CCaptionBlock *cc = m_ccReorderBuffer.back();
+ m_ccReorderBuffer.pop_back();
+ m_curPts = cc->m_pts;
+ m_ccDecoder->Decode(cc->m_data, cc->m_size);
+ delete cc;
+ }
+
+ if (m_hasData)
+ {
+ for (unsigned int i=0; i<m_streamdata.size(); i++)
+ {
+ if (m_streamdata[i].hasData)
+ {
+ int service = m_streamdata[i].service;
+
+ char *data;
+ int len;
+ if (service == 0)
+ {
+ data = m_ccDecoder->m_cc608decoder->text;
+ len = m_ccDecoder->m_cc608decoder->textlen;
+ }
+ else
+ {
+ data = m_ccDecoder->m_cc708decoders[service].text;
+ len = m_ccDecoder->m_cc708decoders[service].textlen;
+ }
+
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(len);
+ pPacket->iSize = len;
+ memcpy(pPacket->pData, data, pPacket->iSize);
+
+ pPacket->iStreamId = service;
+ pPacket->pts = m_streamdata[i].pts;
+ pPacket->duration = 0;
+ m_streamdata[i].hasData = false;
+ break;
+ }
+ m_hasData = false;
+ }
+ }
+ return pPacket;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.h
new file mode 100644
index 0000000..3662709
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+
+#include <vector>
+
+class CCaptionBlock;
+class CDecoderCC708;
+
+class CDVDDemuxCC : public CDVDDemux
+{
+public:
+ explicit CDVDDemuxCC(AVCodecID codec);
+ ~CDVDDemuxCC() override;
+
+ bool Reset() override { return true; }
+ void Flush() override {};
+ DemuxPacket* Read() override { return NULL; }
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override
+ {
+ return true;
+ }
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+
+ DemuxPacket* Read(DemuxPacket *packet);
+ static void Handler(int service, void *userdata);
+
+protected:
+ bool OpenDecoder();
+ void Dispose();
+ DemuxPacket* Decode();
+
+ struct streamdata
+ {
+ int streamIdx;
+ int service;
+ bool hasData ;
+ double pts;
+ };
+ std::vector<streamdata> m_streamdata;
+ std::vector<CDemuxStreamSubtitle> m_streams;
+ bool m_hasData;
+ double m_curPts;
+ std::vector<CCaptionBlock*> m_ccReorderBuffer;
+ std::vector<CCaptionBlock*> m_ccTempBuffer;
+ CDecoderCC708 *m_ccDecoder;
+ AVCodecID m_codec;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.cpp
new file mode 100644
index 0000000..8d86082
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxCDDA.h"
+
+#include "DVDDemuxUtils.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+
+// CDDA audio demuxer based on AirTunes audio Demuxer.
+
+class CDemuxStreamAudioCDDA
+ : public CDemuxStreamAudio
+{
+};
+
+CDVDDemuxCDDA::CDVDDemuxCDDA() : CDVDDemux()
+{
+ m_stream = NULL;
+ m_bytes = 0;
+}
+
+CDVDDemuxCDDA::~CDVDDemuxCDDA()
+{
+ Dispose();
+}
+
+bool CDVDDemuxCDDA::Open(const std::shared_ptr<CDVDInputStream>& pInput)
+{
+ Abort();
+
+ Dispose();
+
+ if(!pInput || !pInput->IsStreamType(DVDSTREAM_TYPE_FILE))
+ return false;
+
+ m_pInput = pInput;
+
+ m_stream = new CDemuxStreamAudioCDDA();
+
+ if(!m_stream)
+ return false;
+
+ m_stream->iSampleRate = 44100;
+ m_stream->iBitsPerSample = 16;
+ m_stream->iBitRate = 44100 * 2 * 16;
+ m_stream->iChannels = 2;
+ m_stream->type = STREAM_AUDIO;
+ m_stream->codec = AV_CODEC_ID_PCM_S16LE;
+
+ return true;
+}
+
+void CDVDDemuxCDDA::Dispose()
+{
+ delete m_stream;
+ m_stream = NULL;
+
+ m_pInput = NULL;
+ m_bytes = 0;
+}
+
+bool CDVDDemuxCDDA::Reset()
+{
+ std::shared_ptr<CDVDInputStream> pInputStream = m_pInput;
+ Dispose();
+ return Open(pInputStream);
+}
+
+void CDVDDemuxCDDA::Abort()
+{
+ if(m_pInput)
+ return m_pInput->Abort();
+}
+
+void CDVDDemuxCDDA::Flush()
+{
+}
+
+#define CDDA_READ_SIZE 4096
+DemuxPacket* CDVDDemuxCDDA::Read()
+{
+ if(!m_pInput)
+ return NULL;
+
+ DemuxPacket* pPacket = CDVDDemuxUtils::AllocateDemuxPacket(CDDA_READ_SIZE);
+
+ if (!pPacket)
+ {
+ if (m_pInput)
+ m_pInput->Close();
+ return NULL;
+ }
+
+ pPacket->iSize = m_pInput->Read(pPacket->pData, CDDA_READ_SIZE);
+ pPacket->iStreamId = 0;
+
+ if(pPacket->iSize < 1)
+ {
+ delete pPacket;
+ pPacket = NULL;
+ }
+ else
+ {
+ int n = m_stream->iBitRate>>3;
+ if (n > 0)
+ {
+ m_bytes += pPacket->iSize;
+ pPacket->dts = (double)m_bytes * DVD_TIME_BASE / n;
+ pPacket->pts = pPacket->dts;
+ }
+ else
+ {
+ pPacket->dts = DVD_NOPTS_VALUE;
+ pPacket->pts = DVD_NOPTS_VALUE;
+ }
+ }
+
+ return pPacket;
+}
+
+bool CDVDDemuxCDDA::SeekTime(double time, bool backwards, double* startpts)
+{
+ int bytes_per_second = m_stream->iBitRate>>3;
+ // clamp seeks to bytes per full sample
+ int clamp_bytes = (m_stream->iBitsPerSample>>3) * m_stream->iChannels;
+
+ // time is in milliseconds
+ int64_t seekPos = m_pInput->Seek((((int64_t)time * bytes_per_second / 1000) / clamp_bytes ) * clamp_bytes, SEEK_SET) > 0;
+ if (seekPos > 0)
+ m_bytes = seekPos;
+
+ if (startpts)
+ *startpts = (double)m_bytes * DVD_TIME_BASE / bytes_per_second;
+
+ return seekPos > 0;
+};
+
+int CDVDDemuxCDDA::GetStreamLength()
+{
+ int64_t num_track_bytes = m_pInput->GetLength();
+ int bytes_per_second = (m_stream->iBitRate>>3);
+ int64_t track_mseconds = num_track_bytes*1000 / bytes_per_second;
+ return (int)track_mseconds;
+}
+
+CDemuxStream* CDVDDemuxCDDA::GetStream(int iStreamId) const
+{
+ if(iStreamId != 0)
+ return NULL;
+
+ return m_stream;
+}
+
+std::vector<CDemuxStream*> CDVDDemuxCDDA::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ if (m_stream != nullptr)
+ {
+ streams.push_back(m_stream);
+ }
+
+ return streams;
+}
+
+int CDVDDemuxCDDA::GetNrOfStreams() const
+{
+ return (m_stream == NULL ? 0 : 1);
+}
+
+std::string CDVDDemuxCDDA::GetFileName()
+{
+ if(m_pInput)
+ return m_pInput->GetFileName();
+ else
+ return "";
+}
+
+std::string CDVDDemuxCDDA::GetStreamCodecName(int iStreamId)
+{
+ if (m_stream && iStreamId == 0)
+ return "pcm";
+ else
+ return "";
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.h
new file mode 100644
index 0000000..01b61f1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCDDA.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+
+#include <vector>
+
+#ifdef TARGET_WINDOWS
+#define __attribute__(dummy_val)
+#endif
+
+class CDemuxStreamAudioCDDA;
+
+class CDVDDemuxCDDA : public CDVDDemux
+{
+public:
+
+ CDVDDemuxCDDA();
+ ~CDVDDemuxCDDA() override;
+
+ bool Open(const std::shared_ptr<CDVDInputStream>& pInput);
+ void Dispose();
+ bool Reset() override;
+ void Abort() override;
+ void Flush() override;
+ DemuxPacket* Read() override;
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override;
+ int GetStreamLength() override;
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+ std::string GetFileName() override;
+ std::string GetStreamCodecName(int iStreamId) override;
+
+protected:
+ friend class CDemuxStreamAudioCDDA;
+ std::shared_ptr<CDVDInputStream> m_pInput;
+ int64_t m_bytes;
+
+ CDemuxStreamAudioCDDA *m_stream;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp
new file mode 100644
index 0000000..2bdc3ea
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxClient.h"
+
+#include "DVDDemuxUtils.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+#include <utility>
+
+#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - AV_INPUT_BUFFER_PADDING_SIZE)
+
+class CDemuxStreamClientInternal
+{
+public:
+ ~CDemuxStreamClientInternal()
+ {
+ DisposeParser();
+ }
+
+ void DisposeParser()
+ {
+ if (m_parser)
+ {
+ av_parser_close(m_parser);
+ m_parser = nullptr;
+ }
+ if (m_context)
+ {
+ avcodec_free_context(&m_context);
+ m_context = nullptr;
+ }
+ }
+
+ AVCodecParserContext *m_parser = nullptr;
+ AVCodecContext *m_context = nullptr;
+ bool m_parser_split = false;
+};
+
+template <class T>
+class CDemuxStreamClientInternalTpl : public CDemuxStreamClientInternal, public T
+{
+};
+
+CDVDDemuxClient::CDVDDemuxClient() : CDVDDemux()
+{
+ m_streams.clear();
+}
+
+CDVDDemuxClient::~CDVDDemuxClient()
+{
+ Dispose();
+}
+
+bool CDVDDemuxClient::Open(std::shared_ptr<CDVDInputStream> pInput)
+{
+ Abort();
+
+ m_pInput = std::move(pInput);
+ m_IDemux = std::dynamic_pointer_cast<CDVDInputStream::IDemux>(m_pInput);
+ if (!m_IDemux)
+ return false;
+
+ if (!m_IDemux->OpenDemux())
+ return false;
+
+ RequestStreams();
+
+ m_displayTime = 0;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+ return true;
+}
+
+void CDVDDemuxClient::Dispose()
+{
+ DisposeStreams();
+
+ m_pInput = nullptr;
+ m_IDemux = nullptr;
+}
+
+void CDVDDemuxClient::DisposeStreams()
+{
+ m_streams.clear();
+ m_videoStreamPlaying = -1;
+}
+
+bool CDVDDemuxClient::Reset()
+{
+ std::shared_ptr<CDVDInputStream> pInputStream = m_pInput;
+ Dispose();
+ return Open(pInputStream);
+}
+
+void CDVDDemuxClient::Abort()
+{
+ if (m_IDemux)
+ m_IDemux->AbortDemux();
+}
+
+void CDVDDemuxClient::Flush()
+{
+ if (m_IDemux)
+ m_IDemux->FlushDemux();
+
+ m_displayTime = 0;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+}
+
+bool CDVDDemuxClient::ParsePacket(DemuxPacket* pkt)
+{
+ bool change = false;
+
+ CDemuxStream* st = GetStream(pkt->iStreamId);
+ if (st == nullptr || st->changes < 0 || st->ExtraSize || !CodecHasExtraData(st->codec))
+ return change;
+
+ CDemuxStreamClientInternal* stream = dynamic_cast<CDemuxStreamClientInternal*>(st);
+
+ if (stream == nullptr ||
+ stream->m_parser == nullptr)
+ return change;
+
+ if (stream->m_context == nullptr)
+ {
+ AVCodec *codec = avcodec_find_decoder(st->codec);
+ if (codec == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - can't find decoder", __FUNCTION__);
+ stream->DisposeParser();
+ return change;
+ }
+
+ stream->m_context = avcodec_alloc_context3(codec);
+ if (stream->m_context == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - can't allocate context", __FUNCTION__);
+ stream->DisposeParser();
+ return change;
+ }
+ stream->m_context->time_base.num = 1;
+ stream->m_context->time_base.den = DVD_TIME_BASE;
+ }
+
+ if (stream->m_parser_split && stream->m_parser->parser->split)
+ {
+ int len = stream->m_parser->parser->split(stream->m_context, pkt->pData, pkt->iSize);
+ if (len > 0 && len < FF_MAX_EXTRADATA_SIZE)
+ {
+ st->changes++;
+ st->disabled = false;
+ st->ExtraSize = len;
+ st->ExtraData = std::make_unique<uint8_t[]>(len + AV_INPUT_BUFFER_PADDING_SIZE);
+ memcpy(st->ExtraData.get(), pkt->pData, len);
+ memset(st->ExtraData.get() + len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+ stream->m_parser_split = false;
+ change = true;
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - split extradata");
+
+ // Allow ffmpeg to transport codec information to stream->m_context
+ if (!avcodec_open2(stream->m_context, stream->m_context->codec, nullptr))
+ {
+ AVPacket* avpkt = av_packet_alloc();
+ if (!avpkt)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+ avpkt->data = pkt->pData;
+ avpkt->size = pkt->iSize;
+ avpkt->dts = avpkt->pts = AV_NOPTS_VALUE;
+ avcodec_send_packet(stream->m_context, avpkt);
+ avcodec_close(stream->m_context);
+ av_packet_free(&avpkt);
+ }
+ }
+ }
+
+ uint8_t *outbuf = nullptr;
+ int outbuf_size = 0;
+ int len = av_parser_parse2(stream->m_parser,
+ stream->m_context, &outbuf, &outbuf_size,
+ pkt->pData, pkt->iSize,
+ (int64_t)(pkt->pts * DVD_TIME_BASE),
+ (int64_t)(pkt->dts * DVD_TIME_BASE),
+ 0);
+
+ // our parse is setup to parse complete frames, so we don't care about outbufs
+ if (len >= 0)
+ {
+ if (stream->m_context->profile != st->profile &&
+ stream->m_context->profile != FF_PROFILE_UNKNOWN)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) profile changed from {} to {}", st->uniqueId, st->profile, stream->m_context->profile);
+ st->profile = stream->m_context->profile;
+ st->changes++;
+ st->disabled = false;
+ }
+
+ if (stream->m_context->level != st->level &&
+ stream->m_context->level != FF_LEVEL_UNKNOWN)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) level changed from {} to {}", st->uniqueId, st->level, stream->m_context->level);
+ st->level = stream->m_context->level;
+ st->changes++;
+ st->disabled = false;
+ }
+
+ switch (st->type)
+ {
+ case STREAM_AUDIO:
+ {
+ CDemuxStreamClientInternalTpl<CDemuxStreamAudio>* sta = static_cast<CDemuxStreamClientInternalTpl<CDemuxStreamAudio>*>(st);
+ if (stream->m_context->channels != sta->iChannels && stream->m_context->channels != 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) channels changed from {} to {}", st->uniqueId, sta->iChannels, stream->m_context->channels);
+ sta->iChannels = stream->m_context->channels;
+ sta->changes++;
+ sta->disabled = false;
+ }
+ if (stream->m_context->sample_rate != sta->iSampleRate &&
+ stream->m_context->sample_rate != 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) samplerate changed from {} to {}", st->uniqueId, sta->iSampleRate, stream->m_context->sample_rate);
+ sta->iSampleRate = stream->m_context->sample_rate;
+ sta->changes++;
+ sta->disabled = false;
+ }
+ if (stream->m_context->channels)
+ st->changes = -1; // stop parsing
+ break;
+ }
+ case STREAM_VIDEO:
+ {
+ CDemuxStreamClientInternalTpl<CDemuxStreamVideo>* stv = static_cast<CDemuxStreamClientInternalTpl<CDemuxStreamVideo>*>(st);
+ if (stream->m_parser->width != stv->iWidth && stream->m_parser->width != 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) width changed from {} to {}",
+ st->uniqueId, stv->iWidth, stream->m_parser->width);
+ stv->iWidth = stream->m_parser->width;
+ stv->changes++;
+ stv->disabled = false;
+ }
+ if (stream->m_parser->height != stv->iHeight && stream->m_parser->height != 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) height changed from {} to {}",
+ st->uniqueId, stv->iHeight, stream->m_parser->height);
+ stv->iHeight = stream->m_parser->height;
+ stv->changes++;
+ stv->disabled = false;
+ }
+ if (stream->m_context->sample_aspect_ratio.num && stream->m_context->height)
+ {
+ double fAspect =
+ (av_q2d(stream->m_context->sample_aspect_ratio) * stream->m_context->width) /
+ stream->m_context->height;
+ if (abs(fAspect - stv->fAspect) > 0.001 && fAspect >= 0.001)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) aspect changed from {} to {}",
+ st->uniqueId, stv->fAspect, fAspect);
+ stv->fAspect = fAspect;
+ stv->changes++;
+ stv->disabled = false;
+ }
+ }
+ if (stream->m_context->framerate.num &&
+ (stv->iFpsRate != stream->m_context->framerate.num ||
+ stv->iFpsScale != stream->m_context->framerate.den))
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::ParsePacket - ({}) fps changed from {}/{} to {}/{}",
+ st->uniqueId, stv->iFpsRate, stv->iFpsScale, stream->m_context->framerate.num,
+ stream->m_context->framerate.den);
+ stv->iFpsRate = stream->m_context->framerate.num;
+ stv->iFpsScale = stream->m_context->framerate.den;
+ stv->changes++;
+ stv->disabled = false;
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ else
+ CLog::Log(LOGDEBUG, "{} - parser returned error {}", __FUNCTION__, len);
+
+ return change;
+}
+
+DemuxPacket* CDVDDemuxClient::Read()
+{
+ if (!m_IDemux)
+ return nullptr;
+
+ if (m_packet)
+ return m_packet.release();
+
+ m_packet.reset(m_IDemux->ReadDemux());
+ if (!m_packet)
+ {
+ return nullptr;
+ }
+
+ if (m_packet->iStreamId == DMX_SPECIALID_STREAMINFO)
+ {
+ RequestStreams();
+ CDVDDemuxUtils::FreeDemuxPacket(m_packet.release());
+ return CDVDDemuxUtils::AllocateDemuxPacket(0);
+ }
+ else if (m_packet->iStreamId == DMX_SPECIALID_STREAMCHANGE)
+ {
+ RequestStreams();
+ }
+ else if (m_packet->iStreamId >= 0 && m_streams.count(m_packet->iStreamId) > 0)
+ {
+ if (ParsePacket(m_packet.get()))
+ {
+ RequestStreams();
+ DemuxPacket *pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
+ pPacket->iStreamId = DMX_SPECIALID_STREAMCHANGE;
+ pPacket->demuxerId = m_demuxerId;
+ return pPacket;
+ }
+ }
+
+ if (!IsVideoReady())
+ {
+ CDVDDemuxUtils::FreeDemuxPacket(m_packet.release());
+ DemuxPacket *pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
+ pPacket->demuxerId = m_demuxerId;
+ return pPacket;
+ }
+
+ //! @todo drop this block
+ CDVDInputStream::IDisplayTime *inputStream = m_pInput->GetIDisplayTime();
+ if (inputStream)
+ {
+ int dispTime = inputStream->GetTime();
+ if (m_displayTime != dispTime)
+ {
+ m_displayTime = dispTime;
+ if (m_packet->dts != DVD_NOPTS_VALUE)
+ {
+ m_dtsAtDisplayTime = m_packet->dts;
+ }
+ }
+ if (m_dtsAtDisplayTime != DVD_NOPTS_VALUE && m_packet->dts != DVD_NOPTS_VALUE)
+ {
+ m_packet->dispTime = m_displayTime;
+ m_packet->dispTime += DVD_TIME_TO_MSEC(m_packet->dts - m_dtsAtDisplayTime);
+ }
+ }
+
+ return m_packet.release();
+}
+
+CDemuxStream* CDVDDemuxClient::GetStream(int iStreamId) const
+{
+ auto stream = m_streams.find(iStreamId);
+ if (stream == m_streams.end())
+ return nullptr;
+
+ return stream->second.get();
+}
+
+std::vector<CDemuxStream*> CDVDDemuxClient::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ streams.reserve(m_streams.size());
+ for (auto &st : m_streams)
+ streams.push_back(st.second.get());
+
+ return streams;
+}
+
+void CDVDDemuxClient::RequestStreams()
+{
+ std::map<int, std::shared_ptr<CDemuxStream>> newStreamMap;
+ for (auto stream : m_IDemux->GetStreams())
+ SetStreamProps(stream, newStreamMap, false);
+ m_streams = newStreamMap;
+}
+
+void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map<int, std::shared_ptr<CDemuxStream>> &map, bool forceInit)
+{
+ if (!stream)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid stream");
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStream> currentStream(GetStreamInternal(stream->uniqueId));
+ std::shared_ptr<CDemuxStream> toStream;
+
+ if (stream->type == STREAM_AUDIO)
+ {
+ CDemuxStreamAudio *source = dynamic_cast<CDemuxStreamAudio*>(stream);
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid audio stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamAudio>> streamAudio;
+ if (currentStream)
+ streamAudio = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamAudio>>(currentStream);
+ if (forceInit || !streamAudio || streamAudio->codec != source->codec)
+ {
+ streamAudio.reset(new CDemuxStreamClientInternalTpl<CDemuxStreamAudio>());
+ streamAudio->m_parser = av_parser_init(source->codec);
+ if (streamAudio->m_parser)
+ streamAudio->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+ streamAudio->iSampleRate = source->iSampleRate;
+ streamAudio->iChannels = source->iChannels;
+ }
+
+ streamAudio->iBlockAlign = source->iBlockAlign;
+ streamAudio->iBitRate = source->iBitRate;
+ streamAudio->iBitsPerSample = source->iBitsPerSample;
+ if (source->ExtraSize > 0 && source->ExtraData)
+ {
+ streamAudio->ExtraData = std::make_unique<uint8_t[]>(source->ExtraSize);
+ streamAudio->ExtraSize = source->ExtraSize;
+ for (unsigned int j=0; j<source->ExtraSize; j++)
+ streamAudio->ExtraData[j] = source->ExtraData[j];
+ }
+ streamAudio->m_parser_split = true;
+ streamAudio->changes++;
+ map[stream->uniqueId] = streamAudio;
+ toStream = streamAudio;
+ }
+ else if (stream->type == STREAM_VIDEO)
+ {
+ CDemuxStreamVideo *source = dynamic_cast<CDemuxStreamVideo*>(stream);
+
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid video stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamVideo>> streamVideo;
+ if (currentStream)
+ streamVideo = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamVideo>>(currentStream);
+ if (forceInit || !streamVideo || streamVideo->codec != source->codec)
+ {
+ streamVideo.reset(new CDemuxStreamClientInternalTpl<CDemuxStreamVideo>());
+ streamVideo->m_parser = av_parser_init(source->codec);
+ if (streamVideo->m_parser)
+ streamVideo->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+ streamVideo->iHeight = source->iHeight;
+ streamVideo->iWidth = source->iWidth;
+ streamVideo->fAspect = source->fAspect;
+ streamVideo->iFpsScale = source->iFpsScale;
+ streamVideo->iFpsRate = source->iFpsRate;
+ }
+ streamVideo->iBitRate = source->iBitRate;
+ if (source->ExtraSize > 0 && source->ExtraData)
+ {
+ streamVideo->ExtraData = std::make_unique<uint8_t[]>(source->ExtraSize);
+ streamVideo->ExtraSize = source->ExtraSize;
+ for (unsigned int j=0; j<source->ExtraSize; j++)
+ streamVideo->ExtraData[j] = source->ExtraData[j];
+ }
+ streamVideo->colorPrimaries = source->colorPrimaries;
+ streamVideo->colorRange = source->colorRange;
+ streamVideo->colorSpace = source->colorSpace;
+ streamVideo->colorTransferCharacteristic = source->colorTransferCharacteristic;
+ streamVideo->masteringMetaData = source->masteringMetaData;
+ streamVideo->contentLightMetaData = source->contentLightMetaData;
+
+ streamVideo->m_parser_split = true;
+ streamVideo->changes++;
+ map[stream->uniqueId] = streamVideo;
+ toStream = streamVideo;
+ }
+ else if (stream->type == STREAM_SUBTITLE)
+ {
+ CDemuxStreamSubtitle *source = dynamic_cast<CDemuxStreamSubtitle*>(stream);
+
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid subtitle stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamSubtitle>> streamSubtitle;
+ if (currentStream)
+ streamSubtitle = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamSubtitle>>(currentStream);
+ if (!streamSubtitle || streamSubtitle->codec != source->codec)
+ {
+ streamSubtitle.reset(new CDemuxStreamClientInternalTpl<CDemuxStreamSubtitle>());
+ streamSubtitle->m_parser = av_parser_init(source->codec);
+ if (streamSubtitle->m_parser)
+ streamSubtitle->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+ }
+
+ if (source->ExtraSize == 4)
+ {
+ streamSubtitle->ExtraData = std::make_unique<uint8_t[]>(4);
+ streamSubtitle->ExtraSize = 4;
+ for (int j=0; j<4; j++)
+ streamSubtitle->ExtraData[j] = source->ExtraData[j];
+ }
+ map[stream->uniqueId] = streamSubtitle;
+ toStream = streamSubtitle;
+ }
+ else if (stream->type == STREAM_TELETEXT)
+ {
+ CDemuxStreamTeletext *source = dynamic_cast<CDemuxStreamTeletext*>(stream);
+
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid teletext stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamTeletext>> streamTeletext;
+ if (currentStream)
+ streamTeletext = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamTeletext>>(currentStream);
+ if (!streamTeletext || streamTeletext->codec != source->codec)
+ {
+ streamTeletext.reset(new CDemuxStreamClientInternalTpl<CDemuxStreamTeletext>());
+ }
+
+ map[stream->uniqueId] = streamTeletext;
+ toStream = streamTeletext;
+ }
+ else if (stream->type == STREAM_RADIO_RDS)
+ {
+ CDemuxStreamRadioRDS *source = dynamic_cast<CDemuxStreamRadioRDS*>(stream);
+
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid radio-rds stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamRadioRDS>> streamRDS;
+ if (currentStream)
+ streamRDS = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamRadioRDS>>(currentStream);
+ if (!streamRDS || streamRDS->codec != source->codec)
+ {
+ streamRDS.reset(new CDemuxStreamClientInternalTpl<CDemuxStreamRadioRDS>());
+ }
+
+ map[stream->uniqueId] = streamRDS;
+ toStream = streamRDS;
+ }
+ else if (stream->type == STREAM_AUDIO_ID3)
+ {
+ CDemuxStreamAudioID3* source = dynamic_cast<CDemuxStreamAudioID3*>(stream);
+
+ if (!source)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxClient::RequestStream - invalid audio ID3 stream with id {}",
+ stream->uniqueId);
+ DisposeStreams();
+ return;
+ }
+
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStreamAudioID3>> streamID3;
+ if (currentStream)
+ streamID3 = std::dynamic_pointer_cast<CDemuxStreamClientInternalTpl<CDemuxStreamAudioID3>>(
+ currentStream);
+ if (!streamID3 || streamID3->codec != source->codec)
+ {
+ streamID3 = std::make_shared<CDemuxStreamClientInternalTpl<CDemuxStreamAudioID3>>();
+ }
+
+ map[stream->uniqueId] = streamID3;
+ toStream = streamID3;
+ }
+ else
+ {
+ std::shared_ptr<CDemuxStreamClientInternalTpl<CDemuxStream>> streamGen;
+ streamGen = std::make_shared<CDemuxStreamClientInternalTpl<CDemuxStream>>();
+ map[stream->uniqueId] = streamGen;
+ toStream = streamGen;
+ }
+
+ // only update profile / level if we create a new stream
+ // existing streams may be corrected by ParsePacket
+ if (!currentStream || !CodecHasExtraData(stream->codec))
+ {
+ toStream->profile = stream->profile;
+ toStream->level = stream->level;
+ }
+
+ toStream->uniqueId = stream->uniqueId;
+ toStream->codec = stream->codec;
+ toStream->codecName = stream->codecName;
+ toStream->codec_fourcc = stream->codec_fourcc;
+ toStream->flags = stream->flags;
+ toStream->cryptoSession = stream->cryptoSession;
+ toStream->externalInterfaces = stream->externalInterfaces;
+ toStream->language = stream->language;
+ toStream->name = stream->name;
+
+ CLog::Log(LOGDEBUG, "CDVDDemuxClient::RequestStream(): added/updated stream {} with codec_id {}",
+ toStream->uniqueId, toStream->codec);
+}
+
+std::shared_ptr<CDemuxStream> CDVDDemuxClient::GetStreamInternal(int iStreamId)
+{
+ auto stream = m_streams.find(iStreamId);
+ if (stream != m_streams.end())
+ {
+ return stream->second;
+ }
+ else
+ return nullptr;
+}
+
+int CDVDDemuxClient::GetNrOfStreams() const
+{
+ return m_streams.size();
+}
+
+bool CDVDDemuxClient::IsVideoReady()
+{
+ for (const auto& stream : m_streams)
+ {
+ if (stream.first == m_videoStreamPlaying &&
+ stream.second->type == STREAM_VIDEO &&
+ CodecHasExtraData(stream.second->codec) &&
+ stream.second->ExtraData == nullptr)
+ return false;
+ }
+ return true;
+}
+
+std::string CDVDDemuxClient::GetFileName()
+{
+ if (m_pInput)
+ return m_pInput->GetFileName();
+ else
+ return "";
+}
+
+std::string CDVDDemuxClient::GetStreamCodecName(int iStreamId)
+{
+ CDemuxStream *stream = GetStream(iStreamId);
+ std::string strName;
+ if (stream)
+ {
+ if (stream->codec == AV_CODEC_ID_AC3)
+ strName = "ac3";
+ else if (stream->codec == AV_CODEC_ID_MP2)
+ strName = "mp2";
+ else if (stream->codec == AV_CODEC_ID_AAC)
+ strName = "aac";
+ else if (stream->codec == AV_CODEC_ID_DTS)
+ strName = "dca";
+ else if (stream->codec == AV_CODEC_ID_MPEG2VIDEO)
+ strName = "mpeg2video";
+ else if (stream->codec == AV_CODEC_ID_H264)
+ strName = "h264";
+ else if (stream->codec == AV_CODEC_ID_EAC3)
+ strName = "eac3";
+ else if (stream->codec == AV_CODEC_ID_VP8)
+ strName = "vp8";
+ else if (stream->codec == AV_CODEC_ID_VP9)
+ strName = "vp9";
+ else if (stream->codec == AV_CODEC_ID_HEVC)
+ strName = "hevc";
+ else if (stream->codec == AV_CODEC_ID_AV1)
+ strName = "av1";
+ }
+ return strName;
+}
+
+bool CDVDDemuxClient::SeekTime(double timems, bool backwards, double *startpts)
+{
+ if (m_IDemux)
+ {
+ m_displayTime = 0;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+ return m_IDemux->SeekTime(timems, backwards, startpts);
+ }
+ return false;
+}
+
+void CDVDDemuxClient::SetSpeed (int speed)
+{
+ if (m_IDemux)
+ {
+ m_IDemux->SetSpeed(speed);
+ }
+}
+
+void CDVDDemuxClient::FillBuffer(bool mode)
+{
+ if (m_IDemux)
+ {
+ m_IDemux->FillBuffer(mode);
+ }
+}
+
+void CDVDDemuxClient::EnableStream(int id, bool enable)
+{
+ if (m_IDemux)
+ {
+ m_IDemux->EnableStream(id, enable);
+ }
+}
+
+void CDVDDemuxClient::OpenStream(int id)
+{
+ // OpenStream may change some parameters
+ // in this case we need to reset our stream properties
+ if (m_IDemux)
+ {
+ bool bOpenStream = m_IDemux->OpenStream(id);
+
+ CDemuxStream *stream(m_IDemux->GetStream(id));
+ if (stream && stream->type == STREAM_VIDEO)
+ m_videoStreamPlaying = id;
+
+ if (bOpenStream)
+ SetStreamProps(stream, m_streams, true);
+ }
+}
+
+void CDVDDemuxClient::SetVideoResolution(unsigned int width, unsigned int height)
+{
+ if (m_IDemux)
+ {
+ m_IDemux->SetVideoResolution(width, height, width, height);
+ }
+}
+
+bool CDVDDemuxClient::CodecHasExtraData(AVCodecID id)
+{
+ switch (id)
+ {
+ case AV_CODEC_ID_VP9:
+ return false;
+ default:
+ return true;
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.h
new file mode 100644
index 0000000..9b5ac42
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+#include "DVDInputStreams/DVDInputStream.h"
+
+#include <map>
+#include <vector>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+
+class CDVDDemuxClient : public CDVDDemux
+{
+public:
+
+ CDVDDemuxClient();
+ ~CDVDDemuxClient() override;
+
+ bool Open(std::shared_ptr<CDVDInputStream> pInput);
+ void Dispose();
+ bool Reset() override;
+ void Abort() override;
+ void Flush() override;
+ DemuxPacket* Read() override;
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override;
+ void SetSpeed(int iSpeed) override;
+ void FillBuffer(bool mode) override;
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+ std::string GetFileName() override;
+ std::string GetStreamCodecName(int iStreamId) override;
+ void EnableStream(int id, bool enable) override;
+ void OpenStream(int id) override;
+ void SetVideoResolution(unsigned int width, unsigned int height) override;
+
+protected:
+ void RequestStreams();
+ void SetStreamProps(CDemuxStream *stream, std::map<int, std::shared_ptr<CDemuxStream>> &map, bool forceInit);
+ bool ParsePacket(DemuxPacket* pPacket);
+ void DisposeStreams();
+ std::shared_ptr<CDemuxStream> GetStreamInternal(int iStreamId);
+ bool IsVideoReady();
+
+ std::shared_ptr<CDVDInputStream> m_pInput;
+ std::shared_ptr<CDVDInputStream::IDemux> m_IDemux;
+ std::map<int, std::shared_ptr<CDemuxStream>> m_streams;
+ int m_displayTime;
+ double m_dtsAtDisplayTime;
+ std::unique_ptr<DemuxPacket> m_packet;
+ int m_videoStreamPlaying = -1;
+
+private:
+ static inline bool CodecHasExtraData(AVCodecID id);
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp
new file mode 100644
index 0000000..739bf51
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp
@@ -0,0 +1,2549 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxFFmpeg.h"
+
+#include "DVDDemuxUtils.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "DVDInputStreams/DVDInputStreamFFmpeg.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "commons/Exception.h"
+#include "cores/FFmpeg.h"
+#include "cores/MenuType.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h" // for DVD_TIME_BASE
+#include "filesystem/CurlFile.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/FontUtils.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <sstream>
+#include <utility>
+
+extern "C"
+{
+#include "libavutil/pixdesc.h"
+}
+
+#ifdef HAVE_LIBBLURAY
+#include "DVDInputStreams/DVDInputStreamBluray.h"
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+#define __STDC_CONSTANT_MACROS
+#endif
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+#ifdef TARGET_POSIX
+#include <stdint.h>
+#endif
+
+extern "C" {
+#include <libavutil/dict.h>
+#include <libavutil/opt.h>
+}
+
+using namespace std::chrono_literals;
+
+struct StereoModeConversionMap
+{
+ const char* name;
+ const char* mode;
+};
+
+// we internally use the matroska string representation of stereoscopic modes.
+// This struct is a conversion map to convert stereoscopic mode values
+// from asf/wmv to the internally used matroska ones
+static const struct StereoModeConversionMap WmvToInternalStereoModeMap[] =
+{
+ { "SideBySideRF", "right_left" },
+ { "SideBySideLF", "left_right" },
+ { "OverUnderRT", "bottom_top" },
+ { "OverUnderLT", "top_bottom" },
+ {}
+};
+
+namespace
+{
+const std::vector<std::string> font_mimetypes = {"application/x-truetype-font",
+ "application/vnd.ms-opentype",
+ "application/x-font-ttf",
+ "application/x-font", // probably incorrect
+ "application/font-sfnt",
+ "font/collection",
+ "font/otf",
+ "font/sfnt",
+ "font/ttf"};
+
+bool AttachmentIsFont(const AVDictionaryEntry* dict)
+{
+ if (dict)
+ {
+ const std::string mimeType = dict->value;
+ return std::find_if(font_mimetypes.begin(), font_mimetypes.end(),
+ [&mimeType](const std::string& str) { return str == mimeType; }) !=
+ font_mimetypes.end();
+ }
+ return false;
+}
+} // namespace
+
+#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - AV_INPUT_BUFFER_PADDING_SIZE)
+
+std::string CDemuxStreamAudioFFmpeg::GetStreamName()
+{
+ if (!m_stream)
+ return "";
+ if (!m_description.empty())
+ return m_description;
+ else
+ return CDemuxStream::GetStreamName();
+}
+
+std::string CDemuxStreamSubtitleFFmpeg::GetStreamName()
+{
+ if (!m_stream)
+ return "";
+ if (!m_description.empty())
+ return m_description;
+ else
+ return CDemuxStream::GetStreamName();
+}
+
+std::string CDemuxStreamVideoFFmpeg::GetStreamName()
+{
+ if (!m_stream)
+ return "";
+ if (!m_description.empty())
+ return m_description;
+ else
+ return CDemuxStream::GetStreamName();
+}
+
+CDemuxParserFFmpeg::~CDemuxParserFFmpeg()
+{
+ if (m_codecCtx)
+ avcodec_free_context(&m_codecCtx);
+ if (m_parserCtx)
+ {
+ av_parser_close(m_parserCtx);
+ m_parserCtx = nullptr;
+ }
+}
+
+static int interrupt_cb(void* ctx)
+{
+ CDVDDemuxFFmpeg* demuxer = static_cast<CDVDDemuxFFmpeg*>(ctx);
+ if (demuxer && demuxer->Aborted())
+ return 1;
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+static int dvd_file_open(URLContext* h, const char* filename, int flags)
+{
+ return -1;
+}
+*/
+
+static int dvd_file_read(void* h, uint8_t* buf, int size)
+{
+ if (interrupt_cb(h))
+ return AVERROR_EXIT;
+
+ std::shared_ptr<CDVDInputStream> pInputStream = static_cast<CDVDDemuxFFmpeg*>(h)->m_pInput;
+ int len = pInputStream->Read(buf, size);
+ if (len == 0)
+ return AVERROR_EOF;
+ else
+ return len;
+}
+/*
+static int dvd_file_write(URLContext* h, uint8_t* buf, int size)
+{
+ return -1;
+}
+*/
+static int64_t dvd_file_seek(void* h, int64_t pos, int whence)
+{
+ if (interrupt_cb(h))
+ return AVERROR_EXIT;
+
+ std::shared_ptr<CDVDInputStream> pInputStream = static_cast<CDVDDemuxFFmpeg*>(h)->m_pInput;
+ if (whence == AVSEEK_SIZE)
+ return pInputStream->GetLength();
+ else
+ return pInputStream->Seek(pos, whence & ~AVSEEK_FORCE);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+CDVDDemuxFFmpeg::CDVDDemuxFFmpeg() : CDVDDemux()
+{
+ m_pFormatContext = NULL;
+ m_ioContext = NULL;
+ m_currentPts = DVD_NOPTS_VALUE;
+ m_bMatroska = false;
+ m_bAVI = false;
+ m_bSup = false;
+ m_speed = DVD_PLAYSPEED_NORMAL;
+ m_program = UINT_MAX;
+ m_pkt.result = -1;
+ memset(&m_pkt.pkt, 0, sizeof(AVPacket));
+ m_streaminfo = true; /* set to true if we want to look for streams before playback */
+ m_checkTransportStream = false;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+}
+
+CDVDDemuxFFmpeg::~CDVDDemuxFFmpeg()
+{
+ Dispose();
+ ff_flush_avutil_log_buffers();
+}
+
+bool CDVDDemuxFFmpeg::Aborted()
+{
+ if (m_timeout.IsTimePast())
+ return true;
+
+ std::shared_ptr<CDVDInputStreamFFmpeg> input = std::dynamic_pointer_cast<CDVDInputStreamFFmpeg>(m_pInput);
+ if (input && input->Aborted())
+ return true;
+
+ return false;
+}
+
+bool CDVDDemuxFFmpeg::Open(const std::shared_ptr<CDVDInputStream>& pInput, bool fileinfo)
+{
+ AVInputFormat* iformat = NULL;
+ std::string strFile;
+ m_streaminfo = !pInput->IsRealtime() && !m_reopen;
+ m_reopen = false;
+ m_currentPts = DVD_NOPTS_VALUE;
+ m_speed = DVD_PLAYSPEED_NORMAL;
+ m_program = UINT_MAX;
+ m_seekToKeyFrame = false;
+
+ const AVIOInterruptCB int_cb = { interrupt_cb, this };
+
+ if (!pInput)
+ return false;
+
+ m_pInput = pInput;
+ strFile = m_pInput->GetFileName();
+
+ if (m_pInput->GetContent().length() > 0)
+ {
+ std::string content = m_pInput->GetContent();
+ StringUtils::ToLower(content);
+
+ /* check if we can get a hint from content */
+ if (content.compare("video/x-vobsub") == 0)
+ iformat = av_find_input_format("mpeg");
+ else if (content.compare("video/x-dvd-mpeg") == 0)
+ iformat = av_find_input_format("mpeg");
+ else if (content.compare("video/mp2t") == 0)
+ iformat = av_find_input_format("mpegts");
+ else if (content.compare("multipart/x-mixed-replace") == 0)
+ iformat = av_find_input_format("mjpeg");
+ }
+
+ // open the demuxer
+ m_pFormatContext = avformat_alloc_context();
+ m_pFormatContext->interrupt_callback = int_cb;
+
+ // try to abort after 30 seconds
+ m_timeout.Set(30s);
+
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_FFMPEG))
+ {
+ // special stream type that makes avformat handle file opening
+ // allows internal ffmpeg protocols to be used
+ AVDictionary* options = GetFFMpegOptionsFromInput();
+
+ CURL url = m_pInput->GetURL();
+
+ int result = -1;
+ if (url.IsProtocol("mms"))
+ {
+ // try mmsh, then mmst
+ url.SetProtocol("mmsh");
+ url.SetProtocolOptions("");
+ result = avformat_open_input(&m_pFormatContext, url.Get().c_str(), iformat, &options);
+ if (result < 0)
+ {
+ url.SetProtocol("mmst");
+ strFile = url.Get();
+ }
+ }
+ else if (url.IsProtocol("udp") || url.IsProtocol("rtp"))
+ {
+ std::string strURL = url.Get();
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::Open() UDP/RTP Original URL '{}'", strURL);
+ size_t found = strURL.find("://");
+ if (found != std::string::npos)
+ {
+ size_t start = found + 3;
+ found = strURL.find('@');
+
+ if (found != std::string::npos && found > start)
+ {
+ // sourceip found
+ std::string strSourceIp = strURL.substr(start, found - start);
+
+ strFile = strURL.substr(0, start);
+ strFile += strURL.substr(found);
+ if (strFile.back() == '/')
+ strFile.pop_back();
+ strFile += "?sources=";
+ strFile += strSourceIp;
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::Open() UDP/RTP URL '{}'", strFile);
+ }
+ }
+ }
+ if (result < 0)
+ {
+ m_pFormatContext->flags |= AVFMT_FLAG_PRIV_OPT;
+ if (avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, &options) < 0)
+ {
+ CLog::Log(LOGDEBUG, "Error, could not open file {}", CURL::GetRedacted(strFile));
+ Dispose();
+ av_dict_free(&options);
+ return false;
+ }
+ av_dict_free(&options);
+ avformat_close_input(&m_pFormatContext);
+ m_pFormatContext = avformat_alloc_context();
+ m_pFormatContext->interrupt_callback = int_cb;
+ m_pFormatContext->flags &= ~AVFMT_FLAG_PRIV_OPT;
+ AVDictionary* options = GetFFMpegOptionsFromInput();
+ av_dict_set_int(&options, "load_all_variants", 0, AV_OPT_SEARCH_CHILDREN);
+ if (avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, &options) < 0)
+ {
+ CLog::Log(LOGDEBUG, "Error, could not open file (2) {}", CURL::GetRedacted(strFile));
+ Dispose();
+ av_dict_free(&options);
+ return false;
+ }
+ }
+ av_dict_free(&options);
+ }
+ else
+ {
+ bool seekable = true;
+ if (m_pInput->Seek(0, SEEK_POSSIBLE) == 0)
+ {
+ seekable = false;
+ }
+ int bufferSize = 4096;
+ int blockSize = m_pInput->GetBlockSize();
+
+ if (blockSize > 1 && seekable) // non seakable input streams are not supposed to set block size
+ bufferSize = blockSize;
+
+ unsigned char* buffer = (unsigned char*)av_malloc(bufferSize);
+ m_ioContext = avio_alloc_context(buffer, bufferSize, 0, this, dvd_file_read, NULL, dvd_file_seek);
+
+ if (blockSize > 1 && seekable)
+ m_ioContext->max_packet_size = bufferSize;
+
+ if (!seekable)
+ m_ioContext->seekable = 0;
+
+ std::string content = m_pInput->GetContent();
+ StringUtils::ToLower(content);
+ if (StringUtils::StartsWith(content, "audio/l16"))
+ iformat = av_find_input_format("s16be");
+
+ if (iformat == nullptr)
+ {
+ // let ffmpeg decide which demuxer we have to open
+ bool trySPDIFonly = (m_pInput->GetContent() == "audio/x-spdif-compressed");
+
+ if (!trySPDIFonly)
+ av_probe_input_buffer(m_ioContext, &iformat, strFile.c_str(), NULL, 0, 0);
+
+ // Use the more low-level code in case we have been built against an old
+ // FFmpeg without the above av_probe_input_buffer(), or in case we only
+ // want to probe for spdif (DTS or IEC 61937) compressed audio
+ // specifically, or in case the file is a wav which may contain DTS or
+ // IEC 61937 (e.g. ac3-in-wav) and we want to check for those formats.
+ if (trySPDIFonly || (iformat && strcmp(iformat->name, "wav") == 0))
+ {
+ AVProbeData pd;
+ int probeBufferSize = 32768;
+ std::unique_ptr<uint8_t[]> probe_buffer (new uint8_t[probeBufferSize + AVPROBE_PADDING_SIZE]);
+
+ // init probe data
+ pd.buf = probe_buffer.get();
+ pd.filename = strFile.c_str();
+
+ // read data using avformat's buffers
+ pd.buf_size = avio_read(m_ioContext, pd.buf, probeBufferSize);
+ if (pd.buf_size <= 0)
+ {
+ CLog::Log(LOGERROR, "{} - error reading from input stream, {}", __FUNCTION__,
+ CURL::GetRedacted(strFile));
+ return false;
+ }
+ memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);
+
+ // restore position again
+ avio_seek(m_ioContext , 0, SEEK_SET);
+
+ // the advancedsetting is for allowing the user to force outputting the
+ // 44.1 kHz DTS wav file as PCM, so that an A/V receiver can decode
+ // it (this is temporary until we handle 44.1 kHz passthrough properly)
+ if (trySPDIFonly || (iformat && strcmp(iformat->name, "wav") == 0 && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_VideoPlayerIgnoreDTSinWAV))
+ {
+ // check for spdif and dts
+ // This is used with wav files and audio CDs that may contain
+ // a DTS or AC3 track padded for S/PDIF playback. If neither of those
+ // is present, we assume it is PCM audio.
+ // AC3 is always wrapped in iec61937 (ffmpeg "spdif"), while DTS
+ // may be just padded.
+ AVInputFormat* iformat2;
+ iformat2 = av_find_input_format("spdif");
+
+ if (iformat2 && iformat2->read_probe(&pd) > AVPROBE_SCORE_MAX / 4)
+ {
+ iformat = iformat2;
+ }
+ else
+ {
+ // not spdif or no spdif demuxer, try dts
+ iformat2 = av_find_input_format("dts");
+
+ if (iformat2 && iformat2->read_probe(&pd) > AVPROBE_SCORE_MAX / 4)
+ {
+ iformat = iformat2;
+ }
+ else if (trySPDIFonly)
+ {
+ // not dts either, return false in case we were explicitly
+ // requested to only check for S/PDIF padded compressed audio
+ CLog::Log(LOGDEBUG, "{} - not spdif or dts file, falling back", __FUNCTION__);
+ return false;
+ }
+ }
+ }
+ }
+
+ if (!iformat)
+ {
+ std::string content = m_pInput->GetContent();
+
+ /* check if we can get a hint from content */
+ if (content.compare("audio/aacp") == 0)
+ iformat = av_find_input_format("aac");
+ else if (content.compare("audio/aac") == 0)
+ iformat = av_find_input_format("aac");
+ else if (content.compare("video/flv") == 0)
+ iformat = av_find_input_format("flv");
+ else if (content.compare("video/x-flv") == 0)
+ iformat = av_find_input_format("flv");
+ }
+
+ if (!iformat)
+ {
+ CLog::Log(LOGERROR, "{} - error probing input format, {}", __FUNCTION__,
+ CURL::GetRedacted(strFile));
+ return false;
+ }
+ else
+ {
+ if (iformat->name)
+ CLog::Log(LOGDEBUG, "{} - probing detected format [{}]", __FUNCTION__, iformat->name);
+ else
+ CLog::Log(LOGDEBUG, "{} - probing detected unnamed format", __FUNCTION__);
+ }
+ }
+
+
+ m_pFormatContext->pb = m_ioContext;
+
+ AVDictionary* options = NULL;
+ if (iformat->name && (strcmp(iformat->name, "mp3") == 0 || strcmp(iformat->name, "mp2") == 0))
+ {
+ CLog::Log(LOGDEBUG, "{} - setting usetoc to 0 for accurate VBR MP3 seek", __FUNCTION__);
+ av_dict_set(&options, "usetoc", "0", 0);
+ }
+
+ if (StringUtils::StartsWith(content, "audio/l16"))
+ {
+ int channels = 2;
+ int samplerate = 44100;
+ GetL16Parameters(channels, samplerate);
+ av_dict_set_int(&options, "channels", channels, 0);
+ av_dict_set_int(&options, "sample_rate", samplerate, 0);
+ }
+
+ if (avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, &options) < 0)
+ {
+ CLog::Log(LOGERROR, "{} - Error, could not open file {}", __FUNCTION__,
+ CURL::GetRedacted(strFile));
+ Dispose();
+ av_dict_free(&options);
+ return false;
+ }
+ av_dict_free(&options);
+ }
+
+ // Avoid detecting framerate if advancedsettings.xml says so
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 0)
+ m_pFormatContext->fps_probe_size = 0;
+
+ // analyse very short to speed up mjpeg playback start
+ if (iformat && (strcmp(iformat->name, "mjpeg") == 0) && m_ioContext->seekable == 0)
+ av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
+
+ bool skipCreateStreams = false;
+ bool isBluray = pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY);
+
+ // this should never happen. Log it to inform about the error.
+ if (m_pFormatContext->nb_streams > 0 && m_pFormatContext->streams == nullptr)
+ {
+ CLog::LogF(LOGERROR, "Detected number of streams is greater than zero but AVStream array is "
+ "empty. Please report this bug.");
+ }
+
+ // don't re-open mpegts streams with hevc encoding as the params are not correctly detected again
+ if (iformat && (strcmp(iformat->name, "mpegts") == 0) && !fileinfo && !isBluray &&
+ m_pFormatContext->nb_streams > 0 && m_pFormatContext->streams != nullptr &&
+ m_pFormatContext->streams[0]->codecpar->codec_id != AV_CODEC_ID_HEVC)
+ {
+ av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
+ m_checkTransportStream = true;
+ skipCreateStreams = true;
+ }
+ else if (!iformat || ((strcmp(iformat->name, "mpegts") != 0) ||
+ ((strcmp(iformat->name, "mpegts") == 0) &&
+ m_pFormatContext->nb_streams > 0 && m_pFormatContext->streams != nullptr &&
+ m_pFormatContext->streams[0]->codecpar->codec_id == AV_CODEC_ID_HEVC)))
+ {
+ m_streaminfo = true;
+ }
+
+ if (iformat && (strcmp(iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0))
+ {
+ if (URIUtils::IsRemote(strFile))
+ m_pFormatContext->iformat->flags |= AVFMT_NOGENSEARCH;
+ }
+
+ // we need to know if this is matroska, avi or sup later
+ m_bMatroska = strncmp(m_pFormatContext->iformat->name, "matroska", 8) == 0; // for "matroska.webm"
+ m_bAVI = strcmp(m_pFormatContext->iformat->name, "avi") == 0;
+ m_bSup = strcmp(m_pFormatContext->iformat->name, "sup") == 0;
+
+ if (m_streaminfo)
+ {
+ /* to speed up dvd switches, only analyse very short */
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
+ av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
+
+ CLog::Log(LOGDEBUG, "{} - avformat_find_stream_info starting", __FUNCTION__);
+ int iErr = avformat_find_stream_info(m_pFormatContext, NULL);
+ if (iErr < 0)
+ {
+ CLog::Log(LOGWARNING, "could not find codec parameters for {}", CURL::GetRedacted(strFile));
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD) ||
+ m_pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY) ||
+ (m_pFormatContext->nb_streams == 1 &&
+ m_pFormatContext->streams[0]->codecpar->codec_id == AV_CODEC_ID_AC3) ||
+ m_checkTransportStream)
+ {
+ // special case, our codecs can still handle it.
+ }
+ else
+ {
+ Dispose();
+ return false;
+ }
+ }
+ CLog::Log(LOGDEBUG, "{} - av_find_stream_info finished", __FUNCTION__);
+
+ // print some extra information
+ av_dump_format(m_pFormatContext, 0, CURL::GetRedacted(strFile).c_str(), 0);
+
+ if (m_checkTransportStream)
+ {
+ // make sure we start video with an i-frame
+ ResetVideoStreams();
+ }
+ }
+ else
+ {
+ m_program = 0;
+ m_checkTransportStream = true;
+ skipCreateStreams = true;
+ }
+
+ // reset any timeout
+ m_timeout.SetInfinite();
+
+ // if format can be nonblocking, let's use that
+ m_pFormatContext->flags |= AVFMT_FLAG_NONBLOCK;
+
+ // deprecated, will be always set in future versions
+ m_pFormatContext->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;
+
+ UpdateCurrentPTS();
+
+ // select the correct program if requested
+ m_initialProgramNumber = UINT_MAX;
+ CVariant programProp(pInput->GetProperty("program"));
+ if (!programProp.isNull())
+ m_initialProgramNumber = static_cast<int>(programProp.asInteger());
+
+ // in case of mpegts and we have not seen pat/pmt, defer creation of streams
+ if (!skipCreateStreams || m_pFormatContext->nb_programs > 0)
+ {
+ unsigned int nProgram = UINT_MAX;
+ if (m_pFormatContext->nb_programs > 0)
+ {
+ // select the correct program if requested
+ if (m_initialProgramNumber != UINT_MAX)
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->nb_programs; ++i)
+ {
+ if (m_pFormatContext->programs[i]->program_num == static_cast<int>(m_initialProgramNumber))
+ {
+ nProgram = i;
+ m_initialProgramNumber = UINT_MAX;
+ break;
+ }
+ }
+ }
+ else if (m_pFormatContext->iformat && strcmp(m_pFormatContext->iformat->name, "hls") == 0)
+ {
+ nProgram = HLSSelectProgram();
+ }
+ else
+ {
+ // skip programs without or empty audio/video streams
+ for (unsigned int i = 0; nProgram == UINT_MAX && i < m_pFormatContext->nb_programs; i++)
+ {
+ for (unsigned int j = 0; j < m_pFormatContext->programs[i]->nb_stream_indexes; j++)
+ {
+ int idx = m_pFormatContext->programs[i]->stream_index[j];
+ AVStream* st = m_pFormatContext->streams[idx];
+ if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->codec_info_nb_frames > 0) ||
+ (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && st->codecpar->sample_rate > 0))
+ {
+ nProgram = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ CreateStreams(nProgram);
+ }
+
+ m_newProgram = m_program;
+
+ // allow IsProgramChange to return true
+ if (skipCreateStreams && GetNrOfStreams() == 0)
+ m_program = 0;
+
+ m_displayTime = 0;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+ m_startTime = 0;
+ m_seekStream = -1;
+
+ if (m_checkTransportStream && m_streaminfo)
+ {
+ int64_t duration = m_pFormatContext->duration;
+ std::shared_ptr<CDVDInputStream> pInputStream = m_pInput;
+ Dispose();
+ m_reopen = true;
+ if (!Open(pInputStream, false))
+ return false;
+ m_pFormatContext->duration = duration;
+ }
+
+ return true;
+}
+
+void CDVDDemuxFFmpeg::Dispose()
+{
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+
+ if (m_pFormatContext)
+ {
+ if (m_ioContext && m_pFormatContext->pb && m_pFormatContext->pb != m_ioContext)
+ {
+ CLog::Log(LOGWARNING, "CDVDDemuxFFmpeg::Dispose - demuxer changed our byte context behind our back, possible memleak");
+ m_ioContext = m_pFormatContext->pb;
+ }
+ avformat_close_input(&m_pFormatContext);
+ }
+
+ if (m_ioContext)
+ {
+ av_free(m_ioContext->buffer);
+ av_free(m_ioContext);
+ }
+
+ m_ioContext = NULL;
+ m_pFormatContext = NULL;
+ m_speed = DVD_PLAYSPEED_NORMAL;
+
+ DisposeStreams();
+
+ m_pInput = NULL;
+}
+
+bool CDVDDemuxFFmpeg::Reset()
+{
+ std::shared_ptr<CDVDInputStream> pInputStream = m_pInput;
+ Dispose();
+ return Open(pInputStream, false);
+}
+
+void CDVDDemuxFFmpeg::Flush()
+{
+ if (m_pFormatContext)
+ {
+ if (m_pFormatContext->pb)
+ avio_flush(m_pFormatContext->pb);
+ avformat_flush(m_pFormatContext);
+ }
+
+ m_currentPts = DVD_NOPTS_VALUE;
+
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+
+ m_displayTime = 0;
+ m_dtsAtDisplayTime = DVD_NOPTS_VALUE;
+ m_seekToKeyFrame = false;
+}
+
+void CDVDDemuxFFmpeg::Abort()
+{
+ m_timeout.SetExpired();
+}
+
+void CDVDDemuxFFmpeg::SetSpeed(int iSpeed)
+{
+ if (!m_pFormatContext)
+ return;
+
+ if (m_speed == iSpeed)
+ return;
+
+ if (m_speed != DVD_PLAYSPEED_PAUSE && iSpeed == DVD_PLAYSPEED_PAUSE)
+ av_read_pause(m_pFormatContext);
+ else if (m_speed == DVD_PLAYSPEED_PAUSE && iSpeed != DVD_PLAYSPEED_PAUSE)
+ av_read_play(m_pFormatContext);
+ m_speed = iSpeed;
+
+ AVDiscard discard = AVDISCARD_NONE;
+ if (m_speed > 4 * DVD_PLAYSPEED_NORMAL)
+ discard = AVDISCARD_NONKEY;
+ else if (m_speed > 2 * DVD_PLAYSPEED_NORMAL)
+ discard = AVDISCARD_BIDIR;
+ else if (m_speed < DVD_PLAYSPEED_PAUSE)
+ discard = AVDISCARD_NONKEY;
+
+
+ for(unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ {
+ if (m_pFormatContext->streams[i])
+ {
+ if (m_pFormatContext->streams[i]->discard != AVDISCARD_ALL)
+ m_pFormatContext->streams[i]->discard = discard;
+ }
+ }
+}
+
+AVDictionary* CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput()
+{
+ const std::shared_ptr<CDVDInputStreamFFmpeg> input =
+ std::dynamic_pointer_cast<CDVDInputStreamFFmpeg>(m_pInput);
+
+ CURL url = m_pInput->GetURL();
+ AVDictionary* options = nullptr;
+
+ // For a local file we need the following protocol whitelist
+ if (url.GetProtocol().empty() || url.IsProtocol("file"))
+ av_dict_set(&options, "protocol_whitelist", "file,http,https,tcp,tls,crypto", 0);
+
+ if (url.IsProtocol("http") || url.IsProtocol("https"))
+ {
+ std::map<std::string, std::string> protocolOptions;
+ url.GetProtocolOptions(protocolOptions);
+ std::string headers;
+ bool hasUserAgent = false;
+ bool hasCookies = false;
+ for(std::map<std::string, std::string>::const_iterator it = protocolOptions.begin(); it != protocolOptions.end(); ++it)
+ {
+ std::string name = it->first;
+ StringUtils::ToLower(name);
+ const std::string &value = it->second;
+
+ // set any of these ffmpeg options
+ if (name == "seekable" || name == "reconnect" || name == "reconnect_at_eof" ||
+ name == "reconnect_streamed" || name == "reconnect_delay_max" ||
+ name == "icy" || name == "icy_metadata_headers" || name == "icy_metadata_packet")
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding ffmpeg option '{}: {}'",
+ it->first, value);
+ av_dict_set(&options, name.c_str(), value.c_str(), 0);
+ }
+ // map some standard http headers to the ffmpeg related options
+ else if (name == "user-agent")
+ {
+ av_dict_set(&options, "user_agent", value.c_str(), 0);
+ CLog::Log(
+ LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding ffmpeg option 'user_agent: {}'",
+ value);
+ hasUserAgent = true;
+ }
+ else if (name == "cookies")
+ {
+ // in the plural option expect multiple Set-Cookie values. They are passed \n delimited to FFMPEG
+ av_dict_set(&options, "cookies", value.c_str(), 0);
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding ffmpeg option 'cookies: {}'",
+ value);
+ hasCookies = true;
+ }
+ else if (name == "cookie")
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding ffmpeg header value 'cookie: {}'",
+ value);
+ headers.append(it->first).append(": ").append(value).append("\r\n");
+ hasCookies = true;
+ }
+ // other standard headers (see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields) are appended as actual headers
+ else if (name == "accept" || name == "accept-language" || name == "accept-datetime" ||
+ name == "authorization" || name == "cache-control" || name == "connection" || name == "content-md5" ||
+ name == "date" || name == "expect" || name == "forwarded" || name == "from" || name == "if-match" ||
+ name == "if-modified-since" || name == "if-none-match" || name == "if-range" || name == "if-unmodified-since" ||
+ name == "max-forwards" || name == "origin" || name == "pragma" || name == "range" || name == "referer" ||
+ name == "te" || name == "upgrade" || name == "via" || name == "warning" || name == "x-requested-with" ||
+ name == "dnt" || name == "x-forwarded-for" || name == "x-forwarded-host" || name == "x-forwarded-proto" ||
+ name == "front-end-https" || name == "x-http-method-override" || name == "x-att-deviceid" ||
+ name == "x-wap-profile" || name == "x-uidh" || name == "x-csrf-token" || name == "x-request-id" ||
+ name == "x-correlation-id")
+ {
+ if (name == "authorization")
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding custom header option '{}: "
+ "***********'",
+ it->first);
+ }
+ else
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding custom header option '{}: {}'",
+ it->first, value);
+ }
+ headers.append(it->first).append(": ").append(value).append("\r\n");
+ }
+ // Any other headers that need to be sent would be user defined and should be prefixed
+ // by a `!`. We mask these values so we don't log anything we shouldn't
+ else if (name.length() > 0 && name[0] == '!')
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() adding user custom header option "
+ "'{}: ***********'",
+ it->first);
+ headers.append(it->first.substr(1)).append(": ").append(value).append("\r\n");
+ }
+ // for everything else we ignore the headers options if not specified above
+ else
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput() ignoring header option '{}'",
+ it->first);
+ }
+ }
+ if (!hasUserAgent)
+ {
+ // set default xbmc user-agent.
+ av_dict_set(&options, "user_agent", CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent.c_str(), 0);
+ }
+
+ if (!headers.empty())
+ av_dict_set(&options, "headers", headers.c_str(), 0);
+
+ if (!hasCookies)
+ {
+ std::string cookies;
+ if (XFILE::CCurlFile::GetCookies(url, cookies))
+ av_dict_set(&options, "cookies", cookies.c_str(), 0);
+ }
+ }
+
+ if (input)
+ {
+ const std::string host = input->GetProxyHost();
+ if (!host.empty() && input->GetProxyType() == "http")
+ {
+ std::ostringstream urlStream;
+
+ const uint16_t port = input->GetProxyPort();
+ const std::string user = input->GetProxyUser();
+ const std::string password = input->GetProxyPassword();
+
+ urlStream << "http://";
+
+ if (!user.empty()) {
+ urlStream << user;
+ if (!password.empty())
+ urlStream << ":" << password;
+ urlStream << "@";
+ }
+
+ urlStream << host << ':' << port;
+
+ av_dict_set(&options, "http_proxy", urlStream.str().c_str(), 0);
+ }
+
+ // rtsp options
+ if (url.IsProtocol("rtsp"))
+ {
+ CVariant transportProp{m_pInput->GetProperty("rtsp_transport")};
+ if (!transportProp.isNull() &&
+ (transportProp == "tcp" || transportProp == "udp" || transportProp == "udp_multicast"))
+ {
+ CLog::LogF(LOGDEBUG, "GetFFMpegOptionsFromInput() Forcing rtsp transport protocol to '{}'",
+ transportProp.asString());
+ av_dict_set(&options, "rtsp_transport", transportProp.asString().c_str(), 0);
+ }
+ }
+
+ // rtmp options
+ if (url.IsProtocol("rtmp") || url.IsProtocol("rtmpt") ||
+ url.IsProtocol("rtmpe") || url.IsProtocol("rtmpte") ||
+ url.IsProtocol("rtmps"))
+ {
+ static const std::map<std::string,std::string> optionmap =
+ {{{"SWFPlayer", "rtmp_swfurl"},
+ {"swfplayer", "rtmp_swfurl"},
+ {"PageURL", "rtmp_pageurl"},
+ {"pageurl", "rtmp_pageurl"},
+ {"PlayPath", "rtmp_playpath"},
+ {"playpath", "rtmp_playpath"},
+ {"TcUrl", "rtmp_tcurl"},
+ {"tcurl", "rtmp_tcurl"},
+ {"IsLive", "rtmp_live"},
+ {"islive", "rtmp_live"},
+ {"swfurl", "rtmp_swfurl"},
+ {"swfvfy", "rtmp_swfverify"},
+ }};
+
+ for (const auto& it : optionmap)
+ {
+ if (input->GetItem().HasProperty(it.first))
+ {
+ av_dict_set(&options, it.second.c_str(),
+ input->GetItem().GetProperty(it.first).asString().c_str(),0);
+ }
+ }
+
+ CURL tmpUrl = url;
+ std::vector<std::string> opts = StringUtils::Split(tmpUrl.Get(), " ");
+ if (opts.size() > 1) // inline rtmp options
+ {
+ std::string swfurl;
+ bool swfvfy=false;
+ for (size_t i = 1; i < opts.size(); ++i)
+ {
+ std::vector<std::string> value = StringUtils::Split(opts[i], "=", 2);
+ StringUtils::ToLower(value[0]);
+ auto it = optionmap.find(value[0]);
+ if (it != optionmap.end())
+ {
+ if (value[0] == "swfurl" || value[0] == "SWFPlayer")
+ swfurl = value[1];
+ if (value[0] == "swfvfy" && (value[1] == "true" || value[1] == "1"))
+ swfvfy = true;
+ else
+ av_dict_set(&options, it->second.c_str(), value[1].c_str(), 0);
+ }
+ if (swfvfy)
+ av_dict_set(&options, "rtmp_swfverify", swfurl.c_str(), 0);
+ }
+ tmpUrl = CURL(opts.front());
+ }
+ }
+ }
+
+ return options;
+}
+
+double CDVDDemuxFFmpeg::ConvertTimestamp(int64_t pts, int den, int num)
+{
+ if (pts == (int64_t)AV_NOPTS_VALUE)
+ return DVD_NOPTS_VALUE;
+
+ // do calculations in floats as they can easily overflow otherwise
+ // we don't care for having a completely exact timestamp anyway
+ double timestamp = (double)pts * num / den;
+ double starttime = 0.0;
+
+ const std::shared_ptr<CDVDInputStream::IMenus> menuInterface =
+ std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInput);
+ if ((!menuInterface || menuInterface->GetSupportedMenuType() != MenuType::NATIVE) &&
+ m_pFormatContext->start_time != static_cast<int64_t>(AV_NOPTS_VALUE))
+ {
+ starttime = static_cast<double>(m_pFormatContext->start_time) / AV_TIME_BASE;
+ }
+
+ if (m_checkTransportStream)
+ starttime = m_startTime;
+
+ if (!m_bSup)
+ {
+ if (timestamp > starttime || m_checkTransportStream)
+ timestamp -= starttime;
+ // allow for largest possible difference in pts and dts for a single packet
+ else if (timestamp + 0.5 > starttime)
+ timestamp = 0;
+ }
+
+ return timestamp * DVD_TIME_BASE;
+}
+
+DemuxPacket* CDVDDemuxFFmpeg::Read()
+{
+ DemuxPacket* pPacket = NULL;
+ // on some cases where the received packet is invalid we will need to return an empty packet (0 length) otherwise the main loop (in CVideoPlayer)
+ // would consider this the end of stream and stop.
+ bool bReturnEmpty = false;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection); // open lock scope
+ if (m_pFormatContext)
+ {
+ // assume we are not eof
+ if (m_pFormatContext->pb)
+ m_pFormatContext->pb->eof_reached = 0;
+
+ // check for saved packet after a program change
+ if (m_pkt.result < 0)
+ {
+ // keep track if ffmpeg doesn't always set these
+ m_pkt.pkt.size = 0;
+ m_pkt.pkt.data = NULL;
+
+ // timeout reads after 100ms
+ m_timeout.Set(20s);
+ m_pkt.result = av_read_frame(m_pFormatContext, &m_pkt.pkt);
+ m_timeout.SetInfinite();
+ }
+
+ if (m_pkt.result == AVERROR(EINTR) || m_pkt.result == AVERROR(EAGAIN))
+ {
+ // timeout, probably no real error, return empty packet
+ bReturnEmpty = true;
+ }
+ else if (m_pkt.result == AVERROR_EOF)
+ {
+ }
+ else if (m_pkt.result < 0)
+ {
+ Flush();
+ }
+ // check size and stream index for being in a valid range
+ else if (m_pkt.pkt.size < 0 || m_pkt.pkt.stream_index < 0 ||
+ m_pkt.pkt.stream_index >= (int)m_pFormatContext->nb_streams)
+ {
+ // XXX, in some cases ffmpeg returns a negative packet size
+ if (m_pFormatContext->pb && !m_pFormatContext->pb->eof_reached)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::Read() no valid packet");
+ bReturnEmpty = true;
+ Flush();
+ }
+ else
+ CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::Read() returned invalid packet and eof reached");
+
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+ }
+ else
+ {
+ ParsePacket(&m_pkt.pkt);
+
+ if (IsProgramChange())
+ {
+ CLog::Log(LOGINFO, "CDVDDemuxFFmpeg::Read() stream change");
+ av_dump_format(m_pFormatContext, 0, CURL::GetRedacted(m_pInput->GetFileName()).c_str(),
+ 0);
+
+ // update streams
+ CreateStreams(m_program);
+
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
+ pPacket->iStreamId = DMX_SPECIALID_STREAMCHANGE;
+ pPacket->demuxerId = m_demuxerId;
+
+ return pPacket;
+ }
+
+ AVStream* stream = m_pFormatContext->streams[m_pkt.pkt.stream_index];
+
+ if (IsTransportStreamReady())
+ {
+ if (m_program != UINT_MAX)
+ {
+ /* check so packet belongs to selected program */
+ for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes;
+ i++)
+ {
+ if (m_pkt.pkt.stream_index ==
+ (int)m_pFormatContext->programs[m_program]->stream_index[i])
+ {
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(m_pkt.pkt.size);
+ break;
+ }
+ }
+
+ if (!pPacket)
+ bReturnEmpty = true;
+ }
+ else
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(m_pkt.pkt.size);
+ }
+ else
+ bReturnEmpty = true;
+
+ if (pPacket)
+ {
+ if (m_bAVI && stream->codecpar && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ // AVI's always have borked pts, specially if m_pFormatContext->flags includes
+ // AVFMT_FLAG_GENPTS so always use dts
+ m_pkt.pkt.pts = AV_NOPTS_VALUE;
+ }
+
+ // copy contents into our own packet
+ pPacket->iSize = m_pkt.pkt.size;
+
+ // maybe we can avoid a memcpy here by detecting where pkt.destruct is pointing too?
+ if (m_pkt.pkt.data)
+ memcpy(pPacket->pData, m_pkt.pkt.data, pPacket->iSize);
+
+ pPacket->pts =
+ ConvertTimestamp(m_pkt.pkt.pts, stream->time_base.den, stream->time_base.num);
+ pPacket->dts =
+ ConvertTimestamp(m_pkt.pkt.dts, stream->time_base.den, stream->time_base.num);
+ pPacket->duration = DVD_SEC_TO_TIME((double)m_pkt.pkt.duration * stream->time_base.num /
+ stream->time_base.den);
+
+ CDVDDemuxUtils::StoreSideData(pPacket, &m_pkt.pkt);
+
+ CDVDInputStream::IDisplayTime* inputStream = m_pInput->GetIDisplayTime();
+ if (inputStream)
+ {
+ int dispTime = inputStream->GetTime();
+ if (m_displayTime != dispTime)
+ {
+ m_displayTime = dispTime;
+ if (pPacket->dts != DVD_NOPTS_VALUE)
+ {
+ m_dtsAtDisplayTime = pPacket->dts;
+ }
+ }
+ if (m_dtsAtDisplayTime != DVD_NOPTS_VALUE && pPacket->dts != DVD_NOPTS_VALUE)
+ {
+ pPacket->dispTime = m_displayTime;
+ pPacket->dispTime += DVD_TIME_TO_MSEC(pPacket->dts - m_dtsAtDisplayTime);
+ }
+ }
+
+ // used to guess streamlength
+ if (pPacket->dts != DVD_NOPTS_VALUE &&
+ (pPacket->dts > m_currentPts || m_currentPts == DVD_NOPTS_VALUE))
+ m_currentPts = pPacket->dts;
+
+ // store internal id until we know the continuous id presented to player
+ // the stream might not have been created yet
+ pPacket->iStreamId = m_pkt.pkt.stream_index;
+ }
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+ }
+ }
+ } // end of lock scope
+ if (bReturnEmpty && !pPacket)
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
+
+ if (!pPacket)
+ return nullptr;
+
+ // check streams, can we make this a bit more simple?
+ if (pPacket->iStreamId >= 0)
+ {
+ CDemuxStream* stream = GetStream(pPacket->iStreamId);
+ if (!stream ||
+ stream->pPrivate != m_pFormatContext->streams[pPacket->iStreamId] ||
+ stream->codec != m_pFormatContext->streams[pPacket->iStreamId]->codecpar->codec_id)
+ {
+ // content has changed, or stream did not yet exist
+ stream = AddStream(pPacket->iStreamId);
+ }
+ // we already check for a valid m_streams[pPacket->iStreamId] above
+ else if (stream->type == STREAM_AUDIO)
+ {
+ CDemuxStreamAudioFFmpeg* audiostream = dynamic_cast<CDemuxStreamAudioFFmpeg*>(stream);
+ if (audiostream && (audiostream->iChannels != m_pFormatContext->streams[pPacket->iStreamId]->codecpar->channels ||
+ audiostream->iSampleRate != m_pFormatContext->streams[pPacket->iStreamId]->codecpar->sample_rate))
+ {
+ // content has changed
+ stream = AddStream(pPacket->iStreamId);
+ }
+ }
+ else if (stream->type == STREAM_VIDEO)
+ {
+ if (static_cast<CDemuxStreamVideo*>(stream)->iWidth != m_pFormatContext->streams[pPacket->iStreamId]->codecpar->width ||
+ static_cast<CDemuxStreamVideo*>(stream)->iHeight != m_pFormatContext->streams[pPacket->iStreamId]->codecpar->height)
+ {
+ // content has changed
+ stream = AddStream(pPacket->iStreamId);
+ }
+ if (stream && stream->codec == AV_CODEC_ID_H264)
+ pPacket->recoveryPoint = m_seekToKeyFrame;
+ m_seekToKeyFrame = false;
+ }
+ if (!stream)
+ {
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
+ return pPacket;
+ }
+
+ pPacket->iStreamId = stream->uniqueId;
+ pPacket->demuxerId = m_demuxerId;
+ }
+ return pPacket;
+}
+
+bool CDVDDemuxFFmpeg::SeekTime(double time, bool backwards, double* startpts)
+{
+ bool hitEnd = false;
+
+ if (!m_pInput)
+ return false;
+
+ if (time < 0)
+ {
+ time = 0;
+ hitEnd = true;
+ }
+
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+
+ CDVDInputStream::IPosTime* ist = m_pInput->GetIPosTime();
+ if (ist)
+ {
+ if (!ist->PosTime(static_cast<int>(time)))
+ return false;
+
+ if (startpts)
+ *startpts = DVD_NOPTS_VALUE;
+
+ Flush();
+
+ return true;
+ }
+
+ if (!m_pInput->Seek(0, SEEK_POSSIBLE) &&
+ !m_pInput->IsStreamType(DVDSTREAM_TYPE_FFMPEG))
+ {
+ CLog::Log(LOGDEBUG, "{} - input stream reports it is not seekable", __FUNCTION__);
+ return false;
+ }
+
+ int64_t seek_pts = (int64_t)time * (AV_TIME_BASE / 1000);
+ bool ismp3 = m_pFormatContext->iformat && (strcmp(m_pFormatContext->iformat->name, "mp3") == 0);
+
+ if (m_checkTransportStream)
+ {
+ XbmcThreads::EndTime<> timer(1000ms);
+
+ while (!IsTransportStreamReady())
+ {
+ DemuxPacket* pkt = Read();
+ if (pkt)
+ CDVDDemuxUtils::FreeDemuxPacket(pkt);
+ else
+ KODI::TIME::Sleep(10ms);
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+
+ if (timer.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::{} - Timed out waiting for video to be ready",
+ __FUNCTION__);
+ return false;
+ }
+ }
+
+ AVStream* st = m_pFormatContext->streams[m_seekStream];
+ seek_pts = av_rescale(static_cast<int64_t>(m_startTime + time / 1000), st->time_base.den,
+ st->time_base.num);
+ }
+ else if (m_pFormatContext->start_time != (int64_t)AV_NOPTS_VALUE && !ismp3 && !m_bSup)
+ seek_pts += m_pFormatContext->start_time;
+
+ int ret;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ ret = av_seek_frame(m_pFormatContext, m_seekStream, seek_pts, backwards ? AVSEEK_FLAG_BACKWARD : 0);
+
+ if (ret < 0)
+ {
+ int64_t starttime = m_pFormatContext->start_time;
+ if (m_checkTransportStream)
+ {
+ AVStream* st = m_pFormatContext->streams[m_seekStream];
+ starttime =
+ av_rescale(static_cast<int64_t>(m_startTime), st->time_base.num, st->time_base.den);
+ }
+
+ // demuxer can return failure, if seeking behind eof
+ if (m_pFormatContext->duration &&
+ seek_pts >= (m_pFormatContext->duration + starttime))
+ {
+ // force eof
+ // files of realtime streams may grow
+ if (!m_pInput->IsRealtime())
+ m_pInput->Close();
+ else
+ ret = 0;
+ }
+ else if (m_pInput->IsEOF())
+ ret = 0;
+ }
+
+ if (ret >= 0)
+ {
+ if (m_pFormatContext->iformat->read_seek)
+ m_seekToKeyFrame = true;
+
+ UpdateCurrentPTS();
+ }
+ }
+
+ if (m_currentPts == DVD_NOPTS_VALUE)
+ CLog::Log(LOGDEBUG, "{} - unknown position after seek", __FUNCTION__);
+ else
+ CLog::Log(LOGDEBUG, "{} - seek ended up on time {}", __FUNCTION__,
+ (int)(m_currentPts / DVD_TIME_BASE * 1000));
+
+ // in this case the start time is requested time
+ if (startpts)
+ *startpts = DVD_MSEC_TO_TIME(time);
+
+ if (ret >= 0)
+ {
+ if (!hitEnd)
+ return true;
+ else
+ return false;
+ }
+ else
+ return false;
+}
+
+bool CDVDDemuxFFmpeg::SeekByte(int64_t pos)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ int ret = av_seek_frame(m_pFormatContext, -1, pos, AVSEEK_FLAG_BYTE);
+
+ if (ret >= 0)
+ UpdateCurrentPTS();
+
+ m_pkt.result = -1;
+ av_packet_unref(&m_pkt.pkt);
+
+ return (ret >= 0);
+}
+
+void CDVDDemuxFFmpeg::UpdateCurrentPTS()
+{
+ m_currentPts = DVD_NOPTS_VALUE;
+
+ int idx = av_find_default_stream_index(m_pFormatContext);
+ if (idx >= 0)
+ {
+ AVStream* stream = m_pFormatContext->streams[idx];
+ if (stream && stream->cur_dts != (int64_t)AV_NOPTS_VALUE)
+ {
+ double ts = ConvertTimestamp(stream->cur_dts, stream->time_base.den, stream->time_base.num);
+ m_currentPts = ts;
+ }
+ }
+}
+
+int CDVDDemuxFFmpeg::GetStreamLength()
+{
+ if (!m_pFormatContext)
+ return 0;
+
+ if (m_pFormatContext->duration < 0 ||
+ m_pFormatContext->duration == AV_NOPTS_VALUE)
+ return 0;
+
+ return (int)(m_pFormatContext->duration / (AV_TIME_BASE / 1000));
+}
+
+/**
+ * @brief Finds stream based on unique id
+ */
+CDemuxStream* CDVDDemuxFFmpeg::GetStream(int iStreamId) const
+{
+ auto it = m_streams.find(iStreamId);
+ if (it != m_streams.end())
+ return it->second;
+
+ return nullptr;
+}
+
+std::vector<CDemuxStream*> CDVDDemuxFFmpeg::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ streams.reserve(m_streams.size());
+ for (auto& iter : m_streams)
+ streams.push_back(iter.second);
+
+ return streams;
+}
+
+int CDVDDemuxFFmpeg::GetNrOfStreams() const
+{
+ return static_cast<int>(m_streams.size());
+}
+
+int CDVDDemuxFFmpeg::GetPrograms(std::vector<ProgramInfo>& programs)
+{
+ programs.clear();
+ if (!m_pFormatContext || m_pFormatContext->nb_programs <= 1)
+ return 0;
+
+ for (unsigned int i = 0; i < m_pFormatContext->nb_programs; i++)
+ {
+ std::ostringstream os;
+ ProgramInfo prog;
+ prog.id = i;
+ os << i;
+ prog.name = os.str();
+ if (i == m_program)
+ prog.playing = true;
+
+ if (!m_pFormatContext->programs[i]->metadata)
+ continue;
+
+ AVDictionaryEntry* tag = av_dict_get(m_pFormatContext->programs[i]->metadata, "", nullptr, AV_DICT_IGNORE_SUFFIX);
+ while (tag)
+ {
+ os << " - " << tag->key << ": " << tag->value;
+ tag = av_dict_get(m_pFormatContext->programs[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX);
+ }
+ prog.name = os.str();
+ programs.push_back(prog);
+ }
+ return static_cast<int>(programs.size());
+}
+
+void CDVDDemuxFFmpeg::SetProgram(int progId)
+{
+ m_newProgram = progId;
+}
+
+double CDVDDemuxFFmpeg::SelectAspect(AVStream* st, bool& forced)
+{
+ // trust matroska container
+ if (m_bMatroska && st->sample_aspect_ratio.num != 0)
+ {
+ forced = true;
+ double dar = av_q2d(st->sample_aspect_ratio);
+ // for stereo modes, use codec aspect ratio
+ AVDictionaryEntry* entry = av_dict_get(st->metadata, "stereo_mode", NULL, 0);
+ if (entry)
+ {
+ if (strcmp(entry->value, "left_right") == 0 || strcmp(entry->value, "right_left") == 0)
+ dar /= 2;
+ else if (strcmp(entry->value, "top_bottom") == 0 || strcmp(entry->value, "bottom_top") == 0)
+ dar *= 2;
+ }
+ return dar;
+ }
+
+ /* if stream aspect is 1:1 or 0:0 use codec aspect */
+ if ((st->sample_aspect_ratio.den == 1 || st->sample_aspect_ratio.den == 0) &&
+ (st->sample_aspect_ratio.num == 1 || st->sample_aspect_ratio.num == 0) &&
+ st->codecpar->sample_aspect_ratio.num != 0)
+ {
+ forced = false;
+ return av_q2d(st->codecpar->sample_aspect_ratio);
+ }
+
+ if (st->sample_aspect_ratio.num != 0)
+ {
+ forced = true;
+ return av_q2d(st->sample_aspect_ratio);
+ }
+
+ forced = false;
+ return 0.0;
+}
+
+void CDVDDemuxFFmpeg::CreateStreams(unsigned int program)
+{
+ DisposeStreams();
+
+ // add the ffmpeg streams to our own stream map
+ if (m_pFormatContext->nb_programs)
+ {
+ // check if desired program is available
+ if (program < m_pFormatContext->nb_programs)
+ {
+ m_program = program;
+ m_streamsInProgram = m_pFormatContext->programs[program]->nb_stream_indexes;
+ m_pFormatContext->programs[program]->discard = AVDISCARD_NONE;
+ }
+ else
+ m_program = UINT_MAX;
+
+ // look for first non empty stream and discard nonselected programs
+ for (unsigned int i = 0; i < m_pFormatContext->nb_programs; i++)
+ {
+ if (m_program == UINT_MAX && m_pFormatContext->programs[i]->nb_stream_indexes > 0)
+ {
+ m_program = i;
+ }
+
+ if (i != m_program)
+ m_pFormatContext->programs[i]->discard = AVDISCARD_ALL;
+ }
+ if (m_program != UINT_MAX)
+ {
+ m_pFormatContext->programs[m_program]->discard = AVDISCARD_NONE;
+
+ // add streams from selected program
+ for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
+ {
+ int streamIdx = m_pFormatContext->programs[m_program]->stream_index[i];
+ m_pFormatContext->streams[streamIdx]->discard = AVDISCARD_NONE;
+ AddStream(streamIdx);
+ }
+
+ // discard all unneeded streams
+ for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ {
+ m_pFormatContext->streams[i]->discard = AVDISCARD_NONE;
+ if (GetStream(i) == nullptr)
+ m_pFormatContext->streams[i]->discard = AVDISCARD_ALL;
+ }
+ }
+ }
+ else
+ m_program = UINT_MAX;
+
+ // if there were no programs or they were all empty, add all streams
+ if (m_program == UINT_MAX)
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ AddStream(i);
+ }
+}
+
+void CDVDDemuxFFmpeg::DisposeStreams()
+{
+ std::map<int, CDemuxStream*>::iterator it;
+ for(it = m_streams.begin(); it != m_streams.end(); ++it)
+ delete it->second;
+ m_streams.clear();
+ m_parsers.clear();
+}
+
+CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx)
+{
+ AVStream* pStream = m_pFormatContext->streams[streamIdx];
+ if (pStream && pStream->discard != AVDISCARD_ALL)
+ {
+ // Video (mp4) from GoPro cameras can have a 'meta' track used for a file repair containing
+ // 'fdsc' data, this is also called the SOS track.
+ if (pStream->codecpar->codec_tag == MKTAG('f','d','s','c'))
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream - discarding fdsc stream");
+ pStream->discard = AVDISCARD_ALL;
+ return nullptr;
+ }
+
+ CDemuxStream* stream = nullptr;
+
+ switch (pStream->codecpar->codec_type)
+ {
+ case AVMEDIA_TYPE_AUDIO:
+ {
+ CDemuxStreamAudioFFmpeg* st = new CDemuxStreamAudioFFmpeg(pStream);
+ stream = st;
+ st->iChannels = pStream->codecpar->channels;
+ st->iSampleRate = pStream->codecpar->sample_rate;
+ st->iBlockAlign = pStream->codecpar->block_align;
+ st->iBitRate = static_cast<int>(pStream->codecpar->bit_rate);
+ st->iBitsPerSample = pStream->codecpar->bits_per_raw_sample;
+ st->iChannelLayout = pStream->codecpar->channel_layout;
+ char buf[32] = {};
+ av_get_channel_layout_string(buf, 31, st->iChannels, st->iChannelLayout);
+ st->m_channelLayoutName = buf;
+ if (st->iBitsPerSample == 0)
+ st->iBitsPerSample = pStream->codecpar->bits_per_coded_sample;
+
+ if (av_dict_get(pStream->metadata, "title", NULL, 0))
+ st->m_description = av_dict_get(pStream->metadata, "title", NULL, 0)->value;
+
+ break;
+ }
+ case AVMEDIA_TYPE_VIDEO:
+ {
+ CDemuxStreamVideoFFmpeg* st = new CDemuxStreamVideoFFmpeg(pStream);
+ stream = st;
+ if (strcmp(m_pFormatContext->iformat->name, "flv") == 0)
+ st->bVFR = true;
+ else
+ st->bVFR = false;
+
+ // never trust pts in avi files with h264.
+ if (m_bAVI && pStream->codecpar->codec_id == AV_CODEC_ID_H264)
+ st->bPTSInvalid = true;
+
+ AVRational r_frame_rate = pStream->r_frame_rate;
+
+ //average fps is more accurate for mkv files
+ if (m_bMatroska && pStream->avg_frame_rate.den && pStream->avg_frame_rate.num)
+ {
+ st->iFpsRate = pStream->avg_frame_rate.num;
+ st->iFpsScale = pStream->avg_frame_rate.den;
+ }
+ else if (r_frame_rate.den && r_frame_rate.num)
+ {
+ st->iFpsRate = r_frame_rate.num;
+ st->iFpsScale = r_frame_rate.den;
+ }
+ else
+ {
+ st->iFpsRate = 0;
+ st->iFpsScale = 0;
+ }
+
+ if (pStream->codec_info_nb_frames > 0 &&
+ pStream->codec_info_nb_frames <= 2 &&
+ m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ CLog::Log(LOGDEBUG, "{} - fps may be unreliable since ffmpeg decoded only {} frame(s)",
+ __FUNCTION__, pStream->codec_info_nb_frames);
+ st->iFpsRate = 0;
+ st->iFpsScale = 0;
+ }
+
+ st->iWidth = pStream->codecpar->width;
+ st->iHeight = pStream->codecpar->height;
+ st->fAspect = SelectAspect(pStream, st->bForcedAspect);
+ if (pStream->codecpar->height)
+ st->fAspect *= (double)pStream->codecpar->width / pStream->codecpar->height;
+ st->iOrientation = 0;
+ st->iBitsPerPixel = pStream->codecpar->bits_per_coded_sample;
+ st->iBitRate = static_cast<int>(pStream->codecpar->bit_rate);
+ st->bitDepth = 8;
+ const AVPixFmtDescriptor* desc =
+ av_pix_fmt_desc_get(static_cast<AVPixelFormat>(pStream->codecpar->format));
+ if (desc != nullptr)
+ st->bitDepth = desc->comp[0].depth;
+
+ st->colorPrimaries = pStream->codecpar->color_primaries;
+ st->colorSpace = pStream->codecpar->color_space;
+ st->colorTransferCharacteristic = pStream->codecpar->color_trc;
+ st->colorRange = pStream->codecpar->color_range;
+ st->hdr_type = DetermineHdrType(pStream);
+
+ int size = 0;
+ uint8_t* side_data = nullptr;
+
+ side_data = av_stream_get_side_data(pStream, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, &size);
+ if (side_data && size)
+ {
+ st->masteringMetaData = std::make_shared<AVMasteringDisplayMetadata>(
+ *reinterpret_cast<AVMasteringDisplayMetadata*>(side_data));
+ }
+
+ side_data = av_stream_get_side_data(pStream, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, &size);
+ if (side_data && size)
+ {
+ st->contentLightMetaData = std::make_shared<AVContentLightMetadata>(
+ *reinterpret_cast<AVContentLightMetadata*>(side_data));
+ }
+
+ AVDictionaryEntry* rtag = av_dict_get(pStream->metadata, "rotate", NULL, 0);
+ if (rtag)
+ st->iOrientation = atoi(rtag->value);
+
+ // detect stereoscopic mode
+ std::string stereoMode = GetStereoModeFromMetadata(pStream->metadata);
+ // check for metadata in file if detection in stream failed
+ if (stereoMode.empty())
+ stereoMode = GetStereoModeFromMetadata(m_pFormatContext->metadata);
+ if (!stereoMode.empty())
+ st->stereo_mode = stereoMode;
+
+
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ if (pStream->codecpar->codec_id == AV_CODEC_ID_PROBE)
+ {
+ // fix MPEG-1/MPEG-2 video stream probe returning AV_CODEC_ID_PROBE for still frames.
+ // ffmpeg issue 1871, regression from ffmpeg r22831.
+ if ((pStream->id & 0xF0) == 0xE0)
+ {
+ pStream->codecpar->codec_id = AV_CODEC_ID_MPEG2VIDEO;
+ pStream->codecpar->codec_tag = MKTAG('M','P','2','V');
+ CLog::Log(LOGERROR, "{} - AV_CODEC_ID_PROBE detected, forcing AV_CODEC_ID_MPEG2VIDEO",
+ __FUNCTION__);
+ }
+ }
+ }
+ if (av_dict_get(pStream->metadata, "title", NULL, 0))
+ st->m_description = av_dict_get(pStream->metadata, "title", NULL, 0)->value;
+
+ break;
+ }
+ case AVMEDIA_TYPE_DATA:
+ {
+ stream = new CDemuxStream();
+ stream->type = STREAM_DATA;
+ break;
+ }
+ case AVMEDIA_TYPE_SUBTITLE:
+ {
+ if (pStream->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_TELETEXTENABLED))
+ {
+ CDemuxStreamTeletext* st = new CDemuxStreamTeletext();
+ stream = st;
+ stream->type = STREAM_TELETEXT;
+ break;
+ }
+ else
+ {
+ CDemuxStreamSubtitleFFmpeg* st = new CDemuxStreamSubtitleFFmpeg(pStream);
+ stream = st;
+
+ if (av_dict_get(pStream->metadata, "title", NULL, 0))
+ st->m_description = av_dict_get(pStream->metadata, "title", NULL, 0)->value;
+
+ break;
+ }
+ }
+ case AVMEDIA_TYPE_ATTACHMENT:
+ {
+ // MKV attachments. Only bothering with fonts for now.
+ AVDictionaryEntry* attachmentMimetype =
+ av_dict_get(pStream->metadata, "mimetype", nullptr, 0);
+
+ if (pStream->codecpar->codec_id == AV_CODEC_ID_TTF ||
+ pStream->codecpar->codec_id == AV_CODEC_ID_OTF || AttachmentIsFont(attachmentMimetype))
+ {
+ // Temporary fonts are extracted to the temporary fonts path
+ //! @todo: temporary font file management should be completely
+ //! removed, by sending font data to the subtitle renderer and
+ //! using libass ass_add_font to add the fonts directly in memory.
+ std::string filePath{UTILS::FONT::FONTPATH::TEMP};
+ XFILE::CDirectory::Create(filePath);
+
+ AVDictionaryEntry* nameTag = av_dict_get(pStream->metadata, "filename", NULL, 0);
+ if (nameTag)
+ {
+ filePath += CUtil::MakeLegalFileName(nameTag->value, LEGAL_WIN32_COMPAT);
+ XFILE::CFile file;
+ if (pStream->codecpar->extradata && file.OpenForWrite(filePath))
+ {
+ if (file.Write(pStream->codecpar->extradata, pStream->codecpar->extradata_size) !=
+ pStream->codecpar->extradata_size)
+ {
+ file.Close();
+ XFILE::CFile::Delete(filePath);
+ CLog::LogF(LOGDEBUG, "Error saving font file \"{}\"", filePath);
+ }
+ }
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Attached font has no name");
+ }
+ }
+ stream = new CDemuxStream();
+ stream->type = STREAM_NONE;
+ break;
+ }
+ default:
+ {
+ // if analyzing streams is skipped, unknown streams may become valid later
+ if (m_streaminfo && IsTransportStreamReady())
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream - discarding unknown stream with id: {}",
+ pStream->index);
+ pStream->discard = AVDISCARD_ALL;
+ return nullptr;
+ }
+ stream = new CDemuxStream();
+ stream->type = STREAM_NONE;
+ }
+ }
+
+ // generic stuff
+ if (pStream->duration != (int64_t)AV_NOPTS_VALUE)
+ stream->iDuration = (int)((pStream->duration / AV_TIME_BASE) & 0xFFFFFFFF);
+
+ stream->codec = pStream->codecpar->codec_id;
+ stream->codec_fourcc = pStream->codecpar->codec_tag;
+ stream->profile = pStream->codecpar->profile;
+ stream->level = pStream->codecpar->level;
+
+ stream->source = STREAM_SOURCE_DEMUX;
+ stream->pPrivate = pStream;
+ stream->flags = (StreamFlags)pStream->disposition;
+
+ AVDictionaryEntry* langTag = av_dict_get(pStream->metadata, "language", NULL, 0);
+ if (!langTag)
+ {
+ // only for avi audio streams
+ if ((strcmp(m_pFormatContext->iformat->name, "avi") == 0) && (pStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
+ {
+ // only defined for streams 1 to 9
+ if ((streamIdx > 0) && (streamIdx < 10))
+ {
+ // search for language information in RIFF-Header ("IAS1": first language - "IAS9": ninth language)
+ char riff_tag_string[5] = {'I', 'A', 'S', (char)(streamIdx + '0'), '\0'};
+ langTag = av_dict_get(m_pFormatContext->metadata, riff_tag_string, NULL, 0);
+ if (!langTag && (streamIdx == 1))
+ {
+ // search for language information in RIFF-Header ("ILNG": language)
+ langTag = av_dict_get(m_pFormatContext->metadata, "language", NULL, 0);
+ }
+ }
+ }
+ }
+ if (langTag)
+ {
+ stream->language = std::string(langTag->value, 3);
+ //! @FIXME: Matroska v4 support BCP-47 language code with LanguageIETF element
+ //! that have the priority over the Language element, but this is not currently
+ //! implemented in to ffmpeg library. Since ffmpeg read only the Language element
+ //! all tracks will be identified with same language (of Language element).
+ //! As workaround to allow set the right language code we provide the possibility
+ //! to set the language code in the title field, this allow to kodi to recognize
+ //! the right language and select the right track to be played at playback starts.
+ AVDictionaryEntry* title = av_dict_get(pStream->metadata, "title", NULL, 0);
+ if (title && title->value)
+ {
+ const std::string langCode = g_LangCodeExpander.FindLanguageCodeWithSubtag(title->value);
+ if (!langCode.empty())
+ stream->language = langCode;
+ }
+ }
+
+ if (stream->type != STREAM_NONE && pStream->codecpar->extradata && pStream->codecpar->extradata_size > 0)
+ {
+ stream->ExtraSize = pStream->codecpar->extradata_size;
+ stream->ExtraData = std::make_unique<uint8_t[]>(pStream->codecpar->extradata_size);
+ memcpy(stream->ExtraData.get(), pStream->codecpar->extradata,
+ pStream->codecpar->extradata_size);
+ }
+
+#ifdef HAVE_LIBBLURAY
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY))
+ {
+ // UHD BD have a secondary video stream called by Dolby as enhancement layer.
+ // This is not used by streaming services and devices (ATV, Nvidia Shield, XONE).
+ if (pStream->id == 0x1015)
+ {
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream - discarding Dolby Vision stream");
+ pStream->discard = AVDISCARD_ALL;
+ delete stream;
+ return nullptr;
+ }
+ stream->dvdNavId = pStream->id;
+
+ auto it = std::find_if(m_streams.begin(), m_streams.end(),
+ [&stream](const std::pair<int, CDemuxStream*>& v)
+ {return (v.second->dvdNavId == stream->dvdNavId) && (v.second->type == stream->type); });
+
+ if (it != m_streams.end())
+ {
+ if (stream->codec == AV_CODEC_ID_AC3 && it->second->codec == AV_CODEC_ID_TRUEHD)
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream - discarding duplicated bluray stream (truehd ac3 core)");
+ else
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream - discarding duplicate bluray stream {}",
+ stream->codecName);
+
+ pStream->discard = AVDISCARD_ALL;
+ delete stream;
+ return nullptr;
+ }
+ std::static_pointer_cast<CDVDInputStreamBluray>(m_pInput)->GetStreamInfo(pStream->id, stream->language);
+ }
+#endif
+ if (m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ // this stuff is really only valid for dvd's.
+ // this is so that the physicalid matches the
+ // id's reported from libdvdnav
+ switch (stream->codec)
+ {
+ case AV_CODEC_ID_AC3:
+ stream->dvdNavId = pStream->id - 128;
+ break;
+ case AV_CODEC_ID_DTS:
+ stream->dvdNavId = pStream->id - 136;
+ break;
+ case AV_CODEC_ID_MP2:
+ stream->dvdNavId = pStream->id - 448;
+ break;
+ case AV_CODEC_ID_PCM_S16BE:
+ stream->dvdNavId = pStream->id - 160;
+ break;
+ case AV_CODEC_ID_DVD_SUBTITLE:
+ stream->dvdNavId = pStream->id - 0x20;
+ break;
+ default:
+ stream->dvdNavId = pStream->id & 0x1f;
+ break;
+ }
+ }
+
+ stream->uniqueId = pStream->index;
+ stream->demuxerId = m_demuxerId;
+
+ AddStream(stream->uniqueId, stream);
+ return stream;
+ }
+ else
+ return nullptr;
+}
+
+/**
+ * @brief Adds or updates a demux stream based in ffmpeg id
+ */
+void CDVDDemuxFFmpeg::AddStream(int streamIdx, CDemuxStream* stream)
+{
+ std::pair<std::map<int, CDemuxStream*>::iterator, bool> res;
+
+ res = m_streams.insert(std::make_pair(streamIdx, stream));
+ if (res.second)
+ {
+ /* was new stream */
+ stream->uniqueId = streamIdx;
+ }
+ else
+ {
+ delete res.first->second;
+ res.first->second = stream;
+ }
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream ID: {}", streamIdx);
+}
+
+
+std::string CDVDDemuxFFmpeg::GetFileName()
+{
+ if (m_pInput)
+ return m_pInput->GetFileName();
+ else
+ return "";
+}
+
+int CDVDDemuxFFmpeg::GetChapterCount()
+{
+ std::shared_ptr<CDVDInputStream::IChapter> ich = std::dynamic_pointer_cast<CDVDInputStream::IChapter>(m_pInput);
+ if (ich)
+ return ich->GetChapterCount();
+
+ if (m_pFormatContext == NULL)
+ return 0;
+
+ return m_pFormatContext->nb_chapters;
+}
+
+int CDVDDemuxFFmpeg::GetChapter()
+{
+ std::shared_ptr<CDVDInputStream::IChapter> ich = std::dynamic_pointer_cast<CDVDInputStream::IChapter>(m_pInput);
+ if (ich)
+ return ich->GetChapter();
+
+ if (m_pFormatContext == NULL
+ || m_currentPts == DVD_NOPTS_VALUE)
+ return 0;
+
+ for(unsigned i = 0; i < m_pFormatContext->nb_chapters; i++)
+ {
+ AVChapter* chapter = m_pFormatContext->chapters[i];
+ if (m_currentPts >= ConvertTimestamp(chapter->start, chapter->time_base.den, chapter->time_base.num)
+ && m_currentPts < ConvertTimestamp(chapter->end, chapter->time_base.den, chapter->time_base.num))
+ return i + 1;
+ }
+
+ return 0;
+}
+
+void CDVDDemuxFFmpeg::GetChapterName(std::string& strChapterName, int chapterIdx)
+{
+ if (chapterIdx <= 0 || chapterIdx > GetChapterCount())
+ chapterIdx = GetChapter();
+ std::shared_ptr<CDVDInputStream::IChapter> ich = std::dynamic_pointer_cast<CDVDInputStream::IChapter>(m_pInput);
+ if (ich)
+ ich->GetChapterName(strChapterName, chapterIdx);
+ else
+ {
+ if (chapterIdx <= 0)
+ return;
+
+ AVDictionaryEntry* titleTag = av_dict_get(m_pFormatContext->chapters[chapterIdx - 1]->metadata,
+ "title", NULL, 0);
+ if (titleTag)
+ strChapterName = titleTag->value;
+ }
+}
+
+int64_t CDVDDemuxFFmpeg::GetChapterPos(int chapterIdx)
+{
+ if (chapterIdx <= 0 || chapterIdx > GetChapterCount())
+ chapterIdx = GetChapter();
+ if (chapterIdx <= 0)
+ return 0;
+
+ std::shared_ptr<CDVDInputStream::IChapter> ich = std::dynamic_pointer_cast<CDVDInputStream::IChapter>(m_pInput);
+ if (ich)
+ return ich->GetChapterPos(chapterIdx);
+
+ return static_cast<int64_t>(m_pFormatContext->chapters[chapterIdx - 1]->start * av_q2d(m_pFormatContext->chapters[chapterIdx - 1]->time_base));
+}
+
+bool CDVDDemuxFFmpeg::SeekChapter(int chapter, double* startpts)
+{
+ if (chapter < 1)
+ chapter = 1;
+
+ std::shared_ptr<CDVDInputStream::IChapter> ich = std::dynamic_pointer_cast<CDVDInputStream::IChapter>(m_pInput);
+ if (ich)
+ {
+ CLog::Log(LOGDEBUG, "{} - chapter seeking using input stream", __FUNCTION__);
+ if (!ich->SeekChapter(chapter))
+ return false;
+
+ if (startpts)
+ {
+ *startpts = DVD_SEC_TO_TIME(static_cast<double>(ich->GetChapterPos(chapter)));
+ }
+
+ Flush();
+ return true;
+ }
+
+ if (m_pFormatContext == NULL)
+ return false;
+
+ if (chapter < 1 || chapter > (int)m_pFormatContext->nb_chapters)
+ return false;
+
+ AVChapter* ch = m_pFormatContext->chapters[chapter - 1];
+ double dts = ConvertTimestamp(ch->start, ch->time_base.den, ch->time_base.num);
+ return SeekTime(DVD_TIME_TO_MSEC(dts), true, startpts);
+}
+
+std::string CDVDDemuxFFmpeg::GetStreamCodecName(int iStreamId)
+{
+ CDemuxStream* stream = GetStream(iStreamId);
+ std::string strName;
+ if (stream)
+ {
+ /* use profile to determine the DTS type */
+ if (stream->codec == AV_CODEC_ID_DTS)
+ {
+ if (stream->profile == FF_PROFILE_DTS_HD_MA)
+ strName = "dtshd_ma";
+ else if (stream->profile == FF_PROFILE_DTS_HD_HRA)
+ strName = "dtshd_hra";
+ else
+ strName = "dca";
+
+ return strName;
+ }
+
+ AVCodec* codec = avcodec_find_decoder(stream->codec);
+ if (codec)
+ strName = avcodec_get_name(codec->id);
+ }
+ return strName;
+}
+
+bool CDVDDemuxFFmpeg::IsProgramChange()
+{
+ if (m_program == UINT_MAX)
+ return false;
+
+ if (m_program == 0 && !m_pFormatContext->nb_programs)
+ return false;
+
+ if (m_initialProgramNumber != UINT_MAX)
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->nb_programs; ++i)
+ {
+ if (m_pFormatContext->programs[i]->program_num == static_cast<int>(m_initialProgramNumber))
+ {
+ m_newProgram = i;
+ m_initialProgramNumber = UINT_MAX;
+ break;
+ }
+ }
+ if (m_initialProgramNumber != UINT_MAX)
+ return false;
+ }
+
+ if (m_program != m_newProgram)
+ {
+ m_program = m_newProgram;
+ return true;
+ }
+
+ if (m_pFormatContext->programs[m_program]->nb_stream_indexes != m_streamsInProgram)
+ return true;
+
+ if (m_program >= m_pFormatContext->nb_programs)
+ return true;
+
+ for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
+ {
+ int idx = m_pFormatContext->programs[m_program]->stream_index[i];
+ if (m_pFormatContext->streams[idx]->discard >= AVDISCARD_ALL)
+ continue;
+ CDemuxStream* stream = GetStream(idx);
+ if (!stream)
+ return true;
+ if (m_pFormatContext->streams[idx]->codecpar->codec_id != stream->codec)
+ return true;
+ if (m_pFormatContext->streams[idx]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ CDemuxStreamAudioFFmpeg* audiostream = dynamic_cast<CDemuxStreamAudioFFmpeg*>(stream);
+ if (audiostream &&
+ m_pFormatContext->streams[idx]->codecpar->channels != audiostream->iChannels)
+ {
+ return true;
+ }
+ }
+ if (m_pFormatContext->streams[idx]->codecpar->extradata_size != static_cast<int>(stream->ExtraSize))
+ return true;
+ }
+ return false;
+}
+
+unsigned int CDVDDemuxFFmpeg::HLSSelectProgram()
+{
+ unsigned int prog = UINT_MAX;
+
+ int bandwidth = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_NETWORK_BANDWIDTH) * 1000;
+ if (bandwidth <= 0)
+ bandwidth = INT_MAX;
+
+ int selectedBitrate = 0;
+ int selectedRes = 0;
+ for (unsigned int i = 0; i < m_pFormatContext->nb_programs; ++i)
+ {
+ int strBitrate = 0;
+ AVDictionaryEntry* tag = av_dict_get(m_pFormatContext->programs[i]->metadata, "variant_bitrate", NULL, 0);
+ if (tag)
+ strBitrate = atoi(tag->value);
+ else
+ continue;
+
+ int strRes = 0;
+ for (unsigned int j = 0; j < m_pFormatContext->programs[i]->nb_stream_indexes; j++)
+ {
+ int idx = m_pFormatContext->programs[i]->stream_index[j];
+ AVStream* pStream = m_pFormatContext->streams[idx];
+ if (pStream && pStream->codecpar &&
+ pStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ strRes = pStream->codecpar->width * pStream->codecpar->height;
+ }
+ }
+
+ if ((strRes && strRes < selectedRes) && selectedBitrate < bandwidth)
+ continue;
+
+ bool want = false;
+
+ if (strBitrate <= bandwidth)
+ {
+ if (strBitrate > selectedBitrate || strRes > selectedRes)
+ want = true;
+ }
+ else
+ {
+ if (strBitrate < selectedBitrate)
+ want = true;
+ }
+
+ if (want)
+ {
+ selectedRes = strRes;
+ selectedBitrate = strBitrate;
+ prog = i;
+ }
+ }
+ return prog;
+}
+
+std::string CDVDDemuxFFmpeg::GetStereoModeFromMetadata(AVDictionary* pMetadata)
+{
+ std::string stereoMode;
+ AVDictionaryEntry* tag = NULL;
+
+ // matroska
+ tag = av_dict_get(pMetadata, "stereo_mode", NULL, 0);
+ if (tag && tag->value)
+ stereoMode = tag->value;
+
+ // asf / wmv
+ if (stereoMode.empty())
+ {
+ tag = av_dict_get(pMetadata, "Stereoscopic", NULL, 0);
+ if (tag && tag->value)
+ {
+ tag = av_dict_get(pMetadata, "StereoscopicLayout", NULL, 0);
+ if (tag && tag->value)
+ stereoMode = ConvertCodecToInternalStereoMode(tag->value, WmvToInternalStereoModeMap);
+ }
+ }
+
+ return stereoMode;
+}
+
+std::string CDVDDemuxFFmpeg::ConvertCodecToInternalStereoMode(const std::string &mode, const StereoModeConversionMap* conversionMap)
+{
+ size_t i = 0;
+ while (conversionMap[i].name)
+ {
+ if (mode == conversionMap[i].name)
+ return conversionMap[i].mode;
+ i++;
+ }
+ return "";
+}
+
+void CDVDDemuxFFmpeg::ParsePacket(AVPacket* pkt)
+{
+ AVStream* st = m_pFormatContext->streams[pkt->stream_index];
+
+ if (st && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ auto parser = m_parsers.find(st->index);
+ if (parser == m_parsers.end())
+ {
+ m_parsers.insert(std::make_pair(st->index,
+ std::unique_ptr<CDemuxParserFFmpeg>(new CDemuxParserFFmpeg())));
+ parser = m_parsers.find(st->index);
+
+ parser->second->m_parserCtx = av_parser_init(st->codecpar->codec_id);
+
+ AVCodec* codec = avcodec_find_decoder(st->codecpar->codec_id);
+ if (codec == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - can't find decoder", __FUNCTION__);
+ m_parsers.erase(parser);
+ return;
+ }
+ parser->second->m_codecCtx = avcodec_alloc_context3(codec);
+ }
+
+ CDemuxStream* stream = GetStream(st->index);
+ if (!stream)
+ return;
+
+ if (parser->second->m_parserCtx &&
+ parser->second->m_parserCtx->parser &&
+ parser->second->m_parserCtx->parser->split &&
+ !st->codecpar->extradata)
+ {
+ int i = parser->second->m_parserCtx->parser->split(parser->second->m_codecCtx, pkt->data, pkt->size);
+ if (i > 0 && i < FF_MAX_EXTRADATA_SIZE)
+ {
+ st->codecpar->extradata = (uint8_t*)av_malloc(i + AV_INPUT_BUFFER_PADDING_SIZE);
+ if (st->codecpar->extradata)
+ {
+ CLog::Log(LOGDEBUG,
+ "CDVDDemuxFFmpeg::ParsePacket() fetching extradata, extradata_size({})", i);
+ st->codecpar->extradata_size = i;
+ memcpy(st->codecpar->extradata, pkt->data, i);
+ memset(st->codecpar->extradata + i, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
+ if (parser->second->m_parserCtx->parser->parser_parse)
+ {
+ parser->second->m_codecCtx->extradata = st->codecpar->extradata;
+ parser->second->m_codecCtx->extradata_size = st->codecpar->extradata_size;
+ const uint8_t* outbufptr;
+ int bufSize;
+ parser->second->m_parserCtx->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+ parser->second->m_parserCtx->parser->parser_parse(parser->second->m_parserCtx,
+ parser->second->m_codecCtx,
+ &outbufptr, &bufSize,
+ pkt->data, pkt->size);
+ parser->second->m_codecCtx->extradata = nullptr;
+ parser->second->m_codecCtx->extradata_size = 0;
+
+ if (parser->second->m_parserCtx->width != 0)
+ {
+ st->codecpar->width = parser->second->m_parserCtx->width;
+ st->codecpar->height = parser->second->m_parserCtx->height;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::ParsePacket() invalid width/height");
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState()
+{
+ AVStream* st = nullptr;
+ bool hasAudio = false;
+
+ if (m_program != UINT_MAX)
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
+ {
+ int idx = m_pFormatContext->programs[m_program]->stream_index[i];
+ st = m_pFormatContext->streams[idx];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ if (st->start_time != AV_NOPTS_VALUE)
+ {
+ if (!m_startTime)
+ {
+ m_startTime = av_rescale(st->cur_dts, st->time_base.num, st->time_base.den) - 0.000001;
+ m_seekStream = idx;
+ }
+ return TRANSPORT_STREAM_STATE::READY;
+ }
+ hasAudio = true;
+ }
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ {
+ st = m_pFormatContext->streams[i];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ if (st->start_time != AV_NOPTS_VALUE)
+ {
+ if (!m_startTime)
+ {
+ m_startTime = av_rescale(st->cur_dts, st->time_base.num, st->time_base.den) - 0.000001;
+ m_seekStream = i;
+ }
+ return TRANSPORT_STREAM_STATE::READY;
+ }
+ hasAudio = true;
+ }
+ }
+ }
+
+ return (hasAudio) ? TRANSPORT_STREAM_STATE::NOTREADY : TRANSPORT_STREAM_STATE::NONE;
+}
+
+TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState()
+{
+ AVStream* st = nullptr;
+ bool hasVideo = false;
+
+ if (m_program == 0 && !m_pFormatContext->nb_programs)
+ return TRANSPORT_STREAM_STATE::NONE;
+
+ if (m_program != UINT_MAX)
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
+ {
+ int idx = m_pFormatContext->programs[m_program]->stream_index[i];
+ st = m_pFormatContext->streams[idx];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ if (st->codecpar->extradata)
+ {
+ if (!m_startTime)
+ {
+ m_startTime = av_rescale(st->cur_dts, st->time_base.num, st->time_base.den) - 0.000001;
+ m_seekStream = idx;
+ }
+ return TRANSPORT_STREAM_STATE::READY;
+ }
+ hasVideo = true;
+ }
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ {
+ st = m_pFormatContext->streams[i];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ if (st->codecpar->extradata)
+ {
+ if (!m_startTime)
+ {
+ m_startTime = av_rescale(st->cur_dts, st->time_base.num, st->time_base.den) - 0.000001;
+ m_seekStream = i;
+ }
+ return TRANSPORT_STREAM_STATE::READY;
+ }
+ hasVideo = true;
+ }
+ }
+ }
+
+ return (hasVideo) ? TRANSPORT_STREAM_STATE::NOTREADY : TRANSPORT_STREAM_STATE::NONE;
+}
+
+bool CDVDDemuxFFmpeg::IsTransportStreamReady()
+{
+ if (!m_checkTransportStream)
+ return true;
+
+ if (m_program == 0 && !m_pFormatContext->nb_programs)
+ return false;
+
+ TRANSPORT_STREAM_STATE state = TransportStreamVideoState();
+ if (state == TRANSPORT_STREAM_STATE::NONE)
+ state = TransportStreamAudioState();
+
+ return state == TRANSPORT_STREAM_STATE::READY;
+}
+
+void CDVDDemuxFFmpeg::ResetVideoStreams()
+{
+ AVStream* st;
+ for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
+ {
+ st = m_pFormatContext->streams[i];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ av_freep(&st->codecpar->extradata);
+ st->codecpar->extradata_size = 0;
+ }
+ }
+}
+
+void CDVDDemuxFFmpeg::GetL16Parameters(int &channels, int &samplerate)
+{
+ std::string content;
+ if (XFILE::CCurlFile::GetContentType(m_pInput->GetURL(), content))
+ {
+ StringUtils::ToLower(content);
+ const size_t len = content.length();
+ size_t pos = content.find(';');
+ while (pos < len)
+ {
+ // move to the next non-whitespace character
+ pos = content.find_first_not_of(" \t", pos + 1);
+
+ if (pos != std::string::npos)
+ {
+ if (content.compare(pos, 9, "channels=", 9) == 0)
+ {
+ pos += 9; // move position to char after 'channels='
+ size_t len = content.find(';', pos);
+ if (len != std::string::npos)
+ len -= pos;
+ std::string no_channels(content, pos, len);
+ // as we don't support any charset with ';' in name
+ StringUtils::Trim(no_channels, " \t");
+ if (!no_channels.empty())
+ {
+ int val = strtol(no_channels.c_str(), NULL, 0);
+ if (val > 0)
+ channels = val;
+ else
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::{} - no parameter for channels", __FUNCTION__);
+ }
+ }
+ else if (content.compare(pos, 5, "rate=", 5) == 0)
+ {
+ pos += 5; // move position to char after 'rate='
+ size_t len = content.find(';', pos);
+ if (len != std::string::npos)
+ len -= pos;
+ std::string rate(content, pos, len);
+ // as we don't support any charset with ';' in name
+ StringUtils::Trim(rate, " \t");
+ if (!rate.empty())
+ {
+ int val = strtol(rate.c_str(), NULL, 0);
+ if (val > 0)
+ samplerate = val;
+ else
+ CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::{} - no parameter for samplerate",
+ __FUNCTION__);
+ }
+ }
+ pos = content.find(';', pos); // find next parameter
+ }
+ }
+ }
+}
+
+StreamHdrType CDVDDemuxFFmpeg::DetermineHdrType(AVStream* pStream)
+{
+ StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE;
+
+ if (av_stream_get_side_data(pStream, AV_PKT_DATA_DOVI_CONF, nullptr)) // DoVi
+ hdrType = StreamHdrType::HDR_TYPE_DOLBYVISION;
+ else if (pStream->codecpar->color_trc == AVCOL_TRC_SMPTE2084) // HDR10
+ hdrType = StreamHdrType::HDR_TYPE_HDR10;
+ else if (pStream->codecpar->color_trc == AVCOL_TRC_ARIB_STD_B67) // HLG
+ hdrType = StreamHdrType::HDR_TYPE_HLG;
+ // file could be SMPTE2086 which FFmpeg currently returns as unknown
+ // so use the presence of static metadata to detect it
+ else if (av_stream_get_side_data(pStream, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, nullptr))
+ hdrType = StreamHdrType::HDR_TYPE_HDR10;
+
+ return hdrType;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h
new file mode 100644
index 0000000..966817d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include <map>
+#include <memory>
+#include <vector>
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+class CDVDDemuxFFmpeg;
+class CURL;
+
+enum class TRANSPORT_STREAM_STATE
+{
+ NONE,
+ READY,
+ NOTREADY,
+};
+
+class CDemuxStreamVideoFFmpeg : public CDemuxStreamVideo
+{
+public:
+ explicit CDemuxStreamVideoFFmpeg(AVStream* stream) : m_stream(stream) {}
+ std::string GetStreamName() override;
+
+ std::string m_description;
+protected:
+ AVStream* m_stream = nullptr;
+};
+
+class CDemuxStreamAudioFFmpeg : public CDemuxStreamAudio
+{
+public:
+ explicit CDemuxStreamAudioFFmpeg(AVStream* stream) : m_stream(stream) {}
+ std::string GetStreamName() override;
+
+ std::string m_description;
+protected:
+ CDVDDemuxFFmpeg* m_parent;
+ AVStream* m_stream = nullptr;
+};
+
+class CDemuxStreamSubtitleFFmpeg
+ : public CDemuxStreamSubtitle
+{
+public:
+ explicit CDemuxStreamSubtitleFFmpeg(AVStream* stream) : m_stream(stream) {}
+ std::string GetStreamName() override;
+
+ std::string m_description;
+protected:
+ CDVDDemuxFFmpeg* m_parent;
+ AVStream* m_stream = nullptr;
+};
+
+class CDemuxParserFFmpeg
+{
+public:
+ ~CDemuxParserFFmpeg();
+ AVCodecParserContext* m_parserCtx = nullptr;
+ AVCodecContext* m_codecCtx = nullptr;
+};
+
+#define FFMPEG_DVDNAV_BUFFER_SIZE 2048 // for dvd's
+
+struct StereoModeConversionMap;
+
+class CDVDDemuxFFmpeg : public CDVDDemux
+{
+public:
+ CDVDDemuxFFmpeg();
+ ~CDVDDemuxFFmpeg() override;
+
+ bool Open(const std::shared_ptr<CDVDInputStream>& pInput, bool fileinfo);
+ void Dispose();
+ bool Reset() override ;
+ void Flush() override;
+ void Abort() override;
+ void SetSpeed(int iSpeed) override;
+ std::string GetFileName() override;
+
+ DemuxPacket* Read() override;
+
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override;
+ bool SeekByte(int64_t pos);
+ int GetStreamLength() override;
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+ int GetPrograms(std::vector<ProgramInfo>& programs) override;
+ void SetProgram(int progId) override;
+
+ bool SeekChapter(int chapter, double* startpts = NULL) override;
+ int GetChapterCount() override;
+ int GetChapter() override;
+ void GetChapterName(std::string& strChapterName, int chapterIdx=-1) override;
+ int64_t GetChapterPos(int chapterIdx = -1) override;
+ std::string GetStreamCodecName(int iStreamId) override;
+
+ bool Aborted();
+
+ AVFormatContext* m_pFormatContext;
+ std::shared_ptr<CDVDInputStream> m_pInput;
+
+protected:
+ friend class CDemuxStreamAudioFFmpeg;
+ friend class CDemuxStreamVideoFFmpeg;
+ friend class CDemuxStreamSubtitleFFmpeg;
+
+ CDemuxStream* AddStream(int streamIdx);
+ void AddStream(int streamIdx, CDemuxStream* stream);
+ void CreateStreams(unsigned int program = UINT_MAX);
+ void DisposeStreams();
+ void ParsePacket(AVPacket* pkt);
+ TRANSPORT_STREAM_STATE TransportStreamAudioState();
+ TRANSPORT_STREAM_STATE TransportStreamVideoState();
+ bool IsTransportStreamReady();
+ void ResetVideoStreams();
+ AVDictionary* GetFFMpegOptionsFromInput();
+ double ConvertTimestamp(int64_t pts, int den, int num);
+ void UpdateCurrentPTS();
+ bool IsProgramChange();
+ unsigned int HLSSelectProgram();
+
+ std::string GetStereoModeFromMetadata(AVDictionary* pMetadata);
+ std::string ConvertCodecToInternalStereoMode(const std::string& mode, const StereoModeConversionMap* conversionMap);
+
+ void GetL16Parameters(int& channels, int& samplerate);
+ double SelectAspect(AVStream* st, bool& forced);
+
+ StreamHdrType DetermineHdrType(AVStream* pStream);
+
+ CCriticalSection m_critSection;
+ std::map<int, CDemuxStream*> m_streams;
+ std::map<int, std::unique_ptr<CDemuxParserFFmpeg>> m_parsers;
+
+ AVIOContext* m_ioContext;
+
+ double m_currentPts; // used for stream length estimation
+ bool m_bMatroska;
+ bool m_bAVI;
+ bool m_bSup;
+ int m_speed;
+ unsigned int m_program;
+ unsigned int m_streamsInProgram;
+ unsigned int m_newProgram;
+ unsigned int m_initialProgramNumber;
+ int m_seekStream;
+
+ XbmcThreads::EndTime<> m_timeout;
+
+ // Due to limitations of ffmpeg, we only can detect a program change
+ // with a packet. This struct saves the packet for the next read and
+ // signals STREAMCHANGE to player
+ struct
+ {
+ AVPacket pkt; // packet ffmpeg returned
+ int result; // result from av_read_packet
+ }m_pkt;
+
+ bool m_streaminfo;
+ bool m_reopen = false;
+ bool m_checkTransportStream;
+ int m_displayTime = 0;
+ double m_dtsAtDisplayTime;
+ bool m_seekToKeyFrame = false;
+ double m_startTime = 0;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.cpp
new file mode 100644
index 0000000..66127c2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxUtils.h"
+
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+void CDVDDemuxUtils::FreeDemuxPacket(DemuxPacket* pPacket)
+{
+ if (pPacket)
+ {
+ if (pPacket->pData)
+ KODI::MEMORY::AlignedFree(pPacket->pData);
+ if (pPacket->iSideDataElems)
+ {
+ AVPacket* avPkt = av_packet_alloc();
+ if (!avPkt)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxUtils::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ }
+ else
+ {
+ avPkt->side_data = static_cast<AVPacketSideData*>(pPacket->pSideData);
+ avPkt->side_data_elems = pPacket->iSideDataElems;
+
+ //! @todo: properly handle avpkt side_data. this works around our improper use of the side_data
+ // as we pass pointers to ffmpeg allocated memory for the side_data. we should really be allocating
+ // and storing our own AVPacket. This will require some extensive changes.
+
+ // here we make use of ffmpeg to free the side_data, we shouldn't have to allocate an intermediate AVPacket though
+ av_packet_free(&avPkt);
+ }
+ }
+ if (pPacket->cryptoInfo)
+ delete pPacket->cryptoInfo;
+ delete pPacket;
+ }
+}
+
+DemuxPacket* CDVDDemuxUtils::AllocateDemuxPacket(int iDataSize)
+{
+ DemuxPacket* pPacket = new DemuxPacket();
+
+ if (iDataSize > 0)
+ {
+ // need to allocate a few bytes more.
+ // From avcodec.h (ffmpeg)
+ /**
+ * Required number of additionally allocated bytes at the end of the input bitstream for decoding.
+ * this is mainly needed because some optimized bitstream readers read
+ * 32 or 64 bit at once and could read over the end<br>
+ * Note, if the first 23 bits of the additional bytes are not 0 then damaged
+ * MPEG bitstreams could cause overread and segfault
+ */
+ pPacket->pData = static_cast<uint8_t*>(KODI::MEMORY::AlignedMalloc(iDataSize + AV_INPUT_BUFFER_PADDING_SIZE, 16));
+ if (!pPacket->pData)
+ {
+ FreeDemuxPacket(pPacket);
+ return NULL;
+ }
+
+ // reset the last 8 bytes to 0;
+ memset(pPacket->pData + iDataSize, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+ }
+
+ return pPacket;
+}
+
+DemuxPacket* CDVDDemuxUtils::AllocateDemuxPacket(unsigned int iDataSize, unsigned int encryptedSubsampleCount)
+{
+ DemuxPacket *ret(AllocateDemuxPacket(iDataSize));
+ if (ret && encryptedSubsampleCount > 0)
+ ret->cryptoInfo = new DemuxCryptoInfo(encryptedSubsampleCount);
+ return ret;
+}
+
+void CDVDDemuxUtils::StoreSideData(DemuxPacket *pkt, AVPacket *src)
+{
+ AVPacket* avPkt = av_packet_alloc();
+ if (!avPkt)
+ {
+ CLog::Log(LOGERROR, "CDVDDemuxUtils::{} - av_packet_alloc failed: {}", __FUNCTION__,
+ strerror(errno));
+ return;
+ }
+
+ // here we make allocate an intermediate AVPacket to allow ffmpeg to allocate the side_data
+ // via the copy below. we then reference this allocated memory in the DemuxPacket. this behaviour
+ // is bad and will require a larger rework.
+ av_packet_copy_props(avPkt, src);
+ pkt->pSideData = avPkt->side_data;
+ pkt->iSideDataElems = avPkt->side_data_elems;
+
+ //! @todo: properly handle avpkt side_data. this works around our improper use of the side_data
+ // as we pass pointers to ffmpeg allocated memory for the side_data. we should really be allocating
+ // and storing our own AVPacket. This will require some extensive changes.
+ av_buffer_unref(&avPkt->buf);
+ av_free(avPkt);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h
new file mode 100644
index 0000000..dcb44bc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+class CDVDDemuxUtils
+{
+public:
+ static void FreeDemuxPacket(DemuxPacket* pPacket);
+ static DemuxPacket* AllocateDemuxPacket(int iDataSize = 0);
+ static DemuxPacket* AllocateDemuxPacket(unsigned int iDataSize, unsigned int encryptedSubsampleCount);
+ static void StoreSideData(DemuxPacket *pkt, AVPacket *src);
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp
new file mode 100644
index 0000000..5c7ef1b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDDemuxVobsub.h"
+
+#include "DVDCodecs/DVDCodecs.h"
+#include "DVDDemuxFFmpeg.h"
+#include "DVDInputStreams/DVDFactoryInputStream.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/DVDSubtitleStream.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/StringUtils.h"
+
+CDVDDemuxVobsub::CDVDDemuxVobsub() = default;
+
+CDVDDemuxVobsub::~CDVDDemuxVobsub()
+{
+ for(unsigned i=0;i<m_Streams.size();i++)
+ {
+ delete m_Streams[i];
+ }
+ m_Streams.clear();
+}
+
+std::vector<CDemuxStream*> CDVDDemuxVobsub::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ streams.reserve(m_Streams.size());
+ for (auto iter : m_Streams)
+ streams.push_back(iter);
+
+ return streams;
+}
+
+bool CDVDDemuxVobsub::Open(const std::string& filename, int source, const std::string& subfilename)
+{
+ m_Filename = filename;
+ m_source = source;
+
+ std::unique_ptr<CDVDSubtitleStream> pStream(new CDVDSubtitleStream());
+ if(!pStream->Open(filename))
+ return false;
+
+ std::string vobsub = subfilename;
+ if ( vobsub == "")
+ {
+ vobsub = filename;
+ vobsub.erase(vobsub.rfind('.'), vobsub.size());
+ vobsub += ".sub";
+ }
+
+ CFileItem item(vobsub, false);
+ item.SetMimeType("video/x-vobsub");
+ item.SetContentLookup(false);
+ m_Input = CDVDFactoryInputStream::CreateInputStream(NULL, item);
+ if (!m_Input || !m_Input->Open())
+ return false;
+
+ m_Demuxer.reset(new CDVDDemuxFFmpeg());
+ if (!m_Demuxer->Open(m_Input, false))
+ return false;
+
+ CDVDStreamInfo hints;
+ CDVDCodecOptions options;
+ hints.codec = AV_CODEC_ID_DVD_SUBTITLE;
+
+ std::string line;
+
+ SState state;
+ state.delay = 0;
+ state.id = -1;
+
+ while (pStream->ReadLine(line))
+ {
+ if (line[0] == '#')
+ continue;
+
+ size_t pos = line.find_first_of(':');
+ if (pos != std::string::npos)
+ {
+ pos += 1;
+ std::string param = line.substr(0, pos);
+ std::string data = line.substr(pos, line.size() - pos);
+
+ if (param == "langidx:")
+ ParseLangIdx(state, data);
+ else if (param == "delay:")
+ ParseDelay(state, data);
+ else if (param == "id:")
+ ParseId(state, data);
+ else if (param == "timestamp:")
+ ParseTimestamp(state, data);
+ else if (param == "palette:" || param == "size:" || param == "org:" ||
+ param == "custom colors:" || param == "scale:" || param == "alpha:" ||
+ param == "fadein/out:" || param == "forced subs:")
+ ParseExtra(state, line);
+ else
+ continue;
+ }
+ }
+
+ struct sorter s;
+ sort(m_Timestamps.begin(), m_Timestamps.end(), s);
+ m_Timestamp = m_Timestamps.begin();
+
+ for(unsigned i=0;i<m_Streams.size();i++)
+ {
+ m_Streams[i]->ExtraSize = state.extra.length()+1;
+ m_Streams[i]->ExtraData = std::make_unique<uint8_t[]>(m_Streams[i]->ExtraSize);
+ strcpy((char*)m_Streams[i]->ExtraData.get(), state.extra.c_str());
+ }
+
+ return true;
+}
+
+bool CDVDDemuxVobsub::Reset()
+{
+ Flush();
+ return true;
+}
+
+void CDVDDemuxVobsub::Flush()
+{
+ m_Demuxer->Flush();
+}
+
+bool CDVDDemuxVobsub::SeekTime(double time, bool backwards, double* startpts)
+{
+ double pts = DVD_MSEC_TO_TIME(time);
+ m_Timestamp = m_Timestamps.begin();
+ for (;m_Timestamp != m_Timestamps.end();++m_Timestamp)
+ {
+ if(m_Timestamp->pts > pts)
+ break;
+ }
+ for (unsigned i=0;i<m_Streams.size() && m_Timestamps.begin() != m_Timestamp;i++)
+ {
+ --m_Timestamp;
+ }
+ return true;
+}
+
+DemuxPacket* CDVDDemuxVobsub::Read()
+{
+ std::vector<STimestamp>::iterator current;
+ do {
+ if(m_Timestamp == m_Timestamps.end())
+ return NULL;
+
+ current = m_Timestamp++;
+ } while(m_Streams[current->id]->m_discard == true);
+
+ if(!m_Demuxer->SeekByte(current->pos))
+ return NULL;
+
+ DemuxPacket *packet = m_Demuxer->Read();
+ if(!packet)
+ return NULL;
+
+ packet->iStreamId = current->id;
+ packet->pts = current->pts;
+ packet->dts = current->pts;
+
+ return packet;
+}
+
+bool CDVDDemuxVobsub::ParseLangIdx(SState& state, std::string& line)
+{
+ return true;
+}
+
+bool CDVDDemuxVobsub::ParseDelay(SState& state, std::string& line)
+{
+ int h,m,s,ms;
+ bool negative = false;
+
+ StringUtils::Trim(line);
+
+ if (line[0] == '-')
+ {
+ negative = true;
+ line.erase(0, 1);
+ }
+
+ if (sscanf(line.c_str(), "%d:%d:%d:%d", &h, &m, &s, &ms) != 4)
+ return false;
+ state.delay = h*3600.0 + m*60.0 + s + ms*0.001;
+ if(negative)
+ state.delay *= -1;
+ return true;
+}
+
+bool CDVDDemuxVobsub::ParseId(SState& state, std::string& line)
+{
+ std::unique_ptr<CStream> stream(new CStream(this));
+
+ StringUtils::Trim(line);
+ stream->language = line.substr(0, 2);
+
+ size_t pos = line.find_first_of(',');
+ if (pos != std::string::npos)
+ {
+ pos += 1;
+ line.erase(0, pos);
+ }
+ StringUtils::TrimLeft(line);
+ pos = line.find_first_of(':');
+ if (pos != std::string::npos && line.substr(0, pos + 1) == "index:")
+ {
+ pos += 1;
+ stream->uniqueId = std::atoi(line.substr(pos, line.size() - pos).c_str());
+ }
+ else
+ stream->uniqueId = -1;
+
+ stream->codec = AV_CODEC_ID_DVD_SUBTITLE;
+ stream->uniqueId = m_Streams.size();
+ stream->source = m_source;
+ stream->demuxerId = m_demuxerId;
+
+ state.id = stream->uniqueId;
+ m_Streams.push_back(stream.release());
+ return true;
+}
+
+bool CDVDDemuxVobsub::ParseExtra(SState& state, const std::string& line)
+{
+ state.extra += line;
+ state.extra += '\n';
+ return true;
+}
+
+bool CDVDDemuxVobsub::ParseTimestamp(SState& state, std::string& line)
+{
+ if(state.id < 0)
+ return false;
+
+ int h,m,s,ms;
+ STimestamp timestamp;
+
+ StringUtils::Trim(line);
+ if (sscanf(line.c_str(), "%d:%d:%d:%d, filepos:%" PRIx64, &h, &m, &s, &ms, &timestamp.pos) != 5)
+ return false;
+
+ timestamp.id = state.id;
+ timestamp.pts = DVD_SEC_TO_TIME(state.delay + h*3600.0 + m*60.0 + s + ms*0.001);
+ m_Timestamps.push_back(timestamp);
+ return true;
+}
+
+void CDVDDemuxVobsub::EnableStream(int id, bool enable)
+{
+ for (auto &stream : m_Streams)
+ {
+ if (stream->uniqueId == id)
+ {
+ stream->m_discard = !enable;
+ break;
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.h
new file mode 100644
index 0000000..c82b6a1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CDVDOverlayCodecFFmpeg;
+class CDVDInputStream;
+class CDVDDemuxFFmpeg;
+
+class CDVDDemuxVobsub : public CDVDDemux
+{
+public:
+ CDVDDemuxVobsub();
+ ~CDVDDemuxVobsub() override;
+
+ bool Open(const std::string& filename, int source, const std::string& subfilename);
+
+ // implementation of CDVDDemux
+ bool Reset() override;
+ void Flush() override;
+ DemuxPacket* Read() override;
+ bool SeekTime(double time, bool backwards, double* startpts = NULL) override;
+ CDemuxStream* GetStream(int index) const override { return m_Streams[index]; }
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override { return m_Streams.size(); }
+ std::string GetFileName() override { return m_Filename; }
+ void EnableStream(int id, bool enable) override;
+
+private:
+ class CStream
+ : public CDemuxStreamSubtitle
+ {
+ public:
+ explicit CStream(CDVDDemuxVobsub* parent)
+ : m_discard(false), m_parent(parent)
+ {}
+
+ bool m_discard;
+ CDVDDemuxVobsub* m_parent;
+ };
+
+ typedef struct STimestamp
+ {
+ int64_t pos;
+ double pts;
+ int id;
+ } STimestamp;
+
+ std::string m_Filename;
+ std::shared_ptr<CDVDInputStream> m_Input;
+ std::unique_ptr<CDVDDemuxFFmpeg> m_Demuxer;
+ std::vector<STimestamp> m_Timestamps;
+ std::vector<STimestamp>::iterator m_Timestamp;
+ std::vector<CStream*> m_Streams;
+ int m_source = -1;
+
+ typedef struct SState
+ {
+ int id;
+ double delay;
+ std::string extra;
+ } SState;
+
+ struct sorter
+ {
+ bool operator()(const STimestamp &p1, const STimestamp &p2)
+ {
+ return p1.pts < p2.pts || (p1.pts == p2.pts && p1.id < p2.id);
+ }
+ };
+
+ bool ParseLangIdx(SState& state, std::string& line);
+ bool ParseDelay(SState& state, std::string& line);
+ bool ParseId(SState& state, std::string& line);
+ bool ParseExtra(SState& state, const std::string& line);
+ bool ParseTimestamp(SState& state, std::string& line);
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.cpp
new file mode 100644
index 0000000..35ba376
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDFactoryDemuxer.h"
+
+#include "DVDDemuxBXA.h"
+#include "DVDDemuxCDDA.h"
+#include "DVDDemuxClient.h"
+#include "DVDDemuxFFmpeg.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "DemuxMultiSource.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+CDVDDemux* CDVDFactoryDemuxer::CreateDemuxer(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ bool fileinfo)
+{
+ if (!pInputStream)
+ return NULL;
+
+ // Try to open the AirTunes demuxer
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_FILE) && pInputStream->GetContent().compare("audio/x-xbmc-pcm") == 0 )
+ {
+ // audio/x-xbmc-pcm this is the used codec for AirTunes
+ // (apples audio only streaming)
+ std::unique_ptr<CDVDDemuxBXA> demuxer(new CDVDDemuxBXA());
+ if(demuxer->Open(pInputStream))
+ return demuxer.release();
+ else
+ return NULL;
+ }
+
+ // Try to open CDDA demuxer
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_FILE) && pInputStream->GetContent().compare("application/octet-stream") == 0)
+ {
+ std::string filename = pInputStream->GetFileName();
+ if (filename.substr(0, 7) == "cdda://")
+ {
+ CLog::Log(LOGDEBUG, "DVDFactoryDemuxer: Stream is probably CD audio. Creating CDDA demuxer.");
+
+ std::unique_ptr<CDVDDemuxCDDA> demuxer(new CDVDDemuxCDDA());
+ if (demuxer->Open(pInputStream))
+ {
+ return demuxer.release();
+ }
+ }
+ }
+
+ // Input stream handles demuxing
+ if (pInputStream->GetIDemux())
+ {
+ std::unique_ptr<CDVDDemuxClient> demuxer(new CDVDDemuxClient());
+ if(demuxer->Open(pInputStream))
+ return demuxer.release();
+ else
+ return nullptr;
+ }
+
+ // Try to open the MultiFiles demuxer
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_MULTIFILES))
+ {
+ std::unique_ptr<CDemuxMultiSource> demuxer(new CDemuxMultiSource());
+ if (demuxer->Open(pInputStream))
+ return demuxer.release();
+ else
+ return NULL;
+ }
+
+ std::unique_ptr<CDVDDemuxFFmpeg> demuxer(new CDVDDemuxFFmpeg());
+ if (demuxer->Open(pInputStream, fileinfo))
+ return demuxer.release();
+ else
+ return NULL;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h
new file mode 100644
index 0000000..1d2bddf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+class CDVDDemux;
+class CDVDInputStream;
+
+class CDVDFactoryDemuxer
+{
+public:
+ static CDVDDemux* CreateDemuxer(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ bool fileinfo = false);
+};
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.cpp
new file mode 100644
index 0000000..9f49630
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DemuxMultiSource.h"
+
+#include "DVDDemuxUtils.h"
+#include "DVDFactoryDemuxer.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "Util.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+
+CDemuxMultiSource::CDemuxMultiSource() = default;
+
+CDemuxMultiSource::~CDemuxMultiSource()
+{
+ Dispose();
+}
+
+void CDemuxMultiSource::Abort()
+{
+ for (auto& iter : m_demuxerMap)
+ iter.second->Abort();
+}
+
+void CDemuxMultiSource::Dispose()
+{
+ while (!m_demuxerQueue.empty())
+ {
+ m_demuxerQueue.pop();
+ }
+
+ m_demuxerMap.clear();
+ m_DemuxerToInputStreamMap.clear();
+ m_pInput = NULL;
+
+}
+
+void CDemuxMultiSource::EnableStream(int64_t demuxerId, int id, bool enable)
+{
+ auto iter = m_demuxerMap.find(demuxerId);
+ if (iter != m_demuxerMap.end())
+ {
+ DemuxPtr demuxer = iter->second;
+ demuxer->EnableStream(demuxerId, id, enable);
+ }
+}
+
+void CDemuxMultiSource::Flush()
+{
+ for (auto& iter : m_demuxerMap)
+ iter.second->Flush();
+}
+
+int CDemuxMultiSource::GetNrOfStreams() const
+{
+ int streamsCount = 0;
+ for (auto& iter : m_demuxerMap)
+ streamsCount += iter.second->GetNrOfStreams();
+
+ return streamsCount;
+}
+
+CDemuxStream* CDemuxMultiSource::GetStream(int64_t demuxerId, int iStreamId) const
+{
+ auto iter = m_demuxerMap.find(demuxerId);
+ if (iter != m_demuxerMap.end())
+ {
+ return iter->second->GetStream(demuxerId, iStreamId);
+ }
+ else
+ return NULL;
+}
+
+std::vector<CDemuxStream*> CDemuxMultiSource::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ for (auto& iter : m_demuxerMap)
+ {
+ for (auto& stream : iter.second->GetStreams())
+ {
+ streams.push_back(stream);
+ }
+ }
+ return streams;
+}
+
+std::string CDemuxMultiSource::GetStreamCodecName(int64_t demuxerId, int iStreamId)
+{
+ auto iter = m_demuxerMap.find(demuxerId);
+ if (iter != m_demuxerMap.end())
+ {
+ return iter->second->GetStreamCodecName(demuxerId, iStreamId);
+ }
+ else
+ return "";
+};
+
+int CDemuxMultiSource::GetStreamLength()
+{
+ int length = 0;
+ for (auto& iter : m_demuxerMap)
+ {
+ length = std::max(length, iter.second->GetStreamLength());
+ }
+
+ return length;
+}
+
+bool CDemuxMultiSource::Open(const std::shared_ptr<CDVDInputStream>& pInput)
+{
+ if (!pInput)
+ return false;
+
+ m_pInput = std::dynamic_pointer_cast<InputStreamMultiStreams>(pInput);
+
+ if (!m_pInput)
+ return false;
+
+ auto iter = m_pInput->m_InputStreams.begin();
+ while (iter != m_pInput->m_InputStreams.end())
+ {
+ DemuxPtr demuxer = DemuxPtr(CDVDFactoryDemuxer::CreateDemuxer((*iter)));
+ if (!demuxer)
+ {
+ iter = m_pInput->m_InputStreams.erase(iter);
+ }
+ else
+ {
+ SetMissingStreamDetails(demuxer);
+
+ m_demuxerMap[demuxer->GetDemuxerId()] = demuxer;
+ m_DemuxerToInputStreamMap[demuxer] = *iter;
+ m_demuxerQueue.push(std::make_pair(-1.0, demuxer));
+ ++iter;
+ }
+ }
+ return !m_demuxerMap.empty();
+}
+
+bool CDemuxMultiSource::Reset()
+{
+ bool ret = true;
+ for (auto& iter : m_demuxerMap)
+ {
+ if (!iter.second->Reset())
+ ret = false;
+ }
+ return ret;
+}
+
+DemuxPacket* CDemuxMultiSource::Read()
+{
+ if (m_demuxerQueue.empty())
+ return NULL;
+
+ DemuxPtr currentDemuxer = m_demuxerQueue.top().second;
+ m_demuxerQueue.pop();
+
+ if (!currentDemuxer)
+ return NULL;
+
+ DemuxPacket* packet = currentDemuxer->Read();
+ if (packet)
+ {
+ double readTime = 0;
+ if (packet->dts != DVD_NOPTS_VALUE)
+ readTime = packet->dts;
+ else
+ readTime = packet->pts;
+ m_demuxerQueue.push(std::make_pair(readTime, currentDemuxer));
+ }
+ else
+ {
+ auto input = m_DemuxerToInputStreamMap.find(currentDemuxer);
+ if (input != m_DemuxerToInputStreamMap.end())
+ {
+ if (input->second->IsEOF())
+ {
+ CLog::Log(LOGDEBUG, "{} - Demuxer for file {} is at eof, removed it from the queue",
+ __FUNCTION__, CURL::GetRedacted(currentDemuxer->GetFileName()));
+ }
+ else //maybe add an error counter?
+ m_demuxerQueue.push(std::make_pair(-1.0, currentDemuxer));
+ }
+ }
+
+ return packet;
+}
+
+bool CDemuxMultiSource::SeekTime(double time, bool backwards, double* startpts)
+{
+ DemuxQueue demuxerQueue = DemuxQueue();
+ bool ret = false;
+ for (auto& iter : m_demuxerMap)
+ {
+ if (iter.second->SeekTime(time, false, startpts))
+ {
+ demuxerQueue.push(std::make_pair(*startpts, iter.second));
+ CLog::Log(LOGDEBUG, "{} - starting demuxer from: {:f}", __FUNCTION__, time);
+ ret = true;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - failed to start demuxing from: {:f}", __FUNCTION__, time);
+ }
+ }
+ m_demuxerQueue = demuxerQueue;
+ return ret;
+}
+
+void CDemuxMultiSource::SetMissingStreamDetails(const DemuxPtr& demuxer)
+{
+ std::string baseFileName = m_pInput->GetFileName();
+ std::string fileName = demuxer->GetFileName();
+ for (auto& stream : demuxer->GetStreams())
+ {
+ ExternalStreamInfo info = CUtil::GetExternalStreamDetailsFromFilename(baseFileName, fileName);
+
+ if (stream->flags == StreamFlags::FLAG_NONE)
+ {
+ stream->flags = static_cast<StreamFlags>(info.flag);
+ }
+ if (stream->language.empty())
+ {
+ stream->language = info.language;
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.h
new file mode 100644
index 0000000..4406fd9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DemuxMultiSource.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemux.h"
+#include "DVDInputStreams/InputStreamMultiSource.h"
+
+#include <map>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+
+typedef std::shared_ptr<CDVDDemux> DemuxPtr;
+
+struct comparator{
+ bool operator()(const std::pair<double, DemuxPtr>& x, const std::pair<double, DemuxPtr>& y) const
+ {
+ return x.first > y.first;
+ }
+};
+
+typedef std::priority_queue<std::pair<double, DemuxPtr>, std::vector<std::pair<double, DemuxPtr>>, comparator> DemuxQueue;
+
+class CDemuxMultiSource : public CDVDDemux
+{
+
+public:
+ CDemuxMultiSource();
+ ~CDemuxMultiSource() override;
+
+ bool Open(const std::shared_ptr<CDVDInputStream>& pInput);
+
+ // implementation of CDVDDemux
+ void Abort() override;
+ void EnableStream(int64_t demuxerId, int id, bool enable) override;
+ void Flush() override;
+ int GetNrOfStreams() const override;
+ CDemuxStream* GetStream(int64_t demuxerId, int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ std::string GetStreamCodecName(int64_t demuxerId, int iStreamId) override;
+ int GetStreamLength() override;
+ DemuxPacket* Read() override;
+ bool Reset() override;
+ bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override;
+
+protected:
+ CDemuxStream* GetStream(int iStreamId) const override { return nullptr; }
+
+private:
+ void Dispose();
+ void SetMissingStreamDetails(const DemuxPtr& demuxer);
+
+ std::shared_ptr<InputStreamMultiStreams> m_pInput = NULL;
+ std::map<DemuxPtr, InputStreamPtr> m_DemuxerToInputStreamMap;
+ DemuxQueue m_demuxerQueue;
+ std::map<int64_t, DemuxPtr> m_demuxerMap;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp
new file mode 100644
index 0000000..0860b40
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDFileInfo.h"
+#include "ServiceBroker.h"
+#include "FileItem.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "pictures/Picture.h"
+#include "video/VideoInfoTag.h"
+#include "filesystem/StackDirectory.h"
+#include "utils/log.h"
+#include "utils/URIUtils.h"
+
+#include "DVDStreamInfo.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#ifdef HAVE_LIBBLURAY
+#include "DVDInputStreams/DVDInputStreamBluray.h"
+#endif
+#include "DVDInputStreams/DVDFactoryInputStream.h"
+#include "DVDDemuxers/DVDDemux.h"
+#include "DVDDemuxers/DVDDemuxUtils.h"
+#include "DVDDemuxers/DVDFactoryDemuxer.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "DVDCodecs/Video/DVDVideoCodec.h"
+#include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
+#include "DVDDemuxers/DVDDemuxVobsub.h"
+#include "Process/ProcessInfo.h"
+
+#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
+#include "filesystem/File.h"
+#include "cores/FFmpeg.h"
+#include "TextureCache.h"
+#include "Util.h"
+#include "utils/LangCodeExpander.h"
+
+#include <cstdlib>
+#include <memory>
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+bool CDVDFileInfo::GetFileDuration(const std::string &path, int& duration)
+{
+ std::unique_ptr<CDVDDemux> demux;
+
+ CFileItem item(path, false);
+ auto input = CDVDFactoryInputStream::CreateInputStream(NULL, item);
+ if (!input)
+ return false;
+
+ if (!input->Open())
+ return false;
+
+ demux.reset(CDVDFactoryDemuxer::CreateDemuxer(input, true));
+ if (!demux)
+ return false;
+
+ duration = demux->GetStreamLength();
+ if (duration > 0)
+ return true;
+ else
+ return false;
+}
+
+int DegreeToOrientation(int degrees)
+{
+ switch(degrees)
+ {
+ case 90:
+ return 5;
+ case 180:
+ return 2;
+ case 270:
+ return 7;
+ default:
+ return 0;
+ }
+}
+
+bool CDVDFileInfo::ExtractThumb(const CFileItem& fileItem,
+ CTextureDetails &details,
+ CStreamDetails *pStreamDetails,
+ int64_t pos)
+{
+ const std::string redactPath = CURL::GetRedacted(fileItem.GetPath());
+ auto start = std::chrono::steady_clock::now();
+
+ CFileItem item(fileItem);
+ item.SetMimeTypeForInternetFile();
+ auto pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, item);
+ if (!pInputStream)
+ {
+ CLog::Log(LOGERROR, "InputStream: Error creating stream for {}", redactPath);
+ return false;
+ }
+
+ if (!pInputStream->Open())
+ {
+ CLog::Log(LOGERROR, "InputStream: Error opening, {}", redactPath);
+ return false;
+ }
+
+ CDVDDemux *pDemuxer = NULL;
+
+ try
+ {
+ pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream, true);
+ if(!pDemuxer)
+ {
+ CLog::Log(LOGERROR, "{} - Error creating demuxer", __FUNCTION__);
+ return false;
+ }
+ }
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{} - Exception thrown when opening demuxer", __FUNCTION__);
+ if (pDemuxer)
+ delete pDemuxer;
+
+ return false;
+ }
+
+ if (pStreamDetails)
+ {
+
+ const std::string& strPath = item.GetPath();
+ DemuxerToStreamDetails(pInputStream, pDemuxer, *pStreamDetails, strPath);
+
+ //extern subtitles
+ std::vector<std::string> filenames;
+ std::string video_path;
+ if (strPath.empty())
+ video_path = pInputStream->GetFileName();
+ else
+ video_path = strPath;
+
+ CUtil::ScanForExternalSubtitles(video_path, filenames);
+
+ for(unsigned int i=0;i<filenames.size();i++)
+ {
+ // if vobsub subtitle:
+ if (URIUtils::GetExtension(filenames[i]) == ".idx")
+ {
+ std::string strSubFile;
+ if ( CUtil::FindVobSubPair(filenames, filenames[i], strSubFile) )
+ AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i], strSubFile);
+ }
+ else
+ {
+ if ( !CUtil::IsVobSub(filenames, filenames[i]) )
+ {
+ AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i]);
+ }
+ }
+ }
+ }
+
+ int nVideoStream = -1;
+ int64_t demuxerId = -1;
+ for (CDemuxStream* pStream : pDemuxer->GetStreams())
+ {
+ if (pStream)
+ {
+ // ignore if it's a picture attachment (e.g. jpeg artwork)
+ if (pStream->type == STREAM_VIDEO && !(pStream->flags & AV_DISPOSITION_ATTACHED_PIC))
+ {
+ nVideoStream = pStream->uniqueId;
+ demuxerId = pStream->demuxerId;
+ }
+ else
+ pDemuxer->EnableStream(pStream->demuxerId, pStream->uniqueId, false);
+ }
+ }
+
+ bool bOk = false;
+ int packetsTried = 0;
+
+ if (nVideoStream != -1)
+ {
+ std::unique_ptr<CProcessInfo> pProcessInfo(CProcessInfo::CreateInstance());
+ std::vector<AVPixelFormat> pixFmts;
+ pixFmts.push_back(AV_PIX_FMT_YUV420P);
+ pProcessInfo->SetPixFormats(pixFmts);
+
+ CDVDStreamInfo hint(*pDemuxer->GetStream(demuxerId, nVideoStream), true);
+ hint.codecOptions = CODEC_FORCE_SOFTWARE;
+
+ std::unique_ptr<CDVDVideoCodec> pVideoCodec =
+ CDVDFactoryCodec::CreateVideoCodec(hint, *pProcessInfo);
+
+ if (pVideoCodec)
+ {
+ int nTotalLen = pDemuxer->GetStreamLength();
+ int64_t nSeekTo = (pos == -1) ? nTotalLen / 3 : pos;
+
+ CLog::Log(LOGDEBUG, "{} - seeking to pos {}ms (total: {}ms) in {}", __FUNCTION__, nSeekTo,
+ nTotalLen, redactPath);
+
+ if (pDemuxer->SeekTime(static_cast<double>(nSeekTo), true))
+ {
+ CDVDVideoCodec::VCReturn iDecoderState = CDVDVideoCodec::VC_NONE;
+ VideoPicture picture = {};
+
+ // num streams * 160 frames, should get a valid frame, if not abort.
+ int abort_index = pDemuxer->GetNrOfStreams() * 160;
+ do
+ {
+ DemuxPacket* pPacket = pDemuxer->Read();
+ packetsTried++;
+
+ if (!pPacket)
+ break;
+
+ if (pPacket->iStreamId != nVideoStream)
+ {
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ continue;
+ }
+
+ pVideoCodec->AddData(*pPacket);
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+
+ iDecoderState = CDVDVideoCodec::VC_NONE;
+ while (iDecoderState == CDVDVideoCodec::VC_NONE)
+ {
+ iDecoderState = pVideoCodec->GetPicture(&picture);
+ }
+
+ if (iDecoderState == CDVDVideoCodec::VC_PICTURE)
+ {
+ if(!(picture.iFlags & DVP_FLAG_DROPPED))
+ break;
+ }
+
+ } while (abort_index--);
+
+ if (iDecoderState == CDVDVideoCodec::VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED))
+ {
+ {
+ unsigned int nWidth = std::min(picture.iDisplayWidth, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes);
+ double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight;
+ if(hint.forced_aspect && hint.aspect != 0)
+ aspect = hint.aspect;
+ unsigned int nHeight = (unsigned int)((double)nWidth / aspect);
+
+ // We pass the buffers to sws_scale uses 16 aligned widths when using intrinsics
+ int sizeNeeded = FFALIGN(nWidth, 16) * nHeight * 4;
+ uint8_t *pOutBuf = static_cast<uint8_t*>(av_malloc(sizeNeeded));
+ struct SwsContext *context = sws_getContext(picture.iWidth, picture.iHeight,
+ AV_PIX_FMT_YUV420P, nWidth, nHeight, AV_PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL);
+
+ if (context)
+ {
+ uint8_t *planes[YuvImage::MAX_PLANES];
+ int stride[YuvImage::MAX_PLANES];
+ picture.videoBuffer->GetPlanes(planes);
+ picture.videoBuffer->GetStrides(stride);
+ uint8_t *src[4]= { planes[0], planes[1], planes[2], 0 };
+ int srcStride[] = { stride[0], stride[1], stride[2], 0 };
+ uint8_t *dst[] = { pOutBuf, 0, 0, 0 };
+ int dstStride[] = { (int)nWidth*4, 0, 0, 0 };
+ int orientation = DegreeToOrientation(hint.orientation);
+ sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride);
+ sws_freeContext(context);
+
+ details.width = nWidth;
+ details.height = nHeight;
+ CPicture::CacheTexture(pOutBuf, nWidth, nHeight, nWidth * 4, orientation, nWidth, nHeight, CTextureCache::GetCachedPath(details.file));
+ bOk = true;
+ }
+ av_free(pOutBuf);
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - decode failed in {} after {} packets.", __FUNCTION__,
+ redactPath, packetsTried);
+ }
+ }
+ }
+ }
+
+ if (pDemuxer)
+ delete pDemuxer;
+
+ if(!bOk)
+ {
+ XFILE::CFile file;
+ if(file.OpenForWrite(CTextureCache::GetCachedPath(details.file)))
+ file.Close();
+ }
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+ CLog::Log(LOGDEBUG, "{} - measured {} ms to extract thumb from file <{}> in {} packets. ",
+ __FUNCTION__, duration.count(), redactPath, packetsTried);
+
+ return bOk;
+}
+
+/**
+ * \brief Open the item pointed to by pItem and extract streamdetails
+ * \return true if the stream details have changed
+ */
+bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem)
+{
+ if (!pItem)
+ return false;
+
+ std::string strFileNameAndPath;
+ if (pItem->HasVideoInfoTag())
+ strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
+
+ if (strFileNameAndPath.empty())
+ strFileNameAndPath = pItem->GetDynPath();
+
+ std::string playablePath = strFileNameAndPath;
+ if (URIUtils::IsStack(playablePath))
+ playablePath = XFILE::CStackDirectory::GetFirstStackedFile(playablePath);
+
+ CFileItem item(playablePath, false);
+ item.SetMimeTypeForInternetFile();
+ auto pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, item);
+ if (!pInputStream)
+ return false;
+
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER))
+ {
+ return false;
+ }
+
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open())
+ {
+ return false;
+ }
+
+ CDVDDemux *pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream, true);
+ if (pDemuxer)
+ {
+ bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath);
+ delete pDemuxer;
+ return retVal;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool CDVDFileInfo::DemuxerToStreamDetails(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ CDVDDemux* pDemuxer,
+ const std::vector<CStreamDetailSubtitle>& subs,
+ CStreamDetails& details)
+{
+ bool result = DemuxerToStreamDetails(pInputStream, pDemuxer, details);
+ for (unsigned int i = 0; i < subs.size(); i++)
+ {
+ CStreamDetailSubtitle* sub = new CStreamDetailSubtitle();
+ sub->m_strLanguage = subs[i].m_strLanguage;
+ details.AddStream(sub);
+ result = true;
+ }
+ return result;
+}
+
+/* returns true if details have been added */
+bool CDVDFileInfo::DemuxerToStreamDetails(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ CDVDDemux* pDemux,
+ CStreamDetails& details,
+ const std::string& path)
+{
+ bool retVal = false;
+ details.Reset();
+
+ const CURL pathToUrl(path);
+ for (CDemuxStream* stream : pDemux->GetStreams())
+ {
+ if (stream->type == STREAM_VIDEO && !(stream->flags & AV_DISPOSITION_ATTACHED_PIC))
+ {
+ CStreamDetailVideo *p = new CStreamDetailVideo();
+ CDemuxStreamVideo* vstream = static_cast<CDemuxStreamVideo*>(stream);
+ p->m_iWidth = vstream->iWidth;
+ p->m_iHeight = vstream->iHeight;
+ p->m_fAspect = static_cast<float>(vstream->fAspect);
+ if (p->m_fAspect == 0.0f && p->m_iHeight > 0)
+ p->m_fAspect = (float)p->m_iWidth / p->m_iHeight;
+ p->m_strCodec = pDemux->GetStreamCodecName(stream->demuxerId, stream->uniqueId);
+ p->m_iDuration = pDemux->GetStreamLength();
+ p->m_strStereoMode = vstream->stereo_mode;
+ p->m_strLanguage = vstream->language;
+ p->m_strHdrType = CStreamDetails::HdrTypeToString(vstream->hdr_type);
+
+ // stack handling
+ if (URIUtils::IsStack(path))
+ {
+ CFileItemList files;
+ XFILE::CStackDirectory stack;
+ stack.GetDirectory(pathToUrl, files);
+
+ // skip first path as we already know the duration
+ for (int i = 1; i < files.Size(); i++)
+ {
+ int duration = 0;
+ if (CDVDFileInfo::GetFileDuration(files[i]->GetDynPath(), duration))
+ p->m_iDuration = p->m_iDuration + duration;
+ }
+ }
+
+ // finally, calculate seconds
+ if (p->m_iDuration > 0)
+ p->m_iDuration = p->m_iDuration / 1000;
+
+ details.AddStream(p);
+ retVal = true;
+ }
+
+ else if (stream->type == STREAM_AUDIO)
+ {
+ CStreamDetailAudio *p = new CStreamDetailAudio();
+ p->m_iChannels = static_cast<CDemuxStreamAudio*>(stream)->iChannels;
+ p->m_strLanguage = stream->language;
+ p->m_strCodec = pDemux->GetStreamCodecName(stream->demuxerId, stream->uniqueId);
+ details.AddStream(p);
+ retVal = true;
+ }
+
+ else if (stream->type == STREAM_SUBTITLE)
+ {
+ CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
+ p->m_strLanguage = stream->language;
+ details.AddStream(p);
+ retVal = true;
+ }
+ } /* for iStream */
+
+ details.DetermineBestStreams();
+#ifdef HAVE_LIBBLURAY
+ // correct bluray runtime. we need the duration from the input stream, not the demuxer.
+ if (pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
+ {
+ if (std::static_pointer_cast<CDVDInputStreamBluray>(pInputStream)->GetTotalTime() > 0)
+ {
+ const CStreamDetailVideo* dVideo = static_cast<const CStreamDetailVideo*>(details.GetNthStream(CStreamDetail::VIDEO, 0));
+ CStreamDetailVideo* detailVideo = const_cast<CStreamDetailVideo*>(dVideo);
+ if (detailVideo)
+ detailVideo->m_iDuration = std::static_pointer_cast<CDVDInputStreamBluray>(pInputStream)->GetTotalTime() / 1000;
+ }
+ }
+#endif
+ return retVal;
+}
+
+bool CDVDFileInfo::AddExternalSubtitleToDetails(const std::string &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename)
+{
+ std::string ext = URIUtils::GetExtension(filename);
+ std::string vobsubfile = subfilename;
+ if(ext == ".idx")
+ {
+ if (vobsubfile.empty())
+ vobsubfile = URIUtils::ReplaceExtension(filename, ".sub");
+
+ CDVDDemuxVobsub v;
+ if (!v.Open(filename, STREAM_SOURCE_NONE, vobsubfile))
+ return false;
+
+ for(CDemuxStream* stream : v.GetStreams())
+ {
+ CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
+ std::string lang = stream->language;
+ dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392B(lang);
+ details.AddStream(dsub);
+ }
+ return true;
+ }
+ if(ext == ".sub")
+ {
+ std::string strReplace(URIUtils::ReplaceExtension(filename,".idx"));
+ if (XFILE::CFile::Exists(strReplace))
+ return false;
+ }
+
+ CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
+ ExternalStreamInfo info = CUtil::GetExternalStreamDetailsFromFilename(path, filename);
+ dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392B(info.language);
+ details.AddStream(dsub);
+
+ return true;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.h b/xbmc/cores/VideoPlayer/DVDFileInfo.h
new file mode 100644
index 0000000..bb0a7ab
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDFileInfo.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CFileItem;
+class CDVDDemux;
+class CStreamDetails;
+class CStreamDetailSubtitle;
+class CDVDInputStream;
+class CTextureDetails;
+
+class CDVDFileInfo
+{
+public:
+ // Extract a thumbnail image from the media referenced by fileItem, optionally populating a streamdetails class with the data
+ static bool ExtractThumb(const CFileItem& fileItem,
+ CTextureDetails &details,
+ CStreamDetails *pStreamDetails,
+ int64_t pos);
+
+ // Probe the files streams and store the info in the VideoInfoTag
+ static bool GetFileStreamDetails(CFileItem *pItem);
+ static bool DemuxerToStreamDetails(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ CDVDDemux* pDemux,
+ CStreamDetails& details,
+ const std::string& path = "");
+
+ /** \brief Probe the file's internal and external streams and store the info in the StreamDetails parameter.
+ * \param[out] details The file's StreamDetails consisting of internal streams and external subtitle streams.
+ */
+ static bool DemuxerToStreamDetails(const std::shared_ptr<CDVDInputStream>& pInputStream,
+ CDVDDemux* pDemuxer,
+ const std::vector<CStreamDetailSubtitle>& subs,
+ CStreamDetails& details);
+
+ static bool GetFileDuration(const std::string &path, int &duration);
+
+ /** \brief Probe the streams of an external subtitle file and store the info in the StreamDetails parameter.
+ * \param[out] details The external subtitle file's StreamDetails.
+ */
+ static bool AddExternalSubtitleToDetails(const std::string &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename = "");
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp
new file mode 100644
index 0000000..0a810c7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BlurayStateSerializer.h"
+
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <charconv>
+#include <cstring>
+#include <sstream>
+
+namespace
+{
+// Serializer version - used to avoid processing deprecated/legacy schemas
+constexpr int BLURAYSTATESERIALIZER_VERSION = 1;
+} // namespace
+
+bool CBlurayStateSerializer::BlurayStateToXML(std::string& xmlstate, const BlurayState& state)
+{
+ CXBMCTinyXML xmlDoc{"libbluraystate"};
+
+ TiXmlElement eRoot{"libbluraystate"};
+ eRoot.SetAttribute("version", BLURAYSTATESERIALIZER_VERSION);
+
+ TiXmlElement xmlElement{"playlistId"};
+ TiXmlText xmlElementValue = std::to_string(state.playlistId);
+ xmlElement.InsertEndChild(xmlElementValue);
+ eRoot.InsertEndChild(xmlElement);
+ xmlDoc.InsertEndChild(eRoot);
+
+ std::stringstream stream;
+ stream << xmlDoc;
+ xmlstate = stream.str();
+ return true;
+}
+
+bool CBlurayStateSerializer::XMLToBlurayState(BlurayState& state, const std::string& xmlstate)
+{
+ CXBMCTinyXML xmlDoc;
+
+ xmlDoc.Parse(xmlstate);
+ if (xmlDoc.Error())
+ return false;
+
+ TiXmlHandle hRoot(xmlDoc.RootElement());
+ if (!hRoot.Element() || !StringUtils::EqualsNoCase(hRoot.Element()->Value(), "libbluraystate"))
+ {
+ CLog::LogF(LOGERROR, "Failed to deserialize bluray state - failed to detect root element.");
+ return false;
+ }
+
+ auto version = hRoot.Element()->Attribute("version");
+ if (!version ||
+ !StringUtils::EqualsNoCase(version, std::to_string(BLURAYSTATESERIALIZER_VERSION)))
+ {
+ CLog::LogF(LOGERROR, "Failed to deserialize bluray state - incompatible serializer version.");
+ return false;
+ }
+
+ const TiXmlElement* childElement = hRoot.Element()->FirstChildElement();
+ while (childElement)
+ {
+ const std::string property = childElement->Value();
+ if (property == "playlistId")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()),
+ state.playlistId);
+ }
+ else
+ {
+ CLog::LogF(LOGWARNING, "Unmapped bluray state property {}, ignored.", childElement->Value());
+ }
+ childElement = childElement->NextSiblingElement();
+ }
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h
new file mode 100644
index 0000000..ca808c0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class TiXmlElement;
+
+/*! \brief Pod structure which represents the current Bluray state */
+struct BlurayState
+{
+ /*! The current playlist id */
+ int32_t playlistId = -1;
+};
+
+/*! \brief Auxiliar class to serialize/deserialize the Bluray state (into/from XML)
+*/
+class CBlurayStateSerializer
+{
+public:
+ /*! \brief Default constructor */
+ CBlurayStateSerializer() = default;
+
+ /*! \brief Default destructor */
+ ~CBlurayStateSerializer() = default;
+
+ /*! \brief Provided the state in xml format, fills a BlurayState struct representing the Bluray state and returns the
+ * success status of the operation
+ * \param[in,out] state the Bluray state struct to be filled
+ * \param xmlstate a string describing the Bluray state (XML)
+ * \return true if it was possible to fill the state struct based on the XML content, false otherwise
+ */
+ bool XMLToBlurayState(BlurayState& state, const std::string& xmlstate);
+
+ /*! \brief Provided the BlurayState struct of the current playing dvd, serializes the struct to XML
+ * \param[in,out] xmlstate a string describing the Bluray state (XML)
+ * \param state the Bluray state struct
+ * \return true if it was possible to serialize the struct into XML, false otherwise
+ */
+ bool BlurayStateToXML(std::string& xmlstate, const BlurayState& state);
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt
new file mode 100644
index 0000000..576ddda
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt
@@ -0,0 +1,38 @@
+set(SOURCES BlurayStateSerializer.cpp
+ DVDFactoryInputStream.cpp
+ DVDInputStream.cpp
+ DVDInputStreamFFmpeg.cpp
+ DVDInputStreamFile.cpp
+ DVDInputStreamMemory.cpp
+ DVDInputStreamNavigator.cpp
+ DVDInputStreamStack.cpp
+ DVDStateSerializer.cpp
+ InputStreamAddon.cpp
+ InputStreamMultiSource.cpp
+ InputStreamPVRBase.cpp
+ InputStreamPVRChannel.cpp
+ InputStreamPVRRecording.cpp)
+
+set(HEADERS BlurayStateSerializer.h
+ DVDFactoryInputStream.h
+ DVDInputStream.h
+ DVDInputStreamFFmpeg.h
+ DVDInputStreamFile.h
+ DVDInputStreamMemory.h
+ DVDInputStreamNavigator.h
+ DVDInputStreamStack.h
+ DVDStateSerializer.h
+ DllDvdNav.h
+ InputStreamAddon.h
+ InputStreamMultiStreams.h
+ InputStreamMultiSource.h
+ InputStreamPVRBase.h
+ InputStreamPVRChannel.h
+ InputStreamPVRRecording.h)
+
+if(BLURAY_FOUND)
+ list(APPEND SOURCES DVDInputStreamBluray.cpp)
+ list(APPEND HEADERS DVDInputStreamBluray.h)
+endif()
+
+core_add_library(dvdinputstreams)
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp
new file mode 100644
index 0000000..97c0896
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDFactoryInputStream.h"
+
+#include "DVDInputStream.h"
+#ifdef HAVE_LIBBLURAY
+#include "DVDInputStreamBluray.h"
+#endif
+#include "DVDInputStreamFFmpeg.h"
+#include "DVDInputStreamFile.h"
+#include "DVDInputStreamNavigator.h"
+#include "DVDInputStreamStack.h"
+#include "FileItem.h"
+#include "InputStreamAddon.h"
+#include "InputStreamMultiSource.h"
+#include "InputStreamPVRChannel.h"
+#include "InputStreamPVRRecording.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "cores/VideoPlayer/Interface/InputStreamConstants.h"
+#include "filesystem/CurlFile.h"
+#include "filesystem/IFileTypes.h"
+#include "storage/MediaManager.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+
+
+std::shared_ptr<CDVDInputStream> CDVDFactoryInputStream::CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, bool scanforextaudio)
+{
+ using namespace ADDON;
+
+ const std::string& file = fileitem.GetDynPath();
+ if (scanforextaudio)
+ {
+ // find any available external audio tracks
+ std::vector<std::string> filenames;
+ filenames.push_back(file);
+ CUtil::ScanForExternalAudio(file, filenames);
+ if (filenames.size() >= 2)
+ {
+ return CreateInputStream(pPlayer, fileitem, filenames);
+ }
+ }
+
+ std::vector<AddonInfoPtr> addonInfos;
+ CServiceBroker::GetAddonMgr().GetAddonInfos(addonInfos, true /*enabled only*/,
+ AddonType::INPUTSTREAM);
+ for (const auto& addonInfo : addonInfos)
+ {
+ if (CInputStreamAddon::Supports(addonInfo, fileitem))
+ {
+ // Used to inform input stream about special identifier;
+ const std::string instanceId =
+ fileitem.GetProperty(STREAM_PROPERTY_INPUTSTREAM_INSTANCE_ID).asString();
+
+ return std::make_shared<CInputStreamAddon>(addonInfo, pPlayer, fileitem, instanceId);
+ }
+ }
+
+ if (fileitem.GetProperty(STREAM_PROPERTY_INPUTSTREAM).asString() ==
+ STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG)
+ return std::shared_ptr<CDVDInputStreamFFmpeg>(new CDVDInputStreamFFmpeg(fileitem));
+
+ if (fileitem.IsDiscImage())
+ {
+#ifdef HAVE_LIBBLURAY
+ CURL url("udf://");
+ url.SetHostName(file);
+ url.SetFileName("BDMV/index.bdmv");
+ if (CFileUtils::Exists(url.Get()))
+ return std::shared_ptr<CDVDInputStreamBluray>(new CDVDInputStreamBluray(pPlayer, fileitem));
+ url.SetHostName(file);
+ url.SetFileName("BDMV/INDEX.BDM");
+ if (CFileUtils::Exists(url.Get()))
+ return std::shared_ptr<CDVDInputStreamBluray>(new CDVDInputStreamBluray(pPlayer, fileitem));
+#endif
+
+ return std::shared_ptr<CDVDInputStreamNavigator>(new CDVDInputStreamNavigator(pPlayer, fileitem));
+ }
+
+#ifdef HAS_DVD_DRIVE
+ if (file.compare(CServiceBroker::GetMediaManager().TranslateDevicePath("")) == 0)
+ {
+#ifdef HAVE_LIBBLURAY
+ if (CFileUtils::Exists(URIUtils::AddFileToFolder(file, "BDMV", "index.bdmv")) ||
+ CFileUtils::Exists(URIUtils::AddFileToFolder(file, "BDMV", "INDEX.BDM")))
+ return std::shared_ptr<CDVDInputStreamBluray>(new CDVDInputStreamBluray(pPlayer, fileitem));
+#endif
+
+ return std::shared_ptr<CDVDInputStreamNavigator>(new CDVDInputStreamNavigator(pPlayer, fileitem));
+ }
+#endif
+
+ if (fileitem.IsDVDFile(false, true))
+ return std::shared_ptr<CDVDInputStreamNavigator>(new CDVDInputStreamNavigator(pPlayer, fileitem));
+ else if (URIUtils::IsPVRChannel(file))
+ return std::shared_ptr<CInputStreamPVRChannel>(new CInputStreamPVRChannel(pPlayer, fileitem));
+ else if (URIUtils::IsPVRRecording(file))
+ return std::shared_ptr<CInputStreamPVRRecording>(new CInputStreamPVRRecording(pPlayer, fileitem));
+#ifdef HAVE_LIBBLURAY
+ else if (fileitem.IsType(".bdmv") || fileitem.IsType(".mpls")
+ || fileitem.IsType(".bdm") || fileitem.IsType(".mpl")
+ || StringUtils::StartsWithNoCase(file, "bluray:"))
+ return std::shared_ptr<CDVDInputStreamBluray>(new CDVDInputStreamBluray(pPlayer, fileitem));
+#endif
+ else if (StringUtils::StartsWithNoCase(file, "rtp://") ||
+ StringUtils::StartsWithNoCase(file, "rtsp://") ||
+ StringUtils::StartsWithNoCase(file, "rtsps://") ||
+ StringUtils::StartsWithNoCase(file, "satip://") ||
+ StringUtils::StartsWithNoCase(file, "sdp://") ||
+ StringUtils::StartsWithNoCase(file, "udp://") ||
+ StringUtils::StartsWithNoCase(file, "tcp://") ||
+ StringUtils::StartsWithNoCase(file, "mms://") ||
+ StringUtils::StartsWithNoCase(file, "mmst://") ||
+ StringUtils::StartsWithNoCase(file, "mmsh://") ||
+ StringUtils::StartsWithNoCase(file, "rtmp://") ||
+ StringUtils::StartsWithNoCase(file, "rtmpt://") ||
+ StringUtils::StartsWithNoCase(file, "rtmpe://") ||
+ StringUtils::StartsWithNoCase(file, "rtmpte://") ||
+ StringUtils::StartsWithNoCase(file, "rtmps://"))
+ {
+ return std::shared_ptr<CDVDInputStreamFFmpeg>(new CDVDInputStreamFFmpeg(fileitem));
+ }
+ else if(StringUtils::StartsWithNoCase(file, "stack://"))
+ return std::shared_ptr<CDVDInputStreamStack>(new CDVDInputStreamStack(fileitem));
+
+ CFileItem finalFileitem(fileitem);
+
+ if (finalFileitem.IsInternetStream())
+ {
+ if (finalFileitem.ContentLookup())
+ {
+ CURL origUrl(finalFileitem.GetDynURL());
+ XFILE::CCurlFile curlFile;
+ // try opening the url to resolve all redirects if any
+ try
+ {
+ if (curlFile.Open(finalFileitem.GetDynURL()))
+ {
+ CURL finalUrl(curlFile.GetURL());
+ finalUrl.SetProtocolOptions(origUrl.GetProtocolOptions());
+ finalUrl.SetUserName(origUrl.GetUserName());
+ finalUrl.SetPassword(origUrl.GetPassWord());
+ finalFileitem.SetDynPath(finalUrl.Get());
+ }
+ curlFile.Close();
+ }
+ catch (XFILE::CRedirectException *pRedirectEx)
+ {
+ if (pRedirectEx)
+ {
+ delete pRedirectEx->m_pNewFileImp;
+ delete pRedirectEx;
+ }
+ }
+ }
+
+ if (finalFileitem.IsType(".m3u8"))
+ return std::shared_ptr<CDVDInputStreamFFmpeg>(new CDVDInputStreamFFmpeg(finalFileitem));
+
+ // mime type for m3u8/hls streams
+ if (finalFileitem.GetMimeType() == "application/vnd.apple.mpegurl" ||
+ finalFileitem.GetMimeType() == "application/x-mpegURL")
+ return std::shared_ptr<CDVDInputStreamFFmpeg>(new CDVDInputStreamFFmpeg(finalFileitem));
+
+ if (URIUtils::IsProtocol(finalFileitem.GetPath(), "udp"))
+ return std::shared_ptr<CDVDInputStreamFFmpeg>(new CDVDInputStreamFFmpeg(finalFileitem));
+ }
+
+ // our file interface handles all these types of streams
+ return std::shared_ptr<CDVDInputStreamFile>(new CDVDInputStreamFile(finalFileitem,
+ XFILE::READ_TRUNCATED |
+ XFILE::READ_BITRATE |
+ XFILE::READ_CHUNKED));
+}
+
+std::shared_ptr<CDVDInputStream> CDVDFactoryInputStream::CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, const std::vector<std::string>& filenames)
+{
+ return std::shared_ptr<CInputStreamMultiSource>(new CInputStreamMultiSource(pPlayer, fileitem, filenames));
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h
new file mode 100644
index 0000000..c2feb71
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CFileItem;
+class CDVDInputStream;
+class IVideoPlayer;
+
+class CDVDFactoryInputStream
+{
+public:
+ static std::shared_ptr<CDVDInputStream> CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, bool scanforextaudio = false);
+ static std::shared_ptr<CDVDInputStream> CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, const std::vector<std::string>& filenames);
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.cpp
new file mode 100644
index 0000000..fee72ac
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStream.h"
+
+#include "URL.h"
+#include "cores/VideoPlayer/Interface/InputStreamConstants.h"
+
+CDVDInputStream::CDVDInputStream(DVDStreamType streamType, const CFileItem& fileitem)
+{
+ m_streamType = streamType;
+ m_contentLookup = true;
+ m_realtime = fileitem.GetProperty(STREAM_PROPERTY_ISREALTIMESTREAM).asBoolean(false);
+ m_item = fileitem;
+}
+
+CDVDInputStream::~CDVDInputStream() = default;
+
+bool CDVDInputStream::Open()
+{
+ m_content = m_item.GetMimeType();
+ m_contentLookup = m_item.ContentLookup();
+ return true;
+}
+
+void CDVDInputStream::Close()
+{
+
+}
+
+std::string CDVDInputStream::GetFileName()
+{
+ CURL url(m_item.GetDynPath());
+
+ url.SetProtocolOptions("");
+ return url.Get();
+}
+
+CURL CDVDInputStream::GetURL()
+{
+ return m_item.GetDynURL();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.h
new file mode 100644
index 0000000..4633757
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStream.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "URL.h"
+#include "cores/MenuType.h"
+#include "filesystem/IFileTypes.h"
+#include "utils/BitstreamStats.h"
+#include "utils/Geometry.h"
+
+#include <string>
+#include <vector>
+
+enum DVDStreamType
+{
+ DVDSTREAM_TYPE_NONE = -1,
+ DVDSTREAM_TYPE_FILE = 1,
+ DVDSTREAM_TYPE_DVD = 2,
+ DVDSTREAM_TYPE_HTTP = 3,
+ DVDSTREAM_TYPE_MEMORY = 4,
+ DVDSTREAM_TYPE_FFMPEG = 5,
+ DVDSTREAM_TYPE_TV = 6,
+ DVDSTREAM_TYPE_MPLS = 10,
+ DVDSTREAM_TYPE_BLURAY = 11,
+ DVDSTREAM_TYPE_PVRMANAGER = 12,
+ DVDSTREAM_TYPE_MULTIFILES = 13,
+ DVDSTREAM_TYPE_ADDON = 14
+};
+
+#define SEEK_POSSIBLE 0x10 // flag used to check if protocol allows seeks
+
+#define DVDSTREAM_BLOCK_SIZE_FILE (2048 * 16)
+#define DVDSTREAM_BLOCK_SIZE_DVD 2048
+
+namespace XFILE
+{
+ class CFile;
+}
+
+struct DemuxPacket;
+class CDemuxStream;
+
+class CDVDInputStream
+{
+public:
+
+ class IDisplayTime
+ {
+ public:
+ virtual ~IDisplayTime() = default;
+ virtual int GetTotalTime() = 0;
+ virtual int GetTime() = 0;
+ };
+
+ class ITimes
+ {
+ public:
+ struct Times
+ {
+ time_t startTime;
+ double ptsStart;
+ double ptsBegin;
+ double ptsEnd;
+ };
+ virtual ~ITimes() = default;
+ virtual bool GetTimes(Times &times) = 0;
+ };
+
+ class IPosTime
+ {
+ public:
+ virtual ~IPosTime() = default;
+ virtual bool PosTime(int ms) = 0;
+ };
+
+ class IChapter
+ {
+ public:
+ virtual ~IChapter() = default;
+ virtual int GetChapter() = 0;
+ virtual int GetChapterCount() = 0;
+ virtual void GetChapterName(std::string& name, int ch=-1) = 0;
+ virtual int64_t GetChapterPos(int ch=-1) = 0;
+ virtual bool SeekChapter(int ch) = 0;
+ };
+
+ class IMenus
+ {
+ public:
+ virtual ~IMenus() = default;
+ virtual void ActivateButton() = 0;
+ virtual void SelectButton(int iButton) = 0;
+ virtual int GetCurrentButton() = 0;
+ virtual int GetTotalButtons() = 0;
+ virtual void OnUp() = 0;
+ virtual void OnDown() = 0;
+ virtual void OnLeft() = 0;
+ virtual void OnRight() = 0;
+
+ /*! \brief Open the Menu
+ * \return true if the menu is successfully opened, false otherwise
+ */
+ virtual bool OnMenu() = 0;
+ virtual void OnBack() = 0;
+ virtual void OnNext() = 0;
+ virtual void OnPrevious() = 0;
+ virtual bool OnMouseMove(const CPoint &point) = 0;
+ virtual bool OnMouseClick(const CPoint &point) = 0;
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ virtual MenuType GetSupportedMenuType() = 0;
+
+ virtual bool IsInMenu() = 0;
+ virtual void SkipStill() = 0;
+ virtual double GetTimeStampCorrection() { return 0.0; }
+ virtual bool GetState(std::string &xmlstate) = 0;
+ virtual bool SetState(const std::string &xmlstate) = 0;
+ virtual bool CanSeek() { return !IsInMenu(); }
+ };
+
+ class IDemux
+ {
+ public:
+ virtual ~IDemux() = default;
+ virtual bool OpenDemux() = 0;
+ virtual DemuxPacket* ReadDemux() = 0;
+ virtual CDemuxStream* GetStream(int iStreamId) const = 0;
+ virtual std::vector<CDemuxStream*> GetStreams() const = 0;
+ virtual void EnableStream(int iStreamId, bool enable) {}
+ virtual bool OpenStream(int iStreamId) { return false; }
+ virtual int GetNrOfStreams() const = 0;
+ virtual void SetSpeed(int iSpeed) = 0;
+ virtual void FillBuffer(bool mode) {}
+ virtual bool SeekTime(double time, bool backward = false, double* startpts = NULL) = 0;
+ virtual void AbortDemux() = 0;
+ virtual void FlushDemux() = 0;
+ virtual void SetVideoResolution(unsigned int width,
+ unsigned int height,
+ unsigned int maxWidth,
+ unsigned int maxHeight)
+ {
+ }
+ };
+
+ enum ENextStream
+ {
+ NEXTSTREAM_NONE,
+ NEXTSTREAM_OPEN,
+ NEXTSTREAM_RETRY,
+ };
+
+ CDVDInputStream(DVDStreamType m_streamType, const CFileItem& fileitem);
+ virtual ~CDVDInputStream();
+ virtual bool Open();
+ virtual void Close();
+ virtual int Read(uint8_t* buf, int buf_size) = 0;
+ virtual int64_t Seek(int64_t offset, int whence) = 0;
+ virtual int64_t GetLength() = 0;
+ virtual std::string& GetContent() { return m_content; }
+ virtual std::string GetFileName();
+ virtual CURL GetURL();
+ virtual ENextStream NextStream() { return NEXTSTREAM_NONE; }
+ virtual void Abort() {}
+ virtual int GetBlockSize() { return 0; }
+ virtual bool CanSeek() { return true; } //! @todo drop this
+ virtual bool CanPause() { return false; }
+
+ /*! \brief Indicate expected read rate in bytes per second.
+ * This could be used to throttle caching rate. Should
+ * be seen as only a hint
+ */
+ virtual void SetReadRate(uint32_t rate) {}
+
+ /*! \brief Get the cache status
+ \return true when cache status was successfully obtained
+ */
+ virtual bool GetCacheStatus(XFILE::SCacheStatus *status) { return false; }
+
+ bool IsStreamType(DVDStreamType type) const { return m_streamType == type; }
+ virtual bool IsEOF() = 0;
+ virtual BitstreamStats GetBitstreamStats() const { return m_stats; }
+
+ bool ContentLookup() { return m_contentLookup; }
+
+ virtual bool IsRealtime() { return m_realtime; }
+
+ void SetRealtime(bool realtime) { m_realtime = realtime; }
+
+ // interfaces
+ virtual IDemux* GetIDemux() { return nullptr; }
+ virtual IPosTime* GetIPosTime() { return nullptr; }
+ virtual IDisplayTime* GetIDisplayTime() { return nullptr; }
+ virtual ITimes* GetITimes() { return nullptr; }
+ virtual IChapter* GetIChapter() { return nullptr; }
+
+ const CVariant& GetProperty(const std::string& key) { return m_item.GetProperty(key); }
+
+protected:
+ DVDStreamType m_streamType;
+ BitstreamStats m_stats;
+ std::string m_content;
+ CFileItem m_item;
+ bool m_contentLookup;
+ bool m_realtime;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp
new file mode 100644
index 0000000..8a1f6bc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp
@@ -0,0 +1,1307 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamBluray.h"
+
+#include "DVDCodecs/Overlay/DVDOverlay.h"
+#include "DVDCodecs/Overlay/DVDOverlayImage.h"
+#include "IVideoPlayer.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/BlurayCallback.h"
+#include "filesystem/Directory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/DiscSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Geometry.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <functional>
+#include <limits>
+
+#include <libbluray/bluray.h>
+#include <libbluray/log_control.h>
+
+#define LIBBLURAY_BYTESEEK 0
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+static int read_blocks(void* handle, void* buf, int lba, int num_blocks)
+{
+ int result = -1;
+ CDVDInputStreamFile* lpstream = reinterpret_cast<CDVDInputStreamFile*>(handle);
+ int64_t offset = static_cast<int64_t>(lba) * 2048;
+ if (lpstream->Seek(offset, SEEK_SET) >= 0)
+ {
+ int64_t size = static_cast<int64_t>(num_blocks) * 2048;
+ if (size <= std::numeric_limits<int>::max())
+ result = lpstream->Read(reinterpret_cast<uint8_t*>(buf), static_cast<int>(size)) / 2048;
+ }
+
+ return result;
+}
+
+static void bluray_overlay_cb(void *this_gen, const BD_OVERLAY * ov)
+{
+ static_cast<CDVDInputStreamBluray*>(this_gen)->OverlayCallback(ov);
+}
+
+#ifdef HAVE_LIBBLURAY_BDJ
+void bluray_overlay_argb_cb(void *this_gen, const struct bd_argb_overlay_s * const ov)
+{
+ static_cast<CDVDInputStreamBluray*>(this_gen)->OverlayCallbackARGB(ov);
+}
+#endif
+
+CDVDInputStreamBluray::CDVDInputStreamBluray(IVideoPlayer* player, const CFileItem& fileitem) :
+ CDVDInputStream(DVDSTREAM_TYPE_BLURAY, fileitem), m_player(player)
+{
+ m_content = "video/x-mpegts";
+ memset(&m_event, 0, sizeof(m_event));
+#ifdef HAVE_LIBBLURAY_BDJ
+ memset(&m_argb, 0, sizeof(m_argb));
+#endif
+}
+
+CDVDInputStreamBluray::~CDVDInputStreamBluray()
+{
+ Close();
+}
+
+void CDVDInputStreamBluray::Abort()
+{
+ m_hold = HOLD_EXIT;
+}
+
+bool CDVDInputStreamBluray::IsEOF()
+{
+ return false;
+}
+
+BLURAY_TITLE_INFO* CDVDInputStreamBluray::GetTitleLongest()
+{
+ int titles = bd_get_titles(m_bd, TITLES_RELEVANT, 0);
+
+ BLURAY_TITLE_INFO *s = nullptr;
+ for(int i=0; i < titles; i++)
+ {
+ BLURAY_TITLE_INFO *t = bd_get_title_info(m_bd, i, 0);
+ if(!t)
+ {
+ CLog::Log(LOGDEBUG, "get_main_title - unable to get title {}", i);
+ continue;
+ }
+ if(!s || s->duration < t->duration)
+ std::swap(s, t);
+
+ if(t)
+ bd_free_title_info(t);
+ }
+ return s;
+}
+
+BLURAY_TITLE_INFO* CDVDInputStreamBluray::GetTitleFile(const std::string& filename)
+{
+ unsigned int playlist;
+ if(sscanf(filename.c_str(), "%05u.mpls", &playlist) != 1)
+ {
+ CLog::Log(LOGERROR, "get_playlist_title - unsupported playlist file selected {}",
+ CURL::GetRedacted(filename));
+ return nullptr;
+ }
+
+ return bd_get_playlist_info(m_bd, playlist, 0);
+}
+
+
+bool CDVDInputStreamBluray::Open()
+{
+ if(m_player == nullptr)
+ return false;
+
+ std::string strPath(m_item.GetDynPath());
+ std::string filename;
+ std::string root;
+
+ bool openStream = false;
+ bool openDisc = false;
+ bool resumable = true;
+
+ // The item was selected via the simple menu
+ if (URIUtils::IsProtocol(strPath, "bluray"))
+ {
+ CURL url(strPath);
+ root = url.GetHostName();
+ filename = URIUtils::GetFileName(url.GetFileName());
+
+ // Check whether disc is AACS protected
+ CURL url3(root);
+ CFileItem base(url3, false);
+ openDisc = base.IsProtectedBlurayDisc();
+
+ // check for a menu call for an image file
+ if (StringUtils::EqualsNoCase(filename, "menu"))
+ {
+ //get rid of the udf:// protocol
+ CURL url2(root);
+ const std::string& root2 = url2.GetHostName();
+ CURL url(root2);
+ CFileItem item(url, false);
+ resumable = false;
+
+ // Check whether disc is AACS protected
+ if (!openDisc)
+ openDisc = item.IsProtectedBlurayDisc();
+
+ if (item.IsDiscImage())
+ {
+ if (!OpenStream(item))
+ return false;
+
+ openStream = true;
+ }
+ }
+ }
+ else if (m_item.IsDiscImage())
+ {
+ if (!OpenStream(m_item))
+ return false;
+
+ openStream = true;
+ }
+ else if (m_item.IsProtectedBlurayDisc())
+ {
+ openDisc = true;
+ }
+ else
+ {
+ strPath = URIUtils::GetDirectory(strPath);
+ URIUtils::RemoveSlashAtEnd(strPath);
+
+ if(URIUtils::GetFileName(strPath) == "PLAYLIST")
+ {
+ strPath = URIUtils::GetDirectory(strPath);
+ URIUtils::RemoveSlashAtEnd(strPath);
+ }
+
+ if(URIUtils::GetFileName(strPath) == "BDMV")
+ {
+ strPath = URIUtils::GetDirectory(strPath);
+ URIUtils::RemoveSlashAtEnd(strPath);
+ }
+ root = strPath;
+ filename = URIUtils::GetFileName(m_item.GetPath());
+ }
+
+ // root should not have trailing slash
+ URIUtils::RemoveSlashAtEnd(root);
+
+ bd_set_debug_handler(CBlurayCallback::bluray_logger);
+ bd_set_debug_mask(DBG_CRIT | DBG_BLURAY | DBG_NAV);
+
+ m_bd = bd_init();
+
+ if (!m_bd)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to initialize libbluray");
+ return false;
+ }
+
+ SetupPlayerSettings();
+
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - opening {}", CURL::GetRedacted(root));
+
+ if (openStream)
+ {
+ if (!bd_open_stream(m_bd, m_pstream.get(), read_blocks))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to open {} in stream mode",
+ CURL::GetRedacted(root));
+ return false;
+ }
+ }
+ else if (openDisc)
+ {
+ // This special case is required for opening original AACS protected Blu-ray discs. Otherwise
+ // things like Bus Encryption might not be handled properly and playback will fail.
+ m_rootPath = root;
+ if (!bd_open_disc(m_bd, root.c_str(), nullptr))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to open {} in disc mode",
+ CURL::GetRedacted(root));
+ return false;
+ }
+ }
+ else
+ {
+ m_rootPath = root;
+ if (!bd_open_files(m_bd, &m_rootPath, CBlurayCallback::dir_open, CBlurayCallback::file_open))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to open {} in files mode",
+ CURL::GetRedacted(root));
+ return false;
+ }
+ }
+
+ bd_get_event(m_bd, nullptr);
+
+ const BLURAY_DISC_INFO *disc_info = bd_get_disc_info(m_bd);
+
+ if (!disc_info)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - bd_get_disc_info() failed");
+ return false;
+ }
+
+ if (disc_info->bluray_detected)
+ {
+#if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0))
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - Disc name : {}",
+ disc_info->disc_name ? disc_info->disc_name : "");
+#endif
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - First Play supported: {}",
+ disc_info->first_play_supported);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - Top menu supported : {}",
+ disc_info->top_menu_supported);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - HDMV titles : {}",
+ disc_info->num_hdmv_titles);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - BD-J titles : {}",
+ disc_info->num_bdj_titles);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - BD-J handled : {}",
+ disc_info->bdj_handled);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - UNSUPPORTED titles : {}",
+ disc_info->num_unsupported_titles);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - AACS detected : {}",
+ disc_info->aacs_detected);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - libaacs detected : {}",
+ disc_info->libaacs_detected);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - AACS handled : {}",
+ disc_info->aacs_handled);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - BD+ detected : {}",
+ disc_info->bdplus_detected);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - libbdplus detected : {}",
+ disc_info->libbdplus_detected);
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - BD+ handled : {}",
+ disc_info->bdplus_handled);
+#if (BLURAY_VERSION >= BLURAY_VERSION_CODE(1,0,0))
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::Open - no menus (libmmbd, or profile 6 bdj) : {}",
+ disc_info->no_menu_support);
+#endif
+ }
+ else
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - BluRay not detected");
+
+ if (disc_info->aacs_detected && !disc_info->aacs_handled)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - Media stream scrambled/encrypted with AACS");
+ m_player->OnDiscNavResult(nullptr, BD_EVENT_ENC_ERROR);
+ return false;
+ }
+
+ if (disc_info->bdplus_detected && !disc_info->bdplus_handled)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - Media stream scrambled/encrypted with BD+");
+ m_player->OnDiscNavResult(nullptr, BD_EVENT_ENC_ERROR);
+ return false;
+ }
+
+ int mode = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_DISC_PLAYBACK);
+
+ if (URIUtils::HasExtension(filename, ".mpls"))
+ {
+ m_navmode = false;
+ m_titleInfo = GetTitleFile(filename);
+ }
+ else if (mode == BD_PLAYBACK_MAIN_TITLE)
+ {
+ m_navmode = false;
+ m_titleInfo = GetTitleLongest();
+ }
+ else if (resumable && m_item.GetStartOffset() == STARTOFFSET_RESUME)
+ {
+ // resuming a bluray for which we have a saved state - the playlist will be open later on SetState
+ m_navmode = false;
+ return true;
+ }
+ else
+ {
+ m_navmode = true;
+ if (!disc_info->first_play_supported)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - Can't play disc in HDMV navigation mode - First Play title not supported");
+ m_navmode = false;
+ }
+
+ if (m_navmode && disc_info->num_unsupported_titles > 0) {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - Unsupported titles found - Some titles can't be played in navigation mode");
+ }
+
+ if(!m_navmode)
+ m_titleInfo = GetTitleLongest();
+ }
+
+ if (m_navmode)
+ {
+
+ bd_register_overlay_proc (m_bd, this, bluray_overlay_cb);
+#ifdef HAVE_LIBBLURAY_BDJ
+ bd_register_argb_overlay_proc (m_bd, this, bluray_overlay_argb_cb, nullptr);
+#endif
+
+ if(bd_play(m_bd) <= 0)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed play disk {}",
+ CURL::GetRedacted(strPath));
+ return false;
+ }
+ m_hold = HOLD_DATA;
+ }
+ else
+ {
+ if(!m_titleInfo)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to get title info");
+ return false;
+ }
+
+ if(!bd_select_playlist(m_bd, m_titleInfo->playlist))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Open - failed to select playlist {}",
+ m_titleInfo->idx);
+ return false;
+ }
+ m_clip = nullptr;
+ }
+
+ // Process any events that occurred during opening
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+
+ return true;
+}
+
+// close file and reset everything
+void CDVDInputStreamBluray::Close()
+{
+ FreeTitleInfo();
+
+ if(m_bd)
+ {
+ bd_register_overlay_proc(m_bd, nullptr, nullptr);
+ bd_close(m_bd);
+ }
+
+ m_bd = nullptr;
+ m_pstream.reset();
+ m_rootPath.clear();
+}
+
+void CDVDInputStreamBluray::FreeTitleInfo()
+{
+ if (m_titleInfo)
+ bd_free_title_info(m_titleInfo);
+
+ m_titleInfo = nullptr;
+ m_clip = nullptr;
+}
+
+void CDVDInputStreamBluray::ProcessEvent() {
+
+ int pid = -1;
+ switch (m_event.event) {
+
+ /* errors */
+
+ case BD_EVENT_ERROR:
+ switch (m_event.param)
+ {
+ case BD_ERROR_HDMV:
+ case BD_ERROR_BDJ:
+ m_player->OnDiscNavResult(nullptr, BD_EVENT_MENU_ERROR);
+ break;
+ default:
+ break;
+ }
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray - BD_EVENT_ERROR: Fatal error. Playback can't be continued.");
+ m_hold = HOLD_ERROR;
+ break;
+
+ case BD_EVENT_READ_ERROR:
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray - BD_EVENT_READ_ERROR");
+ break;
+
+ case BD_EVENT_ENCRYPTED:
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray - BD_EVENT_ENCRYPTED");
+ switch (m_event.param)
+ {
+ case BD_ERROR_AACS:
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray - BD_ERROR_AACS");
+ break;
+ case BD_ERROR_BDPLUS:
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray - BD_ERROR_BDPLUS");
+ break;
+ default:
+ break;
+ }
+ m_hold = HOLD_ERROR;
+ m_player->OnDiscNavResult(nullptr, BD_EVENT_ENC_ERROR);
+ break;
+
+ /* playback control */
+
+ case BD_EVENT_SEEK:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_SEEK");
+ //m_player->OnDVDNavResult(nullptr, 1);
+ //bd_read_skip_still(m_bd);
+ //m_hold = HOLD_HELD;
+ break;
+
+ case BD_EVENT_STILL_TIME:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_STILL_TIME {}", m_event.param);
+ pid = m_event.param;
+ m_player->OnDiscNavResult(static_cast<void*>(&pid), BD_EVENT_STILL_TIME);
+ m_hold = HOLD_STILL;
+ break;
+
+ case BD_EVENT_STILL:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_STILL {}", m_event.param);
+
+ pid = m_event.param;
+
+ if (pid == 0)
+ m_player->OnDiscNavResult(static_cast<void*>(&pid), BD_EVENT_STILL);
+ break;
+
+ case BD_EVENT_DISCONTINUITY:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_DISCONTINUITY");
+ m_hold = HOLD_STILL;
+ break;
+
+ /* playback position */
+
+ case BD_EVENT_ANGLE:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_ANGLE {}", m_event.param);
+ m_angle = m_event.param;
+
+ if (m_playlist <= MAX_PLAYLIST_ID)
+ {
+ FreeTitleInfo();
+ m_titleInfo = bd_get_playlist_info(m_bd, m_playlist, m_angle);
+ }
+ break;
+
+ case BD_EVENT_END_OF_TITLE:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_END_OF_TITLE {}", m_event.param);
+ /* when a title ends, playlist WILL eventually change */
+ FreeTitleInfo();
+ break;
+
+ case BD_EVENT_TITLE:
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_TITLE {}", m_event.param);
+ const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
+
+ m_menu = false;
+ m_isInMainMenu = false;
+
+ if (m_event.param == BLURAY_TITLE_TOP_MENU)
+ {
+ m_title = disc_info->top_menu;
+ m_menu = true;
+ m_isInMainMenu = true;
+ }
+ else if (m_event.param == BLURAY_TITLE_FIRST_PLAY)
+ m_title = disc_info->first_play;
+ else if (m_event.param <= disc_info->num_titles)
+ m_title = disc_info->titles[m_event.param];
+ else
+ m_title = nullptr;
+
+ break;
+ }
+ case BD_EVENT_PLAYLIST:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_PLAYLIST {}", m_event.param);
+ m_playlist = m_event.param;
+ FreeTitleInfo();
+ m_titleInfo = bd_get_playlist_info(m_bd, m_playlist, m_angle);
+ break;
+
+ case BD_EVENT_PLAYITEM:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_PLAYITEM {}", m_event.param);
+ if (m_titleInfo && m_event.param < m_titleInfo->clip_count)
+ m_clip = &m_titleInfo->clips[m_event.param];
+ break;
+
+ case BD_EVENT_CHAPTER:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_CHAPTER {}", m_event.param);
+ break;
+
+ /* stream selection */
+
+ case BD_EVENT_AUDIO_STREAM:
+ pid = -1;
+ if (m_titleInfo && m_clip && static_cast<uint32_t>(m_clip->audio_stream_count) > (m_event.param - 1))
+ pid = m_clip->audio_streams[m_event.param - 1].pid;
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_AUDIO_STREAM {} {}", m_event.param, pid);
+ m_player->OnDiscNavResult(static_cast<void*>(&pid), BD_EVENT_AUDIO_STREAM);
+ break;
+
+ case BD_EVENT_PG_TEXTST:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_PG_TEXTST {}", m_event.param);
+ pid = m_event.param;
+ m_player->OnDiscNavResult(static_cast<void*>(&pid), BD_EVENT_PG_TEXTST);
+ break;
+
+ case BD_EVENT_PG_TEXTST_STREAM:
+ pid = -1;
+ if (m_titleInfo && m_clip && static_cast<uint32_t>(m_clip->pg_stream_count) > (m_event.param - 1))
+ pid = m_clip->pg_streams[m_event.param - 1].pid;
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_PG_TEXTST_STREAM {}, {}", m_event.param,
+ pid);
+ m_player->OnDiscNavResult(static_cast<void*>(&pid), BD_EVENT_PG_TEXTST_STREAM);
+ break;
+
+ case BD_EVENT_MENU:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_MENU {}", m_event.param);
+ m_menu = (m_event.param != 0);
+ if (!m_menu)
+ m_isInMainMenu = false;
+ break;
+
+ case BD_EVENT_IDLE:
+ KODI::TIME::Sleep(100ms);
+ break;
+
+ case BD_EVENT_SOUND_EFFECT:
+ {
+ BLURAY_SOUND_EFFECT effect;
+ if (bd_get_sound_effect(m_bd, m_event.param, &effect) <= 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_SOUND_EFFECT {} not valid",
+ m_event.param);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_SOUND_EFFECT {}", m_event.param);
+ }
+ }
+
+ case BD_EVENT_IG_STREAM:
+ case BD_EVENT_SECONDARY_AUDIO:
+ case BD_EVENT_SECONDARY_AUDIO_STREAM:
+ case BD_EVENT_SECONDARY_VIDEO:
+ case BD_EVENT_SECONDARY_VIDEO_SIZE:
+ case BD_EVENT_SECONDARY_VIDEO_STREAM:
+ case BD_EVENT_PLAYMARK:
+ case BD_EVENT_KEY_INTEREST_TABLE:
+ case BD_EVENT_UO_MASK_CHANGED:
+ break;
+
+ case BD_EVENT_PLAYLIST_STOP:
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray - BD_EVENT_PLAYLIST_STOP: flush buffers");
+ m_player->OnDiscNavResult(nullptr, BD_EVENT_PLAYLIST_STOP);
+ break;
+ case BD_EVENT_NONE:
+ break;
+
+ default:
+ CLog::Log(LOGWARNING, "CDVDInputStreamBluray - unhandled libbluray event {} [param {}]",
+ m_event.event, m_event.param);
+ break;
+ }
+
+ /* event has been consumed */
+ m_event.event = BD_EVENT_NONE;
+}
+
+int CDVDInputStreamBluray::Read(uint8_t* buf, int buf_size)
+{
+ int result = 0;
+ m_dispTimeBeforeRead = static_cast<int>((bd_tell_time(m_bd) / 90));
+ if(m_navmode)
+ {
+ do {
+
+ if (m_hold == HOLD_HELD)
+ return 0;
+
+ if( m_hold == HOLD_ERROR
+ || m_hold == HOLD_EXIT)
+ return -1;
+
+ result = bd_read_ext (m_bd, buf, buf_size, &m_event);
+
+ if(result < 0)
+ {
+ m_hold = HOLD_ERROR;
+ return result;
+ }
+
+ /* Check for holding events */
+ switch(m_event.event) {
+ case BD_EVENT_SEEK:
+ case BD_EVENT_TITLE:
+ case BD_EVENT_ANGLE:
+ case BD_EVENT_PLAYLIST:
+ case BD_EVENT_PLAYITEM:
+ if(m_hold != HOLD_DATA)
+ {
+ m_hold = HOLD_HELD;
+ return result;
+ }
+ break;
+
+ case BD_EVENT_STILL_TIME:
+ if(m_hold == HOLD_STILL)
+ m_event.event = 0; /* Consume duplicate still event */
+ else
+ m_hold = HOLD_HELD;
+ return result;
+
+ default:
+ break;
+ }
+
+ if(result > 0)
+ m_hold = HOLD_NONE;
+
+ ProcessEvent();
+
+ } while(result == 0);
+
+ }
+ else
+ {
+ result = bd_read(m_bd, buf, buf_size);
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+ }
+ return result;
+}
+
+static uint8_t clamp(double v)
+{
+ return (v) > 255.0 ? 255 : ((v) < 0.0 ? 0 : static_cast<uint32_t>((v + 0.5)));
+}
+
+static uint32_t build_rgba(const BD_PG_PALETTE_ENTRY &e)
+{
+ double r = 1.164 * (e.Y - 16) + 1.596 * (e.Cr - 128);
+ double g = 1.164 * (e.Y - 16) - 0.391 * (e.Cb - 128) - 0.813 * (e.Cr - 128);
+ double b = 1.164 * (e.Y - 16) + 2.018 * (e.Cb - 128);
+ return static_cast<uint32_t>(e.T) << PIXEL_ASHIFT
+ | static_cast<uint32_t>(clamp(r)) << PIXEL_RSHIFT
+ | static_cast<uint32_t>(clamp(g)) << PIXEL_GSHIFT
+ | static_cast<uint32_t>(clamp(b)) << PIXEL_BSHIFT;
+}
+
+void CDVDInputStreamBluray::OverlayClose()
+{
+#if(BD_OVERLAY_INTERFACE_VERSION >= 2)
+ for(SPlane& plane : m_planes)
+ plane.o.clear();
+ CDVDOverlayGroup* group = new CDVDOverlayGroup();
+ group->bForced = true;
+ m_player->OnDiscNavResult(static_cast<void*>(group), BD_EVENT_MENU_OVERLAY);
+ group->Release();
+ m_hasOverlay = false;
+#endif
+}
+
+void CDVDInputStreamBluray::OverlayInit(SPlane& plane, int w, int h)
+{
+#if(BD_OVERLAY_INTERFACE_VERSION >= 2)
+ plane.o.clear();
+ plane.w = w;
+ plane.h = h;
+#endif
+}
+
+void CDVDInputStreamBluray::OverlayClear(SPlane& plane, int x, int y, int w, int h)
+{
+#if(BD_OVERLAY_INTERFACE_VERSION >= 2)
+ CRectInt ovr(x
+ , y
+ , x + w
+ , y + h);
+
+ /* fixup existing overlays */
+ for(SOverlays::iterator it = plane.o.begin(); it != plane.o.end();)
+ {
+ CRectInt old((*it)->x
+ , (*it)->y
+ , (*it)->x + (*it)->width
+ , (*it)->y + (*it)->height);
+
+ std::vector<CRectInt> rem = old.SubtractRect(ovr);
+
+ /* if no overlap we are done */
+ if(rem.size() == 1 && !(rem[0] != old))
+ {
+ ++it;
+ continue;
+ }
+
+ SOverlays add;
+ for(std::vector<CRectInt>::iterator itr = rem.begin(); itr != rem.end(); ++itr)
+ {
+ SOverlay overlay(new CDVDOverlayImage(*(*it)
+ , itr->x1
+ , itr->y1
+ , itr->Width()
+ , itr->Height())
+ , [](CDVDOverlay* ov) { ov->Release(); });
+ add.push_back(overlay);
+ }
+
+ it = plane.o.erase(it);
+ plane.o.insert(it, add.begin(), add.end());
+ }
+#endif
+}
+
+void CDVDInputStreamBluray::OverlayFlush(int64_t pts)
+{
+#if(BD_OVERLAY_INTERFACE_VERSION >= 2)
+ CDVDOverlayGroup* group = new CDVDOverlayGroup();
+ group->bForced = true;
+ group->iPTSStartTime = static_cast<double>(pts);
+ group->iPTSStopTime = 0;
+
+ for(SPlane& plane : m_planes)
+ {
+ for(SOverlays::iterator it = plane.o.begin(); it != plane.o.end(); ++it)
+ group->m_overlays.push_back((*it)->Acquire());
+ }
+
+ m_player->OnDiscNavResult(static_cast<void*>(group), BD_EVENT_MENU_OVERLAY);
+ group->Release();
+ m_hasOverlay = true;
+#endif
+}
+
+void CDVDInputStreamBluray::OverlayCallback(const BD_OVERLAY * const ov)
+{
+#if(BD_OVERLAY_INTERFACE_VERSION >= 2)
+ if(ov == nullptr || ov->cmd == BD_OVERLAY_CLOSE)
+ {
+ OverlayClose();
+ return;
+ }
+
+ if (ov->plane > 1)
+ {
+ CLog::Log(LOGWARNING, "CDVDInputStreamBluray - Ignoring overlay with multiple planes");
+ return;
+ }
+
+ SPlane& plane(m_planes[ov->plane]);
+
+ if (ov->cmd == BD_OVERLAY_CLEAR)
+ {
+ plane.o.clear();
+ return;
+ }
+
+ if (ov->cmd == BD_OVERLAY_INIT)
+ {
+ OverlayInit(plane, ov->w, ov->h);
+ return;
+ }
+
+ if (ov->cmd == BD_OVERLAY_DRAW || ov->cmd == BD_OVERLAY_WIPE)
+ OverlayClear(plane, ov->x, ov->y, ov->w, ov->h);
+
+ /* uncompress and draw bitmap */
+ if (ov->img && ov->cmd == BD_OVERLAY_DRAW)
+ {
+ SOverlay overlay(new CDVDOverlayImage(), [](CDVDOverlay* ov) { ov->Release(); });
+
+ if (ov->palette)
+ {
+ overlay->palette.resize(256);
+
+ for(unsigned i = 0; i < 256; i++)
+ overlay->palette[i] = build_rgba(ov->palette[i]);
+ }
+ else
+ overlay->palette.clear();
+
+ const BD_PG_RLE_ELEM *rlep = ov->img;
+ size_t bytes = ov->w * ov->h;
+ overlay->pixels.resize(bytes);
+
+ for (size_t i = 0; i < bytes; i += rlep->len, rlep++)
+ memset(overlay->pixels.data() + i, rlep->color, rlep->len);
+
+ overlay->linesize = ov->w;
+ overlay->x = ov->x;
+ overlay->y = ov->y;
+ overlay->height = ov->h;
+ overlay->width = ov->w;
+ overlay->source_height = plane.h;
+ overlay->source_width = plane.w;
+ plane.o.push_back(overlay);
+ }
+
+ if (ov->cmd == BD_OVERLAY_FLUSH)
+ OverlayFlush(ov->pts);
+#endif
+}
+
+#ifdef HAVE_LIBBLURAY_BDJ
+void CDVDInputStreamBluray::OverlayCallbackARGB(const struct bd_argb_overlay_s * const ov)
+{
+ if(ov == nullptr || ov->cmd == BD_ARGB_OVERLAY_CLOSE)
+ {
+ OverlayClose();
+ return;
+ }
+
+ if (ov->plane > 1)
+ {
+ CLog::Log(LOGWARNING, "CDVDInputStreamBluray - Ignoring overlay with multiple planes");
+ return;
+ }
+
+ SPlane& plane(m_planes[ov->plane]);
+
+ if (ov->cmd == BD_ARGB_OVERLAY_INIT)
+ {
+ OverlayInit(plane, ov->w, ov->h);
+ return;
+ }
+
+ if (ov->cmd == BD_ARGB_OVERLAY_DRAW)
+ OverlayClear(plane, ov->x, ov->y, ov->w, ov->h);
+
+ /* uncompress and draw bitmap */
+ if (ov->argb && ov->cmd == BD_ARGB_OVERLAY_DRAW)
+ {
+ SOverlay overlay(new CDVDOverlayImage(), [](CDVDOverlay* ov) { CDVDOverlay::Release(ov); });
+
+ overlay->palette.clear();
+ size_t bytes = static_cast<size_t>(ov->stride * ov->h * 4);
+ overlay->pixels.resize(bytes);
+ memcpy(overlay->pixels.data(), ov->argb, bytes);
+
+ overlay->linesize = ov->stride * 4;
+ overlay->x = ov->x;
+ overlay->y = ov->y;
+ overlay->height = ov->h;
+ overlay->width = ov->w;
+ overlay->source_height = plane.h;
+ overlay->source_width = plane.w;
+ plane.o.push_back(overlay);
+ }
+
+ if(ov->cmd == BD_ARGB_OVERLAY_FLUSH)
+ OverlayFlush(ov->pts);
+}
+#endif
+
+
+int CDVDInputStreamBluray::GetTotalTime()
+{
+ if(m_titleInfo)
+ return static_cast<int>(m_titleInfo->duration / 90);
+ else
+ return 0;
+}
+
+int CDVDInputStreamBluray::GetTime()
+{
+ return m_dispTimeBeforeRead;
+}
+
+bool CDVDInputStreamBluray::PosTime(int ms)
+{
+ if(bd_seek_time(m_bd, ms * 90) < 0)
+ return false;
+
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+
+ return true;
+}
+
+int CDVDInputStreamBluray::GetChapterCount()
+{
+ if(m_titleInfo)
+ return static_cast<int>(m_titleInfo->chapter_count);
+ else
+ return 0;
+}
+
+int CDVDInputStreamBluray::GetChapter()
+{
+ if(m_titleInfo)
+ return static_cast<int>(bd_get_current_chapter(m_bd) + 1);
+ else
+ return 0;
+}
+
+bool CDVDInputStreamBluray::SeekChapter(int ch)
+{
+ if(m_titleInfo && bd_seek_chapter(m_bd, ch-1) < 0)
+ return false;
+
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+
+ return true;
+}
+
+int64_t CDVDInputStreamBluray::GetChapterPos(int ch)
+{
+ if (ch == -1 || ch > GetChapterCount())
+ ch = GetChapter();
+
+ if (m_titleInfo && m_titleInfo->chapters)
+ return m_titleInfo->chapters[ch - 1].start / 90000;
+ else
+ return 0;
+}
+
+int64_t CDVDInputStreamBluray::Seek(int64_t offset, int whence)
+{
+#if LIBBLURAY_BYTESEEK
+ if(whence == SEEK_POSSIBLE)
+ return 1;
+ else if(whence == SEEK_CUR)
+ {
+ if(offset == 0)
+ return bd_tell(m_bd);
+ else
+ offset += bd_tell(m_bd);
+ }
+ else if(whence == SEEK_END)
+ offset += bd_get_title_size(m_bd);
+ else if(whence != SEEK_SET)
+ return -1;
+
+ int64_t pos = bd_seek(m_bd, offset);
+ if(pos < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamBluray::Seek - seek to {}, failed with {}", offset, pos);
+ return -1;
+ }
+
+ if(pos != offset)
+ CLog::Log(LOGWARNING, "CDVDInputStreamBluray::Seek - seek to {}, ended at {}", offset, pos);
+
+ return offset;
+#else
+ if(whence == SEEK_POSSIBLE)
+ return 0;
+ return -1;
+#endif
+}
+
+int64_t CDVDInputStreamBluray::GetLength()
+{
+ return static_cast<int64_t>(bd_get_title_size(m_bd));
+}
+
+static bool find_stream(int pid, BLURAY_STREAM_INFO *info, int count, std::string &language)
+{
+ int i=0;
+ for(;i<count;i++,info++)
+ {
+ if(info->pid == static_cast<uint16_t>(pid))
+ break;
+ }
+ if(i==count)
+ return false;
+ language = reinterpret_cast<char*>(info->lang);
+ return true;
+}
+
+void CDVDInputStreamBluray::GetStreamInfo(int pid, std::string &language)
+{
+ if(!m_titleInfo || !m_clip)
+ return;
+
+ if (pid == HDMV_PID_VIDEO)
+ find_stream(pid, m_clip->video_streams, m_clip->video_stream_count, language);
+ else if (HDMV_PID_AUDIO_FIRST <= pid && pid <= HDMV_PID_AUDIO_LAST)
+ find_stream(pid, m_clip->audio_streams, m_clip->audio_stream_count, language);
+ else if (HDMV_PID_PG_FIRST <= pid && pid <= HDMV_PID_PG_LAST)
+ find_stream(pid, m_clip->pg_streams, m_clip->pg_stream_count, language);
+ else if (HDMV_PID_PG_HDR_FIRST <= pid && pid <= HDMV_PID_PG_HDR_LAST)
+ find_stream(pid, m_clip->pg_streams, m_clip->pg_stream_count, language);
+ else if (HDMV_PID_IG_FIRST <= pid && pid <= HDMV_PID_IG_LAST)
+ find_stream(pid, m_clip->ig_streams, m_clip->ig_stream_count, language);
+ else
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::GetStreamInfo - unhandled pid {}", pid);
+}
+
+CDVDInputStream::ENextStream CDVDInputStreamBluray::NextStream()
+{
+ if(!m_navmode || m_hold == HOLD_EXIT || m_hold == HOLD_ERROR)
+ return NEXTSTREAM_NONE;
+
+ /* process any current event */
+ ProcessEvent();
+
+ /* process all queued up events */
+ while(bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+
+ if(m_hold == HOLD_STILL)
+ return NEXTSTREAM_RETRY;
+
+ m_hold = HOLD_DATA;
+ return NEXTSTREAM_OPEN;
+}
+
+void CDVDInputStreamBluray::UserInput(bd_vk_key_e vk)
+{
+ if(m_bd == nullptr || !m_navmode)
+ return;
+
+ int ret = bd_user_input(m_bd, -1, vk);
+ if (ret < 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::UserInput - user input failed");
+ }
+ else
+ {
+ /* process all queued up events */
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+ }
+}
+
+bool CDVDInputStreamBluray::MouseMove(const CPoint &point)
+{
+ if (m_bd == nullptr || !m_navmode)
+ return false;
+
+ // Disable mouse selection for BD-J menus, since it's not implemented in libbluray as of version 1.0.2
+ if (m_title && m_title->bdj == 1)
+ return false;
+
+ if (bd_mouse_select(m_bd, -1, static_cast<uint16_t>(point.x), static_cast<uint16_t>(point.y)) < 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::MouseMove - mouse select failed");
+ return false;
+ }
+
+ return true;
+}
+
+bool CDVDInputStreamBluray::MouseClick(const CPoint &point)
+{
+ if (m_bd == nullptr || !m_navmode)
+ return false;
+
+ // Disable mouse selection for BD-J menus, since it's not implemented in libbluray as of version 1.0.2
+ if (m_title && m_title->bdj == 1)
+ return false;
+
+ if (bd_mouse_select(m_bd, -1, static_cast<uint16_t>(point.x), static_cast<uint16_t>(point.y)) < 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::MouseClick - mouse select failed");
+ return false;
+ }
+
+ if (bd_user_input(m_bd, -1, BD_VK_MOUSE_ACTIVATE) >= 0)
+ return true;
+
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::MouseClick - mouse click (user input) failed");
+ return false;
+}
+
+bool CDVDInputStreamBluray::OnMenu()
+{
+ if(m_bd == nullptr || !m_navmode)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::OnMenu - navigation mode not enabled");
+ return false;
+ }
+
+ // we can not use this event to track a possible popup menu state since bd-j blu-rays can
+ // toggle the popup menu on their own without firing this event, and if they do this, our
+ // internal tracking state would be wrong. So just process and return.
+ if(bd_user_input(m_bd, -1, BD_VK_POPUP) >= 0)
+ {
+ return true;
+ }
+
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::OnMenu - popup failed, trying root");
+
+ if (bd_user_input(m_bd, -1, BD_VK_ROOT_MENU) >= 0)
+ {
+ return true;
+ }
+
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::OnMenu - root failed, trying explicit");
+ if (bd_menu_call(m_bd, -1) <= 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamBluray::OnMenu - root failed");
+ return false;
+ }
+ return true;
+}
+
+bool CDVDInputStreamBluray::IsInMenu()
+{
+ if(m_bd == nullptr || !m_navmode)
+ return false;
+
+ // since there is no way to tell in a BD-J blu-ray when a popup menu actually is visible,
+ // we have to assume that the blu-ray is in menu/navigation mode when there is an overlay
+ // on screen, even if it might be invisible (which is impossible to detect)
+ if(m_menu || m_hasOverlay)
+ return true;
+ return false;
+}
+
+void CDVDInputStreamBluray::SkipStill()
+{
+ if(m_bd == nullptr || !m_navmode)
+ return;
+
+ if ( m_hold == HOLD_STILL)
+ {
+ m_hold = HOLD_HELD;
+ bd_read_skip_still(m_bd);
+
+ /* process all queued up events */
+ while (bd_get_event(m_bd, &m_event))
+ ProcessEvent();
+ }
+}
+
+bool CDVDInputStreamBluray::CanSeek()
+{
+ return !IsInMenu() || !m_isInMainMenu;
+}
+
+MenuType CDVDInputStreamBluray::GetSupportedMenuType()
+{
+ if (m_navmode)
+ {
+ return MenuType::NATIVE;
+ }
+ return MenuType::NONE;
+}
+
+void CDVDInputStreamBluray::SetupPlayerSettings()
+{
+ int region = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_BLURAY_PLAYERREGION);
+ if ( region != BLURAY_REGION_A
+ && region != BLURAY_REGION_B
+ && region != BLURAY_REGION_C)
+ {
+ CLog::Log(LOGWARNING, "CDVDInputStreamBluray::Open - Blu-ray region must be set in setting, assuming region A");
+ region = BLURAY_REGION_A;
+ }
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_REGION_CODE, static_cast<uint32_t>(region));
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_PARENTAL, 99);
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_3D_CAP, 0xffffffff);
+#if (BLURAY_VERSION >= BLURAY_VERSION_CODE(1, 0, 2))
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_PLAYER_PROFILE, BLURAY_PLAYER_PROFILE_6_v3_1);
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_UHD_CAP, 0xffffffff);
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_UHD_DISPLAY_CAP, 0xffffffff);
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_HDR_PREFERENCE, 0xffffffff);
+#else
+ bd_set_player_setting(m_bd, BLURAY_PLAYER_SETTING_PLAYER_PROFILE, BLURAY_PLAYER_PROFILE_5_v2_4);
+#endif
+
+ std::string langCode;
+ g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDAudioLanguage(), langCode);
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_AUDIO_LANG, langCode.c_str());
+
+ g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDSubtitleLanguage(), langCode);
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_PG_LANG, langCode.c_str());
+
+ g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDMenuLanguage(), langCode);
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_MENU_LANG, langCode.c_str());
+
+ g_LangCodeExpander.ConvertToISO6391(g_langInfo.GetRegionLocale(), langCode);
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_COUNTRY_CODE, langCode.c_str());
+
+#ifdef HAVE_LIBBLURAY_BDJ
+ std::string cacheDir = CSpecialProtocol::TranslatePath("special://userdata/cache/bluray/cache");
+ std::string persistentDir = CSpecialProtocol::TranslatePath("special://userdata/cache/bluray/persistent");
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_PERSISTENT_ROOT, persistentDir.c_str());
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_CACHE_ROOT, cacheDir.c_str());
+#endif
+}
+
+bool CDVDInputStreamBluray::OpenStream(CFileItem &item)
+{
+ m_pstream.reset(new CDVDInputStreamFile(item, 0));
+
+ if (!m_pstream->Open())
+ {
+ CLog::Log(LOGERROR, "Error opening image file {}", CURL::GetRedacted(item.GetPath()));
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+bool CDVDInputStreamBluray::GetState(std::string& xmlstate)
+{
+ if (!m_bd || !m_titleInfo)
+ {
+ return false;
+ }
+
+ BlurayState blurayState;
+ blurayState.playlistId = m_titleInfo->playlist;
+
+ if (!m_blurayStateSerializer.BlurayStateToXML(xmlstate, blurayState))
+ {
+ CLog::LogF(LOGWARNING, "Failed to serialize Bluray state");
+ return false;
+ }
+
+ return true;
+}
+
+bool CDVDInputStreamBluray::SetState(const std::string& xmlstate)
+{
+ if (!m_bd)
+ return false;
+
+ BlurayState blurayState;
+ if (!m_blurayStateSerializer.XMLToBlurayState(blurayState, xmlstate))
+ {
+ CLog::LogF(LOGWARNING, "Failed to deserialize Bluray state");
+ return false;
+ }
+
+ m_titleInfo = bd_get_playlist_info(m_bd, blurayState.playlistId, 0);
+ if (!m_titleInfo)
+ {
+ CLog::LogF(LOGERROR, "Open - failed to get title info");
+ return false;
+ }
+
+ if (!bd_select_playlist(m_bd, m_titleInfo->playlist))
+ {
+ CLog::LogF(LOGERROR, "Open - failed to select playlist {}", m_titleInfo->idx);
+ return false;
+ }
+
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.h
new file mode 100644
index 0000000..05af77e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BlurayStateSerializer.h"
+#include "DVDInputStream.h"
+
+#include <list>
+#include <memory>
+
+extern "C"
+{
+#include <libbluray/bluray.h>
+#include <libbluray/bluray-version.h>
+#include <libbluray/keys.h>
+#include <libbluray/overlay.h>
+#include <libbluray/player_settings.h>
+#include "DVDInputStreamFile.h"
+}
+
+#define MAX_PLAYLIST_ID 99999
+#define MAX_CLIP_ID 99999
+#define BD_EVENT_MENU_OVERLAY -1
+#define BD_EVENT_MENU_ERROR -2
+#define BD_EVENT_ENC_ERROR -3
+
+#define HDMV_PID_VIDEO 0x1011
+#define HDMV_PID_AUDIO_FIRST 0x1100
+#define HDMV_PID_AUDIO_LAST 0x111f
+#define HDMV_PID_PG_FIRST 0x1200
+#define HDMV_PID_PG_LAST 0x121f
+#define HDMV_PID_PG_HDR_FIRST 0x12a0
+#define HDMV_PID_PG_HDR_LAST 0x12bf
+#define HDMV_PID_IG_FIRST 0x1400
+#define HDMV_PID_IG_LAST 0x141f
+
+class CDVDOverlayImage;
+class IVideoPlayer;
+
+class CDVDInputStreamBluray
+ : public CDVDInputStream
+ , public CDVDInputStream::IDisplayTime
+ , public CDVDInputStream::IChapter
+ , public CDVDInputStream::IPosTime
+ , public CDVDInputStream::IMenus
+{
+public:
+ CDVDInputStreamBluray() = delete;
+ CDVDInputStreamBluray(IVideoPlayer* player, const CFileItem& fileitem);
+ ~CDVDInputStreamBluray() override;
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ void Abort() override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+ int GetBlockSize() override { return 6144; }
+ ENextStream NextStream() override;
+
+
+ /* IMenus */
+ void ActivateButton() override { UserInput(BD_VK_ENTER); }
+ void SelectButton(int iButton) override
+ {
+ if(iButton < 10)
+ UserInput((bd_vk_key_e)(BD_VK_0 + iButton));
+ }
+ int GetCurrentButton() override { return 0; }
+ int GetTotalButtons() override { return 0; }
+ void OnUp() override { UserInput(BD_VK_UP); }
+ void OnDown() override { UserInput(BD_VK_DOWN); }
+ void OnLeft() override { UserInput(BD_VK_LEFT); }
+ void OnRight() override { UserInput(BD_VK_RIGHT); }
+
+ /*! \brief Open the Menu
+ * \return true if the menu is successfully opened, false otherwise
+ */
+ bool OnMenu() override;
+ void OnBack() override
+ {
+ if(IsInMenu())
+ OnMenu();
+ }
+ void OnNext() override {}
+ void OnPrevious() override {}
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ MenuType GetSupportedMenuType() override;
+
+ bool IsInMenu() override;
+ bool OnMouseMove(const CPoint &point) override { return MouseMove(point); }
+ bool OnMouseClick(const CPoint &point) override { return MouseClick(point); }
+ void SkipStill() override;
+ bool GetState(std::string& xmlstate) override;
+ bool SetState(const std::string& xmlstate) override;
+ bool CanSeek() override;
+
+
+ void UserInput(bd_vk_key_e vk);
+ bool MouseMove(const CPoint &point);
+ bool MouseClick(const CPoint &point);
+
+ int GetChapter() override;
+ int GetChapterCount() override;
+ void GetChapterName(std::string& name, int ch=-1) override {};
+ int64_t GetChapterPos(int ch) override;
+ bool SeekChapter(int ch) override;
+
+ CDVDInputStream::IDisplayTime* GetIDisplayTime() override { return this; }
+ int GetTotalTime() override;
+ int GetTime() override;
+
+ CDVDInputStream::IPosTime* GetIPosTime() override { return this; }
+ bool PosTime(int ms) override;
+
+ void GetStreamInfo(int pid, std::string &language);
+
+ void OverlayCallback(const BD_OVERLAY * const);
+#ifdef HAVE_LIBBLURAY_BDJ
+ void OverlayCallbackARGB(const struct bd_argb_overlay_s * const);
+#endif
+
+ BLURAY_TITLE_INFO* GetTitleLongest();
+ BLURAY_TITLE_INFO* GetTitleFile(const std::string& name);
+
+ void ProcessEvent();
+
+protected:
+ struct SPlane;
+
+ void OverlayFlush(int64_t pts);
+ void OverlayClose();
+ static void OverlayClear(SPlane& plane, int x, int y, int w, int h);
+ static void OverlayInit (SPlane& plane, int w, int h);
+
+ IVideoPlayer* m_player = nullptr;
+ BLURAY* m_bd = nullptr;
+ const BLURAY_TITLE* m_title = nullptr;
+ BLURAY_TITLE_INFO* m_titleInfo = nullptr;
+ uint32_t m_playlist = MAX_PLAYLIST_ID + 1;
+ BLURAY_CLIP_INFO* m_clip = nullptr;
+ uint32_t m_angle = 0;
+ bool m_menu = false;
+ bool m_isInMainMenu = false;
+ bool m_hasOverlay = false;
+ bool m_navmode = false;
+ int m_dispTimeBeforeRead = 0;
+
+ typedef std::shared_ptr<CDVDOverlayImage> SOverlay;
+ typedef std::list<SOverlay> SOverlays;
+
+ struct SPlane
+ {
+ SOverlays o;
+ int w = 0;
+ int h = 0;
+ };
+
+ SPlane m_planes[2];
+ enum EHoldState {
+ HOLD_NONE = 0,
+ HOLD_HELD,
+ HOLD_DATA,
+ HOLD_STILL,
+ HOLD_ERROR,
+ HOLD_EXIT
+ } m_hold = HOLD_NONE;
+ BD_EVENT m_event;
+#ifdef HAVE_LIBBLURAY_BDJ
+ struct bd_argb_buffer_s m_argb;
+#endif
+
+ private:
+ bool OpenStream(CFileItem &item);
+ void SetupPlayerSettings();
+ void FreeTitleInfo();
+ std::unique_ptr<CDVDInputStreamFile> m_pstream = nullptr;
+ std::string m_rootPath;
+
+ /*! Bluray state serializer handler */
+ CBlurayStateSerializer m_blurayStateSerializer;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp
new file mode 100644
index 0000000..c8baf1b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamFFmpeg.h"
+
+#include "playlists/PlayListM3U.h"
+#include "utils/StringUtils.h"
+
+
+using namespace XFILE;
+
+CDVDInputStreamFFmpeg::CDVDInputStreamFFmpeg(const CFileItem& fileitem)
+ : CDVDInputStream(DVDSTREAM_TYPE_FFMPEG, fileitem)
+{
+
+}
+
+CDVDInputStreamFFmpeg::~CDVDInputStreamFFmpeg()
+{
+ Close();
+}
+
+bool CDVDInputStreamFFmpeg::IsEOF()
+{
+ if(m_aborted)
+ return true;
+ else
+ return false;
+}
+
+bool CDVDInputStreamFFmpeg::Open()
+{
+ if (!CDVDInputStream::Open())
+ return false;
+
+ m_aborted = false;
+
+ if (StringUtils::CompareNoCase(m_item.GetDynPath(), "udp://", 6) == 0 ||
+ StringUtils::CompareNoCase(m_item.GetDynPath(), "rtp://", 6) == 0)
+ {
+ m_realtime = true;
+ }
+
+ return true;
+}
+
+// close file and reset everything
+void CDVDInputStreamFFmpeg::Close()
+{
+ CDVDInputStream::Close();
+}
+
+int CDVDInputStreamFFmpeg::Read(uint8_t* buf, int buf_size)
+{
+ return -1;
+}
+
+int64_t CDVDInputStreamFFmpeg::GetLength()
+{
+ return 0;
+}
+
+int64_t CDVDInputStreamFFmpeg::Seek(int64_t offset, int whence)
+{
+ return -1;
+}
+
+std::string CDVDInputStreamFFmpeg::GetProxyType() const
+{
+ return m_item.HasProperty("proxy.type") ?
+ m_item.GetProperty("proxy.type").asString() : std::string();
+}
+
+std::string CDVDInputStreamFFmpeg::GetProxyHost() const
+{
+ return m_item.HasProperty("proxy.host") ?
+ m_item.GetProperty("proxy.host").asString() : std::string();
+}
+
+uint16_t CDVDInputStreamFFmpeg::GetProxyPort() const
+{
+ if (m_item.HasProperty("proxy.port"))
+ return static_cast<uint16_t>(m_item.GetProperty("proxy.port").asInteger());
+
+ // Select the standard port
+ const std::string value = GetProxyType();
+ if (value == "socks4" || value == "socks4a" ||
+ value == "socks5" || value == "socks5-remote")
+ return 1080;
+ else
+ return 3128;
+}
+
+std::string CDVDInputStreamFFmpeg::GetProxyUser() const
+{
+ return m_item.HasProperty("proxy.user") ?
+ m_item.GetProperty("proxy.user").asString() : std::string();
+}
+
+std::string CDVDInputStreamFFmpeg::GetProxyPassword() const
+{
+ return m_item.HasProperty("proxy.password") ?
+ m_item.GetProperty("proxy.password").asString() : std::string();
+}
+
+std::string CDVDInputStreamFFmpeg::GetFileName()
+{
+ CURL url = GetURL();
+ // rtmp options
+ if (url.IsProtocol("rtmp") || url.IsProtocol("rtmpt") ||
+ url.IsProtocol("rtmpe") || url.IsProtocol("rtmpte") ||
+ url.IsProtocol("rtmps"))
+ {
+ std::vector<std::string> opts = StringUtils::Split(url.Get(), " ");
+ if (opts.size() > 0)
+ {
+ return opts.front();
+ }
+ return url.Get();
+ }
+ return CDVDInputStream::GetFileName();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.h
new file mode 100644
index 0000000..a3ea4d7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+class CDVDInputStreamFFmpeg
+ : public CDVDInputStream
+{
+public:
+ explicit CDVDInputStreamFFmpeg(const CFileItem& fileitem);
+ ~CDVDInputStreamFFmpeg() override;
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+ std::string GetFileName() override;
+
+ void Abort() override { m_aborted = true; }
+ bool Aborted() { return m_aborted; }
+
+ const CFileItem& GetItem() const { return m_item; }
+
+ std::string GetProxyType() const;
+ std::string GetProxyHost() const;
+ uint16_t GetProxyPort() const;
+ std::string GetProxyUser() const;
+ std::string GetProxyPassword() const;
+
+protected:
+ bool m_aborted = false;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.cpp
new file mode 100644
index 0000000..bebc683
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamFile.h"
+
+#include "filesystem/File.h"
+#include "filesystem/IFile.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+CDVDInputStreamFile::CDVDInputStreamFile(const CFileItem& fileitem, unsigned int flags)
+ : CDVDInputStream(DVDSTREAM_TYPE_FILE, fileitem), m_flags(flags)
+{
+ m_pFile = NULL;
+ m_eof = true;
+}
+
+CDVDInputStreamFile::~CDVDInputStreamFile()
+{
+ Close();
+}
+
+bool CDVDInputStreamFile::IsEOF()
+{
+ return !m_pFile || m_eof;
+}
+
+bool CDVDInputStreamFile::Open()
+{
+ if (!CDVDInputStream::Open())
+ return false;
+
+ m_pFile = new CFile();
+ if (!m_pFile)
+ return false;
+
+ unsigned int flags = m_flags;
+
+ // If this file is audio and/or video (= not a subtitle) flag to caller
+ if (!m_item.IsSubtitle())
+ flags |= READ_AUDIO_VIDEO;
+
+ std::string content = m_item.GetMimeType();
+
+ if (content == "video/mp4" ||
+ content == "video/x-msvideo" ||
+ content == "video/avi" ||
+ content == "video/x-matroska" ||
+ content == "video/x-matroska-3d")
+ flags |= READ_MULTI_STREAM;
+
+ // open file in binary mode
+ if (!m_pFile->Open(m_item.GetDynPath(), flags))
+ {
+ delete m_pFile;
+ m_pFile = NULL;
+ return false;
+ }
+
+ if (m_pFile->GetImplementation() && (content.empty() || content == "application/octet-stream"))
+ m_content = m_pFile->GetImplementation()->GetProperty(XFILE::FILE_PROPERTY_CONTENT_TYPE);
+
+ m_eof = false;
+ return true;
+}
+
+// close file and reset everything
+void CDVDInputStreamFile::Close()
+{
+ if (m_pFile)
+ {
+ m_pFile->Close();
+ delete m_pFile;
+ }
+
+ CDVDInputStream::Close();
+ m_pFile = NULL;
+ m_eof = true;
+}
+
+int CDVDInputStreamFile::Read(uint8_t* buf, int buf_size)
+{
+ if(!m_pFile) return -1;
+
+ ssize_t ret = m_pFile->Read(buf, buf_size);
+
+ if (ret < 0)
+ return -1; // player will retry read in case of error until playback is stopped
+
+ /* we currently don't support non completing reads */
+ if (ret == 0)
+ m_eof = true;
+
+ return (int)ret;
+}
+
+int64_t CDVDInputStreamFile::Seek(int64_t offset, int whence)
+{
+ if(!m_pFile) return -1;
+
+ if(whence == SEEK_POSSIBLE)
+ return m_pFile->IoControl(IOCTRL_SEEK_POSSIBLE, NULL);
+
+ int64_t ret = m_pFile->Seek(offset, whence);
+
+ /* if we succeed, we are not eof anymore */
+ if( ret >= 0 ) m_eof = false;
+
+ return ret;
+}
+
+int64_t CDVDInputStreamFile::GetLength()
+{
+ if (m_pFile)
+ return m_pFile->GetLength();
+ return 0;
+}
+
+bool CDVDInputStreamFile::GetCacheStatus(XFILE::SCacheStatus *status)
+{
+ if(m_pFile && m_pFile->IoControl(IOCTRL_CACHE_STATUS, status) >= 0)
+ return true;
+ else
+ return false;
+}
+
+BitstreamStats CDVDInputStreamFile::GetBitstreamStats() const
+{
+ if (!m_pFile)
+ return m_stats; // dummy return. defined in CDVDInputStream
+
+ if(m_pFile->GetBitstreamStats())
+ return *m_pFile->GetBitstreamStats();
+ else
+ return m_stats;
+}
+
+int CDVDInputStreamFile::GetBlockSize()
+{
+ if(m_pFile)
+ return m_pFile->GetChunkSize();
+ else
+ return 0;
+}
+
+void CDVDInputStreamFile::SetReadRate(uint32_t rate)
+{
+ // Increase requested rate by 10%:
+ uint32_t maxrate = static_cast<uint32_t>(1.1 * rate);
+
+ if(m_pFile->IoControl(IOCTRL_CACHE_SETRATE, &maxrate) >= 0)
+ CLog::Log(LOGDEBUG,
+ "CDVDInputStreamFile::SetReadRate - set cache throttle rate to {} bytes per second",
+ maxrate);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.h
new file mode 100644
index 0000000..fc6d3ab
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFile.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+class CDVDInputStreamFile : public CDVDInputStream
+{
+public:
+ explicit CDVDInputStreamFile(const CFileItem& fileitem, unsigned int flags);
+ ~CDVDInputStreamFile() override;
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+ BitstreamStats GetBitstreamStats() const override ;
+ int GetBlockSize() override;
+ void SetReadRate(uint32_t rate) override;
+ bool GetCacheStatus(XFILE::SCacheStatus *status) override;
+
+protected:
+ XFILE::CFile* m_pFile = nullptr;
+ bool m_eof = false;
+ unsigned int m_flags = 0;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.cpp
new file mode 100644
index 0000000..2d5a910
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamMemory.h"
+
+CDVDInputStreamMemory::CDVDInputStreamMemory(CFileItem& fileitem) : CDVDInputStream(DVDSTREAM_TYPE_MEMORY, fileitem)
+{
+ m_pData = NULL;
+ m_iDataSize = 0;
+ m_iDataPos = 0;
+}
+
+CDVDInputStreamMemory::~CDVDInputStreamMemory()
+{
+ Close();
+}
+
+bool CDVDInputStreamMemory::IsEOF()
+{
+ if(m_iDataPos >= m_iDataSize)
+ return true;
+
+ return false;
+}
+
+bool CDVDInputStreamMemory::Open()
+{
+ if (!CDVDInputStream::Open())
+ return false;
+
+ return true;
+}
+
+// close file and reset everything
+void CDVDInputStreamMemory::Close()
+{
+ if (m_pData) delete[] m_pData;
+ m_pData = NULL;
+ m_iDataSize = 0;
+ m_iDataPos = 0;
+
+ CDVDInputStream::Close();
+}
+
+int CDVDInputStreamMemory::Read(uint8_t* buf, int buf_size)
+{
+ int iBytesToCopy = buf_size;
+ int iBytesLeft = m_iDataSize - m_iDataPos;
+ if (iBytesToCopy > iBytesLeft) iBytesToCopy = iBytesLeft;
+
+ if (iBytesToCopy > 0)
+ {
+ memcpy(buf, m_pData + m_iDataPos, iBytesToCopy);
+ m_iDataPos += iBytesToCopy;
+ }
+
+ return iBytesToCopy;
+}
+
+int64_t CDVDInputStreamMemory::Seek(int64_t offset, int whence)
+{
+ switch (whence)
+ {
+ case SEEK_CUR:
+ {
+ if ((m_iDataPos + offset) > m_iDataSize) return -1;
+ else m_iDataPos += (int)offset;
+ break;
+ }
+ case SEEK_END:
+ {
+ m_iDataPos = m_iDataSize;
+ break;
+ }
+ case SEEK_SET:
+ {
+ if (offset > m_iDataSize || offset < 0) return -1;
+ else m_iDataPos = (int)offset;
+ break;
+ }
+ default:
+ return -1;
+ }
+ return m_iDataPos;
+}
+
+int64_t CDVDInputStreamMemory::GetLength()
+{
+ return m_iDataSize;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.h
new file mode 100644
index 0000000..a7d494c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamMemory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+class CDVDInputStreamMemory : public CDVDInputStream
+{
+public:
+ explicit CDVDInputStreamMemory(CFileItem &fileitem);
+ ~CDVDInputStreamMemory() override;
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+
+protected:
+ uint8_t* m_pData;
+ int m_iDataSize;
+ int m_iDataPos;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp
new file mode 100644
index 0000000..e7b54e3
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamNavigator.h"
+#include "filesystem/IFileTypes.h"
+#include "utils/LangCodeExpander.h"
+#include "../DVDDemuxSPU.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "utils/Geometry.h"
+#include "utils/log.h"
+#include "utils/URIUtils.h"
+#include "utils/StringUtils.h"
+#include "guilib/LocalizeStrings.h"
+#if defined(TARGET_DARWIN_OSX)
+#include "platform/darwin/osx/CocoaInterface.h"
+#endif
+#if defined(TARGET_WINDOWS_STORE)
+#include "filesystem/SpecialProtocol.h"
+#include "platform/Environment.h"
+#endif
+
+namespace
+{
+constexpr int HOLDMODE_NONE = 0;
+/* set internally when we wish to flush demuxer */
+constexpr int HOLDMODE_HELD = 1;
+/* set by inputstream user, when they wish to skip the held mode */
+constexpr int HOLDMODE_SKIP = 2;
+/* set after hold mode has been exited, and action that inited it has been executed */
+constexpr int HOLDMODE_DATA = 3;
+
+// DVD Subpicture types
+constexpr int DVD_SUBPICTURE_TYPE_NOTSPECIFIED = 0;
+constexpr int DVD_SUBPICTURE_TYPE_LANGUAGE = 1;
+
+// DVD Subpicture language extensions
+constexpr int DVD_SUBPICTURE_LANG_EXT_NOTSPECIFIED = 0;
+constexpr int DVD_SUBPICTURE_LANG_EXT_NORMALCAPTIONS = 1;
+constexpr int DVD_SUBPICTURE_LANG_EXT_BIGCAPTIONS = 2;
+constexpr int DVD_SUBPICTURE_LANG_EXT_CHILDRENSCAPTIONS = 3;
+constexpr int DVD_SUBPICTURE_LANG_EXT_NORMALCC = 5;
+constexpr int DVD_SUBPICTURE_LANG_EXT_BIGCC = 6;
+constexpr int DVD_SUBPICTURE_LANG_EXT_CHILDRENSCC = 7;
+constexpr int DVD_SUBPICTURE_LANG_EXT_FORCED = 9;
+constexpr int DVD_SUBPICTURE_LANG_EXT_NORMALDIRECTORSCOMMENTS = 13;
+constexpr int DVD_SUBPICTURE_LANG_EXT_BIGDIRECTORSCOMMENTS = 14;
+constexpr int DVD_SUBPICTURE_LANG_EXT_CHILDRENDIRECTORSCOMMENTS = 15;
+
+// DVD Audio language extensions
+constexpr int DVD_AUDIO_LANG_EXT_NOTSPECIFIED = 0;
+constexpr int DVD_AUDIO_LANG_EXT_NORMALCAPTIONS = 1;
+constexpr int DVD_AUDIO_LANG_EXT_VISUALLYIMPAIRED = 2;
+constexpr int DVD_AUDIO_LANG_EXT_DIRECTORSCOMMENTS1 = 3;
+constexpr int DVD_AUDIO_LANG_EXT_DIRECTORSCOMMENTS2 = 4;
+} // namespace
+
+static int dvd_inputstreamnavigator_cb_seek(void * p_stream, uint64_t i_pos);
+static int dvd_inputstreamnavigator_cb_read(void * p_stream, void * buffer, int i_read);
+static int dvd_inputstreamnavigator_cb_readv(void * p_stream, void * p_iovec, int i_blocks);
+static void dvd_logger(void* priv, dvdnav_logger_level_t level, const char* fmt, va_list va);
+
+CDVDInputStreamNavigator::CDVDInputStreamNavigator(IVideoPlayer* player, const CFileItem& fileitem)
+ : CDVDInputStream(DVDSTREAM_TYPE_DVD, fileitem), m_pstream(nullptr)
+{
+ m_dvdnav = 0;
+ m_pVideoPlayer = player;
+ m_bCheckButtons = false;
+ m_iCellStart = 0;
+ m_iVobUnitStart = 0LL;
+ m_iVobUnitStop = 0LL;
+ m_iVobUnitCorrection = 0LL;
+ m_bInMenu = false;
+ m_holdmode = HOLDMODE_NONE;
+ m_iTitle = m_iTitleCount = 0;
+ m_iPart = m_iPartCount = 0;
+ m_iTime = m_iTotalTime = 0;
+ m_bEOF = false;
+ m_lastevent = DVDNAV_NOP;
+ m_dvdnav_stream_cb.pf_read = dvd_inputstreamnavigator_cb_read;
+ m_dvdnav_stream_cb.pf_readv = dvd_inputstreamnavigator_cb_readv;
+ m_dvdnav_stream_cb.pf_seek = dvd_inputstreamnavigator_cb_seek;
+
+ memset(m_lastblock, 0, sizeof(m_lastblock));
+}
+
+CDVDInputStreamNavigator::~CDVDInputStreamNavigator()
+{
+ Close();
+}
+
+bool CDVDInputStreamNavigator::Open()
+{
+ m_item.SetMimeType("video/x-dvd-mpeg");
+ if (!CDVDInputStream::Open())
+ return false;
+
+#if defined(TARGET_WINDOWS_STORE)
+ // libdvdcss
+ CEnvironment::putenv("DVDCSS_METHOD=key");
+ CEnvironment::putenv("DVDCSS_VERBOSE=3");
+ CEnvironment::putenv("DVDCSS_CACHE=" + CSpecialProtocol::TranslatePath("special://masterprofile/cache"));
+#endif
+
+ // load libdvdnav.dll
+ if (!m_dll.Load())
+ return false;
+
+ // load the dvd language codes
+ // g_LangCodeExpander.LoadStandardCodes();
+
+ // libdvdcss fails if the file path contains VIDEO_TS.IFO or VIDEO_TS/VIDEO_TS.IFO
+ // libdvdnav is still able to play without, so strip them.
+
+ std::string path = m_item.GetDynPath();
+ if(URIUtils::GetFileName(path) == "VIDEO_TS.IFO")
+ path = URIUtils::GetParentPath(path);
+ URIUtils::RemoveSlashAtEnd(path);
+ if(URIUtils::GetFileName(path) == "VIDEO_TS")
+ path = URIUtils::GetParentPath(path);
+ URIUtils::RemoveSlashAtEnd(path);
+
+#if defined(TARGET_DARWIN_OSX)
+ // if physical DVDs, libdvdnav wants "/dev/rdiskN" device name for OSX,
+ // strDVDFile will get realloc'ed and replaced IF this is a physical DVD.
+ char* strDVDFile = Cocoa_MountPoint2DeviceName(strdup(path.c_str()));
+ path = strDVDFile;
+ free(strDVDFile);
+#endif
+
+#if DVDNAV_VERSION >= 60100
+ dvdnav_logger_cb loggerCallback;
+ loggerCallback.pf_log = dvd_logger;
+#endif
+
+ // open up the DVD device
+ if (m_item.IsDiscImage())
+ {
+ // if dvd image file (ISO or alike) open using libdvdnav stream callback functions
+ m_pstream.reset(new CDVDInputStreamFile(m_item, XFILE::READ_TRUNCATED | XFILE::READ_BITRATE | XFILE::READ_CHUNKED));
+#if DVDNAV_VERSION >= 60100
+ if (!m_pstream->Open() || m_dll.dvdnav_open_stream2(&m_dvdnav, m_pstream.get(), &loggerCallback,
+ &m_dvdnav_stream_cb) != DVDNAV_STATUS_OK)
+#else
+ if (!m_pstream->Open() || m_dll.dvdnav_open_stream(&m_dvdnav, m_pstream.get(), &m_dvdnav_stream_cb) != DVDNAV_STATUS_OK)
+#endif
+ {
+ CLog::Log(LOGERROR, "Error opening image file or Error on dvdnav_open_stream");
+ Close();
+ return false;
+ }
+ }
+#if DVDNAV_VERSION >= 60100
+ else if (m_dll.dvdnav_open2(&m_dvdnav, nullptr, &loggerCallback, path.c_str()) !=
+ DVDNAV_STATUS_OK)
+#else
+ else if (m_dll.dvdnav_open(&m_dvdnav, path.c_str()) != DVDNAV_STATUS_OK)
+#endif
+ {
+ CLog::Log(LOGERROR, "Error on dvdnav_open");
+ Close();
+ return false;
+ }
+
+ int region = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_DVDS_PLAYERREGION);
+ int mask = 0;
+ if(region > 0)
+ mask = 1 << (region-1);
+ else
+ {
+ // find out what region dvd reports itself to be from, and use that as mask if available
+ if (m_dll.dvdnav_get_disk_region_mask(m_dvdnav, &mask) == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "Error getting DVD region code: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ mask = 0xff;
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "{} - Setting region mask {:02x}", __FUNCTION__, mask);
+ m_dll.dvdnav_set_region_mask(m_dvdnav, mask);
+
+ // get default language settings
+ char language_menu[3];
+ strncpy(language_menu, g_langInfo.GetDVDMenuLanguage().c_str(), sizeof(language_menu)-1);
+ language_menu[2] = '\0';
+
+ char language_audio[3];
+ strncpy(language_audio, g_langInfo.GetDVDAudioLanguage().c_str(), sizeof(language_audio)-1);
+ language_audio[2] = '\0';
+
+ char language_subtitle[3];
+ strncpy(language_subtitle, g_langInfo.GetDVDSubtitleLanguage().c_str(), sizeof(language_subtitle)-1);
+ language_subtitle[2] = '\0';
+
+ // set language settings in case they are not set in xbmc's configuration
+ if (language_menu[0] == '\0') strcpy(language_menu, "en");
+ if (language_audio[0] == '\0') strcpy(language_audio, "en");
+ if (language_subtitle[0] == '\0') strcpy(language_subtitle, "en");
+
+ // set default language settings
+ if (m_dll.dvdnav_menu_language_select(m_dvdnav, (char*)language_menu) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on setting default menu language: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ CLog::Log(LOGERROR, "Defaulting to \"en\"");
+ //! @bug libdvdnav isn't const correct
+ m_dll.dvdnav_menu_language_select(m_dvdnav, const_cast<char*>("en"));
+ }
+
+ if (m_dll.dvdnav_audio_language_select(m_dvdnav, (char*)language_audio) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on setting default audio language: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ CLog::Log(LOGERROR, "Defaulting to \"en\"");
+ //! @bug libdvdnav isn't const correct
+ m_dll.dvdnav_audio_language_select(m_dvdnav, const_cast<char*>("en"));
+ }
+
+ if (m_dll.dvdnav_spu_language_select(m_dvdnav, (char*)language_subtitle) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on setting default subtitle language: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ CLog::Log(LOGERROR, "Defaulting to \"en\"");
+ //! @bug libdvdnav isn't const correct
+ m_dll.dvdnav_spu_language_select(m_dvdnav, const_cast<char*>("en"));
+ }
+
+ // set read ahead cache usage
+ if (m_dll.dvdnav_set_readahead_flag(m_dvdnav, 1) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on dvdnav_set_readahead_flag: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ Close();
+ return false;
+ }
+
+ // set the PGC positioning flag to have position information relatively to the
+ // whole feature instead of just relatively to the current chapter
+ if (m_dll.dvdnav_set_PGC_positioning_flag(m_dvdnav, 1) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on dvdnav_set_PGC_positioning_flag: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ Close();
+ return false;
+ }
+
+ // jump directly to title menu
+ if(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_DVDS_AUTOMENU))
+ {
+ int len, event;
+ uint8_t buf[2048];
+ uint8_t* buf_ptr = buf;
+
+ // must startup vm and pgc
+ m_dll.dvdnav_get_next_cache_block(m_dvdnav,&buf_ptr,&event,&len);
+ m_dll.dvdnav_sector_search(m_dvdnav, 0, SEEK_SET);
+
+ // first try title menu
+ if(m_dll.dvdnav_menu_call(m_dvdnav, DVD_MENU_Title) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on dvdnav_menu_call(Title): {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ // next try root menu
+ if(m_dll.dvdnav_menu_call(m_dvdnav, DVD_MENU_Root) != DVDNAV_STATUS_OK )
+ CLog::Log(LOGERROR, "Error on dvdnav_menu_call(Root): {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ }
+ }
+
+ m_bEOF = false;
+ m_bCheckButtons = false;
+ m_iCellStart = 0;
+ m_iVobUnitStart = 0LL;
+ m_iVobUnitStop = 0LL;
+ m_iVobUnitCorrection = 0LL;
+ m_bInMenu = false;
+ m_holdmode = HOLDMODE_NONE;
+ m_iTitle = m_iTitleCount = 0;
+ m_iPart = m_iPartCount = 0;
+ m_iTime = m_iTotalTime = 0;
+
+ return true;
+}
+
+void CDVDInputStreamNavigator::Close()
+{
+ if (!m_dvdnav) return;
+
+ // finish off by closing the dvdnav device
+ if (m_dll.dvdnav_close(m_dvdnav) != DVDNAV_STATUS_OK)
+ {
+ CLog::Log(LOGERROR, "Error on dvdnav_close: {}", m_dll.dvdnav_err_to_string(m_dvdnav));
+ return ;
+ }
+
+ CDVDInputStream::Close();
+ m_dvdnav = NULL;
+ m_bEOF = true;
+
+ if (m_pstream != nullptr)
+ {
+ m_pstream->Close();
+ m_pstream.reset();
+ }
+}
+
+int CDVDInputStreamNavigator::Read(uint8_t* buf, int buf_size)
+{
+ if (!m_dvdnav || m_bEOF) return 0;
+ if (buf_size < DVD_VIDEO_BLOCKSIZE)
+ {
+ CLog::Log(LOGERROR,
+ "CDVDInputStreamNavigator: buffer size is to small, {} bytes, should be 2048 bytes",
+ buf_size);
+ return -1;
+ }
+
+ int iBytesRead = 0;
+
+ int NOPcount = 0;
+ while (true)
+ {
+ int navresult = ProcessBlock(buf, &iBytesRead);
+
+ if (navresult == NAVRESULT_HOLD)
+ return 0; // return 0 bytes read;
+ else if (navresult == NAVRESULT_ERROR)
+ return -1;
+ else if (navresult == NAVRESULT_DATA)
+ return iBytesRead;
+ else if (navresult == NAVRESULT_NOP)
+ {
+ NOPcount++;
+ if (NOPcount == 1000)
+ {
+ m_bEOF = true;
+ CLog::Log(LOGERROR,"CDVDInputStreamNavigator: Stopping playback due to infinite loop, caused by badly authored DVD navigation structure. Try enabling 'Attempt to skip introduction before DVD menu'.");
+ m_pVideoPlayer->OnDiscNavResult(nullptr, DVDNAV_ERROR);
+ return -1; // fail and stop playback.
+ }
+ }
+ }
+
+ return iBytesRead;
+}
+
+// not working yet, but it is the recommended way for seeking
+int64_t CDVDInputStreamNavigator::Seek(int64_t offset, int whence)
+{
+ if(whence == SEEK_POSSIBLE)
+ return 0;
+ else
+ return -1;
+}
+
+int CDVDInputStreamNavigator::ProcessBlock(uint8_t* dest_buffer, int* read)
+{
+ if (!m_dvdnav)
+ return -1;
+
+ int result;
+ int len = 2048;
+
+ // m_tempbuffer will be used for anything that isn't a normal data block
+ uint8_t* buf = m_lastblock;
+ int iNavresult = -1;
+
+ if (m_holdmode == HOLDMODE_HELD)
+ return NAVRESULT_HOLD;
+
+ // the main reading function
+ if(m_holdmode == HOLDMODE_SKIP)
+ { /* we where holding data, return the data held */
+ m_holdmode = HOLDMODE_DATA;
+ result = DVDNAV_STATUS_OK;
+ }
+ else
+ result = m_dll.dvdnav_get_next_cache_block(m_dvdnav, &buf, &m_lastevent, &len);
+
+ if (result == DVDNAV_STATUS_ERR)
+ {
+ CLog::Log(LOGERROR, "Error getting next block: {}", m_dll.dvdnav_err_to_string(m_dvdnav));
+ m_bEOF = true;
+ return NAVRESULT_ERROR;
+ }
+
+ switch (m_lastevent)
+ {
+ case DVDNAV_BLOCK_OK:
+ {
+ // We have received a regular block of the currently playing MPEG stream.
+ // buf contains the data and len its length (obviously!) (which is always 2048 bytes btw)
+ m_holdmode = HOLDMODE_NONE;
+ memcpy(dest_buffer, buf, len);
+ *read = len;
+ iNavresult = NAVRESULT_DATA;
+ }
+ break;
+
+ case DVDNAV_NOP:
+ // Nothing to do here.
+ break;
+
+ case DVDNAV_STILL_FRAME:
+ {
+ // We have reached a still frame. A real player application would wait
+ // the amount of time specified by the still's length while still handling
+ // user input to make menus and other interactive stills work.
+ // A length of 0xff means an indefinite still which has to be skipped
+ // indirectly by some user interaction.
+ m_holdmode = HOLDMODE_NONE;
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_STILL_FRAME);
+ }
+ break;
+
+ case DVDNAV_WAIT:
+ {
+ // We have reached a point in DVD playback, where timing is critical.
+ // Player application with internal fifos can introduce state
+ // inconsistencies, because libdvdnav is always the fifo's length
+ // ahead in the stream compared to what the application sees.
+ // Such applications should wait until their fifos are empty
+ // when they receive this type of event.
+ if(m_holdmode == HOLDMODE_NONE)
+ {
+ CLog::Log(LOGDEBUG, " - DVDNAV_WAIT (HOLDING)");
+ m_holdmode = HOLDMODE_HELD;
+ iNavresult = NAVRESULT_HOLD;
+ }
+ else
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_WAIT);
+
+ /* if user didn't care for action, just skip it */
+ if(iNavresult == NAVRESULT_NOP)
+ SkipWait();
+ }
+ break;
+
+ case DVDNAV_SPU_CLUT_CHANGE:
+ // Player applications should pass the new colour lookup table to their
+ // SPU decoder. The CLUT is given as 16 uint32_t's in the buffer.
+ {
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_SPU_CLUT_CHANGE);
+ }
+ break;
+
+ case DVDNAV_SPU_STREAM_CHANGE:
+ // Player applications should inform their SPU decoder to switch channels
+ {
+ m_bCheckButtons = true;
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_SPU_STREAM_CHANGE);
+ }
+ break;
+
+ case DVDNAV_AUDIO_STREAM_CHANGE:
+ // Player applications should inform their audio decoder to switch channels
+ {
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_AUDIO_STREAM_CHANGE);
+ }
+
+ break;
+
+ case DVDNAV_HIGHLIGHT:
+ {
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_HIGHLIGHT);
+ }
+ break;
+
+ case DVDNAV_VTS_CHANGE:
+ // Some status information like video aspect and video scale permissions do
+ // not change inside a VTS. Therefore this event can be used to query such
+ // information only when necessary and update the decoding/displaying
+ // accordingly.
+ {
+ if(m_holdmode == HOLDMODE_NONE)
+ {
+ CLog::Log(LOGDEBUG, " - DVDNAV_VTS_CHANGE (HOLDING)");
+ m_holdmode = HOLDMODE_HELD;
+ iNavresult = NAVRESULT_HOLD;
+ }
+ else
+ {
+ bool menu = (0 == m_dll.dvdnav_is_domain_vts(m_dvdnav));
+ if (menu != m_bInMenu)
+ {
+ m_bInMenu = menu;
+ }
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_VTS_CHANGE);
+ }
+ }
+ break;
+
+ case DVDNAV_CELL_CHANGE:
+ {
+ // Some status information like the current Title and Part numbers do not
+ // change inside a cell. Therefore this event can be used to query such
+ // information only when necessary and update the decoding/displaying
+ // accordingly.
+
+ uint32_t pos = 0;
+ uint32_t len = 0;
+
+ m_dll.dvdnav_current_title_info(m_dvdnav, &m_iTitle, &m_iPart);
+ m_dll.dvdnav_get_number_of_titles(m_dvdnav, &m_iTitleCount);
+ if(m_iTitle > 0)
+ m_dll.dvdnav_get_number_of_parts(m_dvdnav, m_iTitle, &m_iPartCount);
+ else
+ m_iPartCount = 0;
+ m_dll.dvdnav_get_position(m_dvdnav, &pos, &len);
+
+ // get chapters' timestamps if we have not cached them yet
+ if (m_mapTitleChapters.find(m_iTitle) == m_mapTitleChapters.end())
+ {
+ uint64_t* times = NULL;
+ uint64_t duration;
+ //dvdnav_describe_title_chapters returns 0 on failure and NULL for times
+ int entries = m_dll.dvdnav_describe_title_chapters(m_dvdnav, m_iTitle, &times, &duration);
+
+ if (entries != m_iPartCount)
+ CLog::Log(LOGDEBUG,
+ "{} - Number of chapters/positions differ: Chapters {}, positions {}",
+ __FUNCTION__, m_iPartCount, entries);
+
+ if (times)
+ {
+ // the times array stores the end timestamps of the chapters, e.g., times[0] stores the position/beginning of chapter 2
+ m_mapTitleChapters[m_iTitle][1] = 0;
+ for (int i = 0; i < entries - 1; ++i)
+ {
+ m_mapTitleChapters[m_iTitle][i + 2] = times[i] / 90000;
+ }
+ free(times);
+ }
+ }
+ CLog::Log(LOGDEBUG, "{} - Cell change: Title {}, Chapter {}", __FUNCTION__, m_iTitle,
+ m_iPart);
+ CLog::Log(LOGDEBUG, "{} - At position {:.0f}% inside the feature", __FUNCTION__,
+ 100 * (double)pos / (double)len);
+ //Get total segment time
+
+ dvdnav_cell_change_event_t* cell_change_event = reinterpret_cast<dvdnav_cell_change_event_t*>(buf);
+ m_iCellStart = cell_change_event->cell_start; // store cell time as we need that for time later
+ m_iTime = (int) (m_iCellStart / 90);
+ m_iTotalTime = (int) (cell_change_event->pgc_length / 90);
+
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(buf, DVDNAV_CELL_CHANGE);
+ }
+ break;
+
+ case DVDNAV_NAV_PACKET:
+ {
+ // A NAV packet provides PTS discontinuity information, angle linking information and
+ // button definitions for DVD menus. Angles are handled completely inside libdvdnav.
+ // For the menus to work, the NAV packet information has to be passed to the overlay
+ // engine of the player so that it knows the dimensions of the button areas.
+
+ // Applications with fifos should not use these functions to retrieve NAV packets,
+ // they should implement their own NAV handling, because the packet you get from these
+ // functions will already be ahead in the stream which can cause state inconsistencies.
+ // Applications with fifos should therefore pass the NAV packet through the fifo
+ // and decoding pipeline just like any other data.
+
+ // Calculate current time
+ //unsigned int pos, len;
+ //m_dll.dvdnav_get_position(m_dvdnav, &pos, &len);
+ //m_iTime = (int)(((int64_t)m_iTotalTime * pos) / len);
+
+ pci_t* pci = m_dll.dvdnav_get_current_nav_pci(m_dvdnav);
+ m_dll.dvdnav_get_current_nav_dsi(m_dvdnav);
+
+ if(!pci)
+ {
+ iNavresult = NAVRESULT_NOP;
+ break;
+ }
+
+ /* if we have any buttons or are not in vts domain we assume we are in menu */
+ bool menu = pci->hli.hl_gi.hli_ss || (0 == m_dll.dvdnav_is_domain_vts(m_dvdnav));
+ if (menu != m_bInMenu)
+ {
+ m_bInMenu = menu;
+ }
+
+ /* check for any gap in the stream, this is likely a discontinuity */
+ int64_t gap = (int64_t)pci->pci_gi.vobu_s_ptm - m_iVobUnitStop;
+ if(gap)
+ {
+ /* make sure demuxer is flushed before we change any correction */
+ if(m_holdmode == HOLDMODE_NONE)
+ {
+ CLog::Log(LOGDEBUG, "DVDNAV_NAV_PACKET (HOLDING)");
+ m_holdmode = HOLDMODE_HELD;
+ iNavresult = NAVRESULT_HOLD;
+ break;
+ }
+ m_iVobUnitCorrection += gap;
+
+ CLog::Log(LOGDEBUG, "DVDNAV_NAV_PACKET - DISCONTINUITY FROM:{} TO:{} DIFF:{}",
+ (m_iVobUnitStop * 1000) / 90, ((int64_t)pci->pci_gi.vobu_s_ptm * 1000) / 90,
+ (gap * 1000) / 90);
+ }
+
+ m_iVobUnitStart = pci->pci_gi.vobu_s_ptm;
+ m_iVobUnitStop = pci->pci_gi.vobu_e_ptm;
+
+ m_iTime = (int) ( m_dll.dvdnav_get_current_time(m_dvdnav) / 90 );
+
+ iNavresult = m_pVideoPlayer->OnDiscNavResult((void*)pci, DVDNAV_NAV_PACKET);
+ }
+ break;
+
+ case DVDNAV_HOP_CHANNEL:
+ // This event is issued whenever a non-seamless operation has been executed.
+ // Applications with fifos should drop the fifos content to speed up responsiveness.
+ {
+ iNavresult = m_pVideoPlayer->OnDiscNavResult(NULL, DVDNAV_HOP_CHANNEL);
+ }
+ break;
+
+ case DVDNAV_STOP:
+ {
+ // Playback should end here.
+
+ // don't read any further, it could be libdvdnav had some problems reading
+ // the disc. reading further results in a crash
+ m_bEOF = true;
+
+ m_pVideoPlayer->OnDiscNavResult(NULL, DVDNAV_STOP);
+ iNavresult = NAVRESULT_ERROR;
+ }
+ break;
+
+ default:
+ {
+ CLog::Log(LOGDEBUG, "CDVDInputStreamNavigator: Unknown event ({})", m_lastevent);
+ }
+ break;
+
+ }
+
+ // check if libdvdnav gave us some other buffer to work with
+ // probably not needed since function will check if buf
+ // is part of the internal cache, but do it for good measure
+ if( buf != m_lastblock )
+ m_dll.dvdnav_free_cache_block(m_dvdnav, buf);
+
+ return iNavresult;
+}
+
+bool CDVDInputStreamNavigator::SetActiveAudioStream(int iId)
+{
+ CLog::Log(LOGDEBUG, "Setting active audio stream id: {}", iId);
+
+ if (!m_dvdnav)
+ return false;
+
+ dvdnav_status_t ret = m_dll.dvdnav_set_active_stream(m_dvdnav, iId, DVD_AUDIO_STREAM);
+ if (ret == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "dvdnav_set_active_stream (audio) failed: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ }
+
+ return ret == DVDNAV_STATUS_OK;
+}
+
+bool CDVDInputStreamNavigator::SetActiveSubtitleStream(int iId)
+{
+ CLog::LogF(LOGDEBUG, "Setting active subtitle stream id: {}", iId);
+
+ if (!m_dvdnav)
+ return false;
+
+ dvdnav_status_t ret = m_dll.dvdnav_set_active_stream(m_dvdnav, iId, DVD_SUBTITLE_STREAM);
+ if (ret == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "dvdnav_set_active_stream (subtitles) failed: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ }
+
+ return ret == DVDNAV_STATUS_OK;
+}
+
+void CDVDInputStreamNavigator::ActivateButton()
+{
+ if (m_dvdnav)
+ {
+ m_dll.dvdnav_button_activate(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav));
+ }
+}
+
+void CDVDInputStreamNavigator::SelectButton(int iButton)
+{
+ if (!m_dvdnav) return;
+ m_dll.dvdnav_button_select(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav), iButton);
+}
+
+int CDVDInputStreamNavigator::GetCurrentButton()
+{
+ if (!m_dvdnav)
+ {
+ return -1;
+ }
+
+ int button = 0;
+ if (m_dll.dvdnav_get_current_highlight(m_dvdnav, &button) == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "dvdnav_get_current_highlight failed: {}",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return -1;
+ }
+ return button;
+}
+
+void CDVDInputStreamNavigator::CheckButtons()
+{
+ if (m_dvdnav && m_bCheckButtons)
+ {
+ m_bCheckButtons = false;
+ pci_t* pci = m_dll.dvdnav_get_current_nav_pci(m_dvdnav);
+ int iCurrentButton = GetCurrentButton();
+
+ if( iCurrentButton > 0 && iCurrentButton < 37 )
+ {
+ btni_t* button = &(pci->hli.btnit[iCurrentButton-1]);
+
+ // menu buttons are always cropped overlays, so if there is no such information
+ // we assume the button is invalid
+ if ((button->x_start || button->x_end || button->y_start || button->y_end))
+ {
+ // button has info, it's valid
+ return;
+ }
+ }
+
+ // select first valid button.
+ for (int i = 0; i < 36; i++)
+ {
+ if (pci->hli.btnit[i].x_start ||
+ pci->hli.btnit[i].x_end ||
+ pci->hli.btnit[i].y_start ||
+ pci->hli.btnit[i].y_end)
+ {
+ CLog::Log(LOGWARNING, "CDVDInputStreamNavigator: found invalid button({})", iCurrentButton);
+ CLog::Log(LOGWARNING, "CDVDInputStreamNavigator: switching to button({}) instead", i + 1);
+ m_dll.dvdnav_button_select(m_dvdnav, pci, i + 1);
+ break;
+ }
+ }
+ }
+}
+
+int CDVDInputStreamNavigator::GetTotalButtons()
+{
+ if (!m_dvdnav) return 0;
+
+ pci_t* pci = m_dll.dvdnav_get_current_nav_pci(m_dvdnav);
+
+ int counter = 0;
+ for (const btni_t& buttonInfo : pci->hli.btnit)
+ {
+ if (buttonInfo.x_start ||
+ buttonInfo.x_end ||
+ buttonInfo.y_start ||
+ buttonInfo.y_end)
+ {
+ counter++;
+ }
+ }
+ return counter;
+}
+
+void CDVDInputStreamNavigator::OnUp()
+{
+ if (m_dvdnav) m_dll.dvdnav_upper_button_select(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav));
+}
+
+void CDVDInputStreamNavigator::OnDown()
+{
+ if (m_dvdnav) m_dll.dvdnav_lower_button_select(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav));
+}
+
+void CDVDInputStreamNavigator::OnLeft()
+{
+ if (m_dvdnav) m_dll.dvdnav_left_button_select(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav));
+}
+
+void CDVDInputStreamNavigator::OnRight()
+{
+ if (m_dvdnav) m_dll.dvdnav_right_button_select(m_dvdnav, m_dll.dvdnav_get_current_nav_pci(m_dvdnav));
+}
+
+bool CDVDInputStreamNavigator::OnMouseMove(const CPoint &point)
+{
+ if (m_dvdnav)
+ {
+ pci_t* pci = m_dll.dvdnav_get_current_nav_pci(m_dvdnav);
+ return (DVDNAV_STATUS_OK == m_dll.dvdnav_mouse_select(m_dvdnav, pci, (int32_t)point.x, (int32_t)point.y));
+ }
+ return false;
+}
+
+bool CDVDInputStreamNavigator::OnMouseClick(const CPoint &point)
+{
+ if (m_dvdnav)
+ {
+ pci_t* pci = m_dll.dvdnav_get_current_nav_pci(m_dvdnav);
+ return (DVDNAV_STATUS_OK == m_dll.dvdnav_mouse_activate(m_dvdnav, pci, (int32_t)point.x, (int32_t)point.y));
+ }
+ return false;
+}
+
+bool CDVDInputStreamNavigator::OnMenu()
+{
+ if (!m_dvdnav)
+ {
+ return false;
+ }
+
+ return m_dll.dvdnav_menu_call(m_dvdnav, DVD_MENU_Escape) == DVDNAV_STATUS_OK;
+}
+
+void CDVDInputStreamNavigator::OnBack()
+{
+ if (m_dvdnav) m_dll.dvdnav_go_up(m_dvdnav);
+}
+
+// we don't allow skipping in menu's cause it will remove menu overlays
+void CDVDInputStreamNavigator::OnNext()
+{
+ if (m_dvdnav && !(IsInMenu() && GetTotalButtons() > 0))
+ {
+ m_dll.dvdnav_next_pg_search(m_dvdnav);
+ }
+}
+
+// we don't allow skipping in menu's cause it will remove menu overlays
+void CDVDInputStreamNavigator::OnPrevious()
+{
+ if (m_dvdnav && !(IsInMenu() && GetTotalButtons() > 0))
+ {
+ m_dll.dvdnav_prev_pg_search(m_dvdnav);
+ }
+}
+
+void CDVDInputStreamNavigator::SkipStill()
+{
+ if (!m_dvdnav)
+ return ;
+ m_dll.dvdnav_still_skip(m_dvdnav);
+}
+
+void CDVDInputStreamNavigator::SkipWait()
+{
+ if (!m_dvdnav) return ;
+ m_dll.dvdnav_wait_skip(m_dvdnav);
+}
+
+CDVDInputStream::ENextStream CDVDInputStreamNavigator::NextStream()
+{
+ if(m_holdmode == HOLDMODE_HELD)
+ m_holdmode = HOLDMODE_SKIP;
+
+ if(m_bEOF)
+ return NEXTSTREAM_NONE;
+ else if(m_lastevent == DVDNAV_VTS_CHANGE)
+ return NEXTSTREAM_OPEN;
+ else
+ return NEXTSTREAM_RETRY;
+}
+
+int CDVDInputStreamNavigator::GetActiveSubtitleStream()
+{
+ int activeStream = 0;
+
+ if (!m_dvdnav)
+ {
+ return activeStream;
+ }
+
+ const int8_t logicalSubStreamId = m_dll.dvdnav_get_active_spu_stream(m_dvdnav);
+ if (logicalSubStreamId < 0)
+ {
+ return activeStream;
+ }
+
+ int subStreamCount = GetSubTitleStreamCount();
+ for (int subpN = 0; subpN < subStreamCount; subpN++)
+ {
+ if (m_dll.dvdnav_get_spu_logical_stream(m_dvdnav, subpN) == logicalSubStreamId)
+ {
+ activeStream = subpN;
+ break;
+ }
+ }
+
+ return activeStream;
+}
+
+SubtitleStreamInfo CDVDInputStreamNavigator::GetSubtitleStreamInfo(const int iId)
+{
+ SubtitleStreamInfo info;
+ if (!m_dvdnav)
+ return info;
+
+ subp_attr_t subp_attributes;
+
+ if (m_dll.dvdnav_get_spu_attr(m_dvdnav, iId, &subp_attributes) == DVDNAV_STATUS_OK)
+ {
+ SetSubtitleStreamName(info, subp_attributes);
+
+ char lang[3];
+ lang[2] = 0;
+ lang[1] = (subp_attributes.lang_code & 255);
+ lang[0] = (subp_attributes.lang_code >> 8) & 255;
+
+ info.language = g_LangCodeExpander.ConvertToISO6392B(lang);
+ }
+
+ return info;
+}
+
+void CDVDInputStreamNavigator::SetSubtitleStreamName(SubtitleStreamInfo &info, const subp_attr_t &subp_attributes)
+{
+ if (subp_attributes.type == DVD_SUBPICTURE_TYPE_LANGUAGE ||
+ subp_attributes.type == DVD_SUBPICTURE_TYPE_NOTSPECIFIED)
+ {
+ switch (subp_attributes.code_extension)
+ {
+ case DVD_SUBPICTURE_LANG_EXT_NOTSPECIFIED:
+ case DVD_SUBPICTURE_LANG_EXT_CHILDRENSCAPTIONS:
+ break;
+
+ case DVD_SUBPICTURE_LANG_EXT_NORMALCAPTIONS:
+ case DVD_SUBPICTURE_LANG_EXT_NORMALCC:
+ case DVD_SUBPICTURE_LANG_EXT_BIGCAPTIONS:
+ case DVD_SUBPICTURE_LANG_EXT_BIGCC:
+ case DVD_SUBPICTURE_LANG_EXT_CHILDRENSCC:
+ info.flags = StreamFlags::FLAG_HEARING_IMPAIRED;
+ break;
+ case DVD_SUBPICTURE_LANG_EXT_FORCED:
+ info.flags = StreamFlags::FLAG_FORCED;
+ break;
+ case DVD_SUBPICTURE_LANG_EXT_NORMALDIRECTORSCOMMENTS:
+ case DVD_SUBPICTURE_LANG_EXT_BIGDIRECTORSCOMMENTS:
+ case DVD_SUBPICTURE_LANG_EXT_CHILDRENDIRECTORSCOMMENTS:
+ info.name = g_localizeStrings.Get(37001);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int CDVDInputStreamNavigator::GetSubTitleStreamCount()
+{
+ if (!m_dvdnav)
+ {
+ return 0;
+ }
+ return m_dll.dvdnav_get_number_of_streams(m_dvdnav, DVD_SUBTITLE_STREAM);
+}
+
+int CDVDInputStreamNavigator::GetActiveAudioStream()
+{
+ if (!m_dvdnav)
+ {
+ return -1;
+ }
+
+ const int8_t logicalAudioStreamId = m_dll.dvdnav_get_active_audio_stream(m_dvdnav);
+ if (logicalAudioStreamId < 0)
+ {
+ return -1;
+ }
+
+ int activeStream = -1;
+ int audioStreamCount = GetAudioStreamCount();
+ for (int audioN = 0; audioN < audioStreamCount; audioN++)
+ {
+ if (m_dll.dvdnav_get_audio_logical_stream(m_dvdnav, audioN) == logicalAudioStreamId)
+ {
+ activeStream = audioN;
+ break;
+ }
+ }
+
+ return activeStream;
+}
+
+void CDVDInputStreamNavigator::SetAudioStreamName(AudioStreamInfo &info, const audio_attr_t &audio_attributes)
+{
+ switch( audio_attributes.code_extension )
+ {
+ case DVD_AUDIO_LANG_EXT_VISUALLYIMPAIRED:
+ info.name = g_localizeStrings.Get(37000);
+ info.flags = StreamFlags::FLAG_VISUAL_IMPAIRED;
+ break;
+ case DVD_AUDIO_LANG_EXT_DIRECTORSCOMMENTS1:
+ info.name = g_localizeStrings.Get(37001);
+ break;
+ case DVD_AUDIO_LANG_EXT_DIRECTORSCOMMENTS2:
+ info.name = g_localizeStrings.Get(37002);
+ break;
+ case DVD_AUDIO_LANG_EXT_NOTSPECIFIED:
+ case DVD_AUDIO_LANG_EXT_NORMALCAPTIONS:
+ default:
+ break;
+ }
+
+ switch(audio_attributes.audio_format)
+ {
+ case DVD_AUDIO_FORMAT_AC3:
+ info.name += " AC3";
+ info.codecName = "ac3";
+ break;
+ case DVD_AUDIO_FORMAT_UNKNOWN_1:
+ info.name += " UNKNOWN #1";
+ break;
+ case DVD_AUDIO_FORMAT_MPEG:
+ info.name += " MPEG AUDIO";
+ info.codecName = "mp1";
+ break;
+ case DVD_AUDIO_FORMAT_MPEG2_EXT:
+ info.name += " MP2 Ext.";
+ info.codecName = "mp2";
+ break;
+ case DVD_AUDIO_FORMAT_LPCM:
+ info.name += " LPCM";
+ info.codecName = "pcm";
+ break;
+ case DVD_AUDIO_FORMAT_UNKNOWN_5:
+ info.name += " UNKNOWN #5";
+ break;
+ case DVD_AUDIO_FORMAT_DTS:
+ info.name += " DTS";
+ info.codecName = "dts";
+ break;
+ case DVD_AUDIO_FORMAT_SDDS:
+ info.name += " SDDS";
+ break;
+ default:
+ info.name += " Other";
+ break;
+ }
+
+ switch(audio_attributes.channels + 1)
+ {
+ case 1:
+ info.name += " Mono";
+ break;
+ case 2:
+ info.name += " Stereo";
+ break;
+ case 6:
+ info.name += " 5.1";
+ break;
+ case 7:
+ info.name += " 6.1";
+ break;
+ default:
+ char temp[32];
+ sprintf(temp, " %d-chs", audio_attributes.channels + 1);
+ info.name += temp;
+ }
+
+ StringUtils::TrimLeft(info.name);
+}
+
+AudioStreamInfo CDVDInputStreamNavigator::GetAudioStreamInfo(const int iId)
+{
+ AudioStreamInfo info;
+ if (!m_dvdnav)
+ return info;
+
+ audio_attr_t audio_attributes;
+
+ if (m_dll.dvdnav_get_audio_attr(m_dvdnav, iId, &audio_attributes) == DVDNAV_STATUS_OK)
+ {
+ SetAudioStreamName(info, audio_attributes);
+
+ char lang[3];
+ lang[2] = 0;
+ lang[1] = (audio_attributes.lang_code & 255);
+ lang[0] = (audio_attributes.lang_code >> 8) & 255;
+
+ info.language = g_LangCodeExpander.ConvertToISO6392B(lang);
+ info.channels = audio_attributes.channels + 1;
+ }
+
+ return info;
+}
+
+int CDVDInputStreamNavigator::GetAudioStreamCount()
+{
+ if (!m_dvdnav)
+ {
+ return 0;
+ }
+ return m_dll.dvdnav_get_number_of_streams(m_dvdnav, DVD_AUDIO_STREAM);
+}
+
+int CDVDInputStreamNavigator::GetAngleCount()
+{
+ if (!m_dvdnav)
+ return -1;
+
+ int number_of_angles;
+ int current_angle;
+ dvdnav_status_t status = m_dll.dvdnav_get_angle_info(m_dvdnav, &current_angle, &number_of_angles);
+
+ if (status == DVDNAV_STATUS_OK)
+ return number_of_angles;
+ else
+ return -1;
+}
+
+int CDVDInputStreamNavigator::GetActiveAngle()
+{
+ if (!m_dvdnav)
+ return -1;
+
+ int number_of_angles;
+ int current_angle;
+ if (m_dll.dvdnav_get_angle_info(m_dvdnav, &current_angle, &number_of_angles) == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "Failed to get current angle: {}", m_dll.dvdnav_err_to_string(m_dvdnav));
+ return -1;
+ }
+ return current_angle;
+}
+
+bool CDVDInputStreamNavigator::SetAngle(int angle)
+{
+ if (!m_dvdnav)
+ return false;
+
+ dvdnav_status_t status = m_dll.dvdnav_angle_change(m_dvdnav, angle);
+
+ return (status == DVDNAV_STATUS_OK);
+}
+
+bool CDVDInputStreamNavigator::GetCurrentButtonInfo(CDVDOverlaySpu* pOverlayPicture, CDVDDemuxSPU* pSPU, int iButtonType)
+{
+ int colorAndAlpha[4][4];
+ dvdnav_highlight_area_t hl;
+
+ if (!m_dvdnav)
+ {
+ return false;
+ }
+
+ int button = GetCurrentButton();
+ if (button < 0)
+ {
+ return false;
+ }
+
+ if (DVDNAV_STATUS_OK == m_dll.dvdnav_get_highlight_area(
+ m_dll.dvdnav_get_current_nav_pci(m_dvdnav), button, iButtonType, &hl))
+ {
+ // button cropping information
+ pOverlayPicture->crop_i_x_start = hl.sx;
+ pOverlayPicture->crop_i_x_end = hl.ex;
+ pOverlayPicture->crop_i_y_start = hl.sy;
+ pOverlayPicture->crop_i_y_end = hl.ey;
+ }
+
+ if (pSPU->m_bHasClut)
+ {
+ // get color stored in the highlight area palete using the previously stored clut
+ for (unsigned i = 0; i < 4; i++)
+ {
+ uint8_t* yuvColor = pSPU->m_clut[(hl.palette >> (16 + i * 4)) & 0x0f];
+ uint8_t alpha = (((hl.palette >> (i * 4)) & 0x0f));
+
+ colorAndAlpha[i][0] = yuvColor[0];
+ colorAndAlpha[i][1] = yuvColor[1];
+ colorAndAlpha[i][2] = yuvColor[2];
+ colorAndAlpha[i][3] = alpha;
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ pOverlayPicture->highlight_alpha[i] = colorAndAlpha[i][3];
+ for (int j = 0; j < 3; j++)
+ pOverlayPicture->highlight_color[i][j] = colorAndAlpha[i][j];
+ }
+ }
+
+ return true;
+}
+
+int CDVDInputStreamNavigator::GetTotalTime()
+{
+ //We use buffers of this as they can get called from multiple threads, and could block if we are currently reading data
+ return m_iTotalTime;
+}
+
+int CDVDInputStreamNavigator::GetTime()
+{
+ //We use buffers of this as they can get called from multiple threads, and could block if we are currently reading data
+ return m_iTime;
+}
+
+bool CDVDInputStreamNavigator::PosTime(int iTimeInMsec)
+{
+ if( m_dll.dvdnav_jump_to_sector_by_time(m_dvdnav, iTimeInMsec * 90, 0) == DVDNAV_STATUS_ERR )
+ {
+ CLog::Log(LOGDEBUG, "dvdnav: dvdnav_jump_to_sector_by_time failed( {} )",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return false;
+ }
+ m_iTime = iTimeInMsec;
+ return true;
+}
+
+bool CDVDInputStreamNavigator::SeekChapter(int iChapter)
+{
+ if (!m_dvdnav)
+ return false;
+
+ // cannot allow to return true in case of buttons (overlays) because otherwise back in VideoPlayer FlushBuffers will remove menu overlays
+ // therefore we just skip the request in case there are buttons and return false
+ if (IsInMenu() && GetTotalButtons() > 0)
+ {
+ CLog::Log(LOGDEBUG, "{} - Seeking chapter is not allowed in menu set with buttons",
+ __FUNCTION__);
+ return false;
+ }
+
+ bool enabled = IsSubtitleStreamEnabled();
+ int audio = GetActiveAudioStream();
+ int subtitle = GetActiveSubtitleStream();
+
+ if (iChapter == (m_iPart + 1))
+ {
+ if (m_dll.dvdnav_next_pg_search(m_dvdnav) == DVDNAV_STATUS_ERR)
+ {
+ CLog::Log(LOGERROR, "dvdnav: dvdnav_next_pg_search( {} )",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return false;
+ }
+ }
+ else if (iChapter == (m_iPart - 1))
+ {
+ if (m_dll.dvdnav_prev_pg_search(m_dvdnav) == DVDNAV_STATUS_ERR)
+ {
+ CLog::Log(LOGERROR, "dvdnav: dvdnav_prev_pg_search( {} )",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return false;
+ }
+ }
+ else if (m_dll.dvdnav_part_play(m_dvdnav, m_iTitle, iChapter) == DVDNAV_STATUS_ERR)
+ {
+ CLog::Log(LOGERROR, "dvdnav: dvdnav_part_play failed( {} )",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return false;
+ }
+
+ SetActiveSubtitleStream(subtitle);
+ SetActiveAudioStream(audio);
+ EnableSubtitleStream(enabled);
+ return true;
+}
+
+float CDVDInputStreamNavigator::GetVideoAspectRatio()
+{
+ int iAspect = m_dll.dvdnav_get_video_aspect(m_dvdnav);
+ int iPerm = m_dll.dvdnav_get_video_scale_permission(m_dvdnav);
+
+ //The video scale permissions should give if the source is letterboxed
+ //and such. should be able to give us info that we can zoom in automatically
+ //not sure what to do with it currently
+
+ CLog::Log(LOGINFO, "{} - Aspect wanted: {}, Scale permissions: {}", __FUNCTION__, iAspect, iPerm);
+ switch(iAspect)
+ {
+ case 0: //4:3
+ return 4.0f / 3.0f;
+ case 3: //16:9
+ return 16.0f / 9.0f;
+ default: //Unknown, use libmpeg2
+ return 0.0f;
+ }
+}
+
+void CDVDInputStreamNavigator::EnableSubtitleStream(bool bEnable)
+{
+ if (!m_dvdnav)
+ return;
+
+ m_dll.dvdnav_toggle_spu_stream(m_dvdnav, static_cast<uint8_t>(bEnable));
+}
+
+bool CDVDInputStreamNavigator::IsSubtitleStreamEnabled()
+{
+ if (!m_dvdnav)
+ return false;
+
+ return m_dll.dvdnav_get_active_spu_stream(m_dvdnav) >= 0;
+}
+
+bool CDVDInputStreamNavigator::FillDVDState(DVDState& dvdState)
+{
+ if (!m_dvdnav)
+ {
+ return false;
+ }
+
+ if (m_dll.dvdnav_current_title_program(m_dvdnav, &dvdState.title, &dvdState.pgcn,
+ &dvdState.pgn) == DVDNAV_STATUS_ERR)
+ {
+ CLog::LogF(LOGERROR, "Failed to get current title info ({})",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ return false;
+ }
+
+ int current_angle = GetActiveAngle();
+ if (current_angle == -1)
+ {
+ CLog::LogF(LOGERROR, "Could not detect current angle, ignoring saved state");
+ return false;
+ }
+ dvdState.current_angle = current_angle;
+ dvdState.audio_num = GetActiveAudioStream();
+ dvdState.subp_num = GetActiveSubtitleStream();
+ dvdState.sub_enabled = IsSubtitleStreamEnabled();
+
+ return true;
+}
+
+bool CDVDInputStreamNavigator::GetState(std::string& xmlstate)
+{
+ if( !m_dvdnav )
+ {
+ return false;
+ }
+
+ // do not save state if we are not playing a title stream (e.g. if we are in menus)
+ if (!m_dll.dvdnav_is_domain_vts(m_dvdnav))
+ {
+ return false;
+ }
+
+ DVDState dvdState;
+ if (!FillDVDState(dvdState))
+ {
+ CLog::LogF(LOGWARNING, "Failed to obtain current dvdnav state");
+ return false;
+ }
+
+ if (!m_dvdStateSerializer.DVDStateToXML(xmlstate, dvdState))
+ {
+ CLog::Log(LOGWARNING,
+ "CDVDInputStreamNavigator::SetNavigatorState - Failed to serialize state");
+ return false;
+ }
+
+ return true;
+}
+
+bool CDVDInputStreamNavigator::SetState(const std::string& xmlstate)
+{
+ if (!m_dvdnav)
+ return false;
+
+ DVDState dvdState;
+ if (!m_dvdStateSerializer.XMLToDVDState(dvdState, xmlstate))
+ {
+ CLog::LogF(LOGWARNING, "Failed to deserialize state");
+ return false;
+ }
+
+ m_dll.dvdnav_program_play(m_dvdnav, dvdState.title, dvdState.pgcn, dvdState.pgn);
+ m_dll.dvdnav_angle_change(m_dvdnav, dvdState.current_angle);
+ SetActiveSubtitleStream(dvdState.subp_num);
+ SetActiveAudioStream(dvdState.audio_num);
+ EnableSubtitleStream(dvdState.sub_enabled);
+ return true;
+}
+
+std::string CDVDInputStreamNavigator::GetDVDTitleString()
+{
+ if (!m_dvdnav)
+ return "";
+
+ const char* str = NULL;
+ if (m_dll.dvdnav_get_title_string(m_dvdnav, &str) == DVDNAV_STATUS_OK)
+ return str;
+ else
+ return "";
+}
+
+std::string CDVDInputStreamNavigator::GetDVDSerialString()
+{
+ if (!m_dvdnav)
+ return "";
+
+ const char* str = NULL;
+ if (m_dll.dvdnav_get_serial_string(m_dvdnav, &str) == DVDNAV_STATUS_OK)
+ return str;
+ else
+ return "";
+}
+
+std::string CDVDInputStreamNavigator::GetDVDVolIdString()
+{
+ if (!m_dvdnav)
+ return "";
+
+ const char* volIdTmp = m_dll.dvdnav_get_volid_string(m_dvdnav);
+ if (volIdTmp)
+ {
+ std::string volId{volIdTmp};
+ free(const_cast<char*>(volIdTmp));
+ return volId;
+ }
+ return "";
+}
+
+int64_t CDVDInputStreamNavigator::GetChapterPos(int ch)
+{
+ if (ch == -1 || ch > GetChapterCount())
+ ch = GetChapter();
+
+ std::map<int, std::map<int, int64_t>>::iterator title = m_mapTitleChapters.find(m_iTitle);
+ if (title != m_mapTitleChapters.end())
+ {
+ std::map<int, int64_t>::iterator chapter = title->second.find(ch);
+ if (chapter != title->second.end())
+ return chapter->second;
+ }
+ return 0;
+}
+
+void CDVDInputStreamNavigator::GetVideoResolution(uint32_t* width, uint32_t* height)
+{
+ if (!m_dvdnav) return;
+
+ // for version <= 5.0.3 this functions returns 0 instead of DVDNAV_STATUS_OK and -1 instead of DVDNAV_STATUS_ERR
+ int status = m_dll.dvdnav_get_video_resolution(m_dvdnav, width, height);
+ if (status == -1)
+ {
+ CLog::Log(LOGWARNING,
+ "CDVDInputStreamNavigator::GetVideoResolution - Failed to get resolution ({})",
+ m_dll.dvdnav_err_to_string(m_dvdnav));
+ *width = 0;
+ *height = 0;
+ }
+}
+
+VideoStreamInfo CDVDInputStreamNavigator::GetVideoStreamInfo()
+{
+ VideoStreamInfo info;
+ if (!m_dvdnav)
+ return info;
+
+ info.angles = GetAngleCount();
+ info.videoAspectRatio = GetVideoAspectRatio();
+ uint32_t width = 0;
+ uint32_t height = 0;
+ GetVideoResolution(&width, &height);
+
+ info.width = static_cast<int>(width);
+ info.height = static_cast<int>(height);
+
+ // Until we add get_video_attr or get_video_codec we can't distinguish MPEG-1 (h261)
+ // from MPEG-2 (h262). The latter is far more common, so use this.
+ info.codecName = "mpeg2";
+
+ return info;
+}
+
+int dvd_inputstreamnavigator_cb_seek(void * p_stream, uint64_t i_pos)
+{
+ CDVDInputStreamFile *lpstream = reinterpret_cast<CDVDInputStreamFile*>(p_stream);
+ if (lpstream->Seek(i_pos, SEEK_SET) >= 0)
+ return 0;
+ else
+ return -1;
+}
+
+int dvd_inputstreamnavigator_cb_read(void * p_stream, void * buffer, int i_read)
+{
+ CDVDInputStreamFile *lpstream = reinterpret_cast<CDVDInputStreamFile*>(p_stream);
+
+ int i_ret = 0;
+ while (i_ret < i_read)
+ {
+ int i_r;
+ i_r = lpstream->Read(reinterpret_cast<uint8_t *>(buffer) + i_ret, i_read - i_ret);
+ if (i_r < 0)
+ {
+ CLog::Log(LOGERROR,"read error dvd_inputstreamnavigator_cb_read");
+ return i_r;
+ }
+ if (i_r == 0)
+ break;
+
+ i_ret += i_r;
+ }
+
+ return i_ret;
+}
+
+void dvd_logger(void* priv, dvdnav_logger_level_t level, const char* fmt, va_list va)
+{
+ const std::string message = StringUtils::FormatV(fmt, va);
+ auto logLevel = LOGDEBUG;
+ switch (level)
+ {
+ case DVDNAV_LOGGER_LEVEL_INFO:
+ logLevel = LOGINFO;
+ break;
+ case DVDNAV_LOGGER_LEVEL_ERROR:
+ logLevel = LOGERROR;
+ break;
+ case DVDNAV_LOGGER_LEVEL_WARN:
+ logLevel = LOGWARNING;
+ break;
+ case DVDNAV_LOGGER_LEVEL_DEBUG:
+ logLevel = LOGDEBUG;
+ break;
+ default:
+ break;
+ };
+ CLog::Log(logLevel, "Libdvd: {}", message);
+}
+
+int dvd_inputstreamnavigator_cb_readv(void * p_stream, void * p_iovec, int i_blocks)
+{
+ // NOTE/TODO: this vectored read callback somehow doesn't seem to be called by libdvdnav.
+ // Therefore, the code below isn't really tested, but inspired from the libc_readv code for Win32 in libdvdcss (device.c:713).
+ CDVDInputStreamFile *lpstream = reinterpret_cast<CDVDInputStreamFile*>(p_stream);
+ const struct iovec* lpiovec = reinterpret_cast<const struct iovec*>(p_iovec);
+
+ int i_index, i_len, i_total = 0;
+ unsigned char *p_base;
+ int i_bytes;
+
+ for (i_index = i_blocks; i_index; i_index--, lpiovec++)
+ {
+ i_len = lpiovec->iov_len;
+ p_base = reinterpret_cast<unsigned char*>(lpiovec->iov_base);
+
+ if (i_len <= 0)
+ continue;
+
+ i_bytes = lpstream->Read(p_base, i_len);
+ if (i_bytes < 0)
+ return -1;
+ else
+ i_total += i_bytes;
+
+ if (i_bytes != i_len)
+ {
+ /* We reached the end of the file or a signal interrupted
+ * the read. Return a partial read. */
+ int i_seek = lpstream->Seek(i_total,0);
+ if (i_seek < 0)
+ return i_seek;
+
+ /* We have to return now so that i_pos isn't clobbered */
+ return i_total;
+ }
+ }
+ return i_total;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.h
new file mode 100644
index 0000000..d33364b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "../IVideoPlayer.h"
+#include "DVDDemuxers/DVDDemux.h"
+#include "DVDInputStream.h"
+#include "DVDInputStreamFile.h"
+#include "DVDStateSerializer.h"
+#include "DllDvdNav.h"
+#include "cores/MenuType.h"
+#include "utils/Geometry.h"
+
+#include <string>
+
+#define DVD_VIDEO_BLOCKSIZE DVD_VIDEO_LB_LEN // 2048 bytes
+
+#define NAVRESULT_NOP 0x00000001 // keep processing messages
+#define NAVRESULT_DATA 0x00000002 // return data to demuxer
+#define NAVRESULT_ERROR 0x00000003 // return read error to demuxer
+#define NAVRESULT_HOLD 0x00000004 // return eof to demuxer
+
+#define LIBDVDNAV_BUTTON_NORMAL 0
+#define LIBDVDNAV_BUTTON_CLICKED 1
+
+#define DVDNAV_ERROR -1
+
+class CDVDDemuxSPU;
+class CSPUInfo;
+class CDVDOverlayPicture;
+
+struct dvdnav_s;
+
+class CDVDInputStreamNavigator
+ : public CDVDInputStream
+ , public CDVDInputStream::IDisplayTime
+ , public CDVDInputStream::IChapter
+ , public CDVDInputStream::IPosTime
+ , public CDVDInputStream::IMenus
+{
+public:
+ CDVDInputStreamNavigator(IVideoPlayer* player, const CFileItem& fileitem);
+ ~CDVDInputStreamNavigator() override;
+
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ int GetBlockSize() override { return DVDSTREAM_BLOCK_SIZE_DVD; }
+ bool IsEOF() override { return m_bEOF; }
+ int64_t GetLength() override { return 0; }
+ ENextStream NextStream() override ;
+
+ void ActivateButton() override;
+ void SelectButton(int iButton) override;
+ void SkipStill() override;
+ void SkipWait();
+ void OnUp() override;
+ void OnDown() override;
+ void OnLeft() override;
+ void OnRight() override;
+
+ /*! \brief Open the Menu
+ * \return true if the menu is successfully opened, false otherwise
+ */
+ bool OnMenu() override;
+
+ void OnBack() override;
+ void OnNext() override;
+ void OnPrevious() override;
+ bool OnMouseMove(const CPoint &point) override;
+ bool OnMouseClick(const CPoint &point) override;
+
+ int GetCurrentButton() override;
+ int GetTotalButtons() override;
+ bool GetCurrentButtonInfo(CDVDOverlaySpu* pOverlayPicture, CDVDDemuxSPU* pSPU, int iButtonType /* 0 = selection, 1 = action (clicked)*/);
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ MenuType GetSupportedMenuType() override { return MenuType::NATIVE; }
+
+ bool IsInMenu() override { return m_bInMenu; }
+ double GetTimeStampCorrection() override { return (double)(m_iVobUnitCorrection * 1000) / 90; }
+
+ int GetActiveSubtitleStream();
+ int GetSubTitleStreamCount();
+ SubtitleStreamInfo GetSubtitleStreamInfo(const int iId);
+
+ bool SetActiveSubtitleStream(int iId);
+ void EnableSubtitleStream(bool bEnable);
+ bool IsSubtitleStreamEnabled();
+
+ int GetActiveAudioStream();
+ int GetAudioStreamCount();
+ int GetActiveAngle();
+ bool SetAngle(int angle);
+ bool SetActiveAudioStream(int iId);
+ AudioStreamInfo GetAudioStreamInfo(const int iId);
+
+ bool GetState(std::string &xmlstate) override;
+ bool SetState(const std::string &xmlstate) override;
+
+ int GetChapter() override { return m_iPart; } // the current part in the current title
+ int GetChapterCount() override { return m_iPartCount; } // the number of parts in the current title
+ void GetChapterName(std::string& name, int idx=-1) override {};
+ int64_t GetChapterPos(int ch=-1) override;
+ bool SeekChapter(int iChapter) override;
+
+ CDVDInputStream::IDisplayTime* GetIDisplayTime() override { return this; }
+ int GetTotalTime() override; // the total time in milli seconds
+ int GetTime() override; // the current position in milli seconds
+
+ float GetVideoAspectRatio();
+
+ CDVDInputStream::IPosTime* GetIPosTime() override { return this; }
+ bool PosTime(int iTimeInMsec) override; //seek within current pg(c)
+
+ std::string GetDVDTitleString();
+
+ /*!
+ * \brief Get the DVD volume ID string. Alternative to the dvd title (since some DVD authors
+ even forget to set it).
+ * \return The DVD volume id
+ */
+ std::string GetDVDVolIdString();
+
+ std::string GetDVDSerialString();
+
+ void CheckButtons();
+
+ VideoStreamInfo GetVideoStreamInfo();
+
+protected:
+
+ int ProcessBlock(uint8_t* buffer, int* read);
+
+ static void SetAudioStreamName(AudioStreamInfo &info, const audio_attr_t &audio_attributes);
+ static void SetSubtitleStreamName(SubtitleStreamInfo &info, const subp_attr_t &subp_attributes);
+
+ int GetAngleCount();
+ void GetVideoResolution(uint32_t * width, uint32_t * height);
+
+ /*! \brief Provided a pod DVDState struct, fill it with the current dvdnav state
+ * \param[in,out] dvdstate the DVD state struct to be filled
+ * \return true if it was possible to fill the state struct based on the current dvdnav state, false otherwise
+ */
+ bool FillDVDState(DVDState& dvdstate);
+
+ DllDvdNav m_dll;
+ bool m_bCheckButtons;
+ bool m_bEOF;
+
+ int m_holdmode;
+
+ int m_iTotalTime;
+ int m_iTime;
+ int64_t m_iCellStart; // start time of current cell in pts units (90khz clock)
+
+ bool m_bInMenu;
+
+ int64_t m_iVobUnitStart;
+ int64_t m_iVobUnitStop;
+ int64_t m_iVobUnitCorrection;
+
+ int m_iTitleCount;
+ int m_iTitle;
+
+ int m_iPartCount;
+ int m_iPart;
+
+ struct dvdnav_s* m_dvdnav;
+ dvdnav_stream_cb m_dvdnav_stream_cb;
+ std::unique_ptr<CDVDInputStreamFile> m_pstream;
+
+ IVideoPlayer* m_pVideoPlayer;
+
+ uint8_t m_lastblock[DVD_VIDEO_BLOCKSIZE];
+ int m_lastevent;
+
+ std::map<int, std::map<int, int64_t>> m_mapTitleChapters;
+
+ /*! DVD state serializer handler */
+ CDVDStateSerializer m_dvdStateSerializer;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.cpp
new file mode 100644
index 0000000..1f01861
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDInputStreamStack.h"
+
+#include "FileItem.h"
+#include "filesystem/File.h"
+#include "filesystem/StackDirectory.h"
+#include "utils/log.h"
+
+#include <limits.h>
+
+using namespace XFILE;
+
+CDVDInputStreamStack::CDVDInputStreamStack(const CFileItem& fileitem) : CDVDInputStream(DVDSTREAM_TYPE_FILE, fileitem)
+{
+ m_eof = true;
+ m_pos = 0;
+ m_length = 0;
+}
+
+CDVDInputStreamStack::~CDVDInputStreamStack()
+{
+ Close();
+}
+
+bool CDVDInputStreamStack::IsEOF()
+{
+ return m_eof;
+}
+
+bool CDVDInputStreamStack::Open()
+{
+ if (!CDVDInputStream::Open())
+ return false;
+
+ CStackDirectory dir;
+ CFileItemList items;
+
+ const CURL pathToUrl(m_item.GetDynPath());
+ if(!dir.GetDirectory(pathToUrl, items))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamStack::Open - failed to get list of stacked items");
+ return false;
+ }
+
+ m_length = 0;
+ m_eof = false;
+
+ for(int index = 0; index < items.Size(); index++)
+ {
+ TFile file(new CFile());
+
+ if (!file->Open(items[index]->GetDynPath(), READ_TRUNCATED))
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamStack::Open - failed to open stack part '{}' - skipping",
+ items[index]->GetDynPath());
+ continue;
+ }
+ TSeg segment;
+ segment.file = file;
+ segment.length = file->GetLength();
+
+ if(segment.length <= 0)
+ {
+ CLog::Log(LOGERROR,
+ "CDVDInputStreamStack::Open - failed to get file length for '{}' - skipping",
+ items[index]->GetDynPath());
+ continue;
+ }
+
+ m_length += segment.length;
+
+ m_files.push_back(segment);
+ }
+
+ if(m_files.empty())
+ return false;
+
+ m_file = m_files[0].file;
+ m_eof = false;
+
+ return true;
+}
+
+// close file and reset everything
+void CDVDInputStreamStack::Close()
+{
+ CDVDInputStream::Close();
+ m_files.clear();
+ m_file.reset();
+ m_eof = true;
+}
+
+int CDVDInputStreamStack::Read(uint8_t* buf, int buf_size)
+{
+ if(m_file == NULL || m_eof)
+ return 0;
+
+ unsigned int ret = m_file->Read(buf, buf_size);
+
+ if(ret > INT_MAX)
+ return -1;
+
+ if(ret == 0)
+ {
+ m_eof = true;
+ if(Seek(m_pos, SEEK_SET) < 0)
+ {
+ CLog::Log(LOGERROR, "CDVDInputStreamStack::Read - failed to seek into next file");
+ m_eof = true;
+ m_file.reset();
+ return -1;
+ }
+ }
+
+ m_pos += ret;
+
+ return (int)ret;
+}
+
+int64_t CDVDInputStreamStack::Seek(int64_t offset, int whence)
+{
+ int64_t pos, len;
+
+ if (whence == SEEK_SET)
+ pos = offset;
+ else if(whence == SEEK_CUR)
+ pos = offset + m_pos;
+ else if(whence == SEEK_END)
+ pos = offset + m_length;
+ else
+ return -1;
+
+ len = 0;
+ for(TSegVec::iterator it = m_files.begin(); it != m_files.end(); ++it)
+ {
+ if(len + it->length > pos)
+ {
+ TFile file = it->file;
+ int64_t file_pos = pos - len;
+ if(file->GetPosition() != file_pos)
+ {
+ if(file->Seek(file_pos, SEEK_SET) < 0)
+ return false;
+ }
+
+ m_file = file;
+ m_pos = pos;
+ m_eof = false;
+ return pos;
+ }
+ len += it->length;
+ }
+
+ return -1;
+}
+
+int64_t CDVDInputStreamStack::GetLength()
+{
+ return m_length;
+}
+
+
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.h
new file mode 100644
index 0000000..02befe8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamStack.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+#include <memory>
+#include <vector>
+
+class CDVDInputStreamStack : public CDVDInputStream
+{
+public:
+ explicit CDVDInputStreamStack(const CFileItem& fileitem);
+ ~CDVDInputStreamStack() override;
+
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+
+protected:
+
+ typedef std::shared_ptr<XFILE::CFile> TFile;
+
+ struct TSeg
+ {
+ TFile file;
+ int64_t length;
+ };
+
+ typedef std::vector<TSeg> TSegVec;
+
+ TSegVec m_files; ///< collection of open ptr's to all files in stack
+ TFile m_file; ///< currently active file
+ bool m_eof;
+ int64_t m_pos;
+ int64_t m_length;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp
new file mode 100644
index 0000000..c09779b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDStateSerializer.h"
+
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <charconv>
+#include <cstring>
+#include <sstream>
+
+namespace
+{
+// Serializer version - used to avoid processing deprecated/legacy schemas
+constexpr int DVDSTATESERIALIZER_VERSION = 2;
+}
+
+bool CDVDStateSerializer::DVDStateToXML(std::string& xmlstate, const DVDState& state)
+{
+ CXBMCTinyXML xmlDoc{"navstate"};
+
+ TiXmlElement eRoot{"navstate"};
+ eRoot.SetAttribute("version", DVDSTATESERIALIZER_VERSION);
+
+ AddXMLElement(eRoot, "title", std::to_string(state.title));
+ AddXMLElement(eRoot, "pgn", std::to_string(state.pgn));
+ AddXMLElement(eRoot, "pgcn", std::to_string(state.pgcn));
+ AddXMLElement(eRoot, "current_angle", std::to_string(state.current_angle));
+ AddXMLElement(eRoot, "audio_num", std::to_string(state.audio_num));
+ AddXMLElement(eRoot, "subp_num", std::to_string(state.subp_num));
+ AddXMLElement(eRoot, "sub_enabled", state.sub_enabled ? "true" : "false");
+ xmlDoc.InsertEndChild(eRoot);
+
+ std::stringstream stream;
+ stream << xmlDoc;
+ xmlstate = stream.str();
+ return true;
+}
+
+bool CDVDStateSerializer::XMLToDVDState(DVDState& state, const std::string& xmlstate)
+{
+ CXBMCTinyXML xmlDoc;
+
+ xmlDoc.Parse(xmlstate);
+ if (xmlDoc.Error())
+ return false;
+
+ TiXmlHandle hRoot(xmlDoc.RootElement());
+ if (!hRoot.Element() || !StringUtils::EqualsNoCase(hRoot.Element()->Value(), "navstate"))
+ {
+ CLog::LogF(LOGERROR, "Failed to deserialize dvd state - failed to detect root element.");
+ return false;
+ }
+
+ auto version = hRoot.Element()->Attribute("version");
+ if (!version || !StringUtils::EqualsNoCase(version, std::to_string(DVDSTATESERIALIZER_VERSION)))
+ {
+ CLog::LogF(LOGERROR, "Failed to deserialize dvd state - incompatible serializer version.");
+ return false;
+ }
+
+ const TiXmlElement* childElement = hRoot.Element()->FirstChildElement();
+ while (childElement)
+ {
+ const std::string property = childElement->Value();
+ if (property == "title")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()), state.title);
+ }
+ else if (property == "pgn")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()), state.pgn);
+ }
+ else if (property == "pgcn")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()), state.pgcn);
+ }
+ else if (property == "current_angle")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()),
+ state.current_angle);
+ }
+ else if (property == "subp_num")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()),
+ state.subp_num);
+ }
+ else if (property == "audio_num")
+ {
+ std::from_chars(childElement->GetText(),
+ childElement->GetText() + std::strlen(childElement->GetText()),
+ state.audio_num);
+ }
+ else if (property == "sub_enabled")
+ {
+ state.sub_enabled = StringUtils::EqualsNoCase(childElement->GetText(), "true");
+ }
+ else
+ {
+ CLog::LogF(LOGWARNING, "Unmapped dvd state property {}, ignored.", childElement->Value());
+ }
+ childElement = childElement->NextSiblingElement();
+ }
+ return true;
+}
+
+void CDVDStateSerializer::AddXMLElement(TiXmlElement& root,
+ const std::string& name,
+ const std::string& value)
+{
+ TiXmlElement xmlElement{name};
+ TiXmlText xmlElementValue = value;
+ xmlElement.InsertEndChild(xmlElementValue);
+ root.InsertEndChild(xmlElement);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h
new file mode 100644
index 0000000..debba10
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class TiXmlElement;
+
+/*! \brief Pod structure which represents the current dvd state with respect to dvdnav properties */
+struct DVDState
+{
+ /*! The current title being played */
+ int32_t title = -1;
+ /*! Program number */
+ int32_t pgn = -1;
+ /*! Program cell number */
+ int32_t pgcn = -1;
+ /*! Current playing angle */
+ int32_t current_angle = -1;
+ /*! Physical subtitle id set in dvdnav */
+ int8_t subp_num = -1;
+ /*! Physical audio stream id set in dvdnav */
+ int8_t audio_num = -1;
+ /*! If subtitles are enabled or disabled */
+ bool sub_enabled = false;
+};
+
+/*! \brief Auxiliar class to serialize/deserialize the dvd state (into/from XML)
+*/
+class CDVDStateSerializer
+{
+public:
+ /*! \brief Default constructor */
+ CDVDStateSerializer() = default;
+
+ /*! \brief Default destructor */
+ ~CDVDStateSerializer() = default;
+
+ /*! \brief Provided the state in xml format, fills a DVDState struct representing the DVD state and returns the
+ * success status of the operation
+ * \param[in,out] state the DVD state struct to be filled
+ * \param xmlstate a string describing the dvd state (XML)
+ * \return true if it was possible to fill the state struct based on the XML content, false otherwise
+ */
+ bool XMLToDVDState(DVDState& state, const std::string& xmlstate);
+
+ /*! \brief Provided the DVDState struct of the current playing dvd, serializes the struct to XML
+ * \param[in,out] xmlstate a string describing the dvd state (XML)
+ * \param state the DVD state struct
+ * \return true if it was possible to serialize the struct into XML, false otherwise
+ */
+ bool DVDStateToXML(std::string& xmlstate, const DVDState& state);
+
+private:
+ /*! \brief Appends a new element with the given name and value to a provided root XML element
+ * \param[in,out] root the root xml element to append the new element
+ * \param name the new element name
+ * \param value the new element value
+ */
+ void AddXMLElement(TiXmlElement& root, const std::string& name, const std::string& value);
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DllDvdNav.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DllDvdNav.h
new file mode 100644
index 0000000..7a0c149
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DllDvdNav.h
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+extern "C" {
+#define DVDNAV_COMPILE
+ #include <stdint.h>
+
+ #include "dvdnav/dvdnav.h"
+
+ #ifndef WIN32
+ #define WIN32
+ #endif // WIN32
+
+ #ifndef HAVE_CONFIG_H
+ #define HAVE_CONFIG_H
+ #endif
+
+ #include "dvdnav/dvd_types.h"
+
+ #ifdef WIN32 // WIN32INCLUDES
+ #undef HAVE_CONFIG_H
+ #endif
+}
+#include "DynamicDll.h"
+
+class DllDvdNavInterface
+{
+public:
+ virtual ~DllDvdNavInterface() = default;
+ virtual dvdnav_status_t dvdnav_open(dvdnav_t **dest, const char *path)=0;
+ virtual dvdnav_status_t dvdnav_open2(dvdnav_t** dest,
+ void*,
+ const dvdnav_logger_cb*,
+ const char* path) = 0;
+ virtual dvdnav_status_t dvdnav_open_stream(dvdnav_t **dest, void *stream, dvdnav_stream_cb *stream_cb) = 0;
+ virtual dvdnav_status_t dvdnav_open_stream2(dvdnav_t** dest,
+ void* stream,
+ const dvdnav_logger_cb*,
+ dvdnav_stream_cb* stream_cb) = 0;
+ virtual dvdnav_status_t dvdnav_close(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_reset(dvdnav_t *self)=0;
+ virtual const char* dvdnav_err_to_string(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int32_t read_ahead_flag)=0;
+ virtual dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *self, int32_t pgc_based_flag)=0;
+ virtual dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *self, uint8_t **buf, int32_t *event, int32_t *len)=0;
+ virtual dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf)=0;
+ virtual dvdnav_status_t dvdnav_still_skip(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_wait_skip(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_stop(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_button_select(dvdnav_t *self, pci_t *pci, int32_t button)=0;
+ virtual dvdnav_status_t dvdnav_button_activate(dvdnav_t *self, pci_t *pci)=0;
+ virtual dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self, pci_t *pci)=0;
+ virtual dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self, pci_t *pci)=0;
+ virtual dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self, pci_t *pci)=0;
+ virtual dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self, pci_t *pci)=0;
+ virtual dvdnav_status_t dvdnav_sector_search(dvdnav_t *self, uint64_t offset, int32_t origin)=0;
+ virtual pci_t* dvdnav_get_current_nav_pci(dvdnav_t *self)=0;
+ virtual dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_get_position(dvdnav_t *self, uint32_t *pos, uint32_t *len)=0;
+ virtual dvdnav_status_t dvdnav_current_title_info(dvdnav_t *self, int32_t *title, int32_t *part)=0;
+ virtual dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *self, char *code)=0;
+ virtual dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *self, char *code)=0;
+ virtual dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *self, char *code)=0;
+ virtual int8_t dvdnav_is_domain_vts(dvdnav_t *self)=0;
+ virtual int8_t dvdnav_get_active_spu_stream(dvdnav_t *self)=0;
+ virtual int8_t dvdnav_get_spu_logical_stream(dvdnav_t *self, uint8_t subp_num)=0;
+ virtual uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream)=0;
+ virtual dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int32_t *button)=0;
+ virtual dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu)=0;
+ virtual dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode, dvdnav_highlight_area_t *highlight)=0;
+ virtual dvdnav_status_t dvdnav_go_up(dvdnav_t *self)=0;
+ virtual int8_t dvdnav_get_active_audio_stream(dvdnav_t *self)=0;
+ virtual uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream)=0;
+ virtual int8_t dvdnav_get_audio_logical_stream(dvdnav_t *self, uint8_t audio_num)=0;
+ virtual dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int32_t region_mask)=0;
+ virtual uint8_t dvdnav_get_video_aspect(dvdnav_t *self)=0;
+ virtual uint8_t dvdnav_get_video_scale_permission(dvdnav_t *self)=0;
+ virtual dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int32_t *titles)=0;
+ virtual dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *self, int32_t title, int32_t *parts)=0;
+ virtual dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int32_t title)=0;
+ virtual dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part)=0;
+ virtual dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t * self, int32_t streamid, audio_attr_t* audio_attributes)=0;
+ virtual dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t * self, int32_t streamid, subp_attr_t* stitle_attributes)=0;
+ virtual dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t* self,
+ uint64_t offset,
+ int32_t origin) = 0;
+ virtual int64_t dvdnav_convert_time(dvd_time_t *time)=0;
+ virtual dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *self, int32_t *current_angle,int32_t *number_of_angles)=0;
+ virtual dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int32_t angle) = 0;
+ virtual dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y)=0;
+ virtual dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y)=0;
+ virtual dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, const char **title_str)=0;
+ virtual dvdnav_status_t dvdnav_get_serial_string(dvdnav_t *self, const char **serial_str)=0;
+ virtual const char* dvdnav_get_volid_string(dvdnav_t* self) = 0;
+ virtual dvdnav_status_t dvdnav_get_disk_region_mask(dvdnav_t* self, int32_t* region_mask) = 0;
+ virtual uint32_t dvdnav_describe_title_chapters(dvdnav_t* self, uint32_t title, uint64_t** times, uint64_t* duration)=0;
+ virtual int64_t dvdnav_get_current_time(dvdnav_t* self) = 0;
+ virtual int dvdnav_get_video_resolution(dvdnav_t* self, uint32_t* width, uint32_t* height)=0;
+ virtual int8_t dvdnav_get_number_of_streams(dvdnav_t* self, dvdnav_stream_type_t stream_type) = 0;
+ virtual dvdnav_status_t dvdnav_toggle_spu_stream(dvdnav_t* self, uint8_t visibility) = 0;
+ virtual dvdnav_status_t dvdnav_set_active_stream(dvdnav_t* self,
+ uint8_t stream_num,
+ dvdnav_stream_type_t stream_type) = 0;
+ virtual dvdnav_status_t dvdnav_program_play(dvdnav_t* self,
+ int32_t title,
+ int32_t pgcn,
+ int32_t pgn) = 0;
+ virtual dvdnav_status_t dvdnav_current_title_program(dvdnav_t* self,
+ int32_t* title,
+ int32_t* pgcn,
+ int32_t* pgn) = 0;
+};
+
+class DllDvdNav : public DllDynamic, DllDvdNavInterface
+{
+ DECLARE_DLL_WRAPPER(DllDvdNav, DLL_PATH_LIBDVDNAV)
+
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_open, (dvdnav_t **p1, const char *p2))
+ DEFINE_METHOD4(dvdnav_status_t,
+ dvdnav_open2,
+ (dvdnav_t * *p1, void* p2, const dvdnav_logger_cb* p3, const char* p4))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_open_stream, (dvdnav_t **p1, void *p2, dvdnav_stream_cb *p3))
+ DEFINE_METHOD4(dvdnav_status_t,
+ dvdnav_open_stream2,
+ (dvdnav_t * *p1, void* p2, const dvdnav_logger_cb* p3, dvdnav_stream_cb* p4))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_close, (dvdnav_t *p1))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_reset, (dvdnav_t *p1))
+ DEFINE_METHOD1(const char*, dvdnav_err_to_string, (dvdnav_t *p1))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_set_readahead_flag, (dvdnav_t *p1, int32_t p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_set_PGC_positioning_flag, (dvdnav_t *p1, int32_t p2))
+ DEFINE_METHOD4(dvdnav_status_t, dvdnav_get_next_cache_block, (dvdnav_t *p1, uint8_t **p2, int32_t *p3, int32_t *p4))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_free_cache_block, (dvdnav_t *p1, unsigned char *p2))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_still_skip, (dvdnav_t *p1))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_wait_skip, (dvdnav_t *p1))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_stop, (dvdnav_t *p1))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_button_select, (dvdnav_t *p1, pci_t *p2, int32_t p3))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_button_activate,(dvdnav_t *p1, pci_t *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_upper_button_select, (dvdnav_t *p1, pci_t *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_lower_button_select, (dvdnav_t *p1, pci_t *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_right_button_select, (dvdnav_t *p1, pci_t *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_left_button_select, (dvdnav_t *p1, pci_t *p2))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_sector_search, (dvdnav_t *p1, uint64_t p2, int32_t p3))
+ DEFINE_METHOD1(pci_t*, dvdnav_get_current_nav_pci, (dvdnav_t *p1))
+ DEFINE_METHOD1(dsi_t*, dvdnav_get_current_nav_dsi, (dvdnav_t *p1))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_get_position, (dvdnav_t *p1, uint32_t *p2, uint32_t *p3))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_current_title_info, (dvdnav_t *p1, int32_t *p2, int32_t *p3))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_spu_language_select, (dvdnav_t *p1, char *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_audio_language_select, (dvdnav_t *p1, char *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_menu_language_select, (dvdnav_t *p1, char *p2))
+ DEFINE_METHOD1(int8_t, dvdnav_is_domain_vts, (dvdnav_t *p1))
+ DEFINE_METHOD1(int8_t, dvdnav_get_active_spu_stream, (dvdnav_t *p1))
+ DEFINE_METHOD2(int8_t, dvdnav_get_spu_logical_stream, (dvdnav_t *p1, uint8_t p2))
+ DEFINE_METHOD2(uint16_t, dvdnav_spu_stream_to_lang, (dvdnav_t *p1, uint8_t p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_get_current_highlight, (dvdnav_t *p1, int32_t *p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_menu_call, (dvdnav_t *p1, DVDMenuID_t p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_get_disk_region_mask, (dvdnav_t * p1, int32_t* p2))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_prev_pg_search, (dvdnav_t *p1))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_next_pg_search, (dvdnav_t *p1))
+ DEFINE_METHOD4(dvdnav_status_t, dvdnav_get_highlight_area, (pci_t *p1, int32_t p2, int32_t p3, dvdnav_highlight_area_t *p4))
+ DEFINE_METHOD1(dvdnav_status_t, dvdnav_go_up, (dvdnav_t *p1))
+ DEFINE_METHOD1(int8_t, dvdnav_get_active_audio_stream, (dvdnav_t *p1))
+ DEFINE_METHOD2(uint16_t, dvdnav_audio_stream_to_lang, (dvdnav_t *p1, uint8_t p2))
+ DEFINE_METHOD2(int8_t, dvdnav_get_audio_logical_stream, (dvdnav_t *p1, uint8_t p2))
+ DEFINE_METHOD2(int8_t, dvdnav_get_number_of_streams, (dvdnav_t * p1, dvdnav_stream_type_t p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_set_region_mask, (dvdnav_t *p1, int32_t p2))
+ DEFINE_METHOD1(uint8_t, dvdnav_get_video_aspect, (dvdnav_t *p1))
+ DEFINE_METHOD1(uint8_t, dvdnav_get_video_scale_permission, (dvdnav_t *p1))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_get_number_of_titles, (dvdnav_t *p1, int32_t *p2))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_get_number_of_parts, (dvdnav_t *p1, int32_t p2, int32_t *p3))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_title_play, (dvdnav_t *p1, int32_t p2))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_part_play, (dvdnav_t *p1, int32_t p2, int32_t p3))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_get_audio_attr, (dvdnav_t * p1, int32_t p2, audio_attr_t* p3))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_get_spu_attr, (dvdnav_t * p1, int32_t p2, subp_attr_t* p3))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_jump_to_sector_by_time, (dvdnav_t * p1, uint64_t p2, int32_t p3))
+ DEFINE_METHOD1(int64_t, dvdnav_convert_time, (dvd_time_t *p1))
+ DEFINE_METHOD3(dvdnav_status_t, dvdnav_get_angle_info, (dvdnav_t *p1, int32_t *p2,int32_t *p3))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_angle_change, (dvdnav_t *p1, int32_t p2))
+ DEFINE_METHOD4(dvdnav_status_t, dvdnav_mouse_activate, (dvdnav_t *p1, pci_t *p2, int32_t p3, int32_t p4))
+ DEFINE_METHOD4(dvdnav_status_t, dvdnav_mouse_select, (dvdnav_t *p1, pci_t *p2, int32_t p3, int32_t p4))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_get_title_string, (dvdnav_t *p1, const char **p2))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_get_serial_string, (dvdnav_t *p1, const char **p2))
+ DEFINE_METHOD1(const char*, dvdnav_get_volid_string, (dvdnav_t * p1))
+ DEFINE_METHOD4(uint32_t, dvdnav_describe_title_chapters, (dvdnav_t* p1, uint32_t p2, uint64_t** p3, uint64_t* p4))
+ DEFINE_METHOD1(int64_t, dvdnav_get_current_time, (dvdnav_t * p1))
+ DEFINE_METHOD3(int, dvdnav_get_video_resolution, (dvdnav_t * p1, uint32_t* p2, uint32_t* p3))
+ DEFINE_METHOD2(dvdnav_status_t, dvdnav_toggle_spu_stream, (dvdnav_t * p1, uint8_t p2))
+ DEFINE_METHOD3(dvdnav_status_t,
+ dvdnav_set_active_stream,
+ (dvdnav_t * p1, uint8_t p2, dvdnav_stream_type_t p3))
+ DEFINE_METHOD4(dvdnav_status_t,
+ dvdnav_program_play,
+ (dvdnav_t * p1, int32_t p2, int32_t p3, int32_t p4))
+ DEFINE_METHOD4(dvdnav_status_t,
+ dvdnav_current_title_program,
+ (dvdnav_t * p1, int32_t* p2, int32_t* p3, int32_t* p4))
+ BEGIN_METHOD_RESOLVE()
+ RESOLVE_METHOD(dvdnav_open)
+ RESOLVE_METHOD(dvdnav_open2)
+ RESOLVE_METHOD(dvdnav_open_stream)
+ RESOLVE_METHOD(dvdnav_open_stream2)
+ RESOLVE_METHOD(dvdnav_close)
+ RESOLVE_METHOD(dvdnav_reset)
+ RESOLVE_METHOD(dvdnav_err_to_string)
+ RESOLVE_METHOD(dvdnav_set_readahead_flag)
+ RESOLVE_METHOD(dvdnav_set_PGC_positioning_flag)
+ RESOLVE_METHOD(dvdnav_get_next_cache_block)
+ RESOLVE_METHOD(dvdnav_free_cache_block)
+ RESOLVE_METHOD(dvdnav_still_skip)
+ RESOLVE_METHOD(dvdnav_wait_skip)
+ RESOLVE_METHOD(dvdnav_stop)
+ RESOLVE_METHOD(dvdnav_get_number_of_streams)
+ RESOLVE_METHOD(dvdnav_get_disk_region_mask)
+ RESOLVE_METHOD(dvdnav_button_select)
+ RESOLVE_METHOD(dvdnav_button_activate)
+ RESOLVE_METHOD(dvdnav_upper_button_select)
+ RESOLVE_METHOD(dvdnav_lower_button_select)
+ RESOLVE_METHOD(dvdnav_right_button_select)
+ RESOLVE_METHOD(dvdnav_left_button_select)
+ RESOLVE_METHOD(dvdnav_sector_search)
+ RESOLVE_METHOD(dvdnav_get_current_nav_pci)
+ RESOLVE_METHOD(dvdnav_get_current_nav_dsi)
+ RESOLVE_METHOD(dvdnav_get_position)
+ RESOLVE_METHOD(dvdnav_current_title_info)
+ RESOLVE_METHOD(dvdnav_spu_language_select)
+ RESOLVE_METHOD(dvdnav_audio_language_select)
+ RESOLVE_METHOD(dvdnav_menu_language_select)
+ RESOLVE_METHOD(dvdnav_is_domain_vts)
+ RESOLVE_METHOD(dvdnav_get_active_spu_stream)
+ RESOLVE_METHOD(dvdnav_get_spu_logical_stream)
+ RESOLVE_METHOD(dvdnav_spu_stream_to_lang)
+ RESOLVE_METHOD(dvdnav_get_current_highlight)
+ RESOLVE_METHOD(dvdnav_menu_call)
+ RESOLVE_METHOD(dvdnav_prev_pg_search)
+ RESOLVE_METHOD(dvdnav_next_pg_search)
+ RESOLVE_METHOD(dvdnav_get_highlight_area)
+ RESOLVE_METHOD(dvdnav_go_up)
+ RESOLVE_METHOD(dvdnav_get_active_audio_stream)
+ RESOLVE_METHOD(dvdnav_audio_stream_to_lang)
+ RESOLVE_METHOD(dvdnav_get_audio_logical_stream)
+ RESOLVE_METHOD(dvdnav_set_region_mask)
+ RESOLVE_METHOD(dvdnav_get_video_aspect)
+ RESOLVE_METHOD(dvdnav_get_video_scale_permission)
+ RESOLVE_METHOD(dvdnav_get_number_of_titles)
+ RESOLVE_METHOD(dvdnav_get_number_of_parts)
+ RESOLVE_METHOD(dvdnav_title_play)
+ RESOLVE_METHOD(dvdnav_part_play)
+ RESOLVE_METHOD(dvdnav_get_audio_attr)
+ RESOLVE_METHOD(dvdnav_get_spu_attr)
+ RESOLVE_METHOD(dvdnav_jump_to_sector_by_time)
+ RESOLVE_METHOD(dvdnav_convert_time)
+ RESOLVE_METHOD(dvdnav_get_angle_info)
+ RESOLVE_METHOD(dvdnav_angle_change)
+ RESOLVE_METHOD(dvdnav_mouse_activate)
+ RESOLVE_METHOD(dvdnav_mouse_select)
+ RESOLVE_METHOD(dvdnav_get_title_string)
+ RESOLVE_METHOD(dvdnav_get_serial_string)
+ RESOLVE_METHOD(dvdnav_get_volid_string)
+ RESOLVE_METHOD(dvdnav_describe_title_chapters)
+ RESOLVE_METHOD(dvdnav_get_current_time)
+ RESOLVE_METHOD(dvdnav_get_video_resolution)
+ RESOLVE_METHOD(dvdnav_toggle_spu_stream)
+ RESOLVE_METHOD(dvdnav_set_active_stream)
+ RESOLVE_METHOD(dvdnav_program_play)
+ RESOLVE_METHOD(dvdnav_current_title_program)
+ END_METHOD_RESOLVE()
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp
new file mode 100644
index 0000000..8ef7975
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "InputStreamAddon.h"
+
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/binary-addons/AddonDll.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/VideoCodec.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h"
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+#include "cores/VideoPlayer/Interface/InputStreamConstants.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "filesystem/SpecialProtocol.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "windowing/Resolution.h"
+
+CInputStreamProvider::CInputStreamProvider(const ADDON::AddonInfoPtr& addonInfo,
+ KODI_HANDLE parentInstance)
+ : m_addonInfo(addonInfo), m_parentInstance(parentInstance)
+{
+}
+
+void CInputStreamProvider::GetAddonInstance(INSTANCE_TYPE instance_type,
+ ADDON::AddonInfoPtr& addonInfo,
+ KODI_HANDLE& parentInstance)
+{
+ if (instance_type == ADDON::IAddonProvider::INSTANCE_VIDEOCODEC)
+ {
+ addonInfo = m_addonInfo;
+ parentInstance = m_parentInstance;
+ }
+}
+
+/*****************************************************************************************************************/
+
+using namespace ADDON;
+
+CInputStreamAddon::CInputStreamAddon(const AddonInfoPtr& addonInfo,
+ IVideoPlayer* player,
+ const CFileItem& fileitem,
+ const std::string& instanceId)
+ : IAddonInstanceHandler(
+ ADDON_INSTANCE_INPUTSTREAM, addonInfo, ADDON_INSTANCE_ID_UNUSED, nullptr, instanceId),
+ CDVDInputStream(DVDSTREAM_TYPE_ADDON, fileitem),
+ m_player(player)
+{
+ std::string listitemprops =
+ addonInfo->Type(AddonType::INPUTSTREAM)->GetValue("@listitemprops").asString();
+ std::string name(addonInfo->ID());
+
+ m_fileItemProps = StringUtils::Tokenize(listitemprops, "|");
+ for (auto &key : m_fileItemProps)
+ {
+ StringUtils::Trim(key);
+ key = name + "." + key;
+ }
+ m_caps = {};
+}
+
+CInputStreamAddon::~CInputStreamAddon()
+{
+ Close();
+}
+
+bool CInputStreamAddon::Supports(const AddonInfoPtr& addonInfo, const CFileItem& fileitem)
+{
+ /// @todo Error for users to show deprecation, can be removed in Kodi 20
+ CVariant oldAddonProp = fileitem.GetProperty("inputstreamaddon");
+ if (!oldAddonProp.isNull())
+ {
+ CLog::Log(LOGERROR,
+ "CInputStreamAddon::{} - 'inputstreamaddon' has been deprecated, "
+ "please use `#KODIPROP:inputstream={}` instead",
+ __func__, oldAddonProp.asString());
+ }
+
+ // check if a specific inputstream addon is requested
+ CVariant addon = fileitem.GetProperty(STREAM_PROPERTY_INPUTSTREAM);
+ if (!addon.isNull())
+ return (addon.asString() == addonInfo->ID());
+
+ // check protocols
+ std::string protocol = CURL(fileitem.GetDynPath()).GetProtocol();
+ if (!protocol.empty())
+ {
+ std::string protocols =
+ addonInfo->Type(AddonType::INPUTSTREAM)->GetValue("@protocols").asString();
+ if (!protocols.empty())
+ {
+ std::vector<std::string> protocolsList = StringUtils::Tokenize(protocols, "|");
+ for (auto& value : protocolsList)
+ {
+ StringUtils::Trim(value);
+ if (value == protocol)
+ return true;
+ }
+ }
+ }
+
+ std::string filetype = fileitem.GetURL().GetFileType();
+ if (!filetype.empty())
+ {
+ std::string extensions =
+ addonInfo->Type(AddonType::INPUTSTREAM)->GetValue("@extension").asString();
+ if (!extensions.empty())
+ {
+ std::vector<std::string> extensionsList = StringUtils::Tokenize(extensions, "|");
+ for (auto& value : extensionsList)
+ {
+ StringUtils::Trim(value);
+ if (value == filetype)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CInputStreamAddon::Open()
+{
+ // Create "C" interface structures, used as own parts to prevent API problems on update
+ m_ifc.inputstream = new AddonInstance_InputStream;
+ m_ifc.inputstream->props = new AddonProps_InputStream();
+ m_ifc.inputstream->toAddon = new KodiToAddonFuncTable_InputStream();
+ m_ifc.inputstream->toKodi = new AddonToKodiFuncTable_InputStream();
+
+ m_ifc.inputstream->toKodi->kodiInstance = this;
+ m_ifc.inputstream->toKodi->free_demux_packet = cb_free_demux_packet;
+ m_ifc.inputstream->toKodi->allocate_demux_packet = cb_allocate_demux_packet;
+ m_ifc.inputstream->toKodi->allocate_encrypted_demux_packet = cb_allocate_encrypted_demux_packet;
+ /*
+ // Way to include part on new API version
+ if (Addon()->GetTypeVersionDll(ADDON_TYPE::ADDON_INSTANCE_INPUTSTREAM) >= AddonVersion("3.0.0")) // Set the version to your new
+ {
+
+ }
+ */
+ if (CreateInstance() != ADDON_STATUS_OK || !m_ifc.inputstream->toAddon->open)
+ return false;
+
+ INPUTSTREAM_PROPERTY props = {};
+ std::map<std::string, std::string> propsMap;
+ for (auto &key : m_fileItemProps)
+ {
+ if (m_item.GetProperty(key).isNull())
+ continue;
+ propsMap[key] = m_item.GetProperty(key).asString();
+ }
+
+ props.m_nCountInfoValues = 0;
+ for (auto &pair : propsMap)
+ {
+ props.m_ListItemProperties[props.m_nCountInfoValues].m_strKey = pair.first.c_str();
+ props.m_ListItemProperties[props.m_nCountInfoValues].m_strValue = pair.second.c_str();
+ props.m_nCountInfoValues++;
+
+ if (props.m_nCountInfoValues >= STREAM_MAX_PROPERTY_COUNT)
+ {
+ CLog::Log(LOGERROR,
+ "CInputStreamAddon::{} - Hit max count of stream properties, "
+ "have {}, actual count: {}",
+ __func__, STREAM_MAX_PROPERTY_COUNT, propsMap.size());
+ break;
+ }
+ }
+
+ props.m_strURL = m_item.GetDynPath().c_str();
+ props.m_mimeType = m_item.GetMimeType().c_str();
+
+ std::string libFolder = URIUtils::GetDirectory(Addon()->Path());
+ std::string profileFolder = CSpecialProtocol::TranslatePath(Addon()->Profile());
+ props.m_libFolder = libFolder.c_str();
+ props.m_profileFolder = profileFolder.c_str();
+
+ DetectScreenResolution();
+
+ bool ret = m_ifc.inputstream->toAddon->open(m_ifc.inputstream, &props);
+ if (ret)
+ {
+ m_caps = {};
+ m_ifc.inputstream->toAddon->get_capabilities(m_ifc.inputstream, &m_caps);
+
+ m_subAddonProvider = std::shared_ptr<CInputStreamProvider>(
+ new CInputStreamProvider(GetAddonInfo(), m_ifc.inputstream->toAddon->addonInstance));
+ }
+ return ret;
+}
+
+void CInputStreamAddon::Close()
+{
+ if (m_ifc.inputstream->toAddon->close)
+ m_ifc.inputstream->toAddon->close(m_ifc.inputstream);
+ DestroyInstance();
+
+ // Delete "C" interface structures
+ delete m_ifc.inputstream->toAddon;
+ delete m_ifc.inputstream->toKodi;
+ delete m_ifc.inputstream->props;
+ delete m_ifc.inputstream;
+ m_ifc.inputstream = nullptr;
+}
+
+bool CInputStreamAddon::IsEOF()
+{
+ return false;
+}
+
+int CInputStreamAddon::Read(uint8_t* buf, int buf_size)
+{
+ if (!m_ifc.inputstream->toAddon->read_stream)
+ return -1;
+
+ return m_ifc.inputstream->toAddon->read_stream(m_ifc.inputstream, buf, buf_size);
+}
+
+int64_t CInputStreamAddon::Seek(int64_t offset, int whence)
+{
+ if (!m_ifc.inputstream->toAddon->seek_stream)
+ return -1;
+
+ return m_ifc.inputstream->toAddon->seek_stream(m_ifc.inputstream, offset, whence);
+}
+
+int64_t CInputStreamAddon::GetLength()
+{
+ if (!m_ifc.inputstream->toAddon->length_stream)
+ return -1;
+
+ return m_ifc.inputstream->toAddon->length_stream(m_ifc.inputstream);
+}
+
+int CInputStreamAddon::GetBlockSize()
+{
+ if (!m_ifc.inputstream->toAddon->block_size_stream)
+ return 0;
+
+ return m_ifc.inputstream->toAddon->block_size_stream(m_ifc.inputstream);
+}
+
+bool CInputStreamAddon::CanSeek()
+{
+ return (m_caps.m_mask & INPUTSTREAM_SUPPORTS_SEEK) != 0;
+}
+
+bool CInputStreamAddon::CanPause()
+{
+ return (m_caps.m_mask & INPUTSTREAM_SUPPORTS_PAUSE) != 0;
+}
+
+// IDisplayTime
+CDVDInputStream::IDisplayTime* CInputStreamAddon::GetIDisplayTime()
+{
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_IDISPLAYTIME) == 0)
+ return nullptr;
+
+ return this;
+}
+
+int CInputStreamAddon::GetTotalTime()
+{
+ if (!m_ifc.inputstream->toAddon->get_total_time)
+ return 0;
+
+ return m_ifc.inputstream->toAddon->get_total_time(m_ifc.inputstream);
+}
+
+int CInputStreamAddon::GetTime()
+{
+ if (!m_ifc.inputstream->toAddon->get_time)
+ return 0;
+
+ return m_ifc.inputstream->toAddon->get_time(m_ifc.inputstream);
+}
+
+// ITime
+CDVDInputStream::ITimes* CInputStreamAddon::GetITimes()
+{
+ // Check if screen resolution is changed during playback
+ // e.g. window resized and callback to add-on
+ DetectScreenResolution();
+
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_ITIME) == 0)
+ return nullptr;
+
+ return this;
+}
+
+bool CInputStreamAddon::GetTimes(Times &times)
+{
+ if (!m_ifc.inputstream->toAddon->get_times)
+ return false;
+
+ INPUTSTREAM_TIMES i_times;
+
+ if (m_ifc.inputstream->toAddon->get_times(m_ifc.inputstream, &i_times))
+ {
+ times.ptsBegin = i_times.ptsBegin;
+ times.ptsEnd = i_times.ptsEnd;
+ times.ptsStart = i_times.ptsStart;
+ times.startTime = i_times.startTime;
+ return true;
+ }
+ return false;
+}
+
+// IPosTime
+CDVDInputStream::IPosTime* CInputStreamAddon::GetIPosTime()
+{
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_IPOSTIME) == 0)
+ return nullptr;
+
+ return this;
+}
+
+bool CInputStreamAddon::PosTime(int ms)
+{
+ if (!m_ifc.inputstream->toAddon->pos_time)
+ return false;
+
+ return m_ifc.inputstream->toAddon->pos_time(m_ifc.inputstream, ms);
+}
+
+// IDemux
+CDVDInputStream::IDemux* CInputStreamAddon::GetIDemux()
+{
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_IDEMUX) == 0)
+ return nullptr;
+
+ return this;
+}
+
+bool CInputStreamAddon::OpenDemux()
+{
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_IDEMUX) != 0)
+ return true;
+ else
+ return false;
+}
+
+DemuxPacket* CInputStreamAddon::ReadDemux()
+{
+ if (!m_ifc.inputstream->toAddon->demux_read)
+ return nullptr;
+
+ return reinterpret_cast<DemuxPacket*>(m_ifc.inputstream->toAddon->demux_read(m_ifc.inputstream));
+}
+
+std::vector<CDemuxStream*> CInputStreamAddon::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ INPUTSTREAM_IDS streamIDs = {};
+ bool ret = m_ifc.inputstream->toAddon->get_stream_ids(m_ifc.inputstream, &streamIDs);
+ if (!ret || streamIDs.m_streamCount > INPUTSTREAM_MAX_STREAM_COUNT)
+ return streams;
+
+ for (unsigned int i = 0; i < streamIDs.m_streamCount; ++i)
+ if (CDemuxStream* stream = GetStream(streamIDs.m_streamIds[i]))
+ streams.push_back(stream);
+
+ return streams;
+}
+
+CDemuxStream* CInputStreamAddon::GetStream(int streamId) const
+{
+ INPUTSTREAM_INFO stream{};
+ KODI_HANDLE demuxStream = nullptr;
+ bool ret = m_ifc.inputstream->toAddon->get_stream(m_ifc.inputstream, streamId, &stream,
+ &demuxStream, cb_get_stream_transfer);
+ if (!ret || stream.m_streamType == INPUTSTREAM_TYPE_NONE)
+ return nullptr;
+
+ return static_cast<CDemuxStream*>(demuxStream);
+}
+
+KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle,
+ int streamId,
+ INPUTSTREAM_INFO* stream)
+{
+ CInputStreamAddon* thisClass = static_cast<CInputStreamAddon*>(handle);
+ if (!thisClass || !stream)
+ return nullptr;
+
+ std::string codecName(stream->m_codecName);
+ AVCodec* codec = nullptr;
+
+ if (stream->m_streamType != INPUTSTREAM_TYPE_TELETEXT &&
+ stream->m_streamType != INPUTSTREAM_TYPE_RDS && stream->m_streamType != INPUTSTREAM_TYPE_ID3)
+ {
+ StringUtils::ToLower(codecName);
+ codec = avcodec_find_decoder_by_name(codecName.c_str());
+ if (!codec)
+ return nullptr;
+ }
+
+ CDemuxStream* demuxStream;
+
+ if (stream->m_streamType == INPUTSTREAM_TYPE_AUDIO)
+ {
+ CDemuxStreamAudio *audioStream = new CDemuxStreamAudio();
+
+ audioStream->iChannels = stream->m_Channels;
+ audioStream->iSampleRate = stream->m_SampleRate;
+ audioStream->iBlockAlign = stream->m_BlockAlign;
+ audioStream->iBitRate = stream->m_BitRate;
+ audioStream->iBitsPerSample = stream->m_BitsPerSample;
+ demuxStream = audioStream;
+ }
+ else if (stream->m_streamType == INPUTSTREAM_TYPE_VIDEO)
+ {
+ CDemuxStreamVideo *videoStream = new CDemuxStreamVideo();
+
+ videoStream->iFpsScale = stream->m_FpsScale;
+ videoStream->iFpsRate = stream->m_FpsRate;
+ videoStream->iWidth = stream->m_Width;
+ videoStream->iHeight = stream->m_Height;
+ videoStream->fAspect = static_cast<double>(stream->m_Aspect);
+ videoStream->iBitRate = stream->m_BitRate;
+ videoStream->profile = ConvertVideoCodecProfile(stream->m_codecProfile);
+
+ /*! Added on API version 2.0.8 */
+ //@{
+ videoStream->colorSpace = static_cast<AVColorSpace>(stream->m_colorSpace);
+ videoStream->colorRange = static_cast<AVColorRange>(stream->m_colorRange);
+ //@}
+
+ /*! Added on API version 2.0.9 */
+ //@{
+ videoStream->colorPrimaries = static_cast<AVColorPrimaries>(stream->m_colorPrimaries);
+ videoStream->colorTransferCharacteristic =
+ static_cast<AVColorTransferCharacteristic>(stream->m_colorTransferCharacteristic);
+
+ if (stream->m_masteringMetadata)
+ {
+ videoStream->masteringMetaData =
+ std::shared_ptr<AVMasteringDisplayMetadata>(new AVMasteringDisplayMetadata);
+ videoStream->masteringMetaData->display_primaries[0][0] =
+ av_d2q(stream->m_masteringMetadata->primary_r_chromaticity_x, INT_MAX);
+ videoStream->masteringMetaData->display_primaries[0][1] =
+ av_d2q(stream->m_masteringMetadata->primary_r_chromaticity_y, INT_MAX);
+ videoStream->masteringMetaData->display_primaries[1][0] =
+ av_d2q(stream->m_masteringMetadata->primary_g_chromaticity_x, INT_MAX);
+ videoStream->masteringMetaData->display_primaries[1][1] =
+ av_d2q(stream->m_masteringMetadata->primary_g_chromaticity_y, INT_MAX);
+ videoStream->masteringMetaData->display_primaries[2][0] =
+ av_d2q(stream->m_masteringMetadata->primary_b_chromaticity_x, INT_MAX);
+ videoStream->masteringMetaData->display_primaries[2][1] =
+ av_d2q(stream->m_masteringMetadata->primary_b_chromaticity_y, INT_MAX);
+ videoStream->masteringMetaData->white_point[0] =
+ av_d2q(stream->m_masteringMetadata->white_point_chromaticity_x, INT_MAX);
+ videoStream->masteringMetaData->white_point[1] =
+ av_d2q(stream->m_masteringMetadata->white_point_chromaticity_y, INT_MAX);
+ videoStream->masteringMetaData->min_luminance =
+ av_d2q(stream->m_masteringMetadata->luminance_min, INT_MAX);
+ videoStream->masteringMetaData->max_luminance =
+ av_d2q(stream->m_masteringMetadata->luminance_max, INT_MAX);
+ videoStream->masteringMetaData->has_luminance =
+ videoStream->masteringMetaData->has_primaries = 1;
+ }
+
+ if (stream->m_contentLightMetadata)
+ {
+ videoStream->contentLightMetaData =
+ std::shared_ptr<AVContentLightMetadata>(new AVContentLightMetadata);
+ videoStream->contentLightMetaData->MaxCLL =
+ static_cast<unsigned>(stream->m_contentLightMetadata->max_cll);
+ videoStream->contentLightMetaData->MaxFALL =
+ static_cast<unsigned>(stream->m_contentLightMetadata->max_fall);
+ }
+ //@}
+
+ /*
+ // Way to include part on new API version
+ if (Addon()->GetTypeVersionDll(ADDON_TYPE::ADDON_INSTANCE_INPUTSTREAM) >= AddonVersion("3.0.0")) // Set the version to your new
+ {
+
+ }
+ */
+
+ demuxStream = videoStream;
+ }
+ else if (stream->m_streamType == INPUTSTREAM_TYPE_SUBTITLE)
+ {
+ CDemuxStreamSubtitle *subtitleStream = new CDemuxStreamSubtitle();
+ demuxStream = subtitleStream;
+ }
+ else if (stream->m_streamType == INPUTSTREAM_TYPE_TELETEXT)
+ {
+ CDemuxStreamTeletext* teletextStream = new CDemuxStreamTeletext();
+ demuxStream = teletextStream;
+ }
+ else if (stream->m_streamType == INPUTSTREAM_TYPE_RDS)
+ {
+ CDemuxStreamRadioRDS* rdsStream = new CDemuxStreamRadioRDS();
+ demuxStream = rdsStream;
+ }
+ else if (stream->m_streamType == INPUTSTREAM_TYPE_ID3)
+ {
+ CDemuxStreamAudioID3* id3Stream = new CDemuxStreamAudioID3();
+ demuxStream = id3Stream;
+ }
+ else
+ return nullptr;
+
+ demuxStream->name = stream->m_name;
+ if (codec)
+ demuxStream->codec = codec->id;
+ else
+ demuxStream->codec = AV_CODEC_ID_DVB_TELETEXT;
+ demuxStream->codecName = stream->m_codecInternalName;
+ demuxStream->uniqueId = streamId;
+ demuxStream->flags = static_cast<StreamFlags>(stream->m_flags);
+ demuxStream->language = stream->m_language;
+
+ if (thisClass->GetAddonInfo()->DependencyVersion(ADDON_INSTANCE_VERSION_INPUTSTREAM_XML_ID) >=
+ CAddonVersion("2.0.8"))
+ {
+ demuxStream->codec_fourcc = stream->m_codecFourCC;
+ }
+
+ if (stream->m_ExtraData && stream->m_ExtraSize)
+ {
+ demuxStream->ExtraData = std::make_unique<uint8_t[]>(stream->m_ExtraSize);
+ demuxStream->ExtraSize = stream->m_ExtraSize;
+ for (unsigned int j = 0; j < stream->m_ExtraSize; ++j)
+ demuxStream->ExtraData[j] = stream->m_ExtraData[j];
+ }
+
+ if (stream->m_cryptoSession.keySystem != STREAM_CRYPTO_KEY_SYSTEM_NONE &&
+ stream->m_cryptoSession.keySystem < STREAM_CRYPTO_KEY_SYSTEM_COUNT)
+ {
+ static const CryptoSessionSystem map[] = {
+ CRYPTO_SESSION_SYSTEM_NONE,
+ CRYPTO_SESSION_SYSTEM_WIDEVINE,
+ CRYPTO_SESSION_SYSTEM_PLAYREADY,
+ CRYPTO_SESSION_SYSTEM_WISEPLAY,
+ };
+ demuxStream->cryptoSession = std::shared_ptr<DemuxCryptoSession>(
+ new DemuxCryptoSession(map[stream->m_cryptoSession.keySystem],
+ stream->m_cryptoSession.sessionId, stream->m_cryptoSession.flags));
+
+ if ((stream->m_features & INPUTSTREAM_FEATURE_DECODE) != 0)
+ demuxStream->externalInterfaces = thisClass->m_subAddonProvider;
+ }
+ return demuxStream;
+}
+
+void CInputStreamAddon::EnableStream(int streamId, bool enable)
+{
+ if (!m_ifc.inputstream->toAddon->enable_stream)
+ return;
+
+ m_ifc.inputstream->toAddon->enable_stream(m_ifc.inputstream, streamId, enable);
+}
+
+bool CInputStreamAddon::OpenStream(int streamId)
+{
+ if (!m_ifc.inputstream->toAddon->open_stream)
+ return false;
+
+ return m_ifc.inputstream->toAddon->open_stream(m_ifc.inputstream, streamId);
+}
+
+int CInputStreamAddon::GetNrOfStreams() const
+{
+ return m_streamCount;
+}
+
+void CInputStreamAddon::SetSpeed(int speed)
+{
+ if (!m_ifc.inputstream->toAddon->demux_set_speed)
+ return;
+
+ m_ifc.inputstream->toAddon->demux_set_speed(m_ifc.inputstream, speed);
+}
+
+bool CInputStreamAddon::SeekTime(double time, bool backward, double* startpts)
+{
+ if (!m_ifc.inputstream->toAddon->demux_seek_time)
+ return false;
+
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_IPOSTIME) != 0)
+ {
+ if (!PosTime(static_cast<int>(time)))
+ return false;
+
+ FlushDemux();
+
+ if(startpts)
+ *startpts = DVD_NOPTS_VALUE;
+ return true;
+ }
+
+ return m_ifc.inputstream->toAddon->demux_seek_time(m_ifc.inputstream, time, backward, startpts);
+}
+
+void CInputStreamAddon::AbortDemux()
+{
+ if (m_ifc.inputstream->toAddon->demux_abort)
+ m_ifc.inputstream->toAddon->demux_abort(m_ifc.inputstream);
+}
+
+void CInputStreamAddon::FlushDemux()
+{
+ if (m_ifc.inputstream->toAddon->demux_flush)
+ m_ifc.inputstream->toAddon->demux_flush(m_ifc.inputstream);
+}
+
+void CInputStreamAddon::SetVideoResolution(unsigned int width,
+ unsigned int height,
+ unsigned int maxWidth,
+ unsigned int maxHeight)
+{
+ if (m_ifc.inputstream->toAddon->set_video_resolution)
+ m_ifc.inputstream->toAddon->set_video_resolution(m_ifc.inputstream, width, height, maxWidth,
+ maxHeight);
+}
+
+bool CInputStreamAddon::IsRealtime()
+{
+ if (m_ifc.inputstream->toAddon->is_real_time_stream)
+ return m_ifc.inputstream->toAddon->is_real_time_stream(m_ifc.inputstream);
+ return false;
+}
+
+
+// IChapter
+CDVDInputStream::IChapter* CInputStreamAddon::GetIChapter()
+{
+ if ((m_caps.m_mask & INPUTSTREAM_SUPPORTS_ICHAPTER) == 0)
+ return nullptr;
+
+ return this;
+}
+
+int CInputStreamAddon::GetChapter()
+{
+ if (m_ifc.inputstream->toAddon->get_chapter)
+ return m_ifc.inputstream->toAddon->get_chapter(m_ifc.inputstream);
+
+ return -1;
+}
+
+int CInputStreamAddon::GetChapterCount()
+{
+ if (m_ifc.inputstream->toAddon->get_chapter_count)
+ return m_ifc.inputstream->toAddon->get_chapter_count(m_ifc.inputstream);
+
+ return 0;
+}
+
+void CInputStreamAddon::GetChapterName(std::string& name, int ch)
+{
+ name.clear();
+ if (m_ifc.inputstream->toAddon->get_chapter_name)
+ {
+ const char* res = m_ifc.inputstream->toAddon->get_chapter_name(m_ifc.inputstream, ch);
+ if (res)
+ name = res;
+ }
+}
+
+int64_t CInputStreamAddon::GetChapterPos(int ch)
+{
+ if (m_ifc.inputstream->toAddon->get_chapter_pos)
+ return m_ifc.inputstream->toAddon->get_chapter_pos(m_ifc.inputstream, ch);
+
+ return 0;
+}
+
+bool CInputStreamAddon::SeekChapter(int ch)
+{
+ if (m_ifc.inputstream->toAddon->seek_chapter)
+ return m_ifc.inputstream->toAddon->seek_chapter(m_ifc.inputstream, ch);
+
+ return false;
+}
+
+int CInputStreamAddon::ConvertVideoCodecProfile(STREAMCODEC_PROFILE profile)
+{
+ switch (profile)
+ {
+ case H264CodecProfileBaseline:
+ return FF_PROFILE_H264_BASELINE;
+ case H264CodecProfileMain:
+ return FF_PROFILE_H264_MAIN;
+ case H264CodecProfileExtended:
+ return FF_PROFILE_H264_EXTENDED;
+ case H264CodecProfileHigh:
+ return FF_PROFILE_H264_HIGH;
+ case H264CodecProfileHigh10:
+ return FF_PROFILE_H264_HIGH_10;
+ case H264CodecProfileHigh422:
+ return FF_PROFILE_H264_HIGH_422;
+ case H264CodecProfileHigh444Predictive:
+ return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
+ case VP9CodecProfile0:
+ return FF_PROFILE_VP9_0;
+ case VP9CodecProfile1:
+ return FF_PROFILE_VP9_1;
+ case VP9CodecProfile2:
+ return FF_PROFILE_VP9_2;
+ case VP9CodecProfile3:
+ return FF_PROFILE_VP9_3;
+ case AV1CodecProfileMain:
+ return FF_PROFILE_AV1_MAIN;
+ case AV1CodecProfileHigh:
+ return FF_PROFILE_AV1_HIGH;
+ case AV1CodecProfileProfessional:
+ return FF_PROFILE_AV1_PROFESSIONAL;
+ default:
+ return FF_PROFILE_UNKNOWN;
+ }
+}
+
+void CInputStreamAddon::DetectScreenResolution()
+{
+ unsigned int videoWidth{1280};
+ unsigned int videoHeight{720};
+ if (m_player)
+ {
+ m_player->GetVideoResolution(videoWidth, videoHeight);
+ }
+ if (m_currentVideoWidth != videoWidth || m_currentVideoHeight != videoHeight)
+ {
+ unsigned int maxWidth{videoWidth};
+ unsigned int maxHeight{videoHeight};
+ // For Adaptive stream technology is needed to know the screen resolution
+ // one parameter used to fit stream resolution to screen resolution.
+ // Currently we provide current GUI resolution, but if Adjust refresh rate
+ // is enabled the GUI resolution is no longer relevant, Adjust refresh rate
+ // will change screen resolution based on whitelist (if any) and only after
+ // that have the video stream in the demuxer, therefore will fail because
+ // the addon has as reference the GUI resolution.
+ // So we have to provide the max resolution info before the playback take place
+ // in order to allow addon to provide in the demuxer the best stream resolution
+ // that can fit the supported screen resolution (changed when playback start).
+ CResolutionUtils::GetMaxAllowedResolution(maxWidth, maxHeight);
+
+ SetVideoResolution(videoWidth, videoHeight, maxWidth, maxHeight);
+
+ m_currentVideoWidth = videoWidth;
+ m_currentVideoHeight = videoHeight;
+ }
+}
+
+/*!
+ * Callbacks from add-on to kodi
+ */
+//@{
+DEMUX_PACKET* CInputStreamAddon::cb_allocate_demux_packet(void* kodiInstance, int data_size)
+{
+ return CDVDDemuxUtils::AllocateDemuxPacket(data_size);
+}
+
+DEMUX_PACKET* CInputStreamAddon::cb_allocate_encrypted_demux_packet(
+ void* kodiInstance, unsigned int dataSize, unsigned int encryptedSubsampleCount)
+{
+ return CDVDDemuxUtils::AllocateDemuxPacket(dataSize, encryptedSubsampleCount);
+}
+
+void CInputStreamAddon::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* packet)
+{
+ CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(packet));
+}
+
+//@}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h
new file mode 100644
index 0000000..8471404
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+#include "IVideoPlayer.h"
+#include "addons/AddonProvider.h"
+#include "addons/binary-addons/AddonInstanceHandler.h"
+#include "addons/kodi-dev-kit/include/kodi/addon-instance/Inputstream.h"
+
+#include <memory>
+#include <vector>
+
+class CInputStreamProvider
+ : public ADDON::IAddonProvider
+{
+public:
+ CInputStreamProvider(const ADDON::AddonInfoPtr& addonInfo, KODI_HANDLE parentInstance);
+
+ void GetAddonInstance(INSTANCE_TYPE instance_type,
+ ADDON::AddonInfoPtr& addonInfo,
+ KODI_HANDLE& parentInstance) override;
+
+private:
+ ADDON::AddonInfoPtr m_addonInfo;
+ KODI_HANDLE m_parentInstance;
+};
+
+//! \brief Input stream class
+class CInputStreamAddon
+ : public ADDON::IAddonInstanceHandler
+ , public CDVDInputStream
+ , public CDVDInputStream::IDisplayTime
+ , public CDVDInputStream::ITimes
+ , public CDVDInputStream::IPosTime
+ , public CDVDInputStream::IDemux
+ , public CDVDInputStream::IChapter
+{
+public:
+ CInputStreamAddon(const ADDON::AddonInfoPtr& addonInfo,
+ IVideoPlayer* player,
+ const CFileItem& fileitem,
+ const std::string& instanceId);
+ ~CInputStreamAddon() override;
+
+ static bool Supports(const ADDON::AddonInfoPtr& addonInfo, const CFileItem& fileitem);
+
+ // CDVDInputStream
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ int64_t GetLength() override;
+ int GetBlockSize() override;
+ bool IsEOF() override;
+ bool CanSeek() override; //! @todo drop this
+ bool CanPause() override;
+
+ // IDisplayTime
+ CDVDInputStream::IDisplayTime* GetIDisplayTime() override;
+ int GetTotalTime() override;
+ int GetTime() override;
+
+ // ITime
+ CDVDInputStream::ITimes* GetITimes() override;
+ bool GetTimes(Times &times) override;
+
+ // IPosTime
+ CDVDInputStream::IPosTime* GetIPosTime() override;
+ bool PosTime(int ms) override;
+
+ // IDemux
+ CDVDInputStream::IDemux* GetIDemux() override;
+ bool OpenDemux() override;
+ DemuxPacket* ReadDemux() override;
+ CDemuxStream* GetStream(int streamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ void EnableStream(int streamId, bool enable) override;
+ bool OpenStream(int streamid) override;
+
+ int GetNrOfStreams() const override;
+ void SetSpeed(int speed) override;
+ bool SeekTime(double time, bool backward = false, double* startpts = nullptr) override;
+ void AbortDemux() override;
+ void FlushDemux() override;
+ void SetVideoResolution(unsigned int width,
+ unsigned int height,
+ unsigned int maxWidth,
+ unsigned int maxHeight) override;
+ bool IsRealtime() override;
+
+ // IChapter
+ CDVDInputStream::IChapter* GetIChapter() override;
+ int GetChapter() override;
+ int GetChapterCount() override;
+ void GetChapterName(std::string& name, int ch = -1) override;
+ int64_t GetChapterPos(int ch = -1) override;
+ bool SeekChapter(int ch) override;
+
+protected:
+ static int ConvertVideoCodecProfile(STREAMCODEC_PROFILE profile);
+
+ IVideoPlayer* m_player;
+
+private:
+ void DetectScreenResolution();
+
+ unsigned int m_currentVideoWidth{0};
+ unsigned int m_currentVideoHeight{0};
+
+ std::vector<std::string> m_fileItemProps;
+ INPUTSTREAM_CAPABILITIES m_caps;
+
+ int m_streamCount = 0;
+
+ std::shared_ptr<CInputStreamProvider> m_subAddonProvider;
+
+ /*!
+ * Callbacks from add-on to kodi
+ */
+ //@{
+ /*!
+ * @brief Allocate a demux packet. Free with FreeDemuxPacket
+ * @param kodiInstance A pointer to the add-on.
+ * @param iDataSize The size of the data that will go into the packet
+ * @return The allocated packet.
+ */
+ static DEMUX_PACKET* cb_allocate_demux_packet(void* kodiInstance, int iDataSize = 0);
+
+ /*!
+ * @brief Allocate an encrypted demux packet. Free with FreeDemuxPacket
+ * @param kodiInstance A pointer to the add-on.
+ * @param dataSize The size of the data that will go into the packet
+ * @param encryptedSubsampleCount The number of subsample description blocks to allocate
+ * @return The allocated packet.
+ */
+ static DEMUX_PACKET* cb_allocate_encrypted_demux_packet(void* kodiInstance,
+ unsigned int dataSize,
+ unsigned int encryptedSubsampleCount);
+
+ /*!
+ * @brief Free a packet that was allocated with AllocateDemuxPacket
+ * @param kodiInstance A pointer to the add-on.
+ * @param pPacket The packet to free.
+ */
+ static void cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket);
+
+ /*!
+ * @brief Callback used by @ref GetStream to get the data
+ *
+ * Used as callback to prevent memleaks as the temporary stack memory on addon
+ * can be used to give data to Kodi.
+ *
+ * @param[in] handle Pointer to identify this class
+ * @param[in] streamId The related stream Identifier
+ * @param[in] stream "C" structure with stream information
+ * @return The created demux stream packet
+ */
+ static KODI_HANDLE cb_get_stream_transfer(KODI_HANDLE handle,
+ int streamId,
+ INPUTSTREAM_INFO* stream);
+ //@}
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.cpp
new file mode 100644
index 0000000..b44ec5a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "InputStreamMultiSource.h"
+
+#include "DVDFactoryInputStream.h"
+#include "utils/log.h"
+
+#include <map>
+
+using namespace XFILE;
+
+CInputStreamMultiSource::CInputStreamMultiSource(IVideoPlayer* pPlayer, const CFileItem& fileitem, const std::vector<std::string>& filenames) : InputStreamMultiStreams(DVDSTREAM_TYPE_MULTIFILES, fileitem),
+ m_pPlayer(pPlayer),
+ m_filenames(filenames)
+{
+}
+
+CInputStreamMultiSource::~CInputStreamMultiSource()
+{
+ Close();
+}
+
+void CInputStreamMultiSource::Abort()
+{
+ for (const auto& iter : m_InputStreams)
+ iter->Abort();
+}
+
+void CInputStreamMultiSource::Close()
+{
+ m_InputStreams.clear();
+ CDVDInputStream::Close();
+}
+
+BitstreamStats CInputStreamMultiSource::GetBitstreamStats() const
+{
+ return m_stats;
+}
+
+int CInputStreamMultiSource::GetBlockSize()
+{
+ return 0;
+}
+
+bool CInputStreamMultiSource::GetCacheStatus(XFILE::SCacheStatus *status)
+{
+ return false;
+}
+
+int64_t CInputStreamMultiSource::GetLength()
+{
+ int64_t length = 0;
+ for (const auto& iter : m_InputStreams)
+ {
+ length = std::max(length, iter->GetLength());
+ }
+
+ return length;
+}
+
+bool CInputStreamMultiSource::IsEOF()
+{
+ if (m_InputStreams.empty())
+ return true;
+
+ for (const auto& iter : m_InputStreams)
+ {
+ if (!(iter->IsEOF()))
+ return false;
+ }
+
+ return true;
+}
+
+CDVDInputStream::ENextStream CInputStreamMultiSource::NextStream()
+{
+ bool eOF = IsEOF();
+ if (m_InputStreams.empty() || eOF)
+ return NEXTSTREAM_NONE;
+
+
+ CDVDInputStream::ENextStream next;
+ for (const auto& iter : m_InputStreams)
+ {
+ next = iter->NextStream();
+ if (next != NEXTSTREAM_NONE)
+ return next;
+ }
+
+ return NEXTSTREAM_RETRY;
+}
+
+bool CInputStreamMultiSource::Open()
+{
+ if (!m_pPlayer || m_filenames.empty())
+ return false;
+
+ for (unsigned int i = 0; i < m_filenames.size(); i++)
+ {
+ CFileItem fileitem = CFileItem(m_filenames[i], false);
+ fileitem.SetMimeTypeForInternetFile();
+ InputStreamPtr inputstream(CDVDFactoryInputStream::CreateInputStream(m_pPlayer, fileitem));
+ if (!inputstream)
+ {
+ CLog::Log(LOGERROR,
+ "CDVDPlayer::OpenInputStream - unable to create input stream for file [{}]",
+ m_filenames[i]);
+ continue;
+ }
+
+ if (!inputstream->Open())
+ {
+ CLog::Log(LOGERROR, "CDVDPlayer::OpenInputStream - error opening file [{}]", m_filenames[i]);
+ continue;
+ }
+ m_InputStreams.push_back(inputstream);
+ }
+ return !m_InputStreams.empty();
+}
+
+int CInputStreamMultiSource::Read(uint8_t* buf, int buf_size)
+{
+ return -1;
+}
+
+int64_t CInputStreamMultiSource::Seek(int64_t offset, int whence)
+{
+ return -1;
+}
+
+void CInputStreamMultiSource::SetReadRate(uint32_t rate)
+{
+ for (const auto& iter : m_InputStreams)
+ iter->SetReadRate(rate);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.h
new file mode 100644
index 0000000..5242f6d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiSource.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+#include "InputStreamMultiStreams.h"
+
+#include <string>
+#include <vector>
+
+class IVideoPlayer;
+
+class CInputStreamMultiSource : public InputStreamMultiStreams
+{
+
+public:
+ CInputStreamMultiSource(IVideoPlayer* pPlayer, const CFileItem& fileitem, const std::vector<std::string>& filenames);
+ ~CInputStreamMultiSource() override;
+
+ void Abort() override;
+ void Close() override;
+ BitstreamStats GetBitstreamStats() const override ;
+ int GetBlockSize() override;
+ bool GetCacheStatus(XFILE::SCacheStatus *status) override;
+ int64_t GetLength() override;
+ bool IsEOF() override;
+ CDVDInputStream::ENextStream NextStream() override;
+ bool Open() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ void SetReadRate(uint32_t rate) override;
+
+protected:
+ IVideoPlayer* m_pPlayer;
+ std::vector<std::string> m_filenames;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiStreams.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiStreams.h
new file mode 100644
index 0000000..6ee1bdb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamMultiStreams.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+#include <string>
+#include <vector>
+
+typedef std::shared_ptr<CDVDInputStream> InputStreamPtr;
+class IDVDPlayer;
+
+class InputStreamMultiStreams : public CDVDInputStream
+{
+ friend class CDemuxMultiSource;
+
+public:
+ InputStreamMultiStreams(DVDStreamType type, const CFileItem& fileitem)
+ : CDVDInputStream(type, fileitem) {}
+
+ ~InputStreamMultiStreams() override = default;
+
+protected:
+ std::vector<InputStreamPtr> m_InputStreams; // input streams for current playing file
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp
new file mode 100644
index 0000000..03c4bf4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "InputStreamPVRBase.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+CInputStreamPVRBase::CInputStreamPVRBase(IVideoPlayer* pPlayer, const CFileItem& fileitem)
+ : CDVDInputStream(DVDSTREAM_TYPE_PVRMANAGER, fileitem),
+ m_eof(true),
+ m_StreamProps(new PVR_STREAM_PROPERTIES()),
+ m_client(CServiceBroker::GetPVRManager().GetClient(fileitem))
+{
+ if (!m_client)
+ CLog::Log(LOGERROR,
+ "CInputStreamPVRBase - {} - unable to obtain pvr addon instance for item '{}'",
+ __FUNCTION__, fileitem.GetPath());
+}
+
+CInputStreamPVRBase::~CInputStreamPVRBase()
+{
+ m_streamMap.clear();
+}
+
+bool CInputStreamPVRBase::IsEOF()
+{
+ return m_eof;
+}
+
+bool CInputStreamPVRBase::Open()
+{
+ if (CDVDInputStream::Open() && OpenPVRStream())
+ {
+ m_eof = false;
+ m_StreamProps->iStreamCount = 0;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void CInputStreamPVRBase::Close()
+{
+ ClosePVRStream();
+ CDVDInputStream::Close();
+ m_eof = true;
+}
+
+int CInputStreamPVRBase::Read(uint8_t* buf, int buf_size)
+{
+ int ret = ReadPVRStream(buf, buf_size);
+
+ // we currently don't support non completing reads
+ if (ret == 0)
+ m_eof = true;
+ else if (ret < -1)
+ ret = -1;
+
+ return ret;
+}
+
+int64_t CInputStreamPVRBase::Seek(int64_t offset, int whence)
+{
+ if (whence == SEEK_POSSIBLE)
+ return CanSeek() ? 1 : 0;
+
+ int64_t ret = SeekPVRStream(offset, whence);
+
+ // if we succeed, we are not eof anymore
+ if (ret >= 0)
+ m_eof = false;
+
+ return ret;
+}
+
+int64_t CInputStreamPVRBase::GetLength()
+{
+ return GetPVRStreamLength();
+}
+
+int CInputStreamPVRBase::GetBlockSize()
+{
+ int ret = -1;
+
+ if (m_client)
+ m_client->GetStreamReadChunkSize(ret);
+
+ return ret;
+}
+
+bool CInputStreamPVRBase::GetTimes(Times &times)
+{
+ PVR_STREAM_TIMES streamTimes = {};
+ if (m_client && m_client->GetStreamTimes(&streamTimes) == PVR_ERROR_NO_ERROR)
+ {
+ times.startTime = streamTimes.startTime;
+ times.ptsStart = streamTimes.ptsStart;
+ times.ptsBegin = streamTimes.ptsBegin;
+ times.ptsEnd = streamTimes.ptsEnd;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+CDVDInputStream::ENextStream CInputStreamPVRBase::NextStream()
+{
+ return NextPVRStream();
+}
+
+bool CInputStreamPVRBase::CanPause()
+{
+ return CanPausePVRStream();
+}
+
+bool CInputStreamPVRBase::CanSeek()
+{
+ return CanSeekPVRStream();
+}
+
+void CInputStreamPVRBase::Pause(bool bPaused)
+{
+ if (m_client)
+ m_client->PauseStream(bPaused);
+}
+
+bool CInputStreamPVRBase::IsRealtime()
+{
+ bool ret = false;
+
+ if (m_client)
+ m_client->IsRealTimeStream(ret);
+
+ return ret;
+}
+
+bool CInputStreamPVRBase::OpenDemux()
+{
+ if (m_client)
+ {
+ m_client->GetStreamProperties(m_StreamProps.get());
+ UpdateStreamMap();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+DemuxPacket* CInputStreamPVRBase::ReadDemux()
+{
+ if (!m_client)
+ return nullptr;
+
+ DemuxPacket* pPacket = nullptr;
+ m_client->DemuxRead(pPacket);
+ if (!pPacket)
+ {
+ return nullptr;
+ }
+ else if (pPacket->iStreamId == DMX_SPECIALID_STREAMINFO)
+ {
+ m_client->GetStreamProperties(m_StreamProps.get());
+ return pPacket;
+ }
+ else if (pPacket->iStreamId == DMX_SPECIALID_STREAMCHANGE)
+ {
+ m_client->GetStreamProperties(m_StreamProps.get());
+ UpdateStreamMap();
+ }
+
+ return pPacket;
+}
+
+CDemuxStream* CInputStreamPVRBase::GetStream(int iStreamId) const
+{
+ const auto stream = m_streamMap.find(iStreamId);
+ if (stream != m_streamMap.end())
+ return stream->second.get();
+ else
+ return nullptr;
+}
+
+std::vector<CDemuxStream*> CInputStreamPVRBase::GetStreams() const
+{
+ std::vector<CDemuxStream*> streams;
+
+ streams.reserve(m_streamMap.size());
+ for (const auto& st : m_streamMap)
+ streams.emplace_back(st.second.get());
+
+ return streams;
+}
+
+int CInputStreamPVRBase::GetNrOfStreams() const
+{
+ return m_StreamProps->iStreamCount;
+}
+
+void CInputStreamPVRBase::SetSpeed(int Speed)
+{
+ if (m_client)
+ m_client->SetSpeed(Speed);
+}
+
+void CInputStreamPVRBase::FillBuffer(bool mode)
+{
+ if (m_client)
+ m_client->FillBuffer(mode);
+}
+
+bool CInputStreamPVRBase::SeekTime(double timems, bool backwards, double *startpts)
+{
+ if (m_client)
+ return m_client->SeekTime(timems, backwards, startpts) == PVR_ERROR_NO_ERROR;
+ else
+ return false;
+}
+
+void CInputStreamPVRBase::AbortDemux()
+{
+ if (m_client)
+ m_client->DemuxAbort();
+}
+
+void CInputStreamPVRBase::FlushDemux()
+{
+ if (m_client)
+ m_client->DemuxFlush();
+}
+
+std::shared_ptr<CDemuxStream> CInputStreamPVRBase::GetStreamInternal(int iStreamId)
+{
+ const auto stream = m_streamMap.find(iStreamId);
+ if (stream != m_streamMap.end())
+ return stream->second;
+ else
+ return nullptr;
+}
+
+void CInputStreamPVRBase::UpdateStreamMap()
+{
+ std::map<int, std::shared_ptr<CDemuxStream>> newStreamMap;
+
+ int num = GetNrOfStreams();
+ for (int i = 0; i < num; ++i)
+ {
+ PVR_STREAM_PROPERTIES::PVR_STREAM stream = m_StreamProps->stream[i];
+
+ std::shared_ptr<CDemuxStream> dStream = GetStreamInternal(stream.iPID);
+
+ if (stream.iCodecType == PVR_CODEC_TYPE_AUDIO)
+ {
+ std::shared_ptr<CDemuxStreamAudio> streamAudio;
+
+ if (dStream)
+ streamAudio = std::dynamic_pointer_cast<CDemuxStreamAudio>(dStream);
+ if (!streamAudio)
+ streamAudio = std::make_shared<CDemuxStreamAudio>();
+
+ streamAudio->iChannels = stream.iChannels;
+ streamAudio->iSampleRate = stream.iSampleRate;
+ streamAudio->iBlockAlign = stream.iBlockAlign;
+ streamAudio->iBitRate = stream.iBitRate;
+ streamAudio->iBitsPerSample = stream.iBitsPerSample;
+
+ dStream = streamAudio;
+ }
+ else if (stream.iCodecType == PVR_CODEC_TYPE_VIDEO)
+ {
+ std::shared_ptr<CDemuxStreamVideo> streamVideo;
+
+ if (dStream)
+ streamVideo = std::dynamic_pointer_cast<CDemuxStreamVideo>(dStream);
+ if (!streamVideo)
+ streamVideo = std::make_shared<CDemuxStreamVideo>();
+
+ streamVideo->iFpsScale = stream.iFPSScale;
+ streamVideo->iFpsRate = stream.iFPSRate;
+ streamVideo->iHeight = stream.iHeight;
+ streamVideo->iWidth = stream.iWidth;
+ streamVideo->fAspect = static_cast<double>(stream.fAspect);
+
+ dStream = streamVideo;
+ }
+ else if (stream.iCodecId == AV_CODEC_ID_DVB_TELETEXT)
+ {
+ std::shared_ptr<CDemuxStreamTeletext> streamTeletext;
+
+ if (dStream)
+ streamTeletext = std::dynamic_pointer_cast<CDemuxStreamTeletext>(dStream);
+ if (!streamTeletext)
+ streamTeletext = std::make_shared<CDemuxStreamTeletext>();
+
+ dStream = streamTeletext;
+ }
+ else if (stream.iCodecType == PVR_CODEC_TYPE_SUBTITLE)
+ {
+ std::shared_ptr<CDemuxStreamSubtitle> streamSubtitle;
+
+ if (dStream)
+ streamSubtitle = std::dynamic_pointer_cast<CDemuxStreamSubtitle>(dStream);
+ if (!streamSubtitle)
+ streamSubtitle = std::make_shared<CDemuxStreamSubtitle>();
+
+ if (stream.iSubtitleInfo)
+ {
+ streamSubtitle->ExtraData = std::make_unique<uint8_t[]>(4);
+ streamSubtitle->ExtraSize = 4;
+ streamSubtitle->ExtraData[0] = (stream.iSubtitleInfo >> 8) & 0xff;
+ streamSubtitle->ExtraData[1] = (stream.iSubtitleInfo >> 0) & 0xff;
+ streamSubtitle->ExtraData[2] = (stream.iSubtitleInfo >> 24) & 0xff;
+ streamSubtitle->ExtraData[3] = (stream.iSubtitleInfo >> 16) & 0xff;
+ }
+ dStream = streamSubtitle;
+ }
+ else if (stream.iCodecType == PVR_CODEC_TYPE_RDS &&
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ "pvrplayback.enableradiords"))
+ {
+ std::shared_ptr<CDemuxStreamRadioRDS> streamRadioRDS;
+
+ if (dStream)
+ streamRadioRDS = std::dynamic_pointer_cast<CDemuxStreamRadioRDS>(dStream);
+ if (!streamRadioRDS)
+ streamRadioRDS = std::make_shared<CDemuxStreamRadioRDS>();
+
+ dStream = streamRadioRDS;
+ }
+ else if (stream.iCodecType == PVR_CODEC_TYPE_ID3)
+ {
+ std::shared_ptr<CDemuxStreamAudioID3> streamAudioID3;
+
+ if (dStream)
+ streamAudioID3 = std::dynamic_pointer_cast<CDemuxStreamAudioID3>(dStream);
+ if (!streamAudioID3)
+ streamAudioID3 = std::make_shared<CDemuxStreamAudioID3>();
+
+ dStream = std::move(streamAudioID3);
+ }
+ else
+ dStream = std::make_shared<CDemuxStream>();
+
+ dStream->codec = (AVCodecID)stream.iCodecId;
+ dStream->uniqueId = stream.iPID;
+ dStream->language = stream.strLanguage;
+
+ newStreamMap[stream.iPID] = dStream;
+ }
+
+ m_streamMap = newStreamMap;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h
new file mode 100644
index 0000000..b42f742
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDInputStream.h"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+class CFileItem;
+class IDemux;
+class IVideoPlayer;
+struct PVR_STREAM_PROPERTIES;
+
+namespace PVR
+{
+ class CPVRClient;
+}
+
+class CInputStreamPVRBase
+ : public CDVDInputStream
+ , public CDVDInputStream::ITimes
+ , public CDVDInputStream::IDemux
+{
+public:
+ CInputStreamPVRBase(IVideoPlayer* pPlayer, const CFileItem& fileitem);
+ ~CInputStreamPVRBase() override;
+ bool Open() override;
+ void Close() override;
+ int Read(uint8_t* buf, int buf_size) override;
+ int64_t Seek(int64_t offset, int whence) override;
+ bool IsEOF() override;
+ int64_t GetLength() override;
+ int GetBlockSize() override;
+
+ ENextStream NextStream() override;
+ bool IsRealtime() override;
+
+ CDVDInputStream::ITimes* GetITimes() override { return this; }
+ bool GetTimes(Times &times) override;
+
+ bool CanSeek() override; //! @todo drop this
+ bool CanPause() override;
+ void Pause(bool bPaused);
+
+ // Demux interface
+ CDVDInputStream::IDemux* GetIDemux() override { return nullptr; }
+ bool OpenDemux() override;
+ DemuxPacket* ReadDemux() override;
+ CDemuxStream* GetStream(int iStreamId) const override;
+ std::vector<CDemuxStream*> GetStreams() const override;
+ int GetNrOfStreams() const override;
+ void SetSpeed(int iSpeed) override;
+ void FillBuffer(bool mode) override;
+ bool SeekTime(double time, bool backward = false, double* startpts = NULL) override;
+ void AbortDemux() override;
+ void FlushDemux() override;
+
+protected:
+ void UpdateStreamMap();
+ std::shared_ptr<CDemuxStream> GetStreamInternal(int iStreamId);
+
+ virtual bool OpenPVRStream() = 0;
+ virtual void ClosePVRStream() = 0;
+ virtual int ReadPVRStream(uint8_t* buf, int buf_size) = 0;
+ virtual int64_t SeekPVRStream(int64_t offset, int whence) = 0;
+ virtual int64_t GetPVRStreamLength() = 0;
+ virtual ENextStream NextPVRStream() = 0;
+ virtual bool CanPausePVRStream() = 0;
+ virtual bool CanSeekPVRStream() = 0;
+
+ bool m_eof;
+ std::shared_ptr<PVR_STREAM_PROPERTIES> m_StreamProps;
+ std::map<int, std::shared_ptr<CDemuxStream>> m_streamMap;
+ std::shared_ptr<PVR::CPVRClient> m_client;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp
new file mode 100644
index 0000000..b2cde61
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "InputStreamPVRChannel.h"
+
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "utils/log.h"
+
+using namespace PVR;
+
+CInputStreamPVRChannel::CInputStreamPVRChannel(IVideoPlayer* pPlayer, const CFileItem& fileitem)
+ : CInputStreamPVRBase(pPlayer, fileitem),
+ m_bDemuxActive(false)
+{
+}
+
+CInputStreamPVRChannel::~CInputStreamPVRChannel()
+{
+ Close();
+}
+
+CDVDInputStream::IDemux* CInputStreamPVRChannel::GetIDemux()
+{
+ if (m_bDemuxActive)
+ return this;
+
+ return CInputStreamPVRBase::GetIDemux();
+}
+
+bool CInputStreamPVRChannel::OpenPVRStream()
+{
+ std::shared_ptr<CPVRChannel> channel = m_item.GetPVRChannelInfoTag();
+ if (!channel)
+ channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetByPath(m_item.GetPath());
+
+ if (!channel)
+ CLog::Log(LOGERROR,
+ "CInputStreamPVRChannel - {} - unable to obtain channel instance for channel {}",
+ __FUNCTION__, m_item.GetPath());
+
+ if (channel && m_client && (m_client->OpenLiveStream(channel) == PVR_ERROR_NO_ERROR))
+ {
+ m_bDemuxActive = m_client->GetClientCapabilities().HandlesDemuxing();
+ CLog::Log(LOGDEBUG, "CInputStreamPVRChannel - {} - opened channel stream {}", __FUNCTION__,
+ m_item.GetPath());
+ return true;
+ }
+ return false;
+}
+
+void CInputStreamPVRChannel::ClosePVRStream()
+{
+ if (m_client && (m_client->CloseLiveStream() == PVR_ERROR_NO_ERROR))
+ {
+ m_bDemuxActive = false;
+ CLog::Log(LOGDEBUG, "CInputStreamPVRChannel - {} - closed channel stream {}", __FUNCTION__,
+ m_item.GetPath());
+ }
+}
+
+int CInputStreamPVRChannel::ReadPVRStream(uint8_t* buf, int buf_size)
+{
+ int ret = -1;
+
+ if (m_client)
+ m_client->ReadLiveStream(buf, buf_size, ret);
+
+ return ret;
+}
+
+int64_t CInputStreamPVRChannel::SeekPVRStream(int64_t offset, int whence)
+{
+ int64_t ret = -1;
+
+ if (m_client)
+ m_client->SeekLiveStream(offset, whence, ret);
+
+ return ret;
+}
+
+int64_t CInputStreamPVRChannel::GetPVRStreamLength()
+{
+ int64_t ret = -1;
+
+ if (m_client)
+ m_client->GetLiveStreamLength(ret);
+
+ return ret;
+}
+
+CDVDInputStream::ENextStream CInputStreamPVRChannel::NextPVRStream()
+{
+ if (m_eof)
+ return NEXTSTREAM_OPEN;
+
+ return NEXTSTREAM_RETRY;
+}
+
+bool CInputStreamPVRChannel::CanPausePVRStream()
+{
+ bool ret = false;
+
+ if (m_client)
+ m_client->CanPauseStream(ret);
+
+ return ret;
+}
+
+bool CInputStreamPVRChannel::CanSeekPVRStream()
+{
+ bool ret = false;
+
+ if (m_client)
+ m_client->CanSeekStream(ret);
+
+ return ret;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h
new file mode 100644
index 0000000..d8d8beb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "InputStreamPVRBase.h"
+
+class CInputStreamPVRChannel : public CInputStreamPVRBase
+{
+public:
+ CInputStreamPVRChannel(IVideoPlayer* pPlayer, const CFileItem& fileitem);
+ ~CInputStreamPVRChannel() override;
+
+ CDVDInputStream::IDemux* GetIDemux() override;
+
+protected:
+ bool OpenPVRStream() override;
+ void ClosePVRStream() override;
+ int ReadPVRStream(uint8_t* buf, int buf_size) override;
+ int64_t SeekPVRStream(int64_t offset, int whence) override;
+ int64_t GetPVRStreamLength() override;
+ ENextStream NextPVRStream() override;
+ bool CanPausePVRStream() override;
+ bool CanSeekPVRStream() override;
+
+private:
+ bool m_bDemuxActive;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp
new file mode 100644
index 0000000..6c41ffb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "InputStreamPVRRecording.h"
+
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "utils/log.h"
+
+using namespace PVR;
+
+CInputStreamPVRRecording::CInputStreamPVRRecording(IVideoPlayer* pPlayer, const CFileItem& fileitem)
+ : CInputStreamPVRBase(pPlayer, fileitem)
+{
+}
+
+CInputStreamPVRRecording::~CInputStreamPVRRecording()
+{
+ Close();
+}
+
+bool CInputStreamPVRRecording::OpenPVRStream()
+{
+ std::shared_ptr<CPVRRecording> recording = m_item.GetPVRRecordingInfoTag();
+ if (!recording)
+ recording = CServiceBroker::GetPVRManager().Recordings()->GetByPath(m_item.GetPath());
+
+ if (!recording)
+ CLog::Log(
+ LOGERROR,
+ "CInputStreamPVRRecording - {} - unable to obtain recording instance for recording {}",
+ __FUNCTION__, m_item.GetPath());
+
+ if (recording && m_client && (m_client->OpenRecordedStream(recording) == PVR_ERROR_NO_ERROR))
+ {
+ CLog::Log(LOGDEBUG, "CInputStreamPVRRecording - {} - opened recording stream {}", __FUNCTION__,
+ m_item.GetPath());
+ return true;
+ }
+ return false;
+}
+
+void CInputStreamPVRRecording::ClosePVRStream()
+{
+ if (m_client && (m_client->CloseRecordedStream() == PVR_ERROR_NO_ERROR))
+ {
+ CLog::Log(LOGDEBUG, "CInputStreamPVRRecording - {} - closed recording stream {}", __FUNCTION__,
+ m_item.GetPath());
+ }
+}
+
+int CInputStreamPVRRecording::ReadPVRStream(uint8_t* buf, int buf_size)
+{
+ int iRead = -1;
+
+ if (m_client)
+ m_client->ReadRecordedStream(buf, buf_size, iRead);
+
+ return iRead;
+}
+
+int64_t CInputStreamPVRRecording::SeekPVRStream(int64_t offset, int whence)
+{
+ int64_t ret = -1;
+
+ if (m_client)
+ m_client->SeekRecordedStream(offset, whence, ret);
+
+ return ret;
+}
+
+int64_t CInputStreamPVRRecording::GetPVRStreamLength()
+{
+ int64_t ret = -1;
+
+ if (m_client)
+ m_client->GetRecordedStreamLength(ret);
+
+ return ret;
+}
+
+CDVDInputStream::ENextStream CInputStreamPVRRecording::NextPVRStream()
+{
+ return NEXTSTREAM_NONE;
+}
+
+bool CInputStreamPVRRecording::CanPausePVRStream()
+{
+ return true;
+}
+
+bool CInputStreamPVRRecording::CanSeekPVRStream()
+{
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h
new file mode 100644
index 0000000..2a873c9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "InputStreamPVRBase.h"
+
+class CInputStreamPVRRecording : public CInputStreamPVRBase
+{
+public:
+ CInputStreamPVRRecording(IVideoPlayer* pPlayer, const CFileItem& fileitem);
+ ~CInputStreamPVRRecording() override;
+
+protected:
+ bool OpenPVRStream() override;
+ void ClosePVRStream() override;
+ int ReadPVRStream(uint8_t* buf, int buf_size) override;
+ int64_t SeekPVRStream(int64_t offset, int whence) override;
+ int64_t GetPVRStreamLength() override;
+ ENextStream NextPVRStream() override;
+ bool CanPausePVRStream() override;
+ bool CanSeekPVRStream() override;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/config.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/config.h
new file mode 100644
index 0000000..0a310ea
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/config.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/* config.h. Generated by hand. */
+#if defined(TARGET_POSIX)
+#include "PlatformDefs.h"
+#else
+#include <windows.h>
+#endif
+#include <stdio.h>
+
+//#define HAVE_DLFCN_H 1
+#define HAVE_DVDCSS_DVDCSS_H 1
+/* #undef HAVE_DVDCSS_DVDCSS_H*/
+/* #undef HAVE_INTTYPES_H */
+#define HAVE_MEMORY_H 1
+#define HAVE_STDINT_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRINGS_H 1
+#define HAVE_STRING_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_SYS_TYPES_H 1
+/* #undef HAVE_UNISTD_H */
+#ifndef PACKAGE
+#define PACKAGE "libdvdread"
+#endif
+#ifndef PACKAGE_BUGREPORT
+#define PACKAGE_BUGREPORT ""
+#endif
+#ifndef PACKAGE_NAME
+#define PACKAGE_NAME ""
+#endif
+#ifndef PACKAGE_STRING
+#define PACKAGE_STRING ""
+#endif
+#ifndef PACKAGE_TARNAME
+#define PACKAGE_TARNAME ""
+#endif
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION ""
+#endif
+#define STDC_HEADERS 1
+#ifndef VERSION
+#define VERSION "1.2.6"
+#endif
+/* #undef WORDS_BIGENDIAN */
+/* #undef __DARWIN__ */
+/* #undef const */
+#define inline __inline
+/* #undef size_t */
+
+#define ssize_t int
+
+#ifndef PATH_MAX
+#define PATH_MAX MAX_PATH
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) ((m) & _S_IFDIR)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(m) ((m) & _S_IFREG)
+#endif
+#ifndef S_ISBLK
+#define S_ISBLK(m) 0
+#endif
+#ifndef S_ISCHR
+#define S_ISCHR(m) 0
+#endif
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_reader.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_reader.h
new file mode 100644
index 0000000..ba80262
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_reader.h
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
+ * Håkan Hjort <d95hjort@dtek.chalmers.se>,
+ * Björn Englund <d4bjorn@dtek.chalmers.se>
+ *
+ * This file is part of libdvdread.
+ *
+ * libdvdread is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdread is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libdvdread; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#ifdef _MSC_VER
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+#include <sys/types.h>
+//#include <inttypes.h>
+#include <stdarg.h>
+
+/*****************************************************************************
+* iovec structure: vectored data entry
+*****************************************************************************/
+#ifdef TARGET_WINDOWS
+struct iovec
+{
+ void *iov_base; /* Pointer to data. */
+ size_t iov_len; /* Length of data. */
+};
+#else
+# include <sys/uio.h> /* struct iovec */
+#endif
+
+/**
+ * The DVD access interface.
+ *
+ * This file contains the functions that form the interface for
+ * reading files located on a DVD.
+ */
+
+/**
+ * The length of one Logical Block of a DVD.
+ */
+#define DVD_VIDEO_LB_LEN 2048
+
+/**
+ * Maximum length of filenames allowed in UDF.
+ */
+#define MAX_UDF_FILE_NAME_LEN 2048
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Opaque type that is used as a handle for one instance of an opened DVD.
+ */
+typedef struct dvd_reader_s dvd_reader_t;
+typedef struct dvd_reader_device_s dvd_reader_device_t;
+
+/**
+ * Opaque type for a file read handle, much like a normal fd or FILE *.
+ */
+typedef struct dvd_file_s dvd_file_t;
+
+struct dvd_reader_stream_cb
+{
+ int (*pf_seek)(void* p_stream, uint64_t i_pos);
+ int (*pf_read)(void* p_stream, void* buffer, int i_read);
+ int (*pf_readv)(void* p_stream, void* p_iovec, int i_blocks);
+};
+typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
+
+/**
+ * Custom logger callback for DVDOpen[Stream]2
+ * @param private Handle as provided in Open functions
+ * @param level Log level
+ * @param fmt Format string
+ * @param args Arguments list
+ * pf_log(priv, level, fmt, args);
+ */
+typedef enum
+{
+ DVD_LOGGER_LEVEL_INFO,
+ DVD_LOGGER_LEVEL_ERROR,
+ DVD_LOGGER_LEVEL_WARN,
+ DVD_LOGGER_LEVEL_DEBUG,
+} dvd_logger_level_t;
+
+typedef struct
+{
+ void (*pf_log)(void*, dvd_logger_level_t, const char*, va_list);
+} dvd_logger_cb;
+
+/**
+ * Public type that is used to provide statistics on a handle.
+ */
+typedef struct {
+ off_t size; /**< Total size of file in bytes */
+ int nr_parts; /**< Number of file parts */
+ off_t parts_size[9]; /**< Size of each part in bytes */
+} dvd_stat_t;
+
+/**
+ * Opens a block device of a DVD-ROM file, or an image file, or a directory
+ * name for a mounted DVD or HD copy of a DVD.
+ * The second form of Open function (DVDOpenStream) can be used to
+ * provide custom stream_cb functions to access the DVD (see libdvdcss).
+ *
+ * If the given file is a block device, or is the mountpoint for a block
+ * device, then that device is used for CSS authentication using libdvdcss.
+ * If no device is available, then no CSS authentication is performed,
+ * and we hope that the image is decrypted.
+ *
+ * If the path given is a directory, then the files in that directory may be
+ * in any one of these formats:
+ *
+ * path/VIDEO_TS/VTS_01_1.VOB
+ * path/video_ts/vts_01_1.vob
+ * path/VTS_01_1.VOB
+ * path/vts_01_1.vob
+ *
+ * @param path Specifies the the device, file or directory to be used.
+ * @param stream is a private handle used by stream_cb
+ * @param stream_cb is a struct containing seek and read functions
+ * @return If successful a a read handle is returned. Otherwise 0 is returned.
+ *
+ * dvd = DVDOpen(path);
+ * dvd = DVDOpenStream(stream, &stream_cb);
+ */
+dvd_reader_t *DVDOpen( const char * );
+dvd_reader_t* DVDOpenStream(void*, dvd_reader_stream_cb*);
+
+/**
+ * Same as DVDOpen, but with private handle to be passed back on callbacks
+ *
+ * @param path Specifies the the device, file or directory to be used.
+ * @param priv is a private handle
+ * @param logcb is a custom logger callback struct, or NULL if none needed
+ * @param stream_cb is a struct containing seek and read functions
+ * @return If successful a a read handle is returned. Otherwise 0 is returned.
+ *
+ * dvd = DVDOpen2(priv, logcb, path);
+ * dvd = DVDOpenStream2(priv, logcb, &stream_cb);
+ */
+dvd_reader_t* DVDOpen2(void*, const dvd_logger_cb*, const char*);
+dvd_reader_t* DVDOpenStream2(void*, const dvd_logger_cb*, dvd_reader_stream_cb*);
+
+/**
+ * Closes and cleans up the DVD reader object.
+ *
+ * You must close all open files before calling this function.
+ *
+ * @param dvd A read handle that should be closed.
+ *
+ * DVDClose(dvd);
+ */
+void DVDClose( dvd_reader_t * );
+
+/**
+ *
+ */
+typedef enum
+{
+ DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */
+ DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP or VTS_XX_0.BUP (title) */
+ DVD_READ_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */
+ DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
+ the title set are opened and read as a
+ single file. */
+} dvd_read_domain_t;
+
+/**
+ * Stats a file on the DVD given the title number and domain.
+ * The information about the file is stored in a dvd_stat_t
+ * which contains information about the size of the file and
+ * the number of parts in case of a multipart file and the respective
+ * sizes of the parts.
+ * A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB
+ * The size of VTS_02_1.VOB will be stored in stat->parts_size[0],
+ * VTS_02_2.VOB in stat->parts_size[1], ...
+ * The total size (sum of all parts) is stored in stat->size and
+ * stat->nr_parts will hold the number of parts.
+ * Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files.
+ *
+ * This function is only of use if you want to get the size of each file
+ * in the filesystem. These sizes are not needed to use any other
+ * functions in libdvdread.
+ *
+ * @param dvd A dvd read handle.
+ * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
+ * @param domain Which domain.
+ * @param stat Pointer to where the result is stored.
+ * @return If successful 0, otherwise -1.
+ *
+ * int DVDFileStat(dvd, titlenum, domain, stat);
+ */
+int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *);
+
+/**
+ * Opens a file on the DVD given the title number and domain.
+ *
+ * If the title number is 0, the video manager information is opened
+ * (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
+ * used for reads, or 0 if the file was not found.
+ *
+ * @param dvd A dvd read handle.
+ * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
+ * @param domain Which domain.
+ * @return If successful a a file read handle is returned, otherwise 0.
+ *
+ * dvd_file = DVDOpenFile(dvd, titlenum, domain); */
+dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
+
+/**
+ * Closes a file and frees the associated structure.
+ *
+ * @param dvd_file The file read handle to be closed.
+ *
+ * DVDCloseFile(dvd_file);
+ */
+void DVDCloseFile( dvd_file_t * );
+
+/**
+ * Reads block_count number of blocks from the file at the given block offset.
+ * Returns number of blocks read on success, -1 on error. This call is only
+ * for reading VOB data, and should not be used when reading the IFO files.
+ * When reading from an encrypted drive, blocks are decrypted using libdvdcss
+ * where required.
+ *
+ * @param dvd_file A file read handle.
+ * @param offset Block offset from the start of the file to start reading at.
+ * @param block_count Number of block to read.
+ * @param data Pointer to a buffer to write the data into.
+ * @return Returns number of blocks read on success, -1 on error.
+ *
+ * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
+ */
+ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
+
+/**
+ * Seek to the given position in the file. Returns the resulting position in
+ * bytes from the beginning of the file. The seek position is only used for
+ * byte reads from the file, the block read call always reads from the given
+ * offset.
+ *
+ * @param dvd_file A file read handle.
+ * @param seek_offset Byte offset from the start of the file to seek to.
+ * @return The resulting position in bytes from the beginning of the file.
+ *
+ * offset_set = DVDFileSeek(dvd_file, seek_offset);
+ */
+int32_t DVDFileSeek( dvd_file_t *, int32_t );
+
+/**
+ * Reads the given number of bytes from the file. This call can only be used
+ * on the information files, and may not be used for reading from a VOB. This
+ * reads from and increments the current seek position for the file.
+ *
+ * @param dvd_file A file read handle.
+ * @param data Pointer to a buffer to write the data into.
+ * @param bytes Number of bytes to read.
+ * @return Returns number of bytes read on success, -1 on error.
+ *
+ * bytes_read = DVDReadBytes(dvd_file, data, bytes);
+ */
+ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
+
+/**
+ * Returns the file size in blocks.
+ *
+ * @param dvd_file A file read handle.
+ * @return The size of the file in blocks, -1 on error.
+ *
+ * blocks = DVDFileSize(dvd_file);
+ */
+ssize_t DVDFileSize( dvd_file_t * );
+
+/**
+ * Get a unique 128 bit disc ID.
+ * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
+ * in title order (those that exist).
+ * If you need a 'text' representation of the id, print it as a
+ * hexadecimal number, using lowercase letters, discid[0] first.
+ * I.e. the same format as the command-line 'md5sum' program uses.
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param discid The buffer to put the disc ID into. The buffer must
+ * have room for 128 bits (16 chars).
+ * @return 0 on success, -1 on error.
+ */
+int DVDDiscID( dvd_reader_t *, unsigned char * );
+
+/**
+ * Get the UDF VolumeIdentifier and VolumeSetIdentifier
+ * from the PrimaryVolumeDescriptor.
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param volid The buffer to put the VolumeIdentifier into.
+ * The VolumeIdentifier is latin-1 encoded (8bit unicode)
+ * null terminated and max 32 bytes (including '\0')
+ * @param volid_size No more than volid_size bytes will be copied to volid.
+ * If the VolumeIdentifier is truncated because of this
+ * it will still be null terminated.
+ * @param volsetid The buffer to put the VolumeSetIdentifier into.
+ * The VolumeIdentifier is 128 bytes as
+ * stored in the UDF PrimaryVolumeDescriptor.
+ * Note that this is not a null terminated string.
+ * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
+ * @return 0 on success, -1 on error.
+ */
+int DVDUDFVolumeInfo(dvd_reader_t*, char*, unsigned int, unsigned char*, unsigned int);
+
+int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
+
+/**
+ * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
+ *
+ * * Only use this function as fallback if DVDUDFVolumeInfo returns -1 *
+ * * this will happen on a disc mastered only with a iso9660 filesystem *
+ * * All video DVD discs have UDF filesystem *
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param volid The buffer to put the VolumeIdentifier into.
+ * The VolumeIdentifier is coded with '0-9','A-Z','_'
+ * null terminated and max 33 bytes (including '\0')
+ * @param volid_size No more than volid_size bytes will be copied to volid.
+ * If the VolumeIdentifier is truncated because of this
+ * it will still be null terminated.
+ * @param volsetid The buffer to put the VolumeSetIdentifier into.
+ * The VolumeIdentifier is 128 bytes as
+ * stored in the ISO9660 PrimaryVolumeDescriptor.
+ * Note that this is not a null terminated string.
+ * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
+ * @return 0 on success, -1 on error.
+ */
+int DVDISOVolumeInfo(dvd_reader_t*, char*, unsigned int, unsigned char*, unsigned int);
+
+/**
+ * Sets the level of caching that is done when reading from a device
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param level The level of caching wanted.
+ * -1 - returns the current setting.
+ * 0 - UDF Cache turned off.
+ * 1 - (default level) Pointers to IFO files and some data from
+ * PrimaryVolumeDescriptor are cached.
+ *
+ * @return The level of caching.
+ */
+int DVDUDFCacheLevel( dvd_reader_t *, int );
+
+#ifdef __cplusplus
+};
+#endif
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_types.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_types.h
new file mode 100644
index 0000000..5b2e802
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvd_types.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2000, 2001 Björn Englund, Håkan Hjort
+ *
+ * This file is part of libdvdnav, a DVD navigation library. It is a modified
+ * file originally part of the Ogle DVD player project.
+ *
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libdvdnav; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Various useful structs and enums for DVDs.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*
+ * DVD Menu ID
+ * (see dvdnav_menu_call())
+ */
+typedef enum {
+ /* When used in VTS domain, DVD_MENU_Escape behaves like DVD_MENU_Root,
+ * but from within a menu domain, DVD_MENU_Escape resumes playback. */
+ DVD_MENU_Escape = 0,
+ DVD_MENU_Title = 2,
+ DVD_MENU_Root = 3,
+ DVD_MENU_Subpicture = 4,
+ DVD_MENU_Audio = 5,
+ DVD_MENU_Angle = 6,
+ DVD_MENU_Part = 7
+} DVDMenuID_t;
+
+/*
+ * Stream Types
+ * (see dvdnav_get_number_of_streams())
+ */
+typedef enum
+{
+ DVD_SUBTITLE_STREAM = 0,
+ DVD_AUDIO_STREAM = 1
+} dvdnav_stream_type_t;
+
+/* Domain */
+typedef enum
+{
+ DVD_DOMAIN_FirstPlay = 1, /* First Play Domain */
+ DVD_DOMAIN_VTSTitle = 2, /* Video Title Set Domain */
+ DVD_DOMAIN_VMGM = 4, /* Video Manager Domain */
+ DVD_DOMAIN_VTSMenu = 8 /* Video Title Set Menu Domain */
+} DVDDomain_t;
+
+/*
+ * Structure containing info on highlight areas
+ * (see dvdnav_get_highlight_area())
+ */
+typedef struct {
+ uint32_t palette; /* The CLUT entries for the highlight palette
+ (4-bits per entry -> 4 entries) */
+ uint16_t sx,sy,ex,ey; /* The start/end x,y positions */
+ uint32_t pts; /* Highlight PTS to match with SPU */
+
+ /* button number for the SPU decoder/overlaying engine */
+ uint32_t buttonN;
+} dvdnav_highlight_area_t;
+
+/* The audio format */
+typedef enum
+{
+ DVD_AUDIO_FORMAT_AC3 = 0,
+ DVD_AUDIO_FORMAT_UNKNOWN_1 = 1,
+ DVD_AUDIO_FORMAT_MPEG = 2,
+ DVD_AUDIO_FORMAT_MPEG2_EXT = 3,
+ DVD_AUDIO_FORMAT_LPCM = 4,
+ DVD_AUDIO_FORMAT_UNKNOWN_5 = 5,
+ DVD_AUDIO_FORMAT_DTS = 6,
+ DVD_AUDIO_FORMAT_SDDS = 7
+} DVDAudioFormat_t;
+
+/* the following types are currently unused */
+
+#if 0
+
+/* User operation permissions */
+typedef enum {
+ UOP_FLAG_TitleOrTimePlay = 0x00000001,
+ UOP_FLAG_ChapterSearchOrPlay = 0x00000002,
+ UOP_FLAG_TitlePlay = 0x00000004,
+ UOP_FLAG_Stop = 0x00000008,
+ UOP_FLAG_GoUp = 0x00000010,
+ UOP_FLAG_TimeOrChapterSearch = 0x00000020,
+ UOP_FLAG_PrevOrTopPGSearch = 0x00000040,
+ UOP_FLAG_NextPGSearch = 0x00000080,
+ UOP_FLAG_ForwardScan = 0x00000100,
+ UOP_FLAG_BackwardScan = 0x00000200,
+ UOP_FLAG_TitleMenuCall = 0x00000400,
+ UOP_FLAG_RootMenuCall = 0x00000800,
+ UOP_FLAG_SubPicMenuCall = 0x00001000,
+ UOP_FLAG_AudioMenuCall = 0x00002000,
+ UOP_FLAG_AngleMenuCall = 0x00004000,
+ UOP_FLAG_ChapterMenuCall = 0x00008000,
+ UOP_FLAG_Resume = 0x00010000,
+ UOP_FLAG_ButtonSelectOrActivate = 0x00020000,
+ UOP_FLAG_StillOff = 0x00040000,
+ UOP_FLAG_PauseOn = 0x00080000,
+ UOP_FLAG_AudioStreamChange = 0x00100000,
+ UOP_FLAG_SubPicStreamChange = 0x00200000,
+ UOP_FLAG_AngleChange = 0x00400000,
+ UOP_FLAG_KaraokeAudioPresModeChange = 0x00800000,
+ UOP_FLAG_VideoPresModeChange = 0x01000000
+} DVDUOP_t;
+
+/* Parental Level */
+typedef enum {
+ DVD_PARENTAL_LEVEL_1 = 1,
+ DVD_PARENTAL_LEVEL_2 = 2,
+ DVD_PARENTAL_LEVEL_3 = 3,
+ DVD_PARENTAL_LEVEL_4 = 4,
+ DVD_PARENTAL_LEVEL_5 = 5,
+ DVD_PARENTAL_LEVEL_6 = 6,
+ DVD_PARENTAL_LEVEL_7 = 7,
+ DVD_PARENTAL_LEVEL_8 = 8,
+ DVD_PARENTAL_LEVEL_None = 15
+} DVDParentalLevel_t;
+
+/* Language ID (ISO-639 language code) */
+typedef uint16_t DVDLangID_t;
+
+/* Country ID (ISO-3166 country code) */
+typedef uint16_t DVDCountryID_t;
+
+/* Register */
+typedef uint16_t DVDRegister_t;
+typedef enum {
+ DVDFalse = 0,
+ DVDTrue = 1
+} DVDBool_t;
+typedef DVDRegister_t DVDGPRMArray_t[16];
+typedef DVDRegister_t DVDSPRMArray_t[24];
+
+/* Navigation */
+typedef int DVDStream_t;
+typedef int DVDPTT_t;
+typedef int DVDTitle_t;
+
+/* Angle number (1-9 or default?) */
+typedef int DVDAngle_t;
+
+/* Timecode */
+typedef struct {
+ uint8_t Hours;
+ uint8_t Minutes;
+ uint8_t Seconds;
+ uint8_t Frames;
+} DVDTimecode_t;
+
+/* Subpicture stream number (0-31,62,63) */
+typedef int DVDSubpictureStream_t;
+
+/* Audio stream number (0-7, 15(none)) */
+typedef int DVDAudioStream_t;
+
+/* The audio application mode */
+typedef enum {
+ DVD_AUDIO_APP_MODE_None = 0,
+ DVD_AUDIO_APP_MODE_Karaoke = 1,
+ DVD_AUDIO_APP_MODE_Surround = 2,
+ DVD_AUDIO_APP_MODE_Other = 3
+} DVDAudioAppMode_t;
+
+/* Audio language extension */
+typedef enum {
+ DVD_AUDIO_LANG_EXT_NotSpecified = 0,
+ DVD_AUDIO_LANG_EXT_NormalCaptions = 1,
+ DVD_AUDIO_LANG_EXT_VisuallyImpaired = 2,
+ DVD_AUDIO_LANG_EXT_DirectorsComments1 = 3,
+ DVD_AUDIO_LANG_EXT_DirectorsComments2 = 4
+} DVDAudioLangExt_t;
+
+/* Subpicture language extension */
+typedef enum {
+ DVD_SUBPICTURE_LANG_EXT_NotSpecified = 0,
+ DVD_SUBPICTURE_LANG_EXT_NormalCaptions = 1,
+ DVD_SUBPICTURE_LANG_EXT_BigCaptions = 2,
+ DVD_SUBPICTURE_LANG_EXT_ChildrensCaptions = 3,
+ DVD_SUBPICTURE_LANG_EXT_NormalCC = 5,
+ DVD_SUBPICTURE_LANG_EXT_BigCC = 6,
+ DVD_SUBPICTURE_LANG_EXT_ChildrensCC = 7,
+ DVD_SUBPICTURE_LANG_EXT_Forced = 9,
+ DVD_SUBPICTURE_LANG_EXT_NormalDirectorsComments = 13,
+ DVD_SUBPICTURE_LANG_EXT_BigDirectorsComments = 14,
+ DVD_SUBPICTURE_LANG_EXT_ChildrensDirectorsComments = 15,
+} DVDSubpictureLangExt_t;
+
+/* Karaoke Downmix mode */
+typedef enum {
+ DVD_KARAOKE_DOWNMIX_0to0 = 0x0001,
+ DVD_KARAOKE_DOWNMIX_1to0 = 0x0002,
+ DVD_KARAOKE_DOWNMIX_2to0 = 0x0004,
+ DVD_KARAOKE_DOWNMIX_3to0 = 0x0008,
+ DVD_KARAOKE_DOWNMIX_4to0 = 0x0010,
+ DVD_KARAOKE_DOWNMIX_Lto0 = 0x0020,
+ DVD_KARAOKE_DOWNMIX_Rto0 = 0x0040,
+ DVD_KARAOKE_DOWNMIX_0to1 = 0x0100,
+ DVD_KARAOKE_DOWNMIX_1to1 = 0x0200,
+ DVD_KARAOKE_DOWNMIX_2to1 = 0x0400,
+ DVD_KARAOKE_DOWNMIX_3to1 = 0x0800,
+ DVD_KARAOKE_DOWNMIX_4to1 = 0x1000,
+ DVD_KARAOKE_DOWNMIX_Lto1 = 0x2000,
+ DVD_KARAOKE_DOWNMIX_Rto1 = 0x4000
+} DVDKaraokeDownmix_t;
+typedef int DVDKaraokeDownmixMask_t;
+
+/* Display mode */
+typedef enum {
+ DVD_DISPLAY_MODE_ContentDefault = 0,
+ DVD_DISPLAY_MODE_16x9 = 1,
+ DVD_DISPLAY_MODE_4x3PanScan = 2,
+ DVD_DISPLAY_MODE_4x3Letterboxed = 3
+} DVDDisplayMode_t;
+
+/* Audio attributes */
+typedef struct {
+ DVDAudioAppMode_t AppMode;
+ DVDAudioFormat_t AudioFormat;
+ DVDLangID_t Language;
+ DVDAudioLangExt_t LanguageExtension;
+ DVDBool_t HasMultichannelInfo;
+ DVDAudioSampleFreq_t SampleFrequency;
+ DVDAudioSampleQuant_t SampleQuantization;
+ DVDChannelNumber_t NumberOfChannels;
+} DVDAudioAttributes_t;
+typedef int DVDAudioSampleFreq_t;
+typedef int DVDAudioSampleQuant_t;
+typedef int DVDChannelNumber_t;
+
+/* Subpicture attributes */
+typedef enum {
+ DVD_SUBPICTURE_TYPE_NotSpecified = 0,
+ DVD_SUBPICTURE_TYPE_Language = 1,
+ DVD_SUBPICTURE_TYPE_Other = 2
+} DVDSubpictureType_t;
+typedef enum {
+ DVD_SUBPICTURE_CODING_RunLength = 0,
+ DVD_SUBPICTURE_CODING_Extended = 1,
+ DVD_SUBPICTURE_CODING_Other = 2
+} DVDSubpictureCoding_t;
+typedef struct {
+ DVDSubpictureType_t Type;
+ DVDSubpictureCoding_t CodingMode;
+ DVDLangID_t Language;
+ DVDSubpictureLangExt_t LanguageExtension;
+} DVDSubpictureAttributes_t;
+
+/* Video attributes */
+typedef struct {
+ DVDBool_t PanscanPermitted;
+ DVDBool_t LetterboxPermitted;
+ int AspectX;
+ int AspectY;
+ int FrameRate;
+ int FrameHeight;
+ DVDVideoCompression_t Compression;
+ DVDBool_t Line21Field1InGop;
+ DVDBool_t Line21Field2InGop;
+ int more_to_come;
+} DVDVideoAttributes_t;
+typedef int DVDVideoCompression_t;
+
+#endif
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav.h
new file mode 100644
index 0000000..63d501b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav.h
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ *
+ * This file is part of libdvdnav, a DVD navigation library.
+ *
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libdvdnav; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This is the main header file applications should include if they want
+ * to access dvdnav functionality.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dvd_reader.h"
+#include "dvd_types.h"
+#include "dvdnav_events.h"
+#include "nav_types.h"
+#include "version.h"
+
+#include <stdarg.h>
+
+ /*********************************************************************
+ * dvdnav data types *
+ *********************************************************************/
+
+ /*
+ * Opaque data-type can be viewed as a 'DVD handle'. You should get
+ * a pointer to a dvdnav_t from the dvdnav_open() function.
+ * Never call free() on the pointer, you have to give it back with
+ * dvdnav_close().
+ */
+ typedef struct dvdnav_s dvdnav_t;
+
+ /* Status as reported by most of libdvdnav's functions */
+ typedef int32_t dvdnav_status_t;
+
+ typedef dvd_reader_stream_cb dvdnav_stream_cb;
+
+/*
+ * Unless otherwise stated, all functions return DVDNAV_STATUS_OK if
+ * they succeeded, otherwise DVDNAV_STATUS_ERR is returned and the error may
+ * be obtained by calling dvdnav_err_to_string().
+ */
+#define DVDNAV_STATUS_ERR 0
+#define DVDNAV_STATUS_OK 1
+
+/*********************************************************************
+ * initialisation & housekeeping functions *
+ *********************************************************************/
+
+/*
+ * Logger callback definition
+ */
+typedef enum
+{
+ DVDNAV_LOGGER_LEVEL_INFO,
+ DVDNAV_LOGGER_LEVEL_ERROR,
+ DVDNAV_LOGGER_LEVEL_WARN,
+ DVDNAV_LOGGER_LEVEL_DEBUG,
+} dvdnav_logger_level_t;
+
+typedef struct
+{
+ void (*pf_log)(void*, dvdnav_logger_level_t, const char*, va_list);
+} dvdnav_logger_cb;
+
+/*
+ * These functions allow you to open a DVD device and associate it
+ * with a dvdnav_t.
+ */
+
+/*
+ * Attempts to open the DVD drive at the specified path or using external
+ * seek/read functions (dvdnav_open_stream) and pre-cache the CSS-keys.
+ * libdvdread is used to access the DVD, so any source supported by libdvdread
+ * can be given with "path" or "stream_cb". Currently, using dvdnav_open,
+ * libdvdread can access : DVD drives, DVD image files, DVD file-by-file
+ * copies. Using dvdnav_open_stream, libdvdread can access any kind of DVD
+ * storage via custom implementation of seek/read functions.
+ *
+ * The resulting dvdnav_t handle will be written to *dest.
+ */
+dvdnav_status_t dvdnav_open(dvdnav_t **dest, const char *path);
+dvdnav_status_t dvdnav_open_stream(dvdnav_t** dest, void* priv, dvdnav_stream_cb* stream_cb);
+
+dvdnav_status_t dvdnav_open2(dvdnav_t** dest, void*, const dvdnav_logger_cb*, const char* path);
+dvdnav_status_t dvdnav_open_stream2(dvdnav_t** dest,
+ void* priv,
+ const dvdnav_logger_cb*,
+ dvdnav_stream_cb* stream_cb);
+
+dvdnav_status_t dvdnav_dup(dvdnav_t** dest, dvdnav_t* src);
+dvdnav_status_t dvdnav_free_dup(dvdnav_t* _this);
+
+/*
+ * Closes a dvdnav_t previously opened with dvdnav_open(), freeing any
+ * memory associated with it.
+ */
+dvdnav_status_t dvdnav_close(dvdnav_t *self);
+
+/*
+ * Resets the DVD virtual machine and cache buffers.
+ */
+dvdnav_status_t dvdnav_reset(dvdnav_t *self);
+
+/*
+ * Fills a pointer with a value pointing to a string describing
+ * the path associated with an open dvdnav_t. It assigns *path to NULL
+ * on error.
+ */
+dvdnav_status_t dvdnav_path(dvdnav_t *self, const char **path);
+
+/*
+ * Returns a human-readable string describing the last error.
+ */
+const char* dvdnav_err_to_string(dvdnav_t *self);
+
+const char* dvdnav_version(void);
+
+/*********************************************************************
+ * changing and reading DVD player characteristics *
+ *********************************************************************/
+
+/*
+ * These functions allow you to manipulate the various global characteristics
+ * of the DVD playback engine.
+ */
+
+/*
+ * Sets the region mask (bit 0 set implies region 1, bit 1 set implies
+ * region 2, etc) of the virtual machine. Generally you will only need to set
+ * this if you are playing RCE discs which query the virtual machine as to its
+ * region setting.
+ *
+ * This has _nothing_ to do with the region setting of the DVD drive.
+ */
+dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int32_t region_mask);
+
+/*
+ * Returns the region mask (bit 0 set implies region 1, bit 1 set implies
+ * region 2, etc) of the virtual machine.
+ *
+ * This has _nothing_ to do with the region setting of the DVD drive.
+ */
+dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *self, int32_t *region_mask);
+
+/*
+ * Specify whether read-ahead caching should be used. You may not want this if your
+ * decoding engine does its own buffering.
+ *
+ * The default read-ahead cache does not use an additional thread for the reading
+ * (see read_cache.c for a threaded cache, but note that this code is currently
+ * unmaintained). It prebuffers on VOBU level by reading ahead several buffers
+ * on every read request. The speed of this prebuffering has been optimized to
+ * also work on slow DVD drives.
+ *
+ * If in addition you want to prevent memcpy's to improve performance, have a look
+ * at dvdnav_get_next_cache_block().
+ */
+dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int32_t read_ahead_flag);
+
+/*
+ * Query whether read-ahead caching/buffering will be used.
+ */
+dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *self, int32_t *read_ahead_flag);
+
+/*
+ * Specify whether the positioning works PGC or PG based.
+ * Programs (PGs) on DVDs are similar to Chapters and a program chain (PGC)
+ * usually covers a whole feature. This affects the behaviour of the
+ * functions dvdnav_get_position() and dvdnav_sector_search(). See there.
+ * Default is PG based positioning.
+ */
+dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *self, int32_t pgc_based_flag);
+
+/*
+ * Query whether positioning is PG or PGC based.
+ */
+dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *self, int32_t *pgc_based_flag);
+
+
+/*********************************************************************
+ * reading data *
+ *********************************************************************/
+
+/*
+ * These functions are used to poll the playback engine and actually get data
+ * off the DVD.
+ */
+
+/*
+ * Attempts to get the next block off the DVD and copies it into the buffer 'buf'.
+ * If there is any special actions that may need to be performed, the value
+ * pointed to by 'event' gets set accordingly.
+ *
+ * If 'event' is DVDNAV_BLOCK_OK then 'buf' is filled with the next block
+ * (note that means it has to be at /least/ 2048 bytes big). 'len' is
+ * then set to 2048.
+ *
+ * Otherwise, buf is filled with an appropriate event structure and
+ * len is set to the length of that structure.
+ *
+ * See the dvdnav_events.h header for information on the various events.
+ */
+dvdnav_status_t dvdnav_get_next_block(dvdnav_t* self, uint8_t* buf, int32_t* event, int32_t* len);
+
+/*
+ * This basically does the same as dvdnav_get_next_block. The only difference is
+ * that it avoids a memcopy, when the requested block was found in the cache.
+ * In such a case (cache hit) this function will return a different pointer than
+ * the one handed in, pointing directly into the relevant block in the cache.
+ * Those pointers must _never_ be freed but instead returned to the library via
+ * dvdnav_free_cache_block().
+ */
+dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t* self,
+ uint8_t** buf,
+ int32_t* event,
+ int32_t* len);
+
+/*
+ * All buffers which came from the internal cache (when dvdnav_get_next_cache_block()
+ * returned a buffer different from the one handed in) have to be freed with this
+ * function. Although handing in other buffers not from the cache doesn't cause any harm.
+ */
+dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf);
+
+/*
+ * If we are currently in a still-frame this function skips it.
+ *
+ * See also the DVDNAV_STILL_FRAME event.
+ */
+dvdnav_status_t dvdnav_still_skip(dvdnav_t *self);
+
+/*
+ * If we are currently in WAIT state, that is: the application is required to
+ * wait for its fifos to become empty, calling this signals libdvdnav that this
+ * is achieved and that it can continue.
+ *
+ * See also the DVDNAV_WAIT event.
+ */
+dvdnav_status_t dvdnav_wait_skip(dvdnav_t *self);
+
+/*
+ * Returns the still time from the currently playing cell.
+ * The still time is given in seconds with 0xff meaning an indefinite still.
+ *
+ * This function can be used to detect still frames before they are reached.
+ * Some players might need this to prepare for a frame to be shown for a
+ * longer time than usual.
+ */
+uint32_t dvdnav_get_next_still_flag(dvdnav_t *self);
+
+/*
+ * Stops playback. The next event obtained with one of the get_next_block
+ * functions will be a DVDNAV_STOP event.
+ *
+ * It is not required to call this before dvdnav_close().
+ */
+dvdnav_status_t dvdnav_stop(dvdnav_t *self);
+
+/*
+ * Returns the region mask (bit 0 set implies region 1, bit 1 set implies
+ * region 2, etc) reported by the dvd disc being played.
+ *
+ * Note this has no relation with the region setting of the DVD drive.
+ * Old DVD drives (RPC-I) used to delegate most of the RCE handling to the CPU and
+ * will actually call the virtual machine (VM) for its region setting. In those cases,
+ * changing the VM region mask via dvdnav_set_region_mask() will circunvent
+ * the region protection scheme. This is no longer the case with more recent (RPC-II) drives
+ * as RCE is handled internally by the drive firmware.
+ *
+ */
+dvdnav_status_t dvdnav_get_disk_region_mask(dvdnav_t* self, int32_t* region_mask);
+
+/*********************************************************************
+ * title/part navigation *
+ *********************************************************************/
+
+/*
+ * Returns the number of titles on the disk.
+ */
+dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int32_t *titles);
+
+/*
+ * Returns the number of parts within the given title.
+ */
+dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *self, int32_t title, int32_t *parts);
+
+/*
+ * Returns the number of angles for the given title.
+ */
+dvdnav_status_t dvdnav_get_number_of_angles(dvdnav_t* self, int32_t title, int32_t* angles);
+
+/*
+ * Plays the specified title of the DVD from its beginning (that is: part 1).
+ */
+dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int32_t title);
+
+/*
+ * Plays the specified title, starting from the specified part.
+ */
+dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part);
+
+/*
+ * Plays the specified title, starting from the specified program
+ */
+dvdnav_status_t dvdnav_program_play(dvdnav_t *self, int32_t title, int32_t pgcn, int32_t pgn);
+
+/*
+ * Stores in *times an array (that the application *must* free) of
+ * dvdtimes corresponding to the chapter times for the chosen title.
+ * *duration will have the duration of the title
+ * The number of entries in *times is the result of the function.
+ * On error *times is NULL and the output is 0
+ */
+uint32_t dvdnav_describe_title_chapters(dvdnav_t *self, int32_t title, uint64_t **times, uint64_t *duration);
+
+/*
+ * Play the specified amount of parts of the specified title of
+ * the DVD then STOP.
+ *
+ * Currently unimplemented!
+ */
+dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t* self,
+ int32_t title,
+ int32_t part,
+ int32_t parts_to_play);
+
+/*
+ * Play the specified title starting from the specified time.
+ *
+ * Currently unimplemented!
+ */
+dvdnav_status_t dvdnav_time_play(dvdnav_t* self, int32_t title, uint64_t time);
+
+/*
+ * Stop playing the current position and jump to the specified menu.
+ *
+ * See also DVDMenuID_t from libdvdread
+ */
+dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu);
+
+/*
+ * Return the title number and part currently being played.
+ * A title of 0 indicates we are in a menu. In this case, part
+ * is set to the current menu's ID.
+ */
+dvdnav_status_t dvdnav_current_title_info(dvdnav_t* self, int32_t* title, int32_t* part);
+
+/*
+ * Return the title number, pgcn and pgn currently being played.
+ * A title of 0 indicates, we are in a menu.
+ */
+dvdnav_status_t dvdnav_current_title_program(dvdnav_t* self,
+ int32_t* title,
+ int32_t* pgcn,
+ int32_t* pgn);
+
+/*
+ * Return the current position (in blocks) within the current
+ * title and the length (in blocks) of said title.
+ *
+ * Current implementation is wrong and likely to behave unpredictably!
+ * Use is discouraged!
+ */
+dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t* self, uint32_t* pos, uint32_t* len);
+
+/*
+ * This function is only available for compatibility reasons.
+ *
+ * Stop playing the current position and start playback of the current title
+ * from the specified part.
+ */
+dvdnav_status_t dvdnav_part_search(dvdnav_t *self, int32_t part);
+
+
+/*********************************************************************
+ * program chain/program navigation *
+ *********************************************************************/
+
+/*
+ * Stop playing the current position and start playback from the last
+ * VOBU boundary before the given sector. The sector number is not
+ * meant to be an absolute physical DVD sector, but a relative sector
+ * in the current program. This function cannot leave the current
+ * program and will fail if asked to do so.
+ *
+ * If program chain based positioning is enabled
+ * (see dvdnav_set_PGC_positioning_flag()), this will seek to the relative
+ * sector inside the current program chain.
+ *
+ * 'origin' can be one of SEEK_SET, SEEK_CUR, SEEK_END as defined in
+ * fcntl.h.
+ */
+dvdnav_status_t dvdnav_sector_search(dvdnav_t* self, int64_t offset, int32_t origin);
+
+/*
+ returns the current stream time in PTS ticks as reported by the IFO structures
+ divide it by 90000 to get the current play time in seconds
+ */
+int64_t dvdnav_get_current_time(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback of the title
+ * from the specified timecode.
+ *
+ * Currently implemented using interpolation. That interpolation is slightly
+ * inaccurate.
+ */
+dvdnav_status_t dvdnav_time_search(dvdnav_t* self, uint64_t time);
+
+/*
+ * Find the nearest vobu and jump to it
+ *
+ * Alternative to dvdnav_time_search (see full documentation on searching.jump_to_time.readme)
+ * Jumps to the provided PTS (which is defined as time_in_ms * 90). mode means the navigation mode,
+ * currently only the Default (0) is implemented:
+ * 0: Default. Jump to a time which may be either <> time_in_pts_ticks
+ * 1: After. Always jump to a time that is > time_in_pts_ticks
+ * -1: Before. Always jump to a time that is < time_in_pts_ticks
+ */
+dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t* self,
+ uint64_t time_in_pts_ticks,
+ int32_t mode);
+
+/*
+ * Stop playing current position and play the "GoUp"-program chain.
+ * (which generally leads to the title menu or a higher-level menu).
+ */
+dvdnav_status_t dvdnav_go_up(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * previous program (if it exists).
+ */
+dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * first program.
+ */
+dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * next program (if it exists).
+ */
+dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self);
+
+/*
+ * Return the current position (in blocks) within the current
+ * program and the length (in blocks) of current program.
+ *
+ * If program chain based positioning is enabled
+ * (see dvdnav_set_PGC_positioning_flag()), this will return the
+ * relative position in and the length of the current program chain.
+ */
+dvdnav_status_t dvdnav_get_position(dvdnav_t* self, uint32_t* pos, uint32_t* len);
+
+/*********************************************************************
+ * menu highlights *
+ *********************************************************************/
+
+/*
+ * Most functions related to highlights take a NAV PCI packet as a parameter.
+ * While you can get such a packet from libdvdnav, this will result in
+ * errors for players with internal FIFOs because due to the FIFO length,
+ * libdvdnav will be ahead in the stream compared to what the user is
+ * seeing on screen. Therefore, player applications who have a NAV
+ * packet available, which is better in sync with the actual playback,
+ * should always pass this one to these functions.
+ */
+
+/*
+ * Get the currently highlighted button
+ * number (1..36) or 0 if no button is highlighted.
+ */
+dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int32_t *button);
+
+/*
+ * Returns the Presentation Control Information (PCI) structure associated
+ * with the current position.
+ *
+ * Read the general notes above.
+ * See also libdvdreads nav_types.h for definition of pci_t.
+ */
+pci_t* dvdnav_get_current_nav_pci(dvdnav_t *self);
+
+/*
+ * Returns the DSI (data search information) structure associated
+ * with the current position.
+ *
+ * Read the general notes above.
+ * See also libdvdreads nav_types.h for definition of dsi_t.
+ */
+dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *self);
+
+/*
+ * Get the area associated with a certain button.
+ */
+dvdnav_status_t dvdnav_get_highlight_area(pci_t* nav_pci,
+ int32_t button,
+ int32_t mode,
+ dvdnav_highlight_area_t* highlight);
+
+/*
+ * Move button highlight around as suggested by function name (e.g. with arrow keys).
+ */
+dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self, pci_t *pci);
+
+/*
+ * Activate ("press") the currently highlighted button.
+ */
+dvdnav_status_t dvdnav_button_activate(dvdnav_t *self, pci_t *pci);
+
+/*
+ * Highlight a specific button.
+ */
+dvdnav_status_t dvdnav_button_select(dvdnav_t *self, pci_t *pci, int32_t button);
+
+/*
+ * Activate ("press") specified button.
+ */
+dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *self, pci_t *pci, int32_t button);
+
+/*
+ * Activate ("press") a button and execute specified command.
+ */
+dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *self, int32_t button, vm_cmd_t *cmd);
+
+/*
+ * Select button at specified video frame coordinates.
+ */
+dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y);
+
+/*
+ * Activate ("press") button at specified video frame coordinates.
+ */
+dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y);
+
+
+/*********************************************************************
+ * languages *
+ *********************************************************************/
+
+/*
+ * The language codes expected by these functions are two character
+ * codes as defined in ISO639.
+ */
+
+/*
+ * Set which menu language we should use per default.
+ */
+dvdnav_status_t dvdnav_menu_language_select(dvdnav_t* self, char* code);
+
+/*
+ * Set which audio language we should use per default.
+ */
+dvdnav_status_t dvdnav_audio_language_select(dvdnav_t* self, char* code);
+
+/*
+ * Set which spu language we should use per default.
+ */
+dvdnav_status_t dvdnav_spu_language_select(dvdnav_t* self, char* code);
+
+/*********************************************************************
+ * obtaining stream attributes *
+ *********************************************************************/
+
+/*
+ * Return a string describing the title of the DVD.
+ * This is an ID string encoded on the disc by the author. In many cases
+ * this is a descriptive string such as `THE_MATRIX' but sometimes is singularly
+ * uninformative such as `PDVD-011421'. Some DVD authors even forget to set this,
+ * so you may also read the default of the authoring software they used, like
+ * `DVDVolume'.
+ */
+dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, const char **title_str);
+
+/*
+ * Returns a string containing the serial number of the DVD.
+ * This has a max of 15 characters and should be more unique than the
+ * title string.
+ */
+dvdnav_status_t dvdnav_get_serial_string(dvdnav_t *self, const char **serial_str);
+
+/*
+ * Returns the VolumeIdentifier of the disc or NULL if it could
+ * not be obtained. The VolumeIdentifier might be latin-1 encoded
+ * (8bit unicode) null terminated and max 32 bytes (including '\0');
+ * or coded with '0-9','A-Z','_' null terminated and max 33 bytes
+ * (including '\0').
+ * See also dvdnav_get_title_string
+ *
+ * Note: The string is malloc'd so caller has to free() the returned
+ * string when done with it.
+ */
+const char* dvdnav_get_volid_string(dvdnav_t* self);
+
+/*
+ * Get video aspect code.
+ * The aspect code does only change on VTS boundaries.
+ * See the DVDNAV_VTS_CHANGE event.
+ *
+ * 0 -- 4:3, 2 -- 16:9
+ */
+uint8_t dvdnav_get_video_aspect(dvdnav_t *self);
+
+/*
+ * Get video resolution.
+ */
+dvdnav_status_t dvdnav_get_video_resolution(dvdnav_t* self, uint32_t* width, uint32_t* height);
+
+/*
+ * Get video scaling permissions.
+ * The scaling permission does only change on VTS boundaries.
+ * See the DVDNAV_VTS_CHANGE event.
+ *
+ * bit0 set = deny letterboxing, bit1 set = deny pan&scan
+ */
+uint8_t dvdnav_get_video_scale_permission(dvdnav_t *self);
+
+/*
+ * Converts a *logical* audio stream id into language code
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Returns the format of *logical* audio stream 'stream'
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_format(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Returns number of channels in *logical* audio stream 'stream'
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_channels(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Converts a *logical* subpicture stream id into country code
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Converts a *physical* (MPEG) audio stream id into a logical stream number.
+ */
+int8_t dvdnav_get_audio_logical_stream(dvdnav_t *self, uint8_t audio_num);
+
+#define HAVE_GET_AUDIO_ATTR
+/*
+ * Get audio attr
+ */
+dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *self, uint8_t audio_mum, audio_attr_t *audio_attr);
+
+/*
+ * Converts a *physical* (MPEG) subpicture stream id into a logical stream number.
+ */
+int8_t dvdnav_get_spu_logical_stream(dvdnav_t *self, uint8_t subp_num);
+
+#define HAVE_GET_SPU_ATTR
+/*
+ * Get spu attr
+ */
+dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *self, uint8_t audio_mum, subp_attr_t *subp_attr);
+
+/*
+ * Get active audio stream.
+ */
+int8_t dvdnav_get_active_audio_stream(dvdnav_t *self);
+
+/*
+ * Get active spu stream.
+ */
+int8_t dvdnav_get_active_spu_stream(dvdnav_t *self);
+
+/*
+ * Get the set of user operations that are currently prohibited.
+ * There are potentially new restrictions right after
+ * DVDNAV_CHANNEL_HOP and DVDNAV_NAV_PACKET.
+ */
+user_ops_t dvdnav_get_restrictions(dvdnav_t *self);
+
+/*
+ * Returns the number of streams provided its type (e.g. subtitles, audio, etc)
+ */
+int8_t dvdnav_get_number_of_streams(dvdnav_t* self, dvdnav_stream_type_t stream_type);
+
+/*********************************************************************
+ * setting stream attributes *
+ *********************************************************************/
+
+/*
+ * Set the visible (enable) status of the current spu stream
+ * (to enable/disable subtitles)
+ * visibility defines if the spu stream should be enabled/visible (1) or disabled (0)
+ */
+dvdnav_status_t dvdnav_toggle_spu_stream(dvdnav_t* self, uint8_t visibility);
+
+/*
+ * Set the given stream id and stream type as active
+ * stream_num - the physical index of the stream
+ * stream_type - the stream type (audio or subtitles)
+ */
+dvdnav_status_t dvdnav_set_active_stream(dvdnav_t* self,
+ uint8_t stream_num,
+ dvdnav_stream_type_t stream_type);
+
+/*********************************************************************
+ * multiple angles *
+ *********************************************************************/
+
+/*
+ * The libdvdnav library abstracts away the difference between seamless and
+ * non-seamless angles. From the point of view of the programmer you just set the
+ * angle number and all is well in the world. You will always see only the
+ * selected angle coming from the get_next_block functions.
+ *
+ * Note:
+ * It is quite possible that some tremendously strange DVD feature might change the
+ * angle number from under you. Generally you should always view the results from
+ * dvdnav_get_angle_info() as definitive only up to the next time you call
+ * dvdnav_get_next_block().
+ */
+
+/*
+ * Sets the current angle. If you try to follow a non existent angle
+ * the call fails.
+ */
+dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int32_t angle);
+
+/*
+ * Returns the current angle and number of angles present.
+ */
+dvdnav_status_t dvdnav_get_angle_info(dvdnav_t* self,
+ int32_t* current_angle,
+ int32_t* number_of_angles);
+
+/*********************************************************************
+ * domain queries *
+ *********************************************************************/
+
+/*
+ * Are we in the First Play domain?
+ */
+int8_t dvdnav_is_domain_fp(dvdnav_t *self);
+
+/*
+ * Are we in the Video management Menu domain?
+ */
+int8_t dvdnav_is_domain_vmgm(dvdnav_t *self);
+
+/*
+ * Are we in the Video Title Menu domain?
+ */
+int8_t dvdnav_is_domain_vtsm(dvdnav_t *self);
+
+/*
+ * Are we in the Video Title Set domain?
+ */
+int8_t dvdnav_is_domain_vts(dvdnav_t *self);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h
new file mode 100644
index 0000000..970fa6a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ *
+ * This file is part of libdvdnav, a DVD navigation library.
+ *
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libdvdnav; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This header defines events and event types
+ */
+
+#pragma once
+
+/*
+ * DVDNAV_BLOCK_OK
+ *
+ * A regular data block from the DVD has been returned.
+ * This one should be demuxed and decoded for playback.
+ */
+#define DVDNAV_BLOCK_OK 0
+
+/*
+ * DVDNAV_NOP
+ *
+ * Just ignore this.
+ */
+#define DVDNAV_NOP 1
+
+/*
+ * DVDNAV_STILL_FRAME
+ *
+ * We have reached a still frame. The player application should wait
+ * the amount of time specified by the still's length while still handling
+ * user input to make menus and other interactive stills work.
+ * The last delivered frame should be kept showing.
+ * Once the still has timed out, call dvdnav_skip_still().
+ * A length of 0xff means an infinite still which has to be skipped
+ * indirectly by some user interaction.
+ */
+#define DVDNAV_STILL_FRAME 2
+
+typedef struct {
+ /* The length (in seconds) the still frame should be displayed for,
+ * or 0xff if infinite. */
+ int length;
+} dvdnav_still_event_t;
+
+
+/*
+ * DVDNAV_SPU_STREAM_CHANGE
+ *
+ * Inform the SPU decoding/overlaying engine to switch SPU channels.
+ */
+#define DVDNAV_SPU_STREAM_CHANGE 3
+
+typedef struct {
+ /* The physical (MPEG) stream number for widescreen SPU display.
+ * Use this, if you blend the SPU on an anamorphic image before
+ * unsqueezing it. */
+ int physical_wide;
+
+ /* The physical (MPEG) stream number for letterboxed display.
+ * Use this, if you blend the SPU on an anamorphic image after
+ * unsqueezing it. */
+ int physical_letterbox;
+
+ /* The physical (MPEG) stream number for pan&scan display.
+ * Use this, if you blend the SPU on an anamorphic image after
+ * unsqueezing it the pan&scan way. */
+ int physical_pan_scan;
+
+ /* The logical (DVD) stream number. */
+ int logical;
+} dvdnav_spu_stream_change_event_t;
+
+
+/*
+ * DVDNAV_AUDIO_STREAM_CHANGE
+ *
+ * Inform the audio decoder to switch channels.
+ */
+#define DVDNAV_AUDIO_STREAM_CHANGE 4
+
+typedef struct {
+ /* The physical (MPEG) stream number. */
+ int physical;
+
+ /* The logical (DVD) stream number. */
+ int logical;
+} dvdnav_audio_stream_change_event_t;
+
+
+/*
+ * DVDNAV_VTS_CHANGE
+ *
+ * Some status information like video aspect and video scale permissions do
+ * not change inside a VTS. Therefore this event can be used to query such
+ * information only when necessary and update the decoding/displaying
+ * accordingly.
+ */
+#define DVDNAV_VTS_CHANGE 5
+
+typedef struct {
+ int old_vtsN; /* the old VTS number */
+ DVDDomain_t old_domain; /* the old domain */
+ int new_vtsN; /* the new VTS number */
+ DVDDomain_t new_domain; /* the new domain */
+} dvdnav_vts_change_event_t;
+
+
+/*
+ * DVDNAV_CELL_CHANGE
+ *
+ * Some status information like the current Title and Part numbers do not
+ * change inside a cell. Therefore this event can be used to query such
+ * information only when necessary and update the decoding/displaying
+ * accordingly.
+ * Some useful information for accurate time display is also reported
+ * together with this event.
+ */
+#define DVDNAV_CELL_CHANGE 6
+
+typedef struct {
+ int cellN; /* the new cell number */
+ int pgN; /* the current program number */
+ int64_t cell_length; /* the length of the current cell in sectors */
+ int64_t pg_length; /* the length of the current program in sectors */
+ int64_t pgc_length; /* the length of the current program chain in PTS ticks */
+ int64_t cell_start; /* the start offset of the current cell relatively to the PGC in sectors */
+ int64_t pg_start; /* the start offset of the current PG relatively to the PGC in sectors */
+} dvdnav_cell_change_event_t;
+
+
+/*
+ * DVDNAV_NAV_PACKET
+ *
+ * NAV packets are useful for various purposes. They define the button
+ * highlight areas and VM commands of DVD menus, so they should in any
+ * case be sent to the SPU decoder/overlaying engine for the menus to work.
+ * NAV packets also provide a way to detect PTS discontinuities, because
+ * they carry the start and end PTS values for the current VOBU.
+ * (pci.vobu_s_ptm and pci.vobu_e_ptm) Whenever the start PTS of the
+ * current NAV does not match the end PTS of the previous NAV, a PTS
+ * discontinuity has occurred.
+ * NAV packets can also be used for time display, because they are
+ * timestamped relatively to the current Cell.
+ */
+#define DVDNAV_NAV_PACKET 7
+
+/*
+ * DVDNAV_STOP
+ *
+ * Applications should end playback here. A subsequent dvdnav_get_next_block()
+ * call will restart the VM from the beginning of the DVD.
+ */
+#define DVDNAV_STOP 8
+
+/*
+ * DVDNAV_HIGHLIGHT
+ *
+ * The current button highlight changed. Inform the overlaying engine to
+ * highlight a different button. Please note, that at the moment only mode 1
+ * highlights are reported this way. That means, when the button highlight
+ * has been moved around by some function call, you will receive an event
+ * telling you the new button. But when a button gets activated, you have
+ * to handle the mode 2 highlighting (that is some different colour the
+ * button turns to on activation) in your application.
+ */
+#define DVDNAV_HIGHLIGHT 9
+
+typedef struct {
+ /* highlight mode: 0 - hide, 1 - show, 2 - activate, currently always 1 */
+ int display;
+
+ /* FIXME: these fields are currently not set */
+ uint32_t palette; /* The CLUT entries for the highlight palette
+ (4-bits per entry -> 4 entries) */
+ uint16_t sx,sy,ex,ey; /* The start/end x,y positions */
+ uint32_t pts; /* Highlight PTS to match with SPU */
+
+ /* button number for the SPU decoder/overlaying engine */
+ uint32_t buttonN;
+} dvdnav_highlight_event_t;
+
+
+/*
+ * DVDNAV_SPU_CLUT_CHANGE
+ *
+ * Inform the SPU decoder/overlaying engine to update its colour lookup table.
+ * The CLUT is given as 16 uint32_t's in the buffer.
+ */
+#define DVDNAV_SPU_CLUT_CHANGE 10
+
+/*
+ * DVDNAV_HOP_CHANNEL
+ *
+ * A non-seamless operation has been performed. Applications can drop all
+ * their internal fifo's content, which will speed up the response.
+ */
+#define DVDNAV_HOP_CHANNEL 12
+
+/*
+ * DVDNAV_WAIT
+ *
+ * We have reached a point in DVD playback, where timing is critical.
+ * Player application with internal fifos can introduce state
+ * inconsistencies, because libdvdnav is always the fifo's length
+ * ahead in the stream compared to what the application sees.
+ * Such applications should wait until their fifos are empty
+ * when they receive this type of event.
+ * Once this is achieved, call dvdnav_skip_wait().
+ */
+#define DVDNAV_WAIT 13
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h
new file mode 100644
index 0000000..4191b67
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2000, 2001 Björn Englund <d4bjorn@dtek.chalmers.se>,
+ * Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This file is part of libdvdread.
+ *
+ * libdvdread is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdread is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libdvdread; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+//#include <inttypes.h>
+#include "dvd_reader.h"
+
+
+#undef ATTRIBUTE_PACKED
+
+#if defined(__GNUC__)
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__)
+#define ATTRIBUTE_PACKED __attribute__((packed, gcc_struct))
+#else
+#define ATTRIBUTE_PACKED __attribute__((packed))
+#endif
+#define PRAGMA_PACK 0
+#endif
+#endif
+
+#if !defined(ATTRIBUTE_PACKED)
+#define ATTRIBUTE_PACKED
+#define PRAGMA_PACK 1
+#endif
+
+#if PRAGMA_PACK
+#pragma pack(1)
+#endif
+
+
+/**
+ * Common
+ *
+ * The following structures are used in both the VMGI and VTSI.
+ */
+
+
+/**
+ * DVD Time Information.
+ */
+typedef struct {
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ uint8_t frame_u; /* The two high bits are the frame rate. */
+} ATTRIBUTE_PACKED dvd_time_t;
+
+/**
+ * Type to store per-command data.
+ */
+typedef struct {
+ uint8_t bytes[8];
+} ATTRIBUTE_PACKED vm_cmd_t;
+#define COMMAND_DATA_SIZE 8U
+
+
+/**
+ * Video Attributes.
+ */
+typedef struct {
+ unsigned char mpeg_version : 2;
+ unsigned char video_format : 2;
+ unsigned char display_aspect_ratio : 2;
+ unsigned char permitted_df : 2;
+
+ unsigned char line21_cc_1 : 1;
+ unsigned char line21_cc_2 : 1;
+ unsigned char unknown1 : 1;
+ unsigned char bit_rate : 1;
+
+ unsigned char picture_size : 2;
+ unsigned char letterboxed : 1;
+ unsigned char film_mode : 1;
+} ATTRIBUTE_PACKED video_attr_t;
+
+/**
+ * Audio Attributes.
+ */
+typedef struct {
+ unsigned char audio_format : 3;
+ unsigned char multichannel_extension : 1;
+ unsigned char lang_type : 2;
+ unsigned char application_mode : 2;
+
+ unsigned char quantization : 2;
+ unsigned char sample_frequency : 2;
+ unsigned char unknown1 : 1;
+ unsigned char channels : 3;
+ uint16_t lang_code;
+ uint8_t lang_extension;
+ uint8_t code_extension;
+ uint8_t unknown3;
+ union {
+ struct ATTRIBUTE_PACKED {
+ unsigned char unknown4 : 1;
+ unsigned char channel_assignment : 3;
+ unsigned char version : 2;
+ unsigned char mc_intro : 1; /* probably 0: true, 1:false */
+ unsigned char mode : 1; /* Karaoke mode 0: solo 1: duet */
+ } karaoke;
+ struct ATTRIBUTE_PACKED {
+ unsigned char unknown5 : 4;
+ unsigned char dolby_encoded : 1; /* suitable for surround decoding */
+ unsigned char unknown6 : 3;
+ } surround;
+ } ATTRIBUTE_PACKED app_info;
+} ATTRIBUTE_PACKED audio_attr_t;
+
+
+/**
+ * MultiChannel Extension
+ */
+typedef struct {
+ unsigned char zero1 : 7;
+ unsigned char ach0_gme : 1;
+
+ unsigned char zero2 : 7;
+ unsigned char ach1_gme : 1;
+
+ unsigned char zero3 : 4;
+ unsigned char ach2_gv1e : 1;
+ unsigned char ach2_gv2e : 1;
+ unsigned char ach2_gm1e : 1;
+ unsigned char ach2_gm2e : 1;
+
+ unsigned char zero4 : 4;
+ unsigned char ach3_gv1e : 1;
+ unsigned char ach3_gv2e : 1;
+ unsigned char ach3_gmAe : 1;
+ unsigned char ach3_se2e : 1;
+
+ unsigned char zero5 : 4;
+ unsigned char ach4_gv1e : 1;
+ unsigned char ach4_gv2e : 1;
+ unsigned char ach4_gmBe : 1;
+ unsigned char ach4_seBe : 1;
+ uint8_t zero6[19];
+} ATTRIBUTE_PACKED multichannel_ext_t;
+
+
+/**
+ * Subpicture Attributes.
+ */
+typedef struct {
+ /*
+ * type: 0 not specified
+ * 1 language
+ * 2 other
+ * coding mode: 0 run length
+ * 1 extended
+ * 2 other
+ * language: indicates language if type == 1
+ * lang extension: if type == 1 contains the lang extension
+ */
+ unsigned char code_mode : 3;
+ unsigned char zero1 : 3;
+ unsigned char type : 2;
+ uint8_t zero2;
+ uint16_t lang_code;
+ uint8_t lang_extension;
+ uint8_t code_extension;
+} ATTRIBUTE_PACKED subp_attr_t;
+
+
+
+/**
+ * PGC Command Table.
+ */
+typedef struct {
+ uint16_t nr_of_pre;
+ uint16_t nr_of_post;
+ uint16_t nr_of_cell;
+ uint16_t last_byte;
+ vm_cmd_t *pre_cmds;
+ vm_cmd_t *post_cmds;
+ vm_cmd_t *cell_cmds;
+} ATTRIBUTE_PACKED pgc_command_tbl_t;
+#define PGC_COMMAND_TBL_SIZE 8U
+
+/**
+ * PGC Program Map
+ */
+typedef uint8_t pgc_program_map_t;
+
+/**
+ * Cell Playback Information.
+ */
+typedef struct {
+ unsigned char block_mode : 2;
+ unsigned char block_type : 2;
+ unsigned char seamless_play : 1;
+ unsigned char interleaved : 1;
+ unsigned char stc_discontinuity : 1;
+ unsigned char seamless_angle : 1;
+ unsigned char zero_1 : 1;
+ unsigned char playback_mode : 1; /**< When set, enter StillMode after each VOBU */
+ unsigned char restricted : 1; /**< ?? drop out of fastforward? */
+ unsigned char cell_type : 5; /** for karaoke, reserved otherwise */
+ uint8_t still_time;
+ uint8_t cell_cmd_nr;
+ dvd_time_t playback_time;
+ uint32_t first_sector;
+ uint32_t first_ilvu_end_sector;
+ uint32_t last_vobu_start_sector;
+ uint32_t last_sector;
+} ATTRIBUTE_PACKED cell_playback_t;
+
+#define BLOCK_TYPE_NONE 0x0
+#define BLOCK_TYPE_ANGLE_BLOCK 0x1
+
+#define BLOCK_MODE_NOT_IN_BLOCK 0x0
+#define BLOCK_MODE_FIRST_CELL 0x1
+#define BLOCK_MODE_IN_BLOCK 0x2
+#define BLOCK_MODE_LAST_CELL 0x3
+
+/**
+ * Cell Position Information.
+ */
+typedef struct {
+ uint16_t vob_id_nr;
+ uint8_t zero_1;
+ uint8_t cell_nr;
+} ATTRIBUTE_PACKED cell_position_t;
+
+/**
+ * User Operations.
+ */
+typedef struct {
+ unsigned char zero : 7; /* 25-31 */
+ unsigned char video_pres_mode_change : 1; /* 24 */
+
+ unsigned char karaoke_audio_pres_mode_change : 1; /* 23 */
+ unsigned char angle_change : 1;
+ unsigned char subpic_stream_change : 1;
+ unsigned char audio_stream_change : 1;
+ unsigned char pause_on : 1;
+ unsigned char still_off : 1;
+ unsigned char button_select_or_activate : 1;
+ unsigned char resume : 1; /* 16 */
+
+ unsigned char chapter_menu_call : 1; /* 15 */
+ unsigned char angle_menu_call : 1;
+ unsigned char audio_menu_call : 1;
+ unsigned char subpic_menu_call : 1;
+ unsigned char root_menu_call : 1;
+ unsigned char title_menu_call : 1;
+ unsigned char backward_scan : 1;
+ unsigned char forward_scan : 1; /* 8 */
+
+ unsigned char next_pg_search : 1; /* 7 */
+ unsigned char prev_or_top_pg_search : 1;
+ unsigned char time_or_chapter_search : 1;
+ unsigned char go_up : 1;
+ unsigned char stop : 1;
+ unsigned char title_play : 1;
+ unsigned char chapter_search_or_play : 1;
+ unsigned char title_or_time_play : 1; /* 0 */
+} ATTRIBUTE_PACKED user_ops_t;
+
+/**
+ * Program Chain Information.
+ */
+typedef struct {
+ uint16_t zero_1;
+ uint8_t nr_of_programs;
+ uint8_t nr_of_cells;
+ dvd_time_t playback_time;
+ user_ops_t prohibited_ops;
+ uint16_t audio_control[8]; /* New type? */
+ uint32_t subp_control[32]; /* New type? */
+ uint16_t next_pgc_nr;
+ uint16_t prev_pgc_nr;
+ uint16_t goup_pgc_nr;
+ uint8_t pg_playback_mode;
+ uint8_t still_time;
+ uint32_t palette[16]; /* New type struct {zero_1, Y, Cr, Cb} ? */
+ uint16_t command_tbl_offset;
+ uint16_t program_map_offset;
+ uint16_t cell_playback_offset;
+ uint16_t cell_position_offset;
+ pgc_command_tbl_t *command_tbl;
+ pgc_program_map_t *program_map;
+ cell_playback_t *cell_playback;
+ cell_position_t *cell_position;
+ int ref_count;
+} ATTRIBUTE_PACKED pgc_t;
+#define PGC_SIZE 236U
+
+/**
+ * Program Chain Information Search Pointer.
+ */
+typedef struct {
+ uint8_t entry_id;
+ unsigned char block_mode : 2;
+ unsigned char block_type : 2;
+ unsigned char zero_1 : 4;
+ uint16_t ptl_id_mask;
+ uint32_t pgc_start_byte;
+ pgc_t *pgc;
+} ATTRIBUTE_PACKED pgci_srp_t;
+#define PGCI_SRP_SIZE 8U
+
+/**
+ * Program Chain Information Table.
+ */
+typedef struct {
+ uint16_t nr_of_pgci_srp;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ pgci_srp_t *pgci_srp;
+ int ref_count;
+} ATTRIBUTE_PACKED pgcit_t;
+#define PGCIT_SIZE 8U
+
+/**
+ * Menu PGCI Language Unit.
+ */
+typedef struct {
+ uint16_t lang_code;
+ uint8_t lang_extension;
+ uint8_t exists;
+ uint32_t lang_start_byte;
+ pgcit_t *pgcit;
+} ATTRIBUTE_PACKED pgci_lu_t;
+#define PGCI_LU_SIZE 8U
+
+/**
+ * Menu PGCI Unit Table.
+ */
+typedef struct {
+ uint16_t nr_of_lus;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ pgci_lu_t *lu;
+} ATTRIBUTE_PACKED pgci_ut_t;
+#define PGCI_UT_SIZE 8U
+
+/**
+ * Cell Address Information.
+ */
+typedef struct {
+ uint16_t vob_id;
+ uint8_t cell_id;
+ uint8_t zero_1;
+ uint32_t start_sector;
+ uint32_t last_sector;
+} ATTRIBUTE_PACKED cell_adr_t;
+
+/**
+ * Cell Address Table.
+ */
+typedef struct {
+ uint16_t nr_of_vobs; /* VOBs */
+ uint16_t zero_1;
+ uint32_t last_byte;
+ cell_adr_t *cell_adr_table; /* No explicit size given. */
+} ATTRIBUTE_PACKED c_adt_t;
+#define C_ADT_SIZE 8U
+
+/**
+ * VOBU Address Map.
+ */
+typedef struct {
+ uint32_t last_byte;
+ uint32_t *vobu_start_sectors;
+} ATTRIBUTE_PACKED vobu_admap_t;
+#define VOBU_ADMAP_SIZE 4U
+
+
+
+
+/**
+ * VMGI
+ *
+ * The following structures relate to the Video Manager.
+ */
+
+/**
+ * Video Manager Information Management Table.
+ */
+typedef struct {
+ char vmg_identifier[12];
+ uint32_t vmg_last_sector;
+ uint8_t zero_1[12];
+ uint32_t vmgi_last_sector;
+ uint8_t zero_2;
+ uint8_t specification_version;
+ uint32_t vmg_category;
+ uint16_t vmg_nr_of_volumes;
+ uint16_t vmg_this_volume_nr;
+ uint8_t disc_side;
+ uint8_t zero_3[19];
+ uint16_t vmg_nr_of_title_sets; /* Number of VTSs. */
+ char provider_identifier[32];
+ uint64_t vmg_pos_code;
+ uint8_t zero_4[24];
+ uint32_t vmgi_last_byte;
+ uint32_t first_play_pgc;
+ uint8_t zero_5[56];
+ uint32_t vmgm_vobs; /* sector */
+ uint32_t tt_srpt; /* sector */
+ uint32_t vmgm_pgci_ut; /* sector */
+ uint32_t ptl_mait; /* sector */
+ uint32_t vts_atrt; /* sector */
+ uint32_t txtdt_mgi; /* sector */
+ uint32_t vmgm_c_adt; /* sector */
+ uint32_t vmgm_vobu_admap; /* sector */
+ uint8_t zero_6[32];
+
+ video_attr_t vmgm_video_attr;
+ uint8_t zero_7;
+ uint8_t nr_of_vmgm_audio_streams; /* should be 0 or 1 */
+ audio_attr_t vmgm_audio_attr;
+ audio_attr_t zero_8[7];
+ uint8_t zero_9[17];
+ uint8_t nr_of_vmgm_subp_streams; /* should be 0 or 1 */
+ subp_attr_t vmgm_subp_attr;
+ subp_attr_t zero_10[27]; /* XXX: how much 'padding' here? */
+} ATTRIBUTE_PACKED vmgi_mat_t;
+
+typedef struct {
+ unsigned char zero_1 : 1;
+ unsigned char multi_or_random_pgc_title : 1; /* 0: one sequential pgc title */
+ unsigned char jlc_exists_in_cell_cmd : 1;
+ unsigned char jlc_exists_in_prepost_cmd : 1;
+ unsigned char jlc_exists_in_button_cmd : 1;
+ unsigned char jlc_exists_in_tt_dom : 1;
+ unsigned char chapter_search_or_play : 1; /* UOP 1 */
+ unsigned char title_or_time_play : 1; /* UOP 0 */
+} ATTRIBUTE_PACKED playback_type_t;
+
+/**
+ * Title Information.
+ */
+typedef struct {
+ playback_type_t pb_ty;
+ uint8_t nr_of_angles;
+ uint16_t nr_of_ptts;
+ uint16_t parental_id;
+ uint8_t title_set_nr;
+ uint8_t vts_ttn;
+ uint32_t title_set_sector;
+} ATTRIBUTE_PACKED title_info_t;
+
+/**
+ * PartOfTitle Search Pointer Table.
+ */
+typedef struct {
+ uint16_t nr_of_srpts;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ title_info_t *title;
+} ATTRIBUTE_PACKED tt_srpt_t;
+#define TT_SRPT_SIZE 8U
+
+
+/**
+ * Parental Management Information Unit Table.
+ * Level 1 (US: G), ..., 7 (US: NC-17), 8
+ */
+#define PTL_MAIT_NUM_LEVEL 8
+typedef uint16_t pf_level_t[PTL_MAIT_NUM_LEVEL];
+
+/**
+ * Parental Management Information Unit Table.
+ */
+typedef struct {
+ uint16_t country_code;
+ uint16_t zero_1;
+ uint16_t pf_ptl_mai_start_byte;
+ uint16_t zero_2;
+ pf_level_t *pf_ptl_mai; /* table of (nr_of_vtss + 1), video_ts is first */
+} ATTRIBUTE_PACKED ptl_mait_country_t;
+#define PTL_MAIT_COUNTRY_SIZE 8U
+
+/**
+ * Parental Management Information Table.
+ */
+typedef struct {
+ uint16_t nr_of_countries;
+ uint16_t nr_of_vtss;
+ uint32_t last_byte;
+ ptl_mait_country_t *countries;
+} ATTRIBUTE_PACKED ptl_mait_t;
+#define PTL_MAIT_SIZE 8U
+
+/**
+ * Video Title Set Attributes.
+ */
+typedef struct {
+ uint32_t last_byte;
+ uint32_t vts_cat;
+
+ video_attr_t vtsm_vobs_attr;
+ uint8_t zero_1;
+ uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */
+ audio_attr_t vtsm_audio_attr;
+ audio_attr_t zero_2[7];
+ uint8_t zero_3[16];
+ uint8_t zero_4;
+ uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */
+ subp_attr_t vtsm_subp_attr;
+ subp_attr_t zero_5[27];
+
+ uint8_t zero_6[2];
+
+ video_attr_t vtstt_vobs_video_attr;
+ uint8_t zero_7;
+ uint8_t nr_of_vtstt_audio_streams;
+ audio_attr_t vtstt_audio_attr[8];
+ uint8_t zero_8[16];
+ uint8_t zero_9;
+ uint8_t nr_of_vtstt_subp_streams;
+ subp_attr_t vtstt_subp_attr[32];
+} ATTRIBUTE_PACKED vts_attributes_t;
+#define VTS_ATTRIBUTES_SIZE 542U
+#define VTS_ATTRIBUTES_MIN_SIZE 356U
+
+/**
+ * Video Title Set Attribute Table.
+ */
+typedef struct {
+ uint16_t nr_of_vtss;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ vts_attributes_t *vts;
+ uint32_t *vts_atrt_offsets; /* offsets table for each vts_attributes */
+} ATTRIBUTE_PACKED vts_atrt_t;
+#define VTS_ATRT_SIZE 8U
+
+/**
+ * Text Data. (Incomplete)
+ */
+typedef struct {
+ uint32_t last_byte; /* offsets are relative here */
+ uint16_t offsets[100]; /* == nr_of_srpts + 1 (first is disc title) */
+#if 0
+ uint16_t unknown; /* 0x48 ?? 0x48 words (16bit) info following */
+ uint16_t zero_1;
+
+ uint8_t type_of_info; /* ?? 01 == disc, 02 == Title, 04 == Title part */
+ uint8_t unknown1;
+ uint8_t unknown2;
+ uint8_t unknown3;
+ uint8_t unknown4; /* ?? always 0x30 language?, text format? */
+ uint8_t unknown5;
+ uint16_t offset; /* from first */
+
+ char text[12]; /* ended by 0x09 */
+#endif
+} ATTRIBUTE_PACKED txtdt_t;
+
+/**
+ * Text Data Language Unit. (Incomplete)
+ */
+typedef struct {
+ uint16_t lang_code;
+ uint8_t zero_1;
+ uint8_t
+ char_set; /* 0x00 reserved Unicode, 0x01 ISO 646, 0x10 JIS Roman & JIS Kanji, 0x11 ISO 8859-1, 0x12 Shift JIS Kanji */
+ uint32_t txtdt_start_byte; /* prt, rel start of vmg_txtdt_mgi */
+ txtdt_t *txtdt;
+} ATTRIBUTE_PACKED txtdt_lu_t;
+#define TXTDT_LU_SIZE 8U
+
+/**
+ * Text Data Manager Information. (Incomplete)
+ */
+typedef struct {
+ char disc_name[12];
+ uint16_t unknown1;
+ uint16_t nr_of_language_units;
+ uint32_t last_byte;
+ txtdt_lu_t *lu;
+} ATTRIBUTE_PACKED txtdt_mgi_t;
+#define TXTDT_MGI_SIZE 20U
+
+
+/**
+ * VTS
+ *
+ * Structures relating to the Video Title Set (VTS).
+ */
+
+/**
+ * Video Title Set Information Management Table.
+ */
+typedef struct {
+ char vts_identifier[12];
+ uint32_t vts_last_sector;
+ uint8_t zero_1[12];
+ uint32_t vtsi_last_sector;
+ uint8_t zero_2;
+ uint8_t specification_version;
+ uint32_t vts_category;
+ uint16_t zero_3;
+ uint16_t zero_4;
+ uint8_t zero_5;
+ uint8_t zero_6[19];
+ uint16_t zero_7;
+ uint8_t zero_8[32];
+ uint64_t zero_9;
+ uint8_t zero_10[24];
+ uint32_t vtsi_last_byte;
+ uint32_t zero_11;
+ uint8_t zero_12[56];
+ uint32_t vtsm_vobs; /* sector */
+ uint32_t vtstt_vobs; /* sector */
+ uint32_t vts_ptt_srpt; /* sector */
+ uint32_t vts_pgcit; /* sector */
+ uint32_t vtsm_pgci_ut; /* sector */
+ uint32_t vts_tmapt; /* sector */
+ uint32_t vtsm_c_adt; /* sector */
+ uint32_t vtsm_vobu_admap; /* sector */
+ uint32_t vts_c_adt; /* sector */
+ uint32_t vts_vobu_admap; /* sector */
+ uint8_t zero_13[24];
+
+ video_attr_t vtsm_video_attr;
+ uint8_t zero_14;
+ uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */
+ audio_attr_t vtsm_audio_attr;
+ audio_attr_t zero_15[7];
+ uint8_t zero_16[17];
+ uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */
+ subp_attr_t vtsm_subp_attr;
+ subp_attr_t zero_17[27];
+ uint8_t zero_18[2];
+
+ video_attr_t vts_video_attr;
+ uint8_t zero_19;
+ uint8_t nr_of_vts_audio_streams;
+ audio_attr_t vts_audio_attr[8];
+ uint8_t zero_20[17];
+ uint8_t nr_of_vts_subp_streams;
+ subp_attr_t vts_subp_attr[32];
+ uint16_t zero_21;
+ multichannel_ext_t vts_mu_audio_attr[8];
+ /* XXX: how much 'padding' here, if any? */
+} ATTRIBUTE_PACKED vtsi_mat_t;
+
+/**
+ * PartOfTitle Unit Information.
+ */
+typedef struct {
+ uint16_t pgcn;
+ uint16_t pgn;
+} ATTRIBUTE_PACKED ptt_info_t;
+
+/**
+ * PartOfTitle Information.
+ */
+typedef struct {
+ uint16_t nr_of_ptts;
+ ptt_info_t *ptt;
+} ATTRIBUTE_PACKED ttu_t;
+
+/**
+ * PartOfTitle Search Pointer Table.
+ */
+typedef struct {
+ uint16_t nr_of_srpts;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ ttu_t *title;
+ uint32_t *ttu_offset; /* offset table for each ttu */
+} ATTRIBUTE_PACKED vts_ptt_srpt_t;
+#define VTS_PTT_SRPT_SIZE 8U
+
+
+/**
+ * Time Map Entry.
+ */
+/* Should this be bit field at all or just the uint32_t? */
+typedef uint32_t map_ent_t;
+
+/**
+ * Time Map.
+ */
+typedef struct {
+ uint8_t tmu; /* Time unit, in seconds */
+ uint8_t zero_1;
+ uint16_t nr_of_entries;
+ map_ent_t *map_ent;
+} ATTRIBUTE_PACKED vts_tmap_t;
+#define VTS_TMAP_SIZE 4U
+
+/**
+ * Time Map Table.
+ */
+typedef struct {
+ uint16_t nr_of_tmaps;
+ uint16_t zero_1;
+ uint32_t last_byte;
+ vts_tmap_t *tmap;
+ uint32_t *tmap_offset; /* offset table for each tmap */
+} ATTRIBUTE_PACKED vts_tmapt_t;
+#define VTS_TMAPT_SIZE 8U
+
+
+#if PRAGMA_PACK
+#pragma pack()
+#endif
+
+
+/**
+ * The following structure defines an IFO file. The structure is divided into
+ * two parts, the VMGI, or Video Manager Information, which is read from the
+ * VIDEO_TS.[IFO,BUP] file, and the VTSI, or Video Title Set Information, which
+ * is read in from the VTS_XX_0.[IFO,BUP] files.
+ */
+typedef struct {
+ /* VMGI */
+ vmgi_mat_t *vmgi_mat;
+ tt_srpt_t *tt_srpt;
+ pgc_t *first_play_pgc;
+ ptl_mait_t *ptl_mait;
+ vts_atrt_t *vts_atrt;
+ txtdt_mgi_t *txtdt_mgi;
+
+ /* Common */
+ pgci_ut_t *pgci_ut;
+ c_adt_t *menu_c_adt;
+ vobu_admap_t *menu_vobu_admap;
+
+ /* VTSI */
+ vtsi_mat_t *vtsi_mat;
+ vts_ptt_srpt_t *vts_ptt_srpt;
+ pgcit_t *vts_pgcit;
+ vts_tmapt_t *vts_tmapt;
+ c_adt_t *vts_c_adt;
+ vobu_admap_t *vts_vobu_admap;
+} ifo_handle_t;
+
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/nav_types.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/nav_types.h
new file mode 100644
index 0000000..aa33f23
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/nav_types.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ * See LICENSES/README.md for more information.
+ *
+ * The data structures in this file should represent the layout of the
+ * pci and dsi packets as they are stored in the stream. Information
+ * found by reading the source to VOBDUMP is the base for the structure
+ * and names of these data types.
+ *
+ * VOBDUMP: a program for examining DVD .VOB files.
+ * Copyright 1998, 1999 Eric Smith <eric@brouhaha.com>
+ */
+
+#pragma once
+
+//#include <inttypes.h>
+#include "ifo_types.h" /* only dvd_time_t, vm_cmd_t and user_ops_t */
+
+
+#undef ATTRIBUTE_PACKED
+#undef PRAGMA_PACK_BEGIN
+#undef PRAGMA_PACK_END
+
+#if defined(__GNUC__)
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define ATTRIBUTE_PACKED __attribute__ ((packed))
+#define PRAGMA_PACK 0
+#endif
+#endif
+
+#if !defined(ATTRIBUTE_PACKED)
+#define ATTRIBUTE_PACKED
+#define PRAGMA_PACK 1
+#endif
+
+
+/* The length including the substream id byte. */
+#define PCI_BYTES 0x3d4
+#define DSI_BYTES 0x3fa
+
+#define PS2_PCI_SUBSTREAM_ID 0x00
+#define PS2_DSI_SUBSTREAM_ID 0x01
+
+/* Remove this */
+#define DSI_START_BYTE 1031
+
+
+#if PRAGMA_PACK
+#pragma pack(1)
+#endif
+
+
+/**
+ * PCI General Information
+ */
+typedef struct {
+ uint32_t nv_pck_lbn; /**< sector address of this nav pack */
+ uint16_t vobu_cat; /**< 'category' of vobu */
+ uint16_t zero1; /**< reserved */
+ user_ops_t vobu_uop_ctl; /**< UOP of vobu */
+ uint32_t vobu_s_ptm; /**< start presentation time of vobu */
+ uint32_t vobu_e_ptm; /**< end presentation time of vobu */
+ uint32_t vobu_se_e_ptm; /**< end ptm of sequence end in vobu */
+ dvd_time_t e_eltm; /**< Cell elapsed time */
+ char vobu_isrc[32];
+} ATTRIBUTE_PACKED pci_gi_t;
+
+/**
+ * Non Seamless Angle Information
+ */
+typedef struct {
+ uint32_t nsml_agl_dsta[9]; /**< address of destination vobu in AGL_C#n */
+} ATTRIBUTE_PACKED nsml_agli_t;
+
+/**
+ * Highlight General Information
+ *
+ * For btngrX_dsp_ty the bits have the following meaning:
+ * 000b: normal 4/3 only buttons
+ * XX1b: wide (16/9) buttons
+ * X1Xb: letterbox buttons
+ * 1XXb: pan&scan buttons
+ */
+typedef struct {
+ uint16_t hli_ss; /**< status, only low 2 bits 0: no buttons, 1: different 2: equal 3: equal except for button cmds */
+ uint32_t hli_s_ptm; /**< start ptm of hli */
+ uint32_t hli_e_ptm; /**< end ptm of hli */
+ uint32_t btn_se_e_ptm; /**< end ptm of button select */
+ unsigned int zero1 : 2; /**< reserved */
+ unsigned int btngr_ns : 2; /**< number of button groups 1, 2 or 3 with 36/18/12 buttons */
+ unsigned int zero2 : 1; /**< reserved */
+ unsigned int btngr1_dsp_ty : 3; /**< display type of subpic stream for button group 1 */
+ unsigned int zero3 : 1; /**< reserved */
+ unsigned int btngr2_dsp_ty : 3; /**< display type of subpic stream for button group 2 */
+ unsigned int zero4 : 1; /**< reserved */
+ unsigned int btngr3_dsp_ty : 3; /**< display type of subpic stream for button group 3 */
+ uint8_t btn_ofn; /**< button offset number range 0-255 */
+ uint8_t btn_ns; /**< number of valid buttons <= 36/18/12 (low 6 bits) */
+ uint8_t nsl_btn_ns; /**< number of buttons selectable by U_BTNNi (low 6 bits) nsl_btn_ns <= btn_ns */
+ uint8_t zero5; /**< reserved */
+ uint8_t fosl_btnn; /**< forcedly selected button (low 6 bits) */
+ uint8_t foac_btnn; /**< forcedly activated button (low 6 bits) */
+} ATTRIBUTE_PACKED hl_gi_t;
+
+
+/**
+ * Button Color Information Table
+ * Each entry being a 32bit word that contains the color indexes and alpha
+ * values to use. They are all represented by 4 bit number and stored
+ * like this [Ci3, Ci2, Ci1, Ci0, A3, A2, A1, A0]. The actual palette
+ * that the indexes reference is in the PGC.
+ * @TODO split the uint32_t into a struct
+ */
+typedef struct {
+ uint32_t btn_coli[3][2]; /**< [button color number-1][select:0/action:1] */
+} ATTRIBUTE_PACKED btn_colit_t;
+
+/**
+ * Button Information
+ *
+ * NOTE: I've had to change the structure from the disk layout to get
+ * the packing to work with Sun's Forte C compiler.
+ * The 4 and 7 bytes are 'rotated' was: ABC DEF GHIJ is: ABCG DEFH IJ
+ */
+typedef struct {
+ unsigned int btn_coln : 2; /**< button color number */
+ unsigned int x_start : 10; /**< x start offset within the overlay */
+ unsigned int zero1 : 2; /**< reserved */
+ unsigned int x_end : 10; /**< x end offset within the overlay */
+
+ unsigned int auto_action_mode : 2; /**< 0: no, 1: activated if selected */
+ unsigned int y_start : 10; /**< y start offset within the overlay */
+ unsigned int zero2 : 2; /**< reserved */
+ unsigned int y_end : 10; /**< y end offset within the overlay */
+
+ unsigned int zero3 : 2; /**< reserved */
+ unsigned int up : 6; /**< button index when pressing up */
+ unsigned int zero4 : 2; /**< reserved */
+ unsigned int down : 6; /**< button index when pressing down */
+ unsigned int zero5 : 2; /**< reserved */
+ unsigned int left : 6; /**< button index when pressing left */
+ unsigned int zero6 : 2; /**< reserved */
+ unsigned int right : 6; /**< button index when pressing right */
+ vm_cmd_t cmd;
+} ATTRIBUTE_PACKED btni_t;
+
+/**
+ * Highlight Information
+ */
+typedef struct {
+ hl_gi_t hl_gi;
+ btn_colit_t btn_colit;
+ btni_t btnit[36];
+} ATTRIBUTE_PACKED hli_t;
+
+/**
+ * PCI packet
+ */
+typedef struct {
+ pci_gi_t pci_gi;
+ nsml_agli_t nsml_agli;
+ hli_t hli;
+ uint8_t zero1[189];
+} ATTRIBUTE_PACKED pci_t;
+
+
+
+
+/**
+ * DSI General Information
+ */
+typedef struct {
+ uint32_t nv_pck_scr;
+ uint32_t nv_pck_lbn; /**< sector address of this nav pack */
+ uint32_t vobu_ea; /**< end address of this VOBU */
+ uint32_t vobu_1stref_ea; /**< end address of the 1st reference image */
+ uint32_t vobu_2ndref_ea; /**< end address of the 2nd reference image */
+ uint32_t vobu_3rdref_ea; /**< end address of the 3rd reference image */
+ uint16_t vobu_vob_idn; /**< VOB Id number that this VOBU is part of */
+ uint8_t zero1; /**< reserved */
+ uint8_t vobu_c_idn; /**< Cell Id number that this VOBU is part of */
+ dvd_time_t c_eltm; /**< Cell elapsed time */
+} ATTRIBUTE_PACKED dsi_gi_t;
+
+/**
+ * Seamless Playback Information
+ */
+typedef struct {
+ uint16_t category; /**< 'category' of seamless VOBU */
+ uint32_t ilvu_ea; /**< end address of interleaved Unit */
+ uint32_t ilvu_sa; /**< start address of next interleaved unit */
+ uint16_t size; /**< size of next interleaved unit */
+ uint32_t vob_v_s_s_ptm; /**< video start ptm in vob */
+ uint32_t vob_v_e_e_ptm; /**< video end ptm in vob */
+ struct {
+ uint32_t stp_ptm1;
+ uint32_t stp_ptm2;
+ uint32_t gap_len1;
+ uint32_t gap_len2;
+ } vob_a[8];
+} ATTRIBUTE_PACKED sml_pbi_t;
+
+/**
+ * Seamless Angle Information for one angle
+ */
+typedef struct {
+ uint32_t address; /**< offset to next ILVU, high bit is before/after */
+ uint16_t size; /**< byte size of the ILVU pointed to by address */
+} ATTRIBUTE_PACKED sml_agl_data_t;
+
+/**
+ * Seamless Angle Information
+ */
+typedef struct {
+ sml_agl_data_t data[9];
+} ATTRIBUTE_PACKED sml_agli_t;
+
+/**
+ * VOBU Search Information
+ */
+typedef struct {
+ uint32_t next_video; /**< Next vobu that contains video */
+ uint32_t fwda[19]; /**< Forwards, time */
+ uint32_t next_vobu;
+ uint32_t prev_vobu;
+ uint32_t bwda[19]; /**< Backwards, time */
+ uint32_t prev_video;
+} ATTRIBUTE_PACKED vobu_sri_t;
+
+#define SRI_END_OF_CELL 0x3fffffff
+
+/**
+ * Synchronous Information
+ */
+typedef struct {
+ uint16_t a_synca[8]; /**< offset to first audio packet for this VOBU */
+ uint32_t sp_synca[32]; /**< offset to first subpicture packet */
+} ATTRIBUTE_PACKED synci_t;
+
+/**
+ * DSI packet
+ */
+typedef struct {
+ dsi_gi_t dsi_gi;
+ sml_pbi_t sml_pbi;
+ sml_agli_t sml_agli;
+ vobu_sri_t vobu_sri;
+ synci_t synci;
+ uint8_t zero1[471];
+} ATTRIBUTE_PACKED dsi_t;
+
+
+#if PRAGMA_PACK
+#pragma pack()
+#endif
+
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/version.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/version.h
new file mode 100644
index 0000000..dced5e7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/version.h
@@ -0,0 +1,29 @@
+/*
+* This file is part of libdvdnav, a DVD navigation library.
+*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+*/
+#pragma once
+
+#define DVDNAV_VERSION_CODE(major, minor, micro) (((major)*10000) + ((minor)*100) + ((micro)*1))
+
+#define DVDNAV_VERSION_MAJOR 6
+#define DVDNAV_VERSION_MINOR 1
+#define DVDNAV_VERSION_MICRO 1
+
+#define DVDNAV_VERSION_STRING "6.1.1"
+
+#define DVDNAV_VERSION \
+ DVDNAV_VERSION_CODE(DVDNAV_VERSION_MAJOR, DVDNAV_VERSION_MINOR, DVDNAV_VERSION_MICRO)
diff --git a/xbmc/cores/VideoPlayer/DVDMessage.cpp b/xbmc/cores/VideoPlayer/DVDMessage.cpp
new file mode 100644
index 0000000..34e8076
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDMessage.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDMessage.h"
+
+#include "DVDDemuxers/DVDDemuxUtils.h"
+#include "threads/Condition.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+class CDVDMsgGeneralSynchronizePriv
+{
+public:
+ CDVDMsgGeneralSynchronizePriv(std::chrono::milliseconds timeout, unsigned int sources)
+ : sources(sources), reached(0), m_timer(timeout)
+ {}
+ unsigned int sources;
+ unsigned int reached;
+ CCriticalSection section;
+ XbmcThreads::ConditionVariable condition;
+ XbmcThreads::EndTime<> m_timer;
+};
+
+/**
+ * CDVDMsgGeneralSynchronize --- GENERAL_SYNCRONIZR
+ */
+CDVDMsgGeneralSynchronize::CDVDMsgGeneralSynchronize(std::chrono::milliseconds timeout,
+ unsigned int sources)
+ : CDVDMsg(GENERAL_SYNCHRONIZE), m_p(new CDVDMsgGeneralSynchronizePriv(timeout, sources))
+{
+}
+
+CDVDMsgGeneralSynchronize::~CDVDMsgGeneralSynchronize()
+{
+ m_p->condition.notifyAll();
+
+ delete m_p;
+}
+
+bool CDVDMsgGeneralSynchronize::Wait(std::chrono::milliseconds timeout, unsigned int source)
+{
+ std::unique_lock<CCriticalSection> lock(m_p->section);
+
+ XbmcThreads::EndTime<> timer{timeout};
+
+ m_p->reached |= (source & m_p->sources);
+ if ((m_p->sources & SYNCSOURCE_ANY) && source)
+ m_p->reached |= SYNCSOURCE_ANY;
+
+ m_p->condition.notifyAll();
+
+ while (m_p->reached != m_p->sources)
+ {
+ timeout = std::min(m_p->m_timer.GetTimeLeft(), timer.GetTimeLeft());
+ if (m_p->condition.wait(lock, timeout))
+ continue;
+
+ if (m_p->m_timer.IsTimePast())
+ {
+ CLog::Log(LOGDEBUG, "CDVDMsgGeneralSynchronize - global timeout");
+ return true; // global timeout, we are done
+ }
+ if (timer.IsTimePast())
+ {
+ return false; /* request timeout, should be retried */
+ }
+ }
+ return true;
+}
+
+void CDVDMsgGeneralSynchronize::Wait(std::atomic<bool>& abort, unsigned int source)
+{
+ while (!Wait(100ms, source) && !abort)
+ ;
+}
+
+/**
+ * CDVDMsgDemuxerPacket --- DEMUXER_PACKET
+ */
+CDVDMsgDemuxerPacket::CDVDMsgDemuxerPacket(DemuxPacket* packet, bool drop) : CDVDMsg(DEMUXER_PACKET)
+{
+ m_packet = packet;
+ m_drop = drop;
+}
+
+CDVDMsgDemuxerPacket::~CDVDMsgDemuxerPacket()
+{
+ if (m_packet)
+ CDVDDemuxUtils::FreeDemuxPacket(m_packet);
+}
+
+unsigned int CDVDMsgDemuxerPacket::GetPacketSize()
+{
+ if (m_packet)
+ return m_packet->iSize;
+ else
+ return 0;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDMessage.h b/xbmc/cores/VideoPlayer/DVDMessage.h
new file mode 100644
index 0000000..5b1dcf7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDMessage.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "cores/IPlayer.h"
+
+#include <atomic>
+#include <string.h>
+#include <string>
+
+struct DemuxPacket;
+
+class CDVDMsg
+{
+public:
+ // clang-format off
+ enum Message
+ {
+ NONE = 1000,
+
+ // messages used in the whole system
+ GENERAL_RESYNC, //
+ GENERAL_FLUSH, // flush all buffers
+ GENERAL_RESET, // reset codecs for new data
+ GENERAL_PAUSE,
+ GENERAL_STREAMCHANGE, //
+ GENERAL_SYNCHRONIZE, //
+ GENERAL_GUI_ACTION, // gui action of some sort
+ GENERAL_EOF, // eof of stream
+
+ // player core related messages (cVideoPlayer.cpp)
+ PLAYER_SET_AUDIOSTREAM, //
+ PLAYER_SET_VIDEOSTREAM, //
+ PLAYER_SET_SUBTITLESTREAM, //
+ PLAYER_SET_SUBTITLESTREAM_VISIBLE, //
+ PLAYER_SET_STATE, // restore the VideoPlayer to a certain state
+ PLAYER_SET_PROGRAM,
+ PLAYER_SET_UPDATE_STREAM_DETAILS, // player should update file item stream details with its current streams
+ PLAYER_SEEK, //
+ PLAYER_SEEK_CHAPTER, //
+ PLAYER_SETSPEED, // set the playback speed
+ PLAYER_REQUEST_STATE,
+ PLAYER_OPENFILE,
+ PLAYER_STARTED, // sent whenever a sub player has finished it's first frame after open
+ PLAYER_AVCHANGE, // signal a change in audio, video or subtitle parameters
+ PLAYER_ABORT,
+ PLAYER_REPORT_STATE,
+ PLAYER_FRAME_ADVANCE,
+ PLAYER_DISPLAY_RESET, // report display reset event
+
+ // demuxer related messages
+ DEMUXER_PACKET, // data packet
+ DEMUXER_RESET, // reset the demuxer
+
+ // video related messages
+ VIDEO_SET_ASPECT, // set aspectratio of video
+ VIDEO_DRAIN, // wait for decoder to output last frame
+
+ // subtitle related messages
+ SUBTITLE_CLUTCHANGE,
+ SUBTITLE_ADDFILE
+ };
+ // clang-format on
+
+ explicit CDVDMsg(Message msg)
+ {
+ m_message = msg;
+ }
+
+ virtual ~CDVDMsg() = default;
+
+ /**
+ * checks for message type
+ */
+ inline bool IsType(Message msg)
+ {
+ return (m_message == msg);
+ }
+
+ inline Message GetMessageType()
+ {
+ return m_message;
+ }
+
+private:
+ Message m_message;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//////
+////// GENERAL_ Messages
+//////
+////////////////////////////////////////////////////////////////////////////////
+
+#define SYNCSOURCE_AUDIO 0x01
+#define SYNCSOURCE_VIDEO 0x02
+#define SYNCSOURCE_PLAYER 0x04
+#define SYNCSOURCE_ANY 0x08
+
+class CDVDMsgGeneralSynchronizePriv;
+class CDVDMsgGeneralSynchronize : public CDVDMsg
+{
+public:
+ CDVDMsgGeneralSynchronize(std::chrono::milliseconds timeout, unsigned int sources);
+ ~CDVDMsgGeneralSynchronize() override;
+
+ // waits until all threads waiting, released the object
+ // if abort is set somehow
+ bool Wait(std::chrono::milliseconds ms, unsigned int source);
+ void Wait(std::atomic<bool>& abort, unsigned int source);
+
+private:
+ class CDVDMsgGeneralSynchronizePriv* m_p;
+};
+
+template <typename T>
+class CDVDMsgType : public CDVDMsg
+{
+public:
+ CDVDMsgType(Message type, const T &value)
+ : CDVDMsg(type)
+ , m_value(value)
+ {}
+
+ ~CDVDMsgType() override = default;
+
+ operator T() { return m_value; }
+ T m_value;
+};
+
+typedef CDVDMsgType<bool> CDVDMsgBool;
+typedef CDVDMsgType<int> CDVDMsgInt;
+typedef CDVDMsgType<double> CDVDMsgDouble;
+
+////////////////////////////////////////////////////////////////////////////////
+//////
+////// PLAYER_ Messages
+//////
+////////////////////////////////////////////////////////////////////////////////
+
+class CDVDMsgPlayerSetAudioStream : public CDVDMsg
+{
+public:
+ explicit CDVDMsgPlayerSetAudioStream(int streamId) : CDVDMsg(PLAYER_SET_AUDIOSTREAM) { m_streamId = streamId; }
+ ~CDVDMsgPlayerSetAudioStream() override = default;
+
+ int GetStreamId() { return m_streamId; }
+private:
+ int m_streamId;
+};
+
+class CDVDMsgPlayerSetVideoStream : public CDVDMsg
+{
+public:
+ explicit CDVDMsgPlayerSetVideoStream(int streamId) : CDVDMsg(PLAYER_SET_VIDEOSTREAM) { m_streamId = streamId; }
+ ~CDVDMsgPlayerSetVideoStream() override = default;
+
+ int GetStreamId() const { return m_streamId; }
+private:
+ int m_streamId;
+};
+
+class CDVDMsgPlayerSetSubtitleStream : public CDVDMsg
+{
+public:
+ explicit CDVDMsgPlayerSetSubtitleStream(int streamId) : CDVDMsg(PLAYER_SET_SUBTITLESTREAM) { m_streamId = streamId; }
+ ~CDVDMsgPlayerSetSubtitleStream() override = default;
+
+ int GetStreamId() { return m_streamId; }
+private:
+ int m_streamId;
+};
+
+class CDVDMsgPlayerSetState : public CDVDMsg
+{
+public:
+ explicit CDVDMsgPlayerSetState(const std::string& state) : CDVDMsg(PLAYER_SET_STATE), m_state(state) {}
+ ~CDVDMsgPlayerSetState() override = default;
+
+ std::string GetState() { return m_state; }
+private:
+ std::string m_state;
+};
+
+class CDVDMsgPlayerSeek : public CDVDMsg
+{
+public:
+ struct CMode
+ {
+ double time = 0;
+ bool relative = false;
+ bool backward = false;
+ bool accurate = true;
+ bool sync = true;
+ bool restore = true;
+ bool trickplay = false;
+ };
+
+ explicit CDVDMsgPlayerSeek(CDVDMsgPlayerSeek::CMode mode) : CDVDMsg(PLAYER_SEEK),
+ m_mode(mode)
+ {}
+ ~CDVDMsgPlayerSeek() override = default;
+
+ double GetTime() { return m_mode.time; }
+ bool GetRelative() { return m_mode.relative; }
+ bool GetBackward() { return m_mode.backward; }
+ bool GetAccurate() { return m_mode.accurate; }
+ bool GetRestore() { return m_mode.restore; }
+ bool GetTrickPlay() { return m_mode.trickplay; }
+ bool GetSync() { return m_mode.sync; }
+
+private:
+ CMode m_mode;
+};
+
+class CDVDMsgPlayerSeekChapter : public CDVDMsg
+{
+ public:
+ explicit CDVDMsgPlayerSeekChapter(int iChapter)
+ : CDVDMsg(PLAYER_SEEK_CHAPTER)
+ , m_iChapter(iChapter)
+ {}
+ ~CDVDMsgPlayerSeekChapter() override = default;
+
+ int GetChapter() const { return m_iChapter; }
+
+ private:
+
+ int m_iChapter;
+};
+
+class CDVDMsgPlayerSetSpeed : public CDVDMsg
+{
+public:
+ struct SpeedParams
+ {
+ int m_speed;
+ bool m_isTempo;
+ };
+
+ explicit CDVDMsgPlayerSetSpeed(SpeedParams params)
+ : CDVDMsg(PLAYER_SETSPEED)
+ , m_params(params)
+ {}
+ ~CDVDMsgPlayerSetSpeed() override = default;
+
+ int GetSpeed() const { return m_params.m_speed; }
+ bool IsTempo() const { return m_params.m_isTempo; }
+
+private:
+
+ SpeedParams m_params;
+
+};
+
+class CDVDMsgOpenFile : public CDVDMsg
+{
+public:
+ struct FileParams
+ {
+ CFileItem m_item;
+ CPlayerOptions m_options;
+ };
+
+ explicit CDVDMsgOpenFile(const FileParams &params)
+ : CDVDMsg(PLAYER_OPENFILE)
+ , m_params(params)
+ {}
+ ~CDVDMsgOpenFile() override = default;
+
+ CFileItem& GetItem() { return m_params.m_item; }
+ CPlayerOptions& GetOptions() { return m_params.m_options; }
+
+private:
+
+ FileParams m_params;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//////
+////// DEMUXER_ Messages
+//////
+////////////////////////////////////////////////////////////////////////////////
+
+class CDVDMsgDemuxerPacket : public CDVDMsg
+{
+public:
+ CDVDMsgDemuxerPacket(DemuxPacket* packet, bool drop = false);
+ ~CDVDMsgDemuxerPacket() override;
+ DemuxPacket* GetPacket() { return m_packet; }
+ unsigned int GetPacketSize();
+ bool GetPacketDrop() { return m_drop; }
+ DemuxPacket* m_packet;
+ bool m_drop;
+};
+
+class CDVDMsgDemuxerReset : public CDVDMsg
+{
+public:
+ CDVDMsgDemuxerReset() : CDVDMsg(DEMUXER_RESET) {}
+ ~CDVDMsgDemuxerReset() override = default;
+};
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//////
+////// VIDEO_ Messages
+//////
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+//////
+////// SUBTITLE_ Messages
+//////
+////////////////////////////////////////////////////////////////////////////////
+
+class CDVDMsgSubtitleClutChange : public CDVDMsg
+{
+public:
+ explicit CDVDMsgSubtitleClutChange(uint8_t* data) : CDVDMsg(SUBTITLE_CLUTCHANGE) { memcpy(m_data, data, 16*4); }
+ ~CDVDMsgSubtitleClutChange() override = default;
+
+ uint8_t m_data[16][4];
+};
diff --git a/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp b/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp
new file mode 100644
index 0000000..12ea7a9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDMessageQueue.h"
+
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+#include <math.h>
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+CDVDMessageQueue::CDVDMessageQueue(const std::string &owner) : m_hEvent(true), m_owner(owner)
+{
+ m_iDataSize = 0;
+ m_bInitialized = false;
+
+ m_TimeBack = DVD_NOPTS_VALUE;
+ m_TimeFront = DVD_NOPTS_VALUE;
+ m_TimeSize = 1.0 / 4.0; /* 4 seconds */
+ m_iMaxDataSize = 0;
+}
+
+CDVDMessageQueue::~CDVDMessageQueue()
+{
+ // remove all remaining messages
+ Flush(CDVDMsg::NONE);
+}
+
+void CDVDMessageQueue::Init()
+{
+ m_iDataSize = 0;
+ m_bAbortRequest = false;
+ m_bInitialized = true;
+ m_TimeBack = DVD_NOPTS_VALUE;
+ m_TimeFront = DVD_NOPTS_VALUE;
+ m_drain = false;
+}
+
+void CDVDMessageQueue::Flush(CDVDMsg::Message type)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ m_messages.remove_if([type](const DVDMessageListItem &item){
+ return type == CDVDMsg::NONE || item.message->IsType(type);
+ });
+
+ m_prioMessages.remove_if([type](const DVDMessageListItem &item){
+ return type == CDVDMsg::NONE || item.message->IsType(type);
+ });
+
+ if (type == CDVDMsg::DEMUXER_PACKET || type == CDVDMsg::NONE)
+ {
+ m_iDataSize = 0;
+ m_TimeBack = DVD_NOPTS_VALUE;
+ m_TimeFront = DVD_NOPTS_VALUE;
+ }
+}
+
+void CDVDMessageQueue::Abort()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ m_bAbortRequest = true;
+
+ // inform waiter for abort action
+ m_hEvent.Set();
+}
+
+void CDVDMessageQueue::End()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ Flush(CDVDMsg::NONE);
+
+ m_bInitialized = false;
+ m_iDataSize = 0;
+ m_bAbortRequest = false;
+}
+
+MsgQueueReturnCode CDVDMessageQueue::Put(const std::shared_ptr<CDVDMsg>& pMsg, int priority)
+{
+ return Put(pMsg, priority, true);
+}
+
+MsgQueueReturnCode CDVDMessageQueue::PutBack(const std::shared_ptr<CDVDMsg>& pMsg, int priority)
+{
+ return Put(pMsg, priority, false);
+}
+
+MsgQueueReturnCode CDVDMessageQueue::Put(const std::shared_ptr<CDVDMsg>& pMsg,
+ int priority,
+ bool front)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (!m_bInitialized)
+ {
+ CLog::Log(LOGWARNING, "CDVDMessageQueue({})::Put MSGQ_NOT_INITIALIZED", m_owner);
+ return MSGQ_NOT_INITIALIZED;
+ }
+ if (!pMsg)
+ {
+ CLog::Log(LOGFATAL, "CDVDMessageQueue({})::Put MSGQ_INVALID_MSG", m_owner);
+ return MSGQ_INVALID_MSG;
+ }
+
+ if (priority > 0)
+ {
+ int prio = priority;
+ if (!front)
+ prio++;
+
+ auto it = std::find_if(m_prioMessages.begin(), m_prioMessages.end(),
+ [prio](const DVDMessageListItem &item){
+ return prio <= item.priority;
+ });
+ m_prioMessages.emplace(it, pMsg, priority);
+ }
+ else
+ {
+ if (m_messages.empty())
+ {
+ m_iDataSize = 0;
+ m_TimeBack = DVD_NOPTS_VALUE;
+ m_TimeFront = DVD_NOPTS_VALUE;
+ }
+
+ if (front)
+ m_messages.emplace_front(pMsg, priority);
+ else
+ m_messages.emplace_back(pMsg, priority);
+ }
+
+ if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET) && priority == 0)
+ {
+ DemuxPacket* packet = static_cast<CDVDMsgDemuxerPacket*>(pMsg.get())->GetPacket();
+ if (packet)
+ {
+ m_iDataSize += packet->iSize;
+ if (front)
+ UpdateTimeFront();
+ else
+ UpdateTimeBack();
+ }
+ }
+
+ // inform waiter for new packet
+ m_hEvent.Set();
+
+ return MSGQ_OK;
+}
+
+MsgQueueReturnCode CDVDMessageQueue::Get(std::shared_ptr<CDVDMsg>& pMsg,
+ unsigned int iTimeoutInMilliSeconds,
+ int& priority)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ int ret = 0;
+
+ if (!m_bInitialized)
+ {
+ CLog::Log(LOGFATAL, "CDVDMessageQueue({})::Get MSGQ_NOT_INITIALIZED", m_owner);
+ return MSGQ_NOT_INITIALIZED;
+ }
+
+ while (!m_bAbortRequest)
+ {
+ std::list<DVDMessageListItem> &msgs = (priority > 0 || !m_prioMessages.empty()) ? m_prioMessages : m_messages;
+
+ if (!msgs.empty() && (msgs.back().priority >= priority || m_drain))
+ {
+ DVDMessageListItem& item(msgs.back());
+ priority = item.priority;
+
+ if (item.message->IsType(CDVDMsg::DEMUXER_PACKET) && item.priority == 0)
+ {
+ DemuxPacket* packet =
+ std::static_pointer_cast<CDVDMsgDemuxerPacket>(item.message)->GetPacket();
+ if (packet)
+ {
+ m_iDataSize -= packet->iSize;
+ }
+ }
+
+ pMsg = std::move(item.message);
+ msgs.pop_back();
+ UpdateTimeBack();
+ ret = MSGQ_OK;
+ break;
+ }
+ else if (!iTimeoutInMilliSeconds)
+ {
+ ret = MSGQ_TIMEOUT;
+ break;
+ }
+ else
+ {
+ m_hEvent.Reset();
+ lock.unlock();
+
+ // wait for a new message
+ if (!m_hEvent.Wait(std::chrono::milliseconds(iTimeoutInMilliSeconds)))
+ return MSGQ_TIMEOUT;
+
+ lock.lock();
+ }
+ }
+
+ if (m_bAbortRequest)
+ return MSGQ_ABORT;
+
+ return (MsgQueueReturnCode)ret;
+}
+
+void CDVDMessageQueue::UpdateTimeFront()
+{
+ if (!m_messages.empty())
+ {
+ auto &item = m_messages.front();
+ if (item.message->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ DemuxPacket* packet =
+ std::static_pointer_cast<CDVDMsgDemuxerPacket>(item.message)->GetPacket();
+ if (packet)
+ {
+ if (packet->dts != DVD_NOPTS_VALUE)
+ m_TimeFront = packet->dts;
+ else if (packet->pts != DVD_NOPTS_VALUE)
+ m_TimeFront = packet->pts;
+
+ if (m_TimeBack == DVD_NOPTS_VALUE)
+ m_TimeBack = m_TimeFront;
+ }
+ }
+ }
+}
+
+void CDVDMessageQueue::UpdateTimeBack()
+{
+ if (!m_messages.empty())
+ {
+ auto &item = m_messages.back();
+ if (item.message->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ DemuxPacket* packet =
+ std::static_pointer_cast<CDVDMsgDemuxerPacket>(item.message)->GetPacket();
+ if (packet)
+ {
+ if (packet->dts != DVD_NOPTS_VALUE)
+ m_TimeBack = packet->dts;
+ else if (packet->pts != DVD_NOPTS_VALUE)
+ m_TimeBack = packet->pts;
+
+ if (m_TimeFront == DVD_NOPTS_VALUE)
+ m_TimeFront = m_TimeBack;
+ }
+ }
+ }
+}
+
+unsigned CDVDMessageQueue::GetPacketCount(CDVDMsg::Message type)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (!m_bInitialized)
+ return 0;
+
+ unsigned count = 0;
+ for (const auto &item : m_messages)
+ {
+ if(item.message->IsType(type))
+ count++;
+ }
+ for (const auto &item : m_prioMessages)
+ {
+ if(item.message->IsType(type))
+ count++;
+ }
+
+ return count;
+}
+
+void CDVDMessageQueue::WaitUntilEmpty()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_drain = true;
+ }
+
+ CLog::Log(LOGINFO, "CDVDMessageQueue({})::WaitUntilEmpty", m_owner);
+ auto msg = std::make_shared<CDVDMsgGeneralSynchronize>(40s, SYNCSOURCE_ANY);
+ Put(msg);
+ msg->Wait(m_bAbortRequest, 0);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_drain = false;
+ }
+}
+
+int CDVDMessageQueue::GetLevel() const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (m_iDataSize > m_iMaxDataSize)
+ return 100;
+ if (m_iDataSize == 0)
+ return 0;
+
+ if (IsDataBased())
+ {
+ return std::min(100, 100 * m_iDataSize / m_iMaxDataSize);
+ }
+
+ int level = std::min(100.0, ceil(100.0 * m_TimeSize * (m_TimeFront - m_TimeBack) / DVD_TIME_BASE ));
+
+ // if we added lots of packets with NOPTS, make sure that the queue is not signalled empty
+ if (level == 0 && m_iDataSize != 0)
+ {
+ CLog::Log(LOGDEBUG, "CDVDMessageQueue::GetLevel() - can't determine level");
+ return 1;
+ }
+
+ return level;
+}
+
+int CDVDMessageQueue::GetTimeSize() const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (IsDataBased())
+ return 0;
+ else
+ return (int)((m_TimeFront - m_TimeBack) / DVD_TIME_BASE);
+}
+
+bool CDVDMessageQueue::IsDataBased() const
+{
+ return (m_TimeBack == DVD_NOPTS_VALUE ||
+ m_TimeFront == DVD_NOPTS_VALUE ||
+ m_TimeFront <= m_TimeBack);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDMessageQueue.h b/xbmc/cores/VideoPlayer/DVDMessageQueue.h
new file mode 100644
index 0000000..81ffd0e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDMessageQueue.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDMessage.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+
+#include <algorithm>
+#include <atomic>
+#include <list>
+#include <string>
+
+struct DVDMessageListItem
+{
+ DVDMessageListItem(std::shared_ptr<CDVDMsg> msg, int prio) : message(std::move(msg))
+ {
+ priority = prio;
+ }
+ DVDMessageListItem() { priority = 0; }
+ DVDMessageListItem(const DVDMessageListItem&) = delete;
+ ~DVDMessageListItem() = default;
+
+ DVDMessageListItem& operator=(const DVDMessageListItem&) = delete;
+
+ std::shared_ptr<CDVDMsg> message;
+ int priority;
+};
+
+enum MsgQueueReturnCode
+{
+ MSGQ_OK = 1,
+ MSGQ_TIMEOUT = 0,
+ MSGQ_ABORT = -1, // negative for legacy, not an error actually
+ MSGQ_NOT_INITIALIZED = -2,
+ MSGQ_INVALID_MSG = -3,
+ MSGQ_OUT_OF_MEMORY = -4
+};
+
+#define MSGQ_IS_ERROR(c) (c < 0)
+
+class CDVDMessageQueue
+{
+public:
+ explicit CDVDMessageQueue(const std::string &owner);
+ virtual ~CDVDMessageQueue();
+
+ void Init();
+ void Flush(CDVDMsg::Message message = CDVDMsg::DEMUXER_PACKET);
+ void Abort();
+ void End();
+
+ MsgQueueReturnCode Put(const std::shared_ptr<CDVDMsg>& pMsg, int priority = 0);
+ MsgQueueReturnCode PutBack(const std::shared_ptr<CDVDMsg>& pMsg, int priority = 0);
+
+ /**
+ * msg, message type from DVDMessage.h
+ * timeout, timeout in msec
+ * priority, minimum priority to get, outputs returned packets priority
+ */
+ MsgQueueReturnCode Get(std::shared_ptr<CDVDMsg>& pMsg,
+ unsigned int iTimeoutInMilliSeconds,
+ int& priority);
+ MsgQueueReturnCode Get(std::shared_ptr<CDVDMsg>& pMsg, unsigned int iTimeoutInMilliSeconds)
+ {
+ int priority = 0;
+ return Get(pMsg, iTimeoutInMilliSeconds, priority);
+ }
+
+ int GetDataSize() const { return m_iDataSize; }
+ int GetTimeSize() const;
+ unsigned GetPacketCount(CDVDMsg::Message type);
+ bool ReceivedAbortRequest() { return m_bAbortRequest; }
+ void WaitUntilEmpty();
+
+ // non messagequeue related functions
+ bool IsFull() const { return GetLevel() == 100; }
+ int GetLevel() const;
+
+ void SetMaxDataSize(int iMaxDataSize) { m_iMaxDataSize = iMaxDataSize; }
+ void SetMaxTimeSize(double sec) { m_TimeSize = 1.0 / std::max(1.0, sec); }
+ int GetMaxDataSize() const { return m_iMaxDataSize; }
+ double GetMaxTimeSize() const { return m_TimeSize; }
+ bool IsInited() const { return m_bInitialized; }
+ bool IsDataBased() const;
+
+private:
+ MsgQueueReturnCode Put(const std::shared_ptr<CDVDMsg>& pMsg, int priority, bool front);
+ void UpdateTimeFront();
+ void UpdateTimeBack();
+
+ CEvent m_hEvent;
+ mutable CCriticalSection m_section;
+
+ std::atomic<bool> m_bAbortRequest = false;
+ bool m_bInitialized;
+ bool m_drain = false;
+
+ int m_iDataSize;
+ double m_TimeFront;
+ double m_TimeBack;
+ double m_TimeSize;
+
+ int m_iMaxDataSize;
+ std::string m_owner;
+
+ std::list<DVDMessageListItem> m_messages;
+ std::list<DVDMessageListItem> m_prioMessages;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDOverlayContainer.cpp b/xbmc/cores/VideoPlayer/DVDOverlayContainer.cpp
new file mode 100644
index 0000000..ec66b2d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDOverlayContainer.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDOverlayContainer.h"
+
+#include "DVDInputStreams/DVDInputStreamNavigator.h"
+
+#include <mutex>
+
+CDVDOverlayContainer::CDVDOverlayContainer() = default;
+
+CDVDOverlayContainer::~CDVDOverlayContainer()
+{
+ Clear();
+}
+
+void CDVDOverlayContainer::ProcessAndAddOverlayIfValid(CDVDOverlay* pOverlay)
+{
+ pOverlay->Acquire();
+
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ // markup any non ending overlays, to finish
+ // when this new one starts, there can be
+ // multiple overlays queued at same start
+ // point so only stop them when we get a
+ // new startpoint
+ for(int i = m_overlays.size();i>0;)
+ {
+ i--;
+ if(m_overlays[i]->iPTSStopTime)
+ {
+ if(!m_overlays[i]->replace)
+ break;
+ if(m_overlays[i]->iPTSStopTime <= pOverlay->iPTSStartTime)
+ break;
+ }
+
+ if (m_overlays[i]->iPTSStartTime != pOverlay->iPTSStartTime)
+ m_overlays[i]->iPTSStopTime = pOverlay->iPTSStartTime;
+ }
+
+ m_overlays.emplace_back(pOverlay);
+}
+
+VecOverlays* CDVDOverlayContainer::GetOverlays()
+{
+ return &m_overlays;
+}
+
+VecOverlaysIter CDVDOverlayContainer::Remove(VecOverlaysIter itOverlay)
+{
+ VecOverlaysIter itNext;
+ CDVDOverlay* pOverlay = *itOverlay;
+
+ {
+ std::unique_lock<CCriticalSection> lock(*this);
+ itNext = m_overlays.erase(itOverlay);
+ }
+
+ pOverlay->Release();
+
+ return itNext;
+}
+
+void CDVDOverlayContainer::CleanUp(double pts)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ VecOverlaysIter it = m_overlays.begin();
+ while (it != m_overlays.end())
+ {
+ CDVDOverlay* pOverlay = *it;
+
+ // never delete forced overlays, they are used in menu's
+ // clear takes care of removing them
+ // also if stoptime = 0, it means the next subtitles will use its starttime as the stoptime
+ // which means we cannot delete overlays with stoptime 0
+ if (!pOverlay->bForced && pOverlay->iPTSStopTime <= pts && pOverlay->iPTSStopTime != 0)
+ {
+ //CLog::Log(LOGDEBUG,"CDVDOverlay::CleanUp, removing {}", (int)(pts / 1000));
+ //CLog::Log(LOGDEBUG,"CDVDOverlay::CleanUp, remove, start : {}, stop : {}", (int)(pOverlay->iPTSStartTime / 1000), (int)(pOverlay->iPTSStopTime / 1000));
+ it = Remove(it);
+ continue;
+ }
+ else if (pOverlay->bForced)
+ {
+ //Check for newer replacements
+ VecOverlaysIter it2 = it;
+ bool bNewer = false;
+ while (!bNewer && ++it2 != m_overlays.end())
+ {
+ CDVDOverlay* pOverlay2 = *it2;
+ if (pOverlay2->bForced && pOverlay2->iPTSStartTime <= pts) bNewer = true;
+ }
+
+ if (bNewer)
+ {
+ it = Remove(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+
+}
+
+void CDVDOverlayContainer::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ // Flush only the overlays marked as flushable
+ m_overlays.erase(std::remove_if(m_overlays.begin(), m_overlays.end(),
+ [](CDVDOverlay* ov) {
+ bool isFlushable = ov->IsOverlayContainerFlushable();
+ if (isFlushable)
+ ov->Release();
+ return isFlushable;
+ }),
+ m_overlays.end());
+}
+
+void CDVDOverlayContainer::Clear()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ for (auto &overlay : m_overlays)
+ {
+ overlay->Release();
+ }
+ m_overlays.clear();
+}
+
+size_t CDVDOverlayContainer::GetSize()
+{
+ return m_overlays.size();
+}
+
+bool CDVDOverlayContainer::ContainsOverlayType(DVDOverlayType type)
+{
+ bool result = false;
+
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ VecOverlaysIter it = m_overlays.begin();
+ while (!result && it != m_overlays.end())
+ {
+ if ((*it)->IsOverlayType(type)) result = true;
+ ++it;
+ }
+
+ return result;
+}
+
+/*
+ * iAction should be LIBDVDNAV_BUTTON_NORMAL or LIBDVDNAV_BUTTON_CLICKED
+ */
+void CDVDOverlayContainer::UpdateOverlayInfo(
+ const std::shared_ptr<CDVDInputStreamNavigator>& pStream, CDVDDemuxSPU* pSpu, int iAction)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ pStream->CheckButtons();
+
+ //Update any forced overlays.
+ for(VecOverlays::iterator it = m_overlays.begin(); it != m_overlays.end(); ++it )
+ {
+ if ((*it)->IsOverlayType(DVDOVERLAY_TYPE_SPU))
+ {
+ CDVDOverlaySpu* pOverlaySpu = (CDVDOverlaySpu*)(*it);
+
+ // make sure its a forced (menu) overlay
+ // set menu spu color and alpha data if there is a valid menu overlay
+ if (pOverlaySpu->bForced)
+ {
+ if (pOverlaySpu->Acquire()->Release() > 1)
+ {
+ pOverlaySpu = new CDVDOverlaySpu(*pOverlaySpu);
+ (*it)->Release();
+ (*it) = pOverlaySpu;
+ }
+
+ if(pStream->GetCurrentButtonInfo(pOverlaySpu, pSpu, iAction))
+ {
+ pOverlaySpu->m_textureid = 0;
+ }
+
+ }
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDOverlayContainer.h b/xbmc/cores/VideoPlayer/DVDOverlayContainer.h
new file mode 100644
index 0000000..98f3373
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDOverlayContainer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDCodecs/Overlay/DVDOverlay.h"
+#include "threads/CriticalSection.h"
+
+#include <memory>
+
+class CDVDInputStreamNavigator;
+class CDVDDemuxSPU;
+
+class CDVDOverlayContainer : public CCriticalSection
+{
+public:
+ CDVDOverlayContainer();
+ virtual ~CDVDOverlayContainer();
+
+ /*!
+ * \brief Adds an overlay into the container by processing the existing overlay collection first
+ *
+ * \details Processes the overlay collection whenever a new overlay is added. Useful to change
+ * the overlay's PTS values of previously added overlays if the collection itself is sequential. This
+ * is, for example, the case of ASS subtitles in which a single call to ass_render_frame generates all
+ * the subtitle images on a single call even if two subtitles exist at the same time frame. Other cases
+ * might exist where an overlay shouldn't be added to the collection if completely contained in another
+ * overlay.
+ *
+ * \param pPicture pointer to the overlay to be evaluated and possibly added to the collection
+ */
+ void ProcessAndAddOverlayIfValid(CDVDOverlay* pPicture);
+
+ VecOverlays* GetOverlays(); // get the first overlay in this fifo
+ bool ContainsOverlayType(DVDOverlayType type);
+
+ void Clear(); // clear the fifo and delete all overlays
+
+ /*
+ * \brief Flush the overlays.
+ */
+ void Flush();
+
+ void CleanUp(double pts); // validates all overlays against current pts
+ size_t GetSize();
+
+ void UpdateOverlayInfo(const std::shared_ptr<CDVDInputStreamNavigator>& pStream,
+ CDVDDemuxSPU* pSpu,
+ int iAction);
+
+private:
+ VecOverlaysIter Remove(VecOverlaysIter itOverlay); // removes a specific overlay
+
+ VecOverlays m_overlays;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDResource.h b/xbmc/cores/VideoPlayer/DVDResource.h
new file mode 100644
index 0000000..56a1e75
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDResource.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <atomic>
+
+template<typename T> struct IDVDResourceCounted
+{
+ IDVDResourceCounted() : m_refs(1) {}
+ virtual ~IDVDResourceCounted() = default;
+
+ IDVDResourceCounted(const IDVDResourceCounted &) = delete;
+ IDVDResourceCounted &operator=(const IDVDResourceCounted &) = delete;
+
+ virtual T* Acquire()
+ {
+ ++m_refs;
+ return static_cast<T*>(this);
+ }
+
+ virtual long Release()
+ {
+ long count = --m_refs;
+ assert(count >= 0);
+ if (count == 0)
+ delete static_cast<T*>(this);
+ return count;
+ }
+ std::atomic<long> m_refs;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDStreamInfo.cpp b/xbmc/cores/VideoPlayer/DVDStreamInfo.cpp
new file mode 100644
index 0000000..f8f7c71
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDStreamInfo.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDStreamInfo.h"
+
+#include "DVDDemuxers/DVDDemux.h"
+#include "cores/VideoPlayer/Interface/DemuxCrypto.h"
+
+CDVDStreamInfo::CDVDStreamInfo() { extradata = NULL; Clear(); }
+CDVDStreamInfo::CDVDStreamInfo(const CDVDStreamInfo &right, bool withextradata ) { extradata = NULL; Clear(); Assign(right, withextradata); }
+CDVDStreamInfo::CDVDStreamInfo(const CDemuxStream &right, bool withextradata ) { extradata = NULL; Clear(); Assign(right, withextradata); }
+
+CDVDStreamInfo::~CDVDStreamInfo()
+{
+ if( extradata && extrasize ) free(extradata);
+
+ extradata = NULL;
+ extrasize = 0;
+}
+
+
+void CDVDStreamInfo::Clear()
+{
+ codec = AV_CODEC_ID_NONE;
+ type = STREAM_NONE;
+ uniqueId = -1;
+ source = STREAM_SOURCE_NONE;
+ codecOptions = 0;
+ codec_tag = 0;
+ flags = 0;
+ filename.clear();
+ dvd = false;
+
+ if( extradata && extrasize ) free(extradata);
+
+ extradata = NULL;
+ extrasize = 0;
+
+ cryptoSession = nullptr;
+ externalInterfaces = nullptr;
+
+ fpsscale = 0;
+ fpsrate = 0;
+ height = 0;
+ width = 0;
+ aspect = 0.0;
+ vfr = false;
+ stills = false;
+ level = 0;
+ profile = 0;
+ ptsinvalid = false;
+ forced_aspect = false;
+ bitsperpixel = 0;
+ colorSpace = AVCOL_SPC_UNSPECIFIED;
+ colorRange = AVCOL_RANGE_UNSPECIFIED;
+ colorPrimaries = AVCOL_PRI_UNSPECIFIED;
+ colorTransferCharacteristic = AVCOL_TRC_UNSPECIFIED;
+ masteringMetadata = nullptr;
+ contentLightMetadata = nullptr;
+ stereo_mode.clear();
+
+ channels = 0;
+ samplerate = 0;
+ blockalign = 0;
+ bitrate = 0;
+ bitspersample = 0;
+ channellayout = 0;
+
+ orientation = 0;
+ bitdepth = 0;
+}
+
+bool CDVDStreamInfo::Equal(const CDVDStreamInfo& right, int compare)
+{
+ if (codec != right.codec || type != right.type ||
+ ((compare & COMPARE_ID) && uniqueId != right.uniqueId) ||
+ ((compare & COMPARE_ID) && demuxerId != right.demuxerId) || codec_tag != right.codec_tag ||
+ flags != right.flags)
+ return false;
+
+ if (compare & COMPARE_EXTRADATA)
+ {
+ if( extrasize != right.extrasize ) return false;
+ if( extrasize )
+ {
+ if( memcmp(extradata, right.extradata, extrasize) != 0 ) return false;
+ }
+ }
+
+ // VIDEO
+ // clang-format off
+ if (fpsscale != right.fpsscale
+ || fpsrate != right.fpsrate
+ || height != right.height
+ || width != right.width
+ || stills != right.stills
+ || level != right.level
+ || profile != right.profile
+ || ptsinvalid != right.ptsinvalid
+ || forced_aspect != right.forced_aspect
+ || bitsperpixel != right.bitsperpixel
+ || bitdepth != right.bitdepth
+ || vfr != right.vfr
+ || colorSpace != right.colorSpace
+ || colorRange != right.colorRange
+ || colorPrimaries != right.colorPrimaries
+ || colorTransferCharacteristic != right.colorTransferCharacteristic
+ || stereo_mode != right.stereo_mode)
+ return false;
+ // clang-format on
+
+ if (masteringMetadata && right.masteringMetadata)
+ {
+ if (masteringMetadata->has_luminance != right.masteringMetadata->has_luminance
+ || masteringMetadata->has_primaries != right.masteringMetadata->has_primaries)
+ return false;
+
+ if (masteringMetadata->has_primaries)
+ {
+ for (unsigned int i(0); i < 3; ++i)
+ for (unsigned int j(0); j < 2; ++j)
+ if (av_cmp_q(masteringMetadata->display_primaries[i][j], right.masteringMetadata->display_primaries[i][j]))
+ return false;
+ for (unsigned int i(0); i < 2; ++i)
+ if (av_cmp_q(masteringMetadata->white_point[i], right.masteringMetadata->white_point[i]))
+ return false;
+ }
+
+ if (masteringMetadata->has_luminance)
+ {
+ if (av_cmp_q(masteringMetadata->min_luminance, right.masteringMetadata->min_luminance)
+ || av_cmp_q(masteringMetadata->max_luminance, right.masteringMetadata->max_luminance))
+ return false;
+ }
+ }
+ else if (masteringMetadata || right.masteringMetadata)
+ return false;
+
+ if (contentLightMetadata && right.contentLightMetadata)
+ {
+ if (contentLightMetadata->MaxCLL != right.contentLightMetadata->MaxCLL
+ || contentLightMetadata->MaxFALL != right.contentLightMetadata->MaxFALL)
+ return false;
+ }
+ else if (contentLightMetadata || right.contentLightMetadata)
+ return false;
+
+ // AUDIO
+ if( channels != right.channels
+ || samplerate != right.samplerate
+ || blockalign != right.blockalign
+ || bitrate != right.bitrate
+ || bitspersample != right.bitspersample
+ || channellayout != right.channellayout)
+ return false;
+
+ // SUBTITLE
+
+ // Crypto
+ if ((cryptoSession == nullptr) != (right.cryptoSession == nullptr))
+ return false;
+
+ if (cryptoSession && !(*cryptoSession == *right.cryptoSession))
+ return false;
+
+ return true;
+}
+
+bool CDVDStreamInfo::Equal(const CDemuxStream& right, bool withextradata)
+{
+ CDVDStreamInfo info;
+ info.Assign(right, withextradata);
+ return Equal(info, withextradata ? COMPARE_ALL : COMPARE_ALL & ~COMPARE_EXTRADATA);
+}
+
+
+// ASSIGNMENT
+void CDVDStreamInfo::Assign(const CDVDStreamInfo& right, bool withextradata)
+{
+ codec = right.codec;
+ type = right.type;
+ uniqueId = right.uniqueId;
+ demuxerId = right.demuxerId;
+ source = right.source;
+ codec_tag = right.codec_tag;
+ flags = right.flags;
+ filename = right.filename;
+ dvd = right.dvd;
+
+ if( extradata && extrasize ) free(extradata);
+
+ if( withextradata && right.extrasize )
+ {
+ extrasize = right.extrasize;
+ extradata = malloc(extrasize);
+ if (!extradata)
+ return;
+ memcpy(extradata, right.extradata, extrasize);
+ }
+ else
+ {
+ extrasize = 0;
+ extradata = 0;
+ }
+
+ cryptoSession = right.cryptoSession;
+ externalInterfaces = right.externalInterfaces;
+
+ // VIDEO
+ fpsscale = right.fpsscale;
+ fpsrate = right.fpsrate;
+ height = right.height;
+ width = right.width;
+ aspect = right.aspect;
+ stills = right.stills;
+ level = right.level;
+ profile = right.profile;
+ ptsinvalid = right.ptsinvalid;
+ forced_aspect = right.forced_aspect;
+ orientation = right.orientation;
+ bitsperpixel = right.bitsperpixel;
+ bitdepth = right.bitdepth;
+ vfr = right.vfr;
+ codecOptions = right.codecOptions;
+ colorSpace = right.colorSpace;
+ colorRange = right.colorRange;
+ colorPrimaries = right.colorPrimaries;
+ colorTransferCharacteristic = right.colorTransferCharacteristic;
+ masteringMetadata = right.masteringMetadata;
+ contentLightMetadata = right.contentLightMetadata;
+ stereo_mode = right.stereo_mode;
+
+ // AUDIO
+ channels = right.channels;
+ samplerate = right.samplerate;
+ blockalign = right.blockalign;
+ bitrate = right.bitrate;
+ bitspersample = right.bitspersample;
+ channellayout = right.channellayout;
+
+ // SUBTITLE
+}
+
+void CDVDStreamInfo::Assign(const CDemuxStream& right, bool withextradata)
+{
+ Clear();
+
+ codec = right.codec;
+ type = right.type;
+ uniqueId = right.uniqueId;
+ demuxerId = right.demuxerId;
+ source = right.source;
+ codec_tag = right.codec_fourcc;
+ profile = right.profile;
+ level = right.level;
+ flags = right.flags;
+
+ if (withextradata && right.ExtraSize)
+ {
+ extrasize = right.ExtraSize;
+ extradata = malloc(extrasize);
+ if (!extradata)
+ return;
+ memcpy(extradata, right.ExtraData.get(), extrasize);
+ }
+
+ cryptoSession = right.cryptoSession;
+ externalInterfaces = right.externalInterfaces;
+
+ if (right.type == STREAM_AUDIO)
+ {
+ const CDemuxStreamAudio *stream = static_cast<const CDemuxStreamAudio*>(&right);
+ channels = stream->iChannels;
+ samplerate = stream->iSampleRate;
+ blockalign = stream->iBlockAlign;
+ bitrate = stream->iBitRate;
+ bitspersample = stream->iBitsPerSample;
+ channellayout = stream->iChannelLayout;
+ }
+ else if (right.type == STREAM_VIDEO)
+ {
+ const CDemuxStreamVideo *stream = static_cast<const CDemuxStreamVideo*>(&right);
+ fpsscale = stream->iFpsScale;
+ fpsrate = stream->iFpsRate;
+ height = stream->iHeight;
+ width = stream->iWidth;
+ aspect = stream->fAspect;
+ vfr = stream->bVFR;
+ ptsinvalid = stream->bPTSInvalid;
+ forced_aspect = stream->bForcedAspect;
+ orientation = stream->iOrientation;
+ bitsperpixel = stream->iBitsPerPixel;
+ bitdepth = stream->bitDepth;
+ colorSpace = stream->colorSpace;
+ colorRange = stream->colorRange;
+ colorPrimaries = stream->colorPrimaries;
+ colorTransferCharacteristic = stream->colorTransferCharacteristic;
+ masteringMetadata = stream->masteringMetaData;
+ contentLightMetadata = stream->contentLightMetaData;
+ stereo_mode = stream->stereo_mode;
+ }
+ else if (right.type == STREAM_SUBTITLE)
+ {
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDStreamInfo.h b/xbmc/cores/VideoPlayer/DVDStreamInfo.h
new file mode 100644
index 0000000..8a3da27
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDStreamInfo.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemuxers/DVDDemux.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+#define CODEC_FORCE_SOFTWARE 0x01
+#define CODEC_ALLOW_FALLBACK 0x02
+
+class CDemuxStream;
+struct DemuxCryptoSession;
+
+class CDVDStreamInfo
+{
+public:
+ CDVDStreamInfo();
+ CDVDStreamInfo(const CDVDStreamInfo &right, bool withextradata = true);
+ CDVDStreamInfo(const CDemuxStream &right, bool withextradata = true);
+
+ ~CDVDStreamInfo();
+
+ void Clear(); // clears current information
+ bool Equal(const CDVDStreamInfo& right, int compare);
+ bool Equal(const CDemuxStream &right, bool withextradata);
+
+ void Assign(const CDVDStreamInfo &right, bool withextradata);
+ void Assign(const CDemuxStream &right, bool withextradata);
+
+ enum
+ {
+ COMPARE_EXTRADATA = 1,
+ COMPARE_ID = 2,
+ COMPARE_ALL = 3,
+ };
+
+ AVCodecID codec;
+ StreamType type;
+ int uniqueId;
+ int demuxerId = -1;
+ int source{STREAM_SOURCE_NONE};
+ int flags;
+ std::string filename;
+ bool dvd;
+ int codecOptions;
+
+ // VIDEO
+ int fpsscale; // scale of 1001 and a rate of 60000 will result in 59.94 fps
+ int fpsrate;
+ int height; // height of the stream reported by the demuxer
+ int width; // width of the stream reported by the demuxer
+ double aspect; // display aspect as reported by demuxer
+ bool vfr; // variable framerate
+ bool stills; // there may be odd still frames in video
+ int level; // encoder level of the stream reported by the decoder. used to qualify hw decoders.
+ int profile; // encoder profile of the stream reported by the decoder. used to qualify hw decoders.
+ bool ptsinvalid; // pts cannot be trusted (avi's).
+ bool forced_aspect; // aspect is forced from container
+ int orientation; // orientation of the video in degrees counter clockwise
+ int bitsperpixel;
+ int bitdepth;
+ AVColorSpace colorSpace;
+ AVColorRange colorRange;
+ AVColorPrimaries colorPrimaries;
+ AVColorTransferCharacteristic colorTransferCharacteristic;
+ std::shared_ptr<AVMasteringDisplayMetadata> masteringMetadata;
+ std::shared_ptr<AVContentLightMetadata> contentLightMetadata;
+ std::string stereo_mode; // stereoscopic 3d mode
+
+ // AUDIO
+ int channels;
+ int samplerate;
+ int bitrate;
+ int blockalign;
+ int bitspersample;
+ uint64_t channellayout;
+
+ // SUBTITLE
+
+ // CODEC EXTRADATA
+ void* extradata; // extra data for codec to use
+ unsigned int extrasize; // size of extra data
+ unsigned int codec_tag; // extra identifier hints for decoding
+
+ // Crypto initialization Data
+ std::shared_ptr<DemuxCryptoSession> cryptoSession;
+ std::shared_ptr<ADDON::IAddonProvider> externalInterfaces;
+
+ bool operator==(const CDVDStreamInfo& right) { return Equal(right, COMPARE_ALL); }
+ bool operator!=(const CDVDStreamInfo& right) { return !Equal(right, COMPARE_ALL); }
+
+ CDVDStreamInfo& operator=(const CDVDStreamInfo& right)
+ {
+ if (this != &right)
+ Assign(right, true);
+
+ return *this;
+ }
+
+ bool operator==(const CDemuxStream& right)
+ {
+ return Equal(CDVDStreamInfo(right, true), COMPARE_ALL);
+ }
+ bool operator!=(const CDemuxStream& right)
+ {
+ return !Equal(CDVDStreamInfo(right, true), COMPARE_ALL);
+ }
+
+ CDVDStreamInfo& operator=(const CDemuxStream& right)
+ {
+ Assign(right, true);
+ return *this;
+ }
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDSubtitles/CMakeLists.txt
new file mode 100644
index 0000000..16d685a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/CMakeLists.txt
@@ -0,0 +1,33 @@
+set(SOURCES DVDFactorySubtitle.cpp
+ DVDSubtitleLineCollection.cpp
+ DVDSubtitleParserMicroDVD.cpp
+ DVDSubtitleParserMPL2.cpp
+ DVDSubtitleParserSami.cpp
+ DVDSubtitleParserSubrip.cpp
+ DVDSubtitleParserVplayer.cpp
+ DVDSubtitleStream.cpp
+ DVDSubtitlesLibass.cpp
+ DVDSubtitleParserSSA.cpp
+ DVDSubtitleTagMicroDVD.cpp
+ DVDSubtitleTagSami.cpp
+ SubtitleParserWebVTT.cpp
+ SubtitlesAdapter.cpp)
+
+set(HEADERS DVDFactorySubtitle.h
+ DVDSubtitleLineCollection.h
+ DVDSubtitleParser.h
+ DVDSubtitleParserMPL2.h
+ DVDSubtitleParserMicroDVD.h
+ DVDSubtitleParserSSA.h
+ DVDSubtitleParserSami.h
+ DVDSubtitleParserSubrip.h
+ DVDSubtitleParserVplayer.h
+ DVDSubtitleStream.h
+ DVDSubtitleTagMicroDVD.h
+ DVDSubtitleTagSami.h
+ DVDSubtitlesLibass.h
+ SubtitleParserWebVTT.h
+ SubtitlesAdapter.h
+ SubtitlesStyle.h)
+
+core_add_library(dvdsubtitles)
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp
new file mode 100644
index 0000000..5127033
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDFactorySubtitle.h"
+
+#include "DVDSubtitleParserMPL2.h"
+#include "DVDSubtitleParserMicroDVD.h"
+#include "DVDSubtitleParserSSA.h"
+#include "DVDSubtitleParserSami.h"
+#include "DVDSubtitleParserSubrip.h"
+#include "DVDSubtitleParserVplayer.h"
+#include "DVDSubtitleStream.h"
+#include "SubtitleParserWebVTT.h"
+#include "utils/StringUtils.h"
+
+#include <cstring>
+#include <memory>
+
+CDVDSubtitleParser* CDVDFactorySubtitle::CreateParser(std::string& strFile)
+{
+ std::string line;
+ int i;
+
+ std::unique_ptr<CDVDSubtitleStream> pStream(new CDVDSubtitleStream());
+ if(!pStream->Open(strFile))
+ {
+ return nullptr;
+ }
+
+ for (int t = 0; t < 256; t++)
+ {
+ if (pStream->ReadLine(line))
+ {
+ if ((sscanf(line.c_str(), "{%d}{}", &i) == 1) ||
+ (sscanf(line.c_str(), "{%d}{%d}", &i, &i) == 2))
+ {
+ return new CDVDSubtitleParserMicroDVD(std::move(pStream), strFile);
+ }
+ else if (sscanf(line.c_str(), "[%d][%d]", &i, &i) == 2)
+ {
+ return new CDVDSubtitleParserMPL2(std::move(pStream), strFile);
+ }
+ else if (sscanf(line.c_str(), "%d:%d:%d%*c%d --> %d:%d:%d%*c%d", &i, &i, &i, &i, &i, &i, &i,
+ &i) == 8)
+ {
+ return new CDVDSubtitleParserSubrip(std::move(pStream), strFile);
+ }
+ else if (sscanf(line.c_str(), "%d:%d:%d:", &i, &i, &i) == 3)
+ {
+ return new CDVDSubtitleParserVplayer(std::move(pStream), strFile);
+ }
+ else if (!StringUtils::CompareNoCase(line, "!: This is a Sub Station Alpha v", 32) ||
+ !StringUtils::CompareNoCase(line, "ScriptType: v4.00", 17) ||
+ !StringUtils::CompareNoCase(line, "Dialogue: Marked", 16) ||
+ !StringUtils::CompareNoCase(line, "Dialogue: ", 10) ||
+ !StringUtils::CompareNoCase(line, "[Events]", 8))
+ {
+ return new CDVDSubtitleParserSSA(std::move(pStream), strFile);
+ }
+ else if (line == "<SAMI>")
+ {
+ return new CDVDSubtitleParserSami(std::move(pStream), strFile);
+ }
+ else if (!StringUtils::CompareNoCase(line, "WEBVTT", 6))
+ {
+ return new CSubtitleParserWebVTT(std::move(pStream), strFile);
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return nullptr;
+}
+
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.h
new file mode 100644
index 0000000..724c558
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+class CDVDSubtitleParser;
+class CDVDSubtitleStream;
+
+typedef std::vector<std::string> VecSubtitleFiles;
+typedef std::vector<std::string>::iterator VecSubtitleFilesIter;
+
+class CDVDFactorySubtitle
+{
+public:
+ static CDVDSubtitleParser* CreateParser(std::string& strFile);
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.cpp
new file mode 100644
index 0000000..3ef2703
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleLineCollection.h"
+
+#include <stddef.h>
+
+
+CDVDSubtitleLineCollection::CDVDSubtitleLineCollection()
+{
+ m_pHead = NULL;
+ m_pCurrent = NULL;
+ m_pTail = NULL;
+
+ m_iSize = 0;
+}
+
+CDVDSubtitleLineCollection::~CDVDSubtitleLineCollection()
+{
+ Clear();
+}
+
+void CDVDSubtitleLineCollection::Add(CDVDOverlay* pOverlay)
+{
+ ListElement* pElement = new ListElement;
+ pElement->pOverlay = pOverlay;
+ pElement->pNext = NULL;
+
+ if (!m_pHead)
+ {
+ m_pHead = m_pTail = pElement;
+ m_pCurrent = m_pHead;
+ }
+ else
+ {
+ m_pTail->pNext = pElement;
+ m_pTail = pElement;
+ }
+
+ m_iSize++;
+}
+
+void CDVDSubtitleLineCollection::Sort()
+{
+ if (!m_pHead || !m_pHead->pNext)
+ return;
+
+ for (ListElement* p1 = m_pHead; p1->pNext != NULL; p1 = p1->pNext)
+ {
+ for (ListElement* p2 = p1->pNext; p2 != NULL; p2 = p2->pNext)
+ {
+ if (p1->pOverlay->iPTSStartTime > p2->pOverlay->iPTSStartTime)
+ {
+ CDVDOverlay* temp = p1->pOverlay;
+ p1->pOverlay = p2->pOverlay;
+ p2->pOverlay = temp;
+ }
+ }
+ }
+}
+
+CDVDOverlay* CDVDSubtitleLineCollection::Get(double iPts)
+{
+ CDVDOverlay* pOverlay = NULL;
+
+ if (m_pCurrent)
+ {
+ while (m_pCurrent && m_pCurrent->pOverlay->iPTSStopTime < iPts)
+ {
+ m_pCurrent = m_pCurrent->pNext;
+ }
+
+ if (m_pCurrent)
+ {
+ pOverlay = m_pCurrent->pOverlay;
+
+ // advance to the next overlay
+ m_pCurrent = m_pCurrent->pNext;
+ }
+ }
+ return pOverlay;
+}
+
+void CDVDSubtitleLineCollection::Reset()
+{
+ m_pCurrent = m_pHead;
+}
+
+void CDVDSubtitleLineCollection::Clear()
+{
+ ListElement* pElement = NULL;
+
+ while (m_pHead)
+ {
+ pElement = m_pHead;
+ m_pHead = pElement->pNext;
+
+ pElement->pOverlay->Release();
+ delete pElement;
+ }
+
+ m_pTail = NULL;
+ m_pHead = NULL;
+ m_pCurrent = NULL;
+ m_iSize = 0;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.h
new file mode 100644
index 0000000..177fe96
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleLineCollection.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../DVDCodecs/Overlay/DVDOverlay.h"
+
+typedef struct stListElement
+{
+ CDVDOverlay* pOverlay;
+ struct stListElement* pNext;
+
+} ListElement;
+
+class CDVDSubtitleLineCollection
+{
+public:
+ CDVDSubtitleLineCollection();
+ virtual ~CDVDSubtitleLineCollection();
+
+ //void Lock() { EnterCriticalSection(&m_critSection); }
+ //void Unlock() { LeaveCriticalSection(&m_critSection); }
+
+ void Add(CDVDOverlay* pSubtitle);
+ void Sort();
+
+ CDVDOverlay* Get(double iPts = 0LL); // get the first overlay in this fifo
+
+ void Reset();
+
+ void Remove();
+ void Clear();
+ int GetSize() { return m_iSize; }
+
+private:
+ ListElement* m_pHead;
+ ListElement* m_pCurrent;
+ ListElement* m_pTail;
+
+ int m_iSize;
+ //CRITICAL_SECTION m_critSection;
+};
+
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h
new file mode 100644
index 0000000..5a31926
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../DVDCodecs/Overlay/DVDOverlay.h"
+#include "DVDSubtitleLineCollection.h"
+#include "DVDSubtitleStream.h"
+
+#include <memory>
+#include <stdio.h>
+#include <string>
+
+class CDVDStreamInfo;
+
+class CDVDSubtitleParser
+{
+public:
+ virtual ~CDVDSubtitleParser() = default;
+ virtual bool Open(CDVDStreamInfo &hints) = 0;
+ virtual void Dispose() = 0;
+ virtual void Reset() = 0;
+ virtual CDVDOverlay* Parse(double iPts) = 0;
+ virtual const std::string& GetName() const = 0;
+};
+
+class CDVDSubtitleParserCollection
+ : public CDVDSubtitleParser
+{
+public:
+ explicit CDVDSubtitleParserCollection(const std::string& strFile) : m_filename(strFile) {}
+ ~CDVDSubtitleParserCollection() override = default;
+ CDVDOverlay* Parse(double iPts) override
+ {
+ CDVDOverlay* o = m_collection.Get(iPts);
+ if(o == NULL)
+ return o;
+ return o->Clone();
+ }
+ void Reset() override { m_collection.Reset(); }
+ void Dispose() override { m_collection.Clear(); }
+
+protected:
+ CDVDSubtitleLineCollection m_collection;
+ std::string m_filename;
+};
+
+class CDVDSubtitleParserText
+ : public CDVDSubtitleParserCollection
+{
+public:
+ CDVDSubtitleParserText(std::unique_ptr<CDVDSubtitleStream>&& stream,
+ const std::string& filename,
+ const char* name)
+ : CDVDSubtitleParserCollection(filename), m_pStream(std::move(stream)), m_parserName(name)
+ {
+ }
+
+ ~CDVDSubtitleParserText() override = default;
+
+ /*
+ * \brief Returns parser name
+ */
+ const std::string& GetName() const override { return m_parserName; }
+
+protected:
+ using CDVDSubtitleParserCollection::Open;
+ bool Open()
+ {
+ if(m_pStream)
+ {
+ if (m_pStream->Seek(0))
+ return true;
+ }
+ else
+ m_pStream.reset(new CDVDSubtitleStream());
+
+ return m_pStream->Open(m_filename);
+ }
+
+ std::unique_ptr<CDVDSubtitleStream> m_pStream;
+ std::string m_parserName;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.cpp
new file mode 100644
index 0000000..1844535
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserMPL2.h"
+
+#include "DVDStreamInfo.h"
+#include "DVDSubtitleTagMicroDVD.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/RegExp.h"
+
+#include <cstdlib>
+
+CDVDSubtitleParserMPL2::CDVDSubtitleParserMPL2(std::unique_ptr<CDVDSubtitleStream>&& stream,
+ const std::string& filename)
+ : CDVDSubtitleParserText(std::move(stream), filename, "MPL2 Subtitle Parser"),
+ m_framerate(DVD_TIME_BASE / 10.0)
+{
+}
+
+CDVDSubtitleParserMPL2::~CDVDSubtitleParserMPL2()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserMPL2::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ // MPL2 is time-based, with 0.1s accuracy
+ m_framerate = DVD_TIME_BASE / 10.0;
+
+ CRegExp reg;
+ if (!reg.RegComp("\\[([0-9]+)\\]\\[([0-9]+)\\](.+)"))
+ return false;
+ CDVDSubtitleTagMicroDVD TagConv;
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ int pos = reg.RegFind(line);
+ if (pos > -1)
+ {
+ std::string startFrame(reg.GetMatch(1));
+ std::string endFrame(reg.GetMatch(2));
+ std::string text(reg.GetMatch(3));
+
+ double iPTSStartTime = m_framerate * std::atoi(startFrame.c_str());
+ double iPTSStopTime = m_framerate * std::atoi(endFrame.c_str());
+
+ TagConv.ConvertLine(text);
+ AddSubtitle(text, iPTSStartTime, iPTSStopTime);
+ }
+ }
+
+ m_collection.Add(CreateOverlay());
+
+ return true;
+}
+
+void CDVDSubtitleParserMPL2::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.h
new file mode 100644
index 0000000..4887196
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMPL2.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CDVDSubtitleParserMPL2 : public CDVDSubtitleParserText, private CSubtitlesAdapter
+{
+public:
+ CDVDSubtitleParserMPL2(std::unique_ptr<CDVDSubtitleStream>&& stream, const std::string& strFile);
+ ~CDVDSubtitleParserMPL2() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+
+private:
+ double m_framerate;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.cpp
new file mode 100644
index 0000000..1965f8f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserMicroDVD.h"
+
+#include "DVDStreamInfo.h"
+#include "DVDSubtitleTagMicroDVD.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/RegExp.h"
+#include "utils/log.h"
+
+#include <cstdlib>
+
+CDVDSubtitleParserMicroDVD::CDVDSubtitleParserMicroDVD(std::unique_ptr<CDVDSubtitleStream>&& stream,
+ const std::string& filename)
+ : CDVDSubtitleParserText(std::move(stream), filename, "MicroDVD Subtitle Parser"),
+ m_framerate(DVD_TIME_BASE / 25.0)
+{
+}
+
+CDVDSubtitleParserMicroDVD::~CDVDSubtitleParserMicroDVD()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserMicroDVD::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ CLog::Log(LOGDEBUG, "{} - framerate {}:{}", __FUNCTION__, hints.fpsrate, hints.fpsscale);
+ if (hints.fpsscale > 0 && hints.fpsrate > 0)
+ {
+ m_framerate = (double)hints.fpsscale / (double)hints.fpsrate;
+ m_framerate *= DVD_TIME_BASE;
+ }
+ else
+ m_framerate = DVD_TIME_BASE / 25.0;
+
+ CRegExp reg;
+ if (!reg.RegComp("\\{([0-9]+)\\}\\{([0-9]+)\\}(.+)"))
+ return false;
+ CDVDSubtitleTagMicroDVD TagConv;
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ int pos = reg.RegFind(line);
+ if (pos > -1)
+ {
+ std::string startFrame(reg.GetMatch(1));
+ std::string endFrame(reg.GetMatch(2));
+ std::string text(reg.GetMatch(3));
+
+ double iPTSStartTime = m_framerate * std::atoi(startFrame.c_str());
+ double iPTSStopTime = m_framerate * std::atoi(endFrame.c_str());
+
+ TagConv.ConvertLine(text);
+ AddSubtitle(text, iPTSStartTime, iPTSStopTime);
+ }
+ }
+
+ m_collection.Add(CreateOverlay());
+
+ return true;
+}
+
+void CDVDSubtitleParserMicroDVD::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.h
new file mode 100644
index 0000000..3986891
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserMicroDVD.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CDVDSubtitleParserMicroDVD : public CDVDSubtitleParserText, private CSubtitlesAdapter
+{
+public:
+ CDVDSubtitleParserMicroDVD(std::unique_ptr<CDVDSubtitleStream>&& stream,
+ const std::string& strFile);
+ ~CDVDSubtitleParserMicroDVD() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+
+private:
+ double m_framerate;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.cpp
new file mode 100644
index 0000000..1332a80
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserSSA.h"
+
+#include "DVDCodecs/Overlay/DVDOverlaySSA.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SubtitlesSettings.h"
+
+using namespace KODI;
+
+CDVDSubtitleParserSSA::CDVDSubtitleParserSSA(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile)
+ : CDVDSubtitleParserText(std::move(pStream), strFile, "SSA Subtitle Parser"),
+ m_libass(std::make_shared<CDVDSubtitlesLibass>())
+{
+ m_libass->Configure();
+}
+
+CDVDSubtitleParserSSA::~CDVDSubtitleParserSSA()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserSSA::Open(CDVDStreamInfo& hints)
+{
+
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ const std::string& data = m_pStream->GetData();
+ if (!m_libass->CreateTrack(const_cast<char*>(data.c_str()), data.length()))
+ return false;
+
+ CDVDOverlaySSA* overlay = new CDVDOverlaySSA(m_libass);
+ overlay->iPTSStartTime = 0.0;
+ overlay->iPTSStopTime = DVD_NOPTS_VALUE;
+ auto overrideStyles{
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetOverrideStyles()};
+ overlay->SetForcedMargins(overrideStyles != SUBTITLES::OverrideStyles::STYLES_POSITIONS &&
+ overrideStyles != SUBTITLES::OverrideStyles::POSITIONS);
+ m_collection.Add(overlay);
+
+ return true;
+}
+
+void CDVDSubtitleParserSSA::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.h
new file mode 100644
index 0000000..2ccb577
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSSA.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "DVDSubtitlesLibass.h"
+
+#include <memory>
+
+class CDVDSubtitleParserSSA : public CDVDSubtitleParserText
+{
+public:
+ CDVDSubtitleParserSSA(std::unique_ptr<CDVDSubtitleStream>&& pStream, const std::string& strFile);
+ ~CDVDSubtitleParserSSA() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+
+private:
+ std::shared_ptr<CDVDSubtitlesLibass> m_libass;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.cpp
new file mode 100644
index 0000000..2108e55
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserSami.h"
+
+#include "DVDStreamInfo.h"
+#include "DVDSubtitleTagSami.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+CDVDSubtitleParserSami::CDVDSubtitleParserSami(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& filename)
+ : CDVDSubtitleParserText(std::move(pStream), filename, "SAMI Subtitle Parser")
+{
+}
+
+CDVDSubtitleParserSami::~CDVDSubtitleParserSami()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserSami::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ CRegExp regLine(true);
+ if (!regLine.RegComp("<SYNC START=\"?([0-9]+)\"?>(.+)?"))
+ return false;
+ CRegExp regClassID(true);
+ if (!regClassID.RegComp("<P Class=\"?([\\w\\d]+)\"?>"))
+ return false;
+
+ std::string strFileName;
+ std::string strClassID;
+ strFileName = StringUtils::ToLower(URIUtils::GetFileName(m_filename));
+
+ CDVDSubtitleTagSami TagConv;
+ if (!TagConv.Init())
+ return false;
+
+ TagConv.LoadHead(m_pStream.get());
+
+ // If there are more languages contained in a file,
+ // try getting the language class ID that matches the language name
+ // specified in the filename
+ if (TagConv.m_Langclass.size() >= 2)
+ {
+ for (unsigned int i = 0; i < TagConv.m_Langclass.size(); i++)
+ {
+ std::string langName = TagConv.m_Langclass[i].Name;
+ StringUtils::ToLower(langName);
+ if (strFileName.find(langName) != std::string::npos)
+ {
+ strClassID = TagConv.m_Langclass[i].ID;
+ break;
+ }
+ }
+ // No language specified or found, try to select the first class ID
+ if (strClassID.empty() && !(TagConv.m_Langclass.empty()))
+ {
+ strClassID = TagConv.m_Langclass[0].ID;
+ }
+ }
+
+ const char* langClassID{nullptr};
+ if (!strClassID.empty())
+ {
+ StringUtils::ToLower(strClassID);
+ langClassID = strClassID.c_str();
+ }
+
+ int prevSubId = NO_SUBTITLE_ID;
+ double lastPTSStartTime = 0;
+ std::string lastLangClassID;
+ // SAMI synchronization provides only the start time value,
+ // for the stop time it takes in consideration the start time of the next line,
+ // that, could, be an empty string with a "&nbsp;" tag.
+ // Last line could not have the stop time then we set as default 4 secs.
+ int defaultDuration = 4 * DVD_TIME_BASE;
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ // Find the language Class ID in current line (if exist)
+ if (regClassID.RegFind(line) > -1)
+ {
+ lastLangClassID = regClassID.GetMatch(1);
+ StringUtils::ToLower(lastLangClassID);
+ }
+
+ int pos = regLine.RegFind(line);
+ if (pos > -1) // Sync tag found
+ {
+ double currStartTime = static_cast<double>(std::atoi(regLine.GetMatch(1).c_str()));
+ double currPTSStartTime = currStartTime * DVD_TIME_BASE / 1000;
+
+ // We set the duration for the previous line (Event) by using the current start time
+ ChangeSubtitleStopTime(prevSubId, currPTSStartTime);
+
+ // We try to get text after Sync tag (if exists)
+ std::string text = regLine.GetMatch(2);
+ if (text.empty())
+ {
+ prevSubId = NO_SUBTITLE_ID;
+ }
+ else
+ {
+ TagConv.ConvertLine(text, langClassID);
+ TagConv.CloseTag(text);
+ prevSubId = AddSubtitle(text, currPTSStartTime, currPTSStartTime + defaultDuration);
+ }
+
+ lastPTSStartTime = currPTSStartTime;
+ }
+ else
+ {
+ // Lines without Sync tag e.g. for multiple styles or lines,
+ // need to be appended to last line added with sync tag
+ // but they have to match the current language Class ID (if set)
+ if (!strClassID.empty() && strClassID != lastLangClassID)
+ continue;
+
+ std::string text(line);
+ TagConv.ConvertLine(text, langClassID);
+ TagConv.CloseTag(text);
+ if (prevSubId != NO_SUBTITLE_ID)
+ {
+ text.insert(0, "\n");
+ AppendToSubtitle(prevSubId, text.c_str());
+ }
+ else
+ {
+ prevSubId = AddSubtitle(text, lastPTSStartTime, lastPTSStartTime + defaultDuration);
+ }
+ }
+ }
+
+ m_collection.Add(CreateOverlay());
+
+ return true;
+}
+
+void CDVDSubtitleParserSami::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.h
new file mode 100644
index 0000000..a7fe497
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSami.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CRegExp;
+
+class CDVDSubtitleParserSami : public CDVDSubtitleParserText, private CSubtitlesAdapter
+{
+public:
+ CDVDSubtitleParserSami(std::unique_ptr<CDVDSubtitleStream>&& pStream, const std::string& strFile);
+ ~CDVDSubtitleParserSami() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.cpp
new file mode 100644
index 0000000..245c782
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserSubrip.h"
+
+#include "DVDSubtitleTagSami.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/StringUtils.h"
+
+CDVDSubtitleParserSubrip::CDVDSubtitleParserSubrip(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile)
+ : CDVDSubtitleParserText(std::move(pStream), strFile, "SubRip Subtitle Parser")
+{
+}
+
+CDVDSubtitleParserSubrip::~CDVDSubtitleParserSubrip()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserSubrip::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ CDVDSubtitleTagSami TagConv;
+ if (!TagConv.Init())
+ return false;
+
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ StringUtils::Trim(line);
+
+ if (line.length() > 0)
+ {
+ char sep;
+ int hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2;
+ int c = sscanf(line.c_str(), "%d%c%d%c%d%c%d --> %d%c%d%c%d%c%d\n", &hh1, &sep, &mm1, &sep,
+ &ss1, &sep, &ms1, &hh2, &sep, &mm2, &sep, &ss2, &sep, &ms2);
+
+ if (c == 1)
+ {
+ // numbering, skip it
+ }
+ else if (c == 14) // time info
+ {
+ double iPTSStartTime =
+ ((double)(((hh1 * 60 + mm1) * 60) + ss1) * 1000 + ms1) * (DVD_TIME_BASE / 1000);
+ double iPTSStopTime =
+ ((double)(((hh2 * 60 + mm2) * 60) + ss2) * 1000 + ms2) * (DVD_TIME_BASE / 1000);
+
+ std::string convText;
+ while (m_pStream->ReadLine(line))
+ {
+ StringUtils::Trim(line);
+
+ // empty line, next subtitle is about to start
+ if (line.empty())
+ break;
+
+ if (convText.size() > 0)
+ convText += "\n";
+ TagConv.ConvertLine(line);
+ convText += line;
+ }
+
+ if (!convText.empty())
+ {
+ TagConv.CloseTag(convText);
+ AddSubtitle(convText, iPTSStartTime, iPTSStopTime);
+ }
+ }
+ }
+ }
+
+ m_collection.Add(CreateOverlay());
+
+ return true;
+}
+
+void CDVDSubtitleParserSubrip::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.h
new file mode 100644
index 0000000..19099f2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserSubrip.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CDVDSubtitleParserSubrip : public CDVDSubtitleParserText, private CSubtitlesAdapter
+{
+public:
+ CDVDSubtitleParserSubrip(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile);
+ ~CDVDSubtitleParserSubrip() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.cpp
new file mode 100644
index 0000000..d8ca31a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleParserVplayer.h"
+
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+
+#include <cstdlib>
+
+CDVDSubtitleParserVplayer::CDVDSubtitleParserVplayer(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile)
+ : CDVDSubtitleParserText(std::move(pStream), strFile, "VPlayer Subtitle Parser"),
+ m_framerate(DVD_TIME_BASE)
+{
+}
+
+CDVDSubtitleParserVplayer::~CDVDSubtitleParserVplayer()
+{
+ Dispose();
+}
+
+bool CDVDSubtitleParserVplayer::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ // Vplayer subtitles have 1-second resolution
+ m_framerate = DVD_TIME_BASE;
+
+ // Vplayer subtitles don't have StopTime, so we use following subtitle's StartTime
+ // for that, unless gap was more than 4 seconds. Then we set subtitle duration
+ // for 4 seconds, to not have text hanging around in silent scenes...
+ int defaultDuration = 4 * (int)m_framerate;
+
+ CRegExp reg;
+ if (!reg.RegComp("([0-9]+):([0-9]+):([0-9]+):(.+)$"))
+ return false;
+
+ int prevSubId = NO_SUBTITLE_ID;
+ double prevPTSStartTime = 0.;
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ if (reg.RegFind(line) > -1)
+ {
+ int hour = std::atoi(reg.GetMatch(1).c_str());
+ int min = std::atoi(reg.GetMatch(2).c_str());
+ int sec = std::atoi(reg.GetMatch(3).c_str());
+ std::string currText = reg.GetMatch(4);
+
+ double currPTSStartTime = m_framerate * (3600 * hour + 60 * min + sec);
+
+ // We have to set the stop time for the previous line (Event)
+ // by using the start time of the current line,
+ // but if the duration is too long we keep the default 4 secs
+ double PTSDuration = currPTSStartTime - prevPTSStartTime;
+
+ if (PTSDuration < defaultDuration)
+ ChangeSubtitleStopTime(prevSubId, currPTSStartTime);
+
+ // A single line can contain multiple lines split by |
+ StringUtils::Replace(currText, "|", "\n");
+
+ prevSubId = AddSubtitle(currText, currPTSStartTime, currPTSStartTime + defaultDuration);
+
+ prevPTSStartTime = currPTSStartTime;
+ }
+ }
+
+ m_collection.Add(CreateOverlay());
+
+ return true;
+}
+
+void CDVDSubtitleParserVplayer::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.h
new file mode 100644
index 0000000..d49caf3
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParserVplayer.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CDVDSubtitleParserVplayer : public CDVDSubtitleParserText, private CSubtitlesAdapter
+{
+public:
+ CDVDSubtitleParserVplayer(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile);
+ ~CDVDSubtitleParserVplayer() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+
+private:
+ double m_framerate;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.cpp
new file mode 100644
index 0000000..3fbe53f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleStream.h"
+
+#include "DVDInputStreams/DVDFactoryInputStream.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "utils/CharsetConverter.h"
+#include "utils/CharsetDetection.h"
+#include "utils/URIUtils.h"
+#include "utils/Utf8Utils.h"
+#include "utils/log.h"
+
+#include <cstdio>
+#include <cstring>
+#include <memory>
+
+CDVDSubtitleStream::CDVDSubtitleStream() = default;
+
+CDVDSubtitleStream::~CDVDSubtitleStream() = default;
+
+bool CDVDSubtitleStream::Open(const std::string& strFile)
+{
+ CFileItem item(strFile, false);
+ item.SetContentLookup(false);
+ std::shared_ptr<CDVDInputStream> pInputStream(CDVDFactoryInputStream::CreateInputStream(NULL, item));
+ if (pInputStream && pInputStream->Open())
+ {
+ // prepare buffer
+ size_t totalread = 0;
+ std::vector<uint8_t> buf(1024);
+
+ if (URIUtils::HasExtension(strFile, ".sub") && IsIncompatible(pInputStream.get(), buf, &totalread))
+ {
+ CLog::Log(LOGDEBUG,
+ "{}: file {} seems to be a vob sub"
+ "file without an idx file, skipping it",
+ __FUNCTION__, CURL::GetRedacted(pInputStream->GetFileName()));
+ buf.clear();
+ return false;
+ }
+
+ static const size_t chunksize = 64 * 1024;
+
+ int read;
+ do
+ {
+ if (totalread == buf.size())
+ buf.resize(buf.size() + chunksize);
+
+ read = pInputStream->Read(buf.data() + totalread, static_cast<int>(buf.size() - totalread));
+ if (read > 0)
+ totalread += read;
+ } while (read > 0);
+
+ if (!totalread)
+ return false;
+
+ std::string tmpStr(reinterpret_cast<char*>(buf.data()), totalread);
+ buf.clear();
+
+ std::string enc(CCharsetDetection::GetBomEncoding(tmpStr));
+ if (enc == "UTF-8" || (enc.empty() && CUtf8Utils::isValidUtf8(tmpStr)))
+ m_subtitleData = tmpStr;
+ else if (!enc.empty())
+ {
+ std::string converted;
+ g_charsetConverter.ToUtf8(enc, tmpStr, converted);
+ if (converted.empty())
+ return false;
+
+ m_subtitleData = converted;
+ }
+ else
+ {
+ std::string converted;
+ g_charsetConverter.subtitleCharsetToUtf8(tmpStr, converted);
+ if (converted.empty())
+ return false;
+
+ m_subtitleData = converted;
+ }
+
+ m_arrayParser.Reset(m_subtitleData.c_str(), m_subtitleData.size());
+ return true;
+ }
+
+ return false;
+}
+
+bool CDVDSubtitleStream::IsIncompatible(CDVDInputStream* pInputStream,
+ std::vector<uint8_t>& buf,
+ size_t* bytesRead)
+{
+ if (!pInputStream)
+ return true;
+
+ static const uint8_t vobsub[] = { 0x00, 0x00, 0x01, 0xBA };
+
+ int read = pInputStream->Read(buf.data(), static_cast<int>(buf.size()));
+
+ if (read < 0)
+ {
+ return true;
+ }
+ else
+ {
+ *bytesRead = (size_t)read;
+ }
+
+ if (read >= 4)
+ {
+ if (!std::memcmp(buf.data(), vobsub, 4))
+ return true;
+ }
+
+ return false;
+}
+
+std::string CDVDSubtitleStream::Read(int length)
+{
+ return m_arrayParser.ReadNextString(length);
+}
+
+bool CDVDSubtitleStream::Seek(int offset)
+{
+ return m_arrayParser.SetPosition(offset);
+}
+
+bool CDVDSubtitleStream::ReadLine(std::string& line)
+{
+ return m_arrayParser.ReadNextLine(line);
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h
new file mode 100644
index 0000000..78ff9ae
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/CharArrayParser.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+class CDVDInputStream;
+
+// buffered class for subtitle reading
+
+class CDVDSubtitleStream
+{
+public:
+ CDVDSubtitleStream();
+ virtual ~CDVDSubtitleStream();
+
+ bool Open(const std::string& strFile);
+
+ /** \brief Checks if the subtitle associated with the pInputStream
+ * is known to be incompatible, e.g., vob sub files.
+ * \param[in] pInputStream The input stream for the subtitle to check.
+ */
+ bool IsIncompatible(CDVDInputStream* pInputStream, std::vector<uint8_t>& buf, size_t* bytesRead);
+
+ /*!
+ * \brief Read some data of specified length, from the current position
+ * \param length The length of data to be read
+ * \return The string read
+ */
+ std::string Read(int length);
+
+ /*!
+ * \brief Change the current data position to the specified offset
+ * \param offset The new position
+ * \return True if success, otherwise false
+ */
+ bool Seek(int offset);
+
+ /*!
+ * \brief Read a line of data
+ * \param[OUT] line The data read
+ * \return True if read, otherwise false if EOF
+ */
+ bool ReadLine(std::string& line);
+
+ /*!
+ * \brief Get the full data
+ * \return The data
+ */
+ const std::string& GetData() { return m_subtitleData; }
+
+private:
+ std::string m_subtitleData;
+ CCharArrayParser m_arrayParser;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.cpp
new file mode 100644
index 0000000..a63345a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleTagMicroDVD.h"
+
+#include "utils/StringUtils.h"
+
+void CDVDSubtitleTagMicroDVD::ConvertLine(std::string& strUTF8)
+{
+ m_flag[FLAG_BOLD] = 0;
+ m_flag[FLAG_ITALIC] = 0;
+ m_flag[FLAG_UNDERLINE] = 0;
+ m_flag[FLAG_STRIKETHROUGH] = 0;
+ m_flag[FLAG_COLOR] = 0;
+
+ int machine_status = 1;
+ size_t pos = 0;
+
+ while (machine_status > 0)
+ {
+ if (machine_status == 1)
+ {
+ if (strUTF8[pos] == '{')
+ {
+ size_t pos2 = strUTF8.find(':', pos);
+ size_t pos3 = strUTF8.find('}', pos2);
+
+ if ((pos2 != std::string::npos) && (pos3 != std::string::npos))
+ {
+ std::string tagName = strUTF8.substr(pos + 1, pos2 - pos - 1);
+ std::string tagValue = strUTF8.substr(pos2 + 1, pos3 - pos2 - 1);
+ StringUtils::ToLower(tagValue);
+ strUTF8.erase(pos, pos3 - pos + 1);
+ if ((tagName == "Y") || (tagName == "y"))
+ {
+ if ((tagValue == "b") && (m_flag[FLAG_BOLD] == 0))
+ {
+ m_flag[FLAG_BOLD] = (tagName == "Y") ? TAG_ALL_LINE : TAG_ONE_LINE;
+ strUTF8.insert(pos, "{\\b1}");
+ pos += 5;
+ }
+ else if ((tagValue == "i") && (m_flag[FLAG_ITALIC] == 0))
+ {
+ m_flag[FLAG_ITALIC] = (tagName == "Y") ? TAG_ALL_LINE : TAG_ONE_LINE;
+ strUTF8.insert(pos, "{\\i1}");
+ pos += 5;
+ }
+ else if ((tagValue == "u") && (m_flag[FLAG_UNDERLINE] == 0))
+ {
+ m_flag[FLAG_UNDERLINE] = (tagName == "U") ? TAG_ALL_LINE : TAG_ONE_LINE;
+ strUTF8.insert(pos, "{\\u1}");
+ pos += 5;
+ }
+ else if ((tagValue == "s") && (m_flag[FLAG_STRIKETHROUGH] == 0))
+ {
+ m_flag[FLAG_STRIKETHROUGH] = (tagName == "S") ? TAG_ALL_LINE : TAG_ONE_LINE;
+ strUTF8.insert(pos, "{\\s1}");
+ pos += 5;
+ }
+ }
+ else if ((tagName == "C") || (tagName == "c"))
+ {
+ if ((tagValue[0] == '$') && (tagValue.size() == 7))
+ {
+ bool bHex = true;
+ for (int i = 1; i < 7; i++)
+ {
+ char temp = tagValue[i];
+ if (!(('0' <= temp && temp <= '9') || ('a' <= temp && temp <= 'f') ||
+ ('A' <= temp && temp <= 'F')))
+ {
+ bHex = false;
+ break;
+ }
+ }
+
+ if (bHex && (m_flag[FLAG_COLOR] == 0))
+ {
+ m_flag[FLAG_COLOR] = (tagName == "C") ? TAG_ALL_LINE : TAG_ONE_LINE;
+ std::string tempColorTag = "{\\c&H" + tagValue.substr(1, 6) + "&}";
+ strUTF8.insert(pos, tempColorTag);
+ pos += tempColorTag.length();
+ }
+ }
+ }
+ }
+ else
+ machine_status = 2;
+ }
+ else if (strUTF8[pos] == '/')
+ {
+ if (m_flag[FLAG_ITALIC] == 0)
+ {
+ m_flag[FLAG_ITALIC] = TAG_ONE_LINE;
+ strUTF8.insert(pos, "{\\i1}");
+ pos += 5;
+ }
+ else
+ strUTF8.erase(pos, 1);
+ }
+ else
+ machine_status = 2;
+ }
+ else if (machine_status == 2)
+ {
+ size_t pos4;
+ if ((pos4 = strUTF8.find('|', pos)) != std::string::npos)
+ {
+ pos = pos4;
+ if (m_flag[FLAG_BOLD] == TAG_ONE_LINE)
+ {
+ m_flag[FLAG_BOLD] = 0;
+ strUTF8.insert(pos, "{\\b0}");
+ pos += 5;
+ }
+ if (m_flag[FLAG_ITALIC] == TAG_ONE_LINE)
+ {
+ m_flag[FLAG_ITALIC] = 0;
+ strUTF8.insert(pos, "{\\i0}");
+ pos += 5;
+ }
+ if (m_flag[FLAG_UNDERLINE] == TAG_ONE_LINE)
+ {
+ m_flag[FLAG_UNDERLINE] = 0;
+ strUTF8.insert(pos, "{\\u0}");
+ pos += 5;
+ }
+ if (m_flag[FLAG_STRIKETHROUGH] == TAG_ONE_LINE)
+ {
+ m_flag[FLAG_STRIKETHROUGH] = 0;
+ strUTF8.insert(pos, "{\\s0}");
+ pos += 5;
+ }
+ if (m_flag[FLAG_COLOR] == TAG_ONE_LINE)
+ {
+ m_flag[FLAG_COLOR] = 0;
+ strUTF8.insert(pos, "{\\c}");
+ pos += 4;
+ }
+ strUTF8.replace(pos, 1, "\n");
+ pos += 1;
+ machine_status = 1;
+ }
+ else
+ {
+ if (m_flag[FLAG_BOLD] != 0)
+ strUTF8.append("{\\b0}");
+ if (m_flag[FLAG_ITALIC] != 0)
+ strUTF8.append("{\\i0}");
+ if (m_flag[FLAG_UNDERLINE] != 0)
+ strUTF8.append("{\\u0}");
+ if (m_flag[FLAG_STRIKETHROUGH] != 0)
+ strUTF8.append("{\\s0}");
+ if (m_flag[FLAG_COLOR] != 0)
+ strUTF8.append("{\\c}");
+ machine_status = 0;
+ }
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.h
new file mode 100644
index 0000000..224f789
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagMicroDVD.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <string>
+
+#define FLAG_BOLD 0
+#define FLAG_ITALIC 1
+#define FLAG_UNDERLINE 2
+#define FLAG_STRIKETHROUGH 3
+#define FLAG_COLOR 4
+
+#define TAG_ONE_LINE 1
+#define TAG_ALL_LINE 2
+
+
+class CDVDSubtitleTagMicroDVD
+{
+public:
+ CDVDSubtitleTagMicroDVD() { memset(&m_flag, 0, sizeof(m_flag)); }
+ void ConvertLine(std::string& strUTF8);
+
+private:
+ int m_flag[5];
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp
new file mode 100644
index 0000000..654b9ee
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitleTagSami.h"
+
+#include "DVDSubtitleStream.h"
+#include "utils/CharsetConverter.h"
+#include "utils/ColorUtils.h"
+#include "utils/HTMLUtil.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+
+#include <algorithm>
+
+namespace
+{
+
+std::string TranslateColorValue(std::string value)
+{
+ // Get hex color limited to first 6 chars only (e.g. #000000)
+ if (value[0] == '#' && value.size() >= 7)
+ return value.substr(1, 6);
+
+ // Find hex by color name
+ //! @todo: is needed to implement a common way to get color resources
+ //! in order to find the color name on CSS colors list
+ StringUtils::ToLower(value);
+ const auto itHtmlColor = UTILS::COLOR::HTML_BASIC_COLORS.find(value);
+ if (itHtmlColor != UTILS::COLOR::HTML_BASIC_COLORS.cend())
+ return UTILS::COLOR::ConvertoToHexRGB(itHtmlColor->second);
+
+ // Try validate hex color value
+ if (value.size() == 6)
+ {
+ bool isHex = true;
+ for (size_t i = 0; i < 6; i++)
+ {
+ const char currChar = value[i];
+ if (!(('0' <= currChar && currChar <= '9') || ('a' <= currChar && currChar <= 'f') ||
+ ('A' <= currChar && currChar <= 'F')))
+ {
+ isHex = false;
+ break;
+ }
+ }
+ if (isHex)
+ return value;
+ }
+
+ // Fallback to white
+ return "FFFFFF";
+}
+
+} // unnamed namespace
+
+CDVDSubtitleTagSami::~CDVDSubtitleTagSami()
+{
+ delete m_tags;
+ delete m_tagOptions;
+}
+
+bool CDVDSubtitleTagSami::Init()
+{
+ delete m_tags;
+ delete m_tagOptions;
+ m_tags = new CRegExp(true);
+ if (!m_tags->RegComp("(<[^>]*>|\\[nh])"))
+ return false;
+
+ m_tagOptions = new CRegExp(true);
+ if (!m_tagOptions->RegComp("([a-z]+)[ \t]*=[ \t]*(?:[\"'])?([^\"'> ]+)(?:[\"'])?(?:>)?"))
+ return false;
+
+ return true;
+}
+
+void CDVDSubtitleTagSami::ConvertLine(std::string& strUTF8, const char* langClassID)
+{
+ StringUtils::Trim(strUTF8);
+
+ int pos = 0;
+ int del_start = 0;
+ while ((pos = m_tags->RegFind(strUTF8.c_str(), pos)) >= 0)
+ {
+ // Parser for SubRip/SAMI Tags
+ std::string fullTag = m_tags->GetMatch(0);
+ StringUtils::ToLower(fullTag);
+ strUTF8.erase(pos, fullTag.length());
+ if (fullTag == "<b>")
+ {
+ m_flag[FLAG_BOLD] = true;
+ strUTF8.insert(pos, "{\\b1}");
+ pos += 5;
+ }
+ else if ((fullTag == "</b>") && m_flag[FLAG_BOLD])
+ {
+ m_flag[FLAG_BOLD] = false;
+ strUTF8.insert(pos, "{\\b0}");
+ pos += 5;
+ }
+ else if (fullTag == "<i>")
+ {
+ m_flag[FLAG_ITALIC] = true;
+ strUTF8.insert(pos, "{\\i1}");
+ pos += 5;
+ }
+ else if ((fullTag == "</i>") && m_flag[FLAG_ITALIC])
+ {
+ m_flag[FLAG_ITALIC] = false;
+ strUTF8.insert(pos, "{\\i0}");
+ pos += 5;
+ }
+ else if (fullTag == "<u>")
+ {
+ m_flag[FLAG_UNDERLINE] = true;
+ strUTF8.insert(pos, "{\\u1}");
+ pos += 5;
+ }
+ else if ((fullTag == "</u>") && m_flag[FLAG_UNDERLINE])
+ {
+ m_flag[FLAG_UNDERLINE] = false;
+ strUTF8.insert(pos, "{\\u0}");
+ pos += 5;
+ }
+ else if (fullTag == "<s>")
+ {
+ m_flag[FLAG_STRIKETHROUGH] = true;
+ strUTF8.insert(pos, "{\\s1}");
+ pos += 5;
+ }
+ else if ((fullTag == "</s>") && m_flag[FLAG_STRIKETHROUGH])
+ {
+ m_flag[FLAG_STRIKETHROUGH] = false;
+ strUTF8.insert(pos, "{\\s0}");
+ pos += 5;
+ }
+ else if ((fullTag == "</font>") && m_flag[FLAG_COLOR])
+ {
+ m_flag[FLAG_COLOR] = false;
+ strUTF8.insert(pos, "{\\c}");
+ pos += 4;
+ }
+ else if (StringUtils::StartsWith(fullTag, "<font"))
+ {
+ int pos2 = 5;
+ while ((pos2 = m_tagOptions->RegFind(fullTag.c_str(), pos2)) >= 0)
+ {
+ std::string tagOptionName = m_tagOptions->GetMatch(1);
+ std::string tagOptionValue = m_tagOptions->GetMatch(2);
+ pos2 += static_cast<int>(tagOptionName.length() + tagOptionValue.length());
+ if (tagOptionName == "color")
+ {
+ m_flag[FLAG_COLOR] = true;
+
+ std::string colorHex = TranslateColorValue(tagOptionValue);
+ // Convert RGB to BGR
+ std::swap(colorHex[0], colorHex[4]);
+ std::swap(colorHex[1], colorHex[5]);
+
+ std::string colorTag = "{\\c&H" + colorHex + "&}";
+ strUTF8.insert(pos, colorTag);
+ pos += static_cast<int>(colorTag.length());
+ }
+ }
+ }
+ // Parse specific SAMI Tags (all below)
+ else if (langClassID && (StringUtils::StartsWith(fullTag, "<p ")))
+ {
+ int pos2 = 3;
+ while ((pos2 = m_tagOptions->RegFind(fullTag.c_str(), pos2)) >= 0)
+ {
+ std::string tagOptionName = m_tagOptions->GetMatch(1);
+ std::string tagOptionValue = StringUtils::ToLower(m_tagOptions->GetMatch(2));
+ pos2 += static_cast<int>(tagOptionName.length() + tagOptionValue.length());
+ if (tagOptionName == "class")
+ {
+ if (m_flag[FLAG_LANGUAGE])
+ {
+ strUTF8.erase(del_start, pos - del_start);
+ pos = del_start;
+ }
+ if (!tagOptionValue.compare(langClassID))
+ {
+ m_flag[FLAG_LANGUAGE] = false;
+ }
+ else
+ {
+ m_flag[FLAG_LANGUAGE] = true;
+ del_start = pos;
+ }
+ break;
+ }
+ }
+ }
+ else if ((fullTag == "</p>") && m_flag[FLAG_LANGUAGE])
+ {
+ strUTF8.erase(del_start, pos - del_start);
+ pos = del_start;
+ m_flag[FLAG_LANGUAGE] = false;
+ }
+ else if ((fullTag == "\\n") || (StringUtils::StartsWith(fullTag, "<br") && !strUTF8.empty()))
+ {
+ strUTF8.insert(pos, "\n");
+ pos += 1;
+ }
+ // SubRip (.srt) hard space
+ else if (fullTag == "\\h")
+ {
+ // Unicode no-break space
+ strUTF8.insert(pos, "\xC2\xA0");
+ pos += 2;
+ }
+ }
+
+ if (m_flag[FLAG_LANGUAGE])
+ strUTF8.erase(del_start);
+
+ if (strUTF8.empty())
+ return;
+ if (strUTF8 == "&nbsp;") // SAMI specific blank paragraph parameter
+ {
+ strUTF8.clear();
+ return;
+ }
+
+ std::wstring wStrHtml, wStr;
+ g_charsetConverter.utf8ToW(strUTF8, wStrHtml, false);
+ HTML::CHTMLUtil::ConvertHTMLToW(wStrHtml, wStr);
+ g_charsetConverter.wToUTF8(wStr, strUTF8);
+}
+
+void CDVDSubtitleTagSami::CloseTag(std::string& text)
+{
+ if (m_flag[FLAG_BOLD])
+ {
+ m_flag[FLAG_BOLD] = false;
+ text += "{\\b0}";
+ }
+ if (m_flag[FLAG_ITALIC])
+ {
+ m_flag[FLAG_ITALIC] = false;
+ text += "{\\i0}";
+ }
+ if (m_flag[FLAG_UNDERLINE])
+ {
+ m_flag[FLAG_UNDERLINE] = false;
+ text += "{\\u0}";
+ }
+ if (m_flag[FLAG_STRIKETHROUGH])
+ {
+ m_flag[FLAG_STRIKETHROUGH] = false;
+ text += "{\\s0}";
+ }
+ if (m_flag[FLAG_COLOR])
+ {
+ m_flag[FLAG_COLOR] = false;
+ text += "{\\c}";
+ }
+ m_flag[FLAG_LANGUAGE] = false;
+}
+
+void CDVDSubtitleTagSami::LoadHead(CDVDSubtitleStream* samiStream)
+{
+ bool inSTYLE = false;
+ CRegExp reg(true);
+ if (!reg.RegComp("\\.([a-z]+)[ \t]*\\{[ \t]*name:([^;]*?);[ \t]*lang:([^;]*?);[ "
+ "\t]*SAMIType:([^;]*?);[ \t]*\\}"))
+ return;
+
+ std::string line;
+ while (samiStream->ReadLine(line))
+ {
+ StringUtils::Trim(line);
+
+ if (StringUtils::EqualsNoCase(line, "<BODY>"))
+ break;
+ if (inSTYLE)
+ {
+ if (StringUtils::EqualsNoCase(line, "</STYLE>"))
+ break;
+ else
+ {
+ if (reg.RegFind(line.c_str()) > -1)
+ {
+ SLangclass lc;
+ lc.ID = reg.GetMatch(1);
+ lc.Name = reg.GetMatch(2);
+ lc.Lang = reg.GetMatch(3);
+ lc.SAMIType = reg.GetMatch(4);
+ StringUtils::Trim(lc.Name);
+ StringUtils::Trim(lc.Lang);
+ StringUtils::Trim(lc.SAMIType);
+ m_Langclass.push_back(lc);
+ }
+ }
+ }
+ else
+ {
+ if (StringUtils::EqualsNoCase(line, "<STYLE TYPE=\"text/css\">"))
+ inSTYLE = true;
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h
new file mode 100644
index 0000000..433fd63
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#define FLAG_BOLD 0
+#define FLAG_ITALIC 1
+#define FLAG_UNDERLINE 2
+#define FLAG_STRIKETHROUGH 3
+#define FLAG_COLOR 4
+#define FLAG_LANGUAGE 5
+
+class CDVDSubtitleStream;
+class CRegExp;
+
+class CDVDSubtitleTagSami
+{
+public:
+ CDVDSubtitleTagSami()
+ {
+ m_tags = NULL;
+ m_tagOptions = NULL;
+ m_flag[FLAG_BOLD] = false;
+ m_flag[FLAG_ITALIC] = false;
+ m_flag[FLAG_UNDERLINE] = false;
+ m_flag[FLAG_STRIKETHROUGH] = false;
+ m_flag[FLAG_COLOR] = false;
+ m_flag[FLAG_LANGUAGE] = false; //set to true when classID != lang
+ }
+ virtual ~CDVDSubtitleTagSami();
+ bool Init();
+ /*!
+ \brief Convert a subtitle text line.
+ \param line The text line
+ \param len The line length
+ \param langClassID The SAMI Class ID language (keep the language lines with this ID and ignore all others)
+ */
+ void ConvertLine(std::string& strUTF8, const char* langClassID = NULL);
+ void CloseTag(std::string& text);
+ void LoadHead(CDVDSubtitleStream* samiStream);
+
+ typedef struct
+ {
+ std::string ID;
+ std::string Name;
+ std::string Lang;
+ std::string SAMIType;
+ } SLangclass;
+
+ std::vector<SLangclass> m_Langclass;
+
+private:
+ CRegExp* m_tags;
+ CRegExp* m_tagOptions;
+ bool m_flag[6];
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp
new file mode 100644
index 0000000..98d9cdf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DVDSubtitlesLibass.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "filesystem/SpecialProtocol.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SubtitlesSettings.h"
+#include "utils/FontUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cstring>
+#include <mutex>
+
+using namespace KODI::SUBTITLES::STYLE;
+using namespace UTILS;
+
+namespace
+{
+constexpr int ASS_BORDER_STYLE_OUTLINE = 1; // Outline + drop shadow
+constexpr int ASS_BORDER_STYLE_BOX = 3; // Box + drop shadow
+constexpr int ASS_BORDER_STYLE_SQUARE_BOX = 4; // Square box + outline
+
+// Convert RGB/ARGB to RGBA by also applying the opacity value
+COLOR::Color ConvColor(COLOR::Color argbColor, int opacity = 100)
+{
+ return COLOR::ConvertToRGBA(COLOR::ChangeOpacity(argbColor, (100.0f - opacity) / 100.0f));
+}
+
+} // namespace
+
+static void libass_log(int level, const char* fmt, va_list args, void* data)
+{
+ if (level >= 5)
+ return;
+ std::string log = StringUtils::FormatV(fmt, args);
+ CLog::Log(LOGDEBUG, "CDVDSubtitlesLibass: [ass] {}", log);
+}
+
+CDVDSubtitlesLibass::CDVDSubtitlesLibass()
+{
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Using libass version {0:x}", ass_library_version());
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Creating ASS library structure");
+ m_library = ass_library_init();
+ if (!m_library)
+ return;
+
+ ass_set_message_cb(m_library, libass_log, this);
+
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Initializing ASS Renderer");
+
+ m_renderer = ass_renderer_init(m_library);
+
+ if (!m_renderer)
+ throw std::runtime_error("Libass render failed to initialize");
+}
+
+CDVDSubtitlesLibass::~CDVDSubtitlesLibass()
+{
+ if (m_track)
+ ass_free_track(m_track);
+ ass_renderer_done(m_renderer);
+ ass_library_done(m_library);
+}
+
+void CDVDSubtitlesLibass::Configure()
+{
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Initializing ASS library font settings");
+
+ if (!m_renderer)
+ {
+ CLog::Log(LOGERROR, "CDVDSubtitlesLibass: Failed to initialize ASS font settings. ASS renderer "
+ "not initialized.");
+ return;
+ }
+
+ ass_set_margins(m_renderer, 0, 0, 0, 0);
+ ass_set_use_margins(m_renderer, 0);
+
+ // Libass uses system font provider (like fontconfig) by default in some
+ // platforms (e.g. linux/windows), on some other systems like android the
+ // font provider is currenlty not supported, then an user can add his
+ // additionals fonts only by using the user fonts folder.
+ ass_set_fonts_dir(m_library,
+ CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::USER).c_str());
+
+ // Load additional fonts into Libass memory
+ CFileItemList items;
+ // Get fonts from system directory
+ if (XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::SYSTEM))
+ {
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, items,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ }
+ // Get temporary fonts
+ if (XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::TEMP, false))
+ {
+ XFILE::CDirectory::GetDirectory(
+ UTILS::FONT::FONTPATH::TEMP, items, UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_BYPASS_CACHE | XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ }
+ for (const auto& item : items)
+ {
+ if (item->m_bIsFolder)
+ continue;
+ const std::string filepath = item->GetPath();
+ const std::string fileName = item->GetLabel();
+ std::vector<uint8_t> buffer;
+ if (XFILE::CFile().LoadFile(filepath, buffer) <= 0)
+ {
+ CLog::LogF(LOGERROR, "Failed to load file {}", filepath);
+ continue;
+ }
+#if LIBASS_VERSION >= 0x01501000
+ ass_add_font(m_library, fileName.c_str(), reinterpret_cast<const char*>(buffer.data()),
+ static_cast<int>(buffer.size()));
+#else
+ ass_add_font(m_library, const_cast<char*>(fileName.c_str()),
+ reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size()));
+#endif
+ if (StringUtils::CompareNoCase(fileName, FONT::FONT_DEFAULT_FILENAME) == 0)
+ {
+ m_defaultFontFamilyName = FONT::GetFontFamily(buffer);
+ }
+ }
+ if (m_defaultFontFamilyName.empty())
+ {
+ CLog::LogF(LOGERROR,
+ "The application font {} is missing. The default subtitle font cannot be set.",
+ FONT::FONT_DEFAULT_FILENAME);
+ }
+
+ ass_set_fonts(m_renderer,
+ UTILS::FONT::FONTPATH::GetSystemFontPath(FONT::FONT_DEFAULT_FILENAME).c_str(),
+ m_defaultFontFamilyName.c_str(), ASS_FONTPROVIDER_AUTODETECT, nullptr, 1);
+
+ // Extract font must be set before loading ASS/SSA data,
+ // after that cannot be changed
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ bool overrideFont = settings->GetBool(CSettings::SETTING_SUBTITLES_OVERRIDEFONTS);
+ ass_set_extract_fonts(m_library, overrideFont ? 0 : 1);
+}
+
+bool CDVDSubtitlesLibass::DecodeHeader(char* data, int size)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library || !data)
+ return false;
+
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Creating new ASS track");
+ m_track = ass_new_track(m_library);
+
+ ass_process_codec_private(m_track, data, size);
+ return true;
+}
+
+bool CDVDSubtitlesLibass::DecodeDemuxPkt(const char* data, int size, double start, double duration)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - No SSA header found.", __FUNCTION__);
+ return false;
+ }
+
+ //! @bug libass isn't const correct
+ ass_process_chunk(m_track, const_cast<char*>(data), size, DVD_TIME_TO_MSEC(start),
+ DVD_TIME_TO_MSEC(duration));
+ return true;
+}
+
+bool CDVDSubtitlesLibass::CreateTrack()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library)
+ {
+ CLog::Log(LOGERROR, "{} - Failed to create ASS track, library not initialized.", __FUNCTION__);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Creating new ASS track");
+ m_track = ass_new_track(m_library);
+ if (m_track == NULL)
+ {
+ CLog::Log(LOGERROR, "{} - Failed to allocate ASS track.", __FUNCTION__);
+ return false;
+ }
+
+ m_track->track_type = m_track->TRACK_TYPE_ASS;
+ m_track->Timer = 100.;
+ // Set fixed values to PlayRes to allow the use of style override code for positioning
+ m_track->PlayResX = static_cast<int>(VIEWPORT_WIDTH);
+ m_track->PlayResY = static_cast<int>(VIEWPORT_HEIGHT);
+ m_track->Kerning = true; // Font kerning improves the letterspacing
+ m_track->WrapStyle = 1; // The line feed \n doesn't break but wraps (instead \N breaks)
+
+ return true;
+}
+
+bool CDVDSubtitlesLibass::CreateStyle()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library)
+ {
+ CLog::Log(LOGERROR, "{} - Failed to create ASS style, library not initialized.", __FUNCTION__);
+ return false;
+ }
+
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Failed to create ASS style, track not initialized.", __FUNCTION__);
+ return false;
+ }
+
+ m_defaultKodiStyleId = ass_alloc_style(m_track);
+ return true;
+}
+
+bool CDVDSubtitlesLibass::CreateTrack(char* buf, size_t size)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library)
+ {
+ CLog::Log(LOGERROR, "{} - No ASS library struct (m_library)", __FUNCTION__);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDVDSubtitlesLibass: Creating m_track from SSA buffer");
+
+ m_track = ass_read_memory(m_library, buf, size, 0);
+ if (m_track == NULL)
+ return false;
+
+ return true;
+}
+
+ASS_Image* CDVDSubtitlesLibass::RenderImage(double pts,
+ renderOpts opts,
+ bool updateStyle,
+ const std::shared_ptr<struct style>& subStyle,
+ int* changes)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_renderer || !m_track)
+ {
+ CLog::Log(LOGERROR, "{} - ASS renderer/ASS track not initialized.", __FUNCTION__);
+ return nullptr;
+ }
+
+ if (!subStyle)
+ {
+ CLog::Log(LOGERROR, "{} - The subtitle overlay style is not set.", __FUNCTION__);
+ return nullptr;
+ }
+
+ if (updateStyle || m_currentDefaultStyleId == ASS_NO_ID)
+ {
+ ApplyStyle(subStyle, opts);
+ }
+
+ // Reversed par value
+ // from: >1 tighter pixels, <1 wider pixels
+ // to: <1 tighter pixels, >1 wider pixels
+ float par = (opts.m_par - 2.0f) * -1;
+ ass_set_pixel_aspect(m_renderer, static_cast<double>(par));
+
+ ass_set_frame_size(m_renderer, static_cast<int>(opts.frameWidth),
+ static_cast<int>(opts.frameHeight));
+
+ bool useFrameMargins;
+
+ if (m_subtitleType == NATIVE)
+ {
+ ass_set_storage_size(m_renderer, static_cast<int>(opts.sourceWidth),
+ static_cast<int>(opts.sourceHeight));
+ useFrameMargins =
+ opts.marginsMode == MarginsMode::DISABLED || opts.marginsMode == MarginsMode::INSIDE_VIDEO;
+ }
+ else
+ {
+ // Keep storage to default to keep consistent subtitles effects
+ // (like borders) when video resolution change while in playback
+ ass_set_storage_size(m_renderer, 0, 0);
+ useFrameMargins = opts.marginsMode == MarginsMode::INSIDE_VIDEO;
+ }
+
+ int marginTop{0};
+ int marginLeft{0};
+ if (useFrameMargins)
+ {
+ marginTop =
+ static_cast<int>((opts.frameHeight - std::min(opts.videoHeight, opts.frameHeight)) / 2);
+ marginLeft =
+ static_cast<int>((opts.frameWidth - std::min(opts.videoWidth, opts.frameWidth)) / 2);
+ }
+
+ ass_set_margins(m_renderer, marginTop, marginTop, marginLeft, marginLeft);
+ ass_set_use_margins(m_renderer, 0);
+
+ float fontScale{1.0f};
+ if (opts.marginsMode == MarginsMode::INSIDE_VIDEO)
+ {
+ // Make font size relative to window size instead of video,
+ // to show same font size even if the video do not cover in full the
+ // window (e.g. cropped videos, zoom effect) and player add black bars.
+ fontScale *= std::max(opts.frameHeight / opts.videoHeight, 1.0f);
+ }
+
+ ass_set_font_scale(m_renderer, static_cast<double>(fontScale));
+
+ ass_set_line_position(m_renderer, opts.position);
+
+ // For posterity ass_render_frame have an inconsistent rendering for overlapped subtitles cases,
+ // if the playback occurs in sequence (without seeks) the overlapped subtitles lines will be rendered in right order
+ // if you seek forward/backward the video, the overlapped subtitles lines could be rendered in the wrong order
+ // this is a known side effect from libass devs and not a bug from our part
+ return ass_render_frame(m_renderer, m_track, DVD_TIME_TO_MSEC(pts), changes);
+}
+
+void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct style>& subStyle, renderOpts opts)
+{
+ CLog::Log(LOGDEBUG, "{} - Start setting up the LibAss style", __FUNCTION__);
+
+ if (!subStyle)
+ {
+ CLog::Log(LOGERROR, "{} - The subtitle overlay style is not set.", __FUNCTION__);
+ return;
+ }
+
+ // ASS_Style is a POD struct need to be initialized with {}
+ ASS_Style defaultStyle{};
+ ASS_Style* style = nullptr;
+
+ if (m_subtitleType == ADAPTED ||
+ (m_subtitleType == NATIVE &&
+ (subStyle->assOverrideStyles != OverrideStyles::DISABLED || subStyle->assOverrideFont)))
+ {
+ m_currentDefaultStyleId = m_defaultKodiStyleId;
+
+ if (m_subtitleType == NATIVE)
+ {
+ style = &defaultStyle;
+ }
+ else
+ {
+ style = &m_track->styles[m_currentDefaultStyleId];
+ }
+
+ free(style->Name);
+ style->Name = strdup("KodiDefault");
+
+ // Calculate the scale
+ // Font size, borders, etc... are specified in pixel unit in scaled
+ // for a window height of 720, so we need to rescale to our PlayResY
+ double playResY{static_cast<double>(m_track->PlayResY)};
+ double scaleDefault{playResY / 720};
+ double scale{scaleDefault};
+ if (m_subtitleType == NATIVE &&
+ (subStyle->assOverrideStyles == OverrideStyles::STYLES ||
+ subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS ||
+ subStyle->assOverrideFont))
+ {
+ // With styles overridden the PlayResY will be changed to 288
+ scale = 288.0 / 720;
+ }
+
+ // It is mandatory set the FontName, the text is case sensitive
+ free(style->FontName);
+ if (subStyle->fontName == KODI::SUBTITLES::FONT_DEFAULT_FAMILYNAME)
+ style->FontName = strdup(m_defaultFontFamilyName.c_str());
+ else
+ style->FontName = strdup(subStyle->fontName.c_str());
+
+ // Configure the font properties
+ style->FontSize = subStyle->fontSize * scale;
+
+ // Modifies the width/height of the font (1 = 100%)
+ style->ScaleX = 1.0;
+ style->ScaleY = 1.0;
+ // Extra space between characters causes the underlined
+ // text line to become more discontinuous (test on LibAss 15.1)
+ style->Spacing = 0;
+
+ bool isFontBold =
+ (subStyle->fontStyle == FontStyle::BOLD || subStyle->fontStyle == FontStyle::BOLD_ITALIC);
+ bool isFontItalic =
+ (subStyle->fontStyle == FontStyle::ITALIC || subStyle->fontStyle == FontStyle::BOLD_ITALIC);
+ style->Bold = isFontBold * -1;
+ style->Italic = isFontItalic * -1;
+
+ //! @todo Libass has a problem with color transparencies when set to:
+ //! PrimaryColour/SecondaryColour/OutlineColour by causing a gap between border
+ //! and text color. As workaround the SecondaryColour must have no transparency
+ //! this will fix just use cases without transparencies, for a full fix will be
+ //! needed in future update libass library having the gap fix.
+
+ // Set default subtitles color
+ style->PrimaryColour = ConvColor(subStyle->fontColor, subStyle->fontOpacity);
+ // Set secondary colour for karaoke
+ // left part is filled with PrimaryColour, right one with SecondaryColour
+ //! @bug in libass - force secondary color without transparency otherwise
+ //! cause a visible color gap, we also avoid reusing the same primary
+ //! color otherwise the karaoke effect will not be visible.
+ style->SecondaryColour = ConvColor(COLOR::BLACK);
+
+ // Configure the effects
+ double lineSpacing = 0.0;
+ if (subStyle->borderStyle == BorderType::OUTLINE ||
+ subStyle->borderStyle == BorderType::OUTLINE_NO_SHADOW)
+ {
+ style->BorderStyle = ASS_BORDER_STYLE_OUTLINE;
+ style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale;
+ style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity);
+ if (subStyle->borderStyle == BorderType::OUTLINE_NO_SHADOW)
+ {
+ style->BackColour = ConvColor(COLOR::NONE, 0); // Set the shadow color
+ style->Shadow = 0; // Set the shadow size
+ }
+ else
+ {
+ style->BackColour =
+ ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the shadow color
+ style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the shadow size
+ }
+ }
+ else if (subStyle->borderStyle == BorderType::BOX)
+ {
+ // This BorderStyle not support outline color/size
+ style->BorderStyle = ASS_BORDER_STYLE_BOX;
+ style->Outline = 4 * scale; // Space between the text and the box edges
+ style->OutlineColour =
+ ConvColor(subStyle->backgroundColor,
+ subStyle->backgroundOpacity); // Set the background border color
+ style->BackColour =
+ ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the box shadow color
+ style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the box shadow size
+ // By default a box overlaps the other, then we increase a bit the line spacing
+ lineSpacing = 8.0 * scaleDefault;
+ }
+ else if (subStyle->borderStyle == BorderType::SQUARE_BOX)
+ {
+ // This BorderStyle not support shadow color/size
+ style->BorderStyle = ASS_BORDER_STYLE_SQUARE_BOX;
+ style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale;
+ style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity);
+ style->BackColour = ConvColor(subStyle->backgroundColor, subStyle->backgroundOpacity);
+ style->Shadow = 4 * scale; // Space between the text and the box edges
+ }
+
+ // ass_set_line_spacing do not scale, so we have to scale to frame size
+ ass_set_line_spacing(m_renderer,
+ lineSpacing / playResY * static_cast<double>(opts.frameHeight));
+
+ style->Blur = (10.00 / 100 * subStyle->blur);
+
+ // Set the margins (in pixel)
+ if (opts.marginsMode == MarginsMode::DISABLED)
+ {
+ style->MarginL = 0;
+ style->MarginR = 0;
+ style->MarginV = 0;
+ }
+ else
+ {
+ double marginLR = 20;
+ if (opts.horizontalAlignment != HorizontalAlign::DISABLED)
+ {
+ // If the subtitle text is aligned on the left or right
+ // of the screen, we set an extra left/right margin
+ marginLR += static_cast<double>(opts.frameWidth) / 10;
+ }
+ style->MarginL = static_cast<int>(marginLR * scaleDefault);
+ style->MarginR = static_cast<int>(marginLR * scaleDefault);
+ style->MarginV = static_cast<int>(subStyle->marginVertical * scaleDefault);
+ }
+
+ // Set the vertical alignment
+ if (subStyle->alignment == FontAlign::TOP_LEFT ||
+ subStyle->alignment == FontAlign::TOP_CENTER || subStyle->alignment == FontAlign::TOP_RIGHT)
+ style->Alignment = VALIGN_TOP;
+ else if (subStyle->alignment == FontAlign::MIDDLE_LEFT ||
+ subStyle->alignment == FontAlign::MIDDLE_CENTER ||
+ subStyle->alignment == FontAlign::MIDDLE_RIGHT)
+ style->Alignment = VALIGN_CENTER;
+ else if (subStyle->alignment == FontAlign::SUB_LEFT ||
+ subStyle->alignment == FontAlign::SUB_CENTER ||
+ subStyle->alignment == FontAlign::SUB_RIGHT)
+ style->Alignment = VALIGN_SUB;
+
+ // Set the horizontal alignment, giving priority to horizontalFontAlign property when set
+ if (opts.horizontalAlignment == HorizontalAlign::LEFT)
+ style->Alignment |= HALIGN_LEFT;
+ else if (opts.horizontalAlignment == HorizontalAlign::CENTER)
+ style->Alignment |= HALIGN_CENTER;
+ else if (opts.horizontalAlignment == HorizontalAlign::RIGHT)
+ style->Alignment |= HALIGN_RIGHT;
+ else if (subStyle->alignment == FontAlign::TOP_LEFT ||
+ subStyle->alignment == FontAlign::MIDDLE_LEFT ||
+ subStyle->alignment == FontAlign::SUB_LEFT)
+ style->Alignment |= HALIGN_LEFT;
+ else if (subStyle->alignment == FontAlign::TOP_CENTER ||
+ subStyle->alignment == FontAlign::MIDDLE_CENTER ||
+ subStyle->alignment == FontAlign::SUB_CENTER)
+ style->Alignment |= HALIGN_CENTER;
+ else if (subStyle->alignment == FontAlign::TOP_RIGHT ||
+ subStyle->alignment == FontAlign::MIDDLE_RIGHT ||
+ subStyle->alignment == FontAlign::SUB_RIGHT)
+ style->Alignment |= HALIGN_RIGHT;
+ }
+
+ if (m_subtitleType == NATIVE)
+ {
+ ConfigureAssOverride(subStyle, style);
+ m_currentDefaultStyleId = m_track->default_style;
+ }
+}
+
+int CDVDSubtitlesLibass::GetPlayResY()
+{
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - ASS renderer/ASS track not initialized.", __FUNCTION__);
+ return VIEWPORT_HEIGHT;
+ }
+ return m_track->PlayResY;
+}
+
+void CDVDSubtitlesLibass::ConfigureAssOverride(const std::shared_ptr<struct style>& subStyle,
+ ASS_Style* style)
+{
+ if (!subStyle)
+ {
+ CLog::Log(LOGERROR, "{} - The subtitle overlay style is not set.", __FUNCTION__);
+ return;
+ }
+
+ // Default behaviour, disable ASS embedded styles override (if has been changed)
+ int stylesFlags{ASS_OVERRIDE_DEFAULT};
+ if (style)
+ {
+ // Manage override cases with ASS embedded styles
+ if (subStyle->assOverrideStyles == OverrideStyles::STYLES)
+ {
+ stylesFlags = ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES |
+ ASS_OVERRIDE_BIT_BORDER | ASS_OVERRIDE_BIT_MARGINS;
+ }
+ else if (subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS)
+ {
+ stylesFlags = ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES |
+ ASS_OVERRIDE_BIT_BORDER | ASS_OVERRIDE_BIT_MARGINS | ASS_OVERRIDE_BIT_ALIGNMENT;
+ }
+ else if (subStyle->assOverrideStyles == OverrideStyles::POSITIONS)
+ {
+ stylesFlags = ASS_OVERRIDE_BIT_ALIGNMENT | ASS_OVERRIDE_BIT_MARGINS;
+ }
+ if (subStyle->assOverrideFont)
+ {
+ stylesFlags |= ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS | ASS_OVERRIDE_BIT_FONT_NAME;
+ }
+ ass_set_selective_style_override(m_renderer, style);
+ }
+
+ ass_set_selective_style_override_enabled(m_renderer, stylesFlags);
+}
+
+ASS_Event* CDVDSubtitlesLibass::GetEvents()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_track)", __FUNCTION__);
+ return NULL;
+ }
+ return m_track->events;
+}
+
+int CDVDSubtitlesLibass::GetNrOfEvents() const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_track)
+ return 0;
+ return m_track->n_events;
+}
+
+int CDVDSubtitlesLibass::AddEvent(const char* text, double startTime, double stopTime)
+{
+ return AddEvent(text, startTime, stopTime, nullptr);
+}
+
+int CDVDSubtitlesLibass::AddEvent(const char* text,
+ double startTime,
+ double stopTime,
+ subtitleOpts* opts)
+{
+ if (text == NULL || text[0] == '\0')
+ {
+ CLog::Log(LOGDEBUG,
+ "{} - Add event skipped due to empty text (with start time: {}, stop time {})",
+ __FUNCTION__, startTime, stopTime);
+ return ASS_NO_ID;
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library || !m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_library or m_track)", __FUNCTION__);
+ return ASS_NO_ID;
+ }
+
+ int eventId = ass_alloc_event(m_track);
+ if (eventId >= 0)
+ {
+ ASS_Event* event = m_track->events + eventId;
+ event->Start = DVD_TIME_TO_MSEC(startTime);
+ event->Duration = DVD_TIME_TO_MSEC(stopTime - startTime);
+ event->Style = m_defaultKodiStyleId;
+ event->ReadOrder = eventId;
+ event->Text = strdup(text);
+ if (opts && opts->useMargins)
+ {
+ event->MarginL = opts->marginLeft;
+ event->MarginR = opts->marginRight;
+ event->MarginV = opts->marginVertical;
+ }
+ return eventId;
+ }
+ else
+ CLog::Log(LOGERROR, "{} - Cannot allocate a new event", __FUNCTION__);
+ return ASS_NO_ID;
+}
+
+void CDVDSubtitlesLibass::AppendTextToEvent(int eventId, const char* text)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (eventId == ASS_NO_ID || text == NULL || text[0] == '\0')
+ return;
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_track)", __FUNCTION__);
+ return;
+ }
+
+ ASS_Event* assEvents = m_track->events;
+ if (!assEvents)
+ {
+ CLog::Log(LOGERROR, "{} - Failed append text to Event ID {}, there are no Events",
+ __FUNCTION__, eventId);
+ return;
+ }
+
+ ASS_Event* assEvent = (assEvents + eventId);
+ if (assEvent)
+ {
+ size_t buffSize = strlen(assEvent->Text) + strlen(text) + 1;
+ char* appendedText = new char[buffSize];
+ strcpy(appendedText, assEvent->Text);
+ strcat(appendedText, text);
+ free(assEvent->Text);
+ assEvent->Text = strdup(appendedText);
+ delete[] appendedText;
+ }
+}
+
+void CDVDSubtitlesLibass::ChangeEventStopTime(int eventId, double stopTime)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (eventId == ASS_NO_ID)
+ return;
+ if (!m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_track)", __FUNCTION__);
+ return;
+ }
+
+ ASS_Event* assEvents = m_track->events;
+ if (!assEvents)
+ {
+ CLog::Log(LOGERROR, "{} - Failed change stop time to Event ID {}, there are no Events",
+ __FUNCTION__, eventId);
+ return;
+ }
+
+ ASS_Event* assEvent = (assEvents + eventId);
+ if (assEvent)
+ assEvent->Duration = (DVD_TIME_TO_MSEC(stopTime) - assEvent->Start);
+}
+
+void CDVDSubtitlesLibass::FlushEvents()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library || !m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_library or m_track)", __FUNCTION__);
+ return;
+ }
+
+ ass_flush_events(m_track);
+}
+
+int CDVDSubtitlesLibass::DeleteEvents(int nEvents, int threshold)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!m_library || !m_track)
+ {
+ CLog::Log(LOGERROR, "{} - Missing ASS structs (m_library or m_track)", __FUNCTION__);
+ return ASS_NO_ID;
+ }
+
+ if (m_track->n_events == 0)
+ return ASS_NO_ID;
+ if (m_track->n_events < (threshold - nEvents))
+ return m_track->n_events - 1;
+
+ // Currently LibAss do not have delete event method we have to free the events
+ // and reassign all events starting with the first empty position
+ int n = 0;
+ for (; n < nEvents; n++)
+ {
+ ass_free_event(m_track, n);
+ m_track->n_events--;
+ }
+ for (int i = 0; n > 0 && i < threshold; i++)
+ {
+ m_track->events[i] = m_track->events[i + n];
+ }
+ return m_track->n_events - 1;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h
new file mode 100644
index 0000000..67ae044
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "SubtitlesStyle.h"
+#include "threads/CriticalSection.h"
+#include "utils/ColorUtils.h"
+
+#include <memory>
+
+#include <ass/ass.h>
+#include <ass/ass_types.h>
+
+/** Wrapper for Libass **/
+
+static constexpr int ASS_NO_ID = -1;
+
+enum ASSSubType
+{
+ NATIVE = 0,
+ ADAPTED
+};
+
+class CDVDSubtitlesLibass
+{
+public:
+ CDVDSubtitlesLibass();
+ ~CDVDSubtitlesLibass();
+
+ /*!
+ * \brief Configure libass. This method groups any configurations
+ * that might change throughout the lifecycle of libass (e.g. fonts)
+ */
+ void Configure();
+
+ ASS_Image* RenderImage(double pts,
+ KODI::SUBTITLES::STYLE::renderOpts opts,
+ bool updateStyle,
+ const std::shared_ptr<struct KODI::SUBTITLES::STYLE::style>& subStyle,
+ int* changes = NULL);
+
+ ASS_Event* GetEvents();
+
+ /*!
+ * \brief Get the number of events (subtitle entries) in the ASS track
+ * \return The number of events in the ASS track
+ */
+ int GetNrOfEvents() const;
+
+ /*!
+ * \brief Decode Header of ASS/SSA, needed to properly decode
+ * demux packets with DecodeDemuxPkt
+ * \return True if success, false if error
+ */
+ bool DecodeHeader(char* data, int size);
+
+ /*!
+ * \brief Decode ASS/SSA demux packet (depends from DecodeHeader)
+ * \return True if success, false if error
+ */
+ bool DecodeDemuxPkt(const char* data, int size, double start, double duration);
+
+ /*!
+ * \brief Create a new ASS track based on an SSA buffer
+ * \return True if success, false if error
+ */
+ bool CreateTrack(char* buf, size_t size);
+
+ /*!
+ * \brief Flush buffered events
+ */
+ void FlushEvents();
+
+ /*!
+ * \brief Get PlayResY value
+ * \return The PlayResY value of current track
+ */
+ int GetPlayResY();
+
+protected:
+ /*!
+ * \brief Create a new empty ASS track
+ * \return True if success, false if error
+ */
+ bool CreateTrack();
+
+ /*!
+ * \brief Create a new empty ASS style
+ * \return True if success, false if error
+ */
+ bool CreateStyle();
+
+ /*!
+ * \brief Specify whether the subtitles are
+ * native (loaded from ASS/SSA file or stream)
+ * or adapted (converted from other types e.g. SubRip)
+ */
+ void SetSubtitleType(ASSSubType type) { m_subtitleType = type; }
+
+ /*!
+ * \brief Add an ASS event to show a subtitle on a specified time
+ * \param text The subtitle text
+ * \param startTime The PTS start time of the Event
+ * \param stopTime The PTS stop time of the Event
+ * \return Return the Event ID, otherwise ASS_NO_ID if fails
+ */
+ int AddEvent(const char* text, double startTime, double stopTime);
+
+ /*!
+ * \brief Add an ASS event to show a subtitle on a specified time
+ * \param text The subtitle text
+ * \param startTime The PTS start time of the Event
+ * \param stopTime The PTS stop time of the Event
+ * \param opts Subtitle options
+ * \return Return the Event ID, otherwise ASS_NO_ID if fails
+ */
+ int AddEvent(const char* text,
+ double startTime,
+ double stopTime,
+ KODI::SUBTITLES::STYLE::subtitleOpts* opts);
+
+ /*!
+ * \brief Append text to the specified event
+ */
+ void AppendTextToEvent(int eventId, const char* text);
+
+ /*!
+ * \brief Delete old events only if the total number of events reaches the threshold
+ * \param nEvents The number of events to delete
+ * \param threshold Start deleting only when the number of events is reached
+ * \return The updated ID of the last Event, otherwise ASS_NO_ID if error or no events
+ */
+ int DeleteEvents(int nEvents, int threshold);
+
+ /*!
+ * \brief Change the stop time of an Event with the specified time
+ * \param eventId The ASS Event ID
+ * \param stopTime The PTS stop time
+ */
+ void ChangeEventStopTime(int eventId, double stopTime);
+
+ friend class CSubtitlesAdapter;
+
+
+private:
+ void ConfigureAssOverride(const std::shared_ptr<struct KODI::SUBTITLES::STYLE::style>& subStyle,
+ ASS_Style* style);
+ void ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLES::STYLE::style>& subStyle,
+ KODI::SUBTITLES::STYLE::renderOpts opts);
+
+ ASS_Library* m_library = nullptr;
+ ASS_Track* m_track = nullptr;
+ ASS_Renderer* m_renderer = nullptr;
+ mutable CCriticalSection m_section;
+ ASSSubType m_subtitleType{NATIVE};
+
+ // current default style ID of the ASS track
+ int m_currentDefaultStyleId{ASS_NO_ID};
+
+ // default allocated style ID for the kodi user configured subtitle style
+ int m_defaultKodiStyleId{ASS_NO_ID};
+ std::string m_defaultFontFamilyName;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.cpp
new file mode 100644
index 0000000..8481240
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SubtitleParserWebVTT.h"
+
+#include "DVDCodecs/Overlay/DVDOverlay.h"
+#include "SubtitlesStyle.h"
+#include "cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h"
+#include "utils/CharArrayParser.h"
+#include "utils/StringUtils.h"
+
+#include <vector>
+
+using namespace KODI;
+
+CSubtitleParserWebVTT::CSubtitleParserWebVTT(std::unique_ptr<CDVDSubtitleStream>&& pStream,
+ const std::string& strFile)
+ : CDVDSubtitleParserText(std::move(pStream), strFile, "WebVTT Subtitle Parser")
+{
+}
+
+CSubtitleParserWebVTT::~CSubtitleParserWebVTT()
+{
+ Dispose();
+}
+
+bool CSubtitleParserWebVTT::Open(CDVDStreamInfo& hints)
+{
+ if (!CDVDSubtitleParserText::Open())
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ CWebVTTHandler m_webvttHandler;
+
+ if (!m_webvttHandler.Initialize())
+ return false;
+
+ // Get the first chars to check WebVTT signature
+ if (!m_webvttHandler.CheckSignature(m_pStream->Read(10)))
+ return false;
+ m_pStream->Seek(0);
+
+ // Start decoding all lines
+ std::vector<subtitleData> subtitleList;
+ std::string line;
+
+ while (m_pStream->ReadLine(line))
+ {
+ m_webvttHandler.DecodeLine(line, &subtitleList);
+ }
+
+ // We send an empty line to mark the end of the last Cue
+ m_webvttHandler.DecodeLine("", &subtitleList);
+
+ // Send decoded lines to the renderer
+ for (auto& subData : subtitleList)
+ {
+ SUBTITLES::STYLE::subtitleOpts opts;
+ opts.useMargins = subData.useMargins;
+ opts.marginLeft = subData.marginLeft;
+ opts.marginRight = subData.marginRight;
+ opts.marginVertical = subData.marginVertical;
+
+ AddSubtitle(subData.text, subData.startTime, subData.stopTime, &opts);
+ }
+
+ CDVDOverlay* overlay = CreateOverlay();
+ overlay->SetForcedMargins(m_webvttHandler.IsForcedMargins());
+ m_collection.Add(overlay);
+
+ return true;
+}
+
+void CSubtitleParserWebVTT::Dispose()
+{
+ CDVDSubtitleParserCollection::Dispose();
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.h b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.h
new file mode 100644
index 0000000..8bc52cf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitleParserWebVTT.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDSubtitleParser.h"
+#include "SubtitlesAdapter.h"
+
+#include <memory>
+
+class CSubtitleParserWebVTT : public CDVDSubtitleParserText, public CSubtitlesAdapter
+{
+public:
+ CSubtitleParserWebVTT(std::unique_ptr<CDVDSubtitleStream>&& pStream, const std::string& strFile);
+ ~CSubtitleParserWebVTT() override;
+
+ bool Open(CDVDStreamInfo& hints) override;
+ void Dispose() override;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.cpp
new file mode 100644
index 0000000..6ceee88
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SubtitlesAdapter.h"
+
+#include "DVDCodecs/Overlay/DVDOverlay.h"
+#include "DVDCodecs/Overlay/DVDOverlayText.h"
+#include "DVDSubtitlesLibass.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+
+#include <memory>
+
+CSubtitlesAdapter::CSubtitlesAdapter() : m_libass(std::make_shared<CDVDSubtitlesLibass>())
+{
+ m_libass->Configure();
+}
+
+CSubtitlesAdapter::~CSubtitlesAdapter()
+{
+}
+
+bool CSubtitlesAdapter::Initialize()
+{
+ m_libass->SetSubtitleType(ADAPTED);
+ return m_libass->CreateTrack() && m_libass->CreateStyle();
+}
+
+int CSubtitlesAdapter::AddSubtitle(std::string& text, double startTime, double stopTime)
+{
+ return AddSubtitle(text, startTime, stopTime, nullptr);
+}
+
+int CSubtitlesAdapter::AddSubtitle(std::string& text,
+ double startTime,
+ double stopTime,
+ KODI::SUBTITLES::STYLE::subtitleOpts* opts)
+{
+ PostProcess(text);
+ int ret = m_libass->AddEvent(text.c_str(), startTime, stopTime, opts);
+ if (ret == ASS_NO_ID)
+ return NO_SUBTITLE_ID;
+ return ret;
+}
+
+void CSubtitlesAdapter::AppendToSubtitle(int subtitleId, const char* text)
+{
+ if (subtitleId == NO_SUBTITLE_ID)
+ subtitleId = ASS_NO_ID;
+ return m_libass->AppendTextToEvent(subtitleId, text);
+}
+
+int CSubtitlesAdapter::DeleteSubtitles(int nSubtitles, int threshold)
+{
+ int ret = m_libass->DeleteEvents(nSubtitles, threshold);
+ if (ret == ASS_NO_ID)
+ return NO_SUBTITLE_ID;
+ return ret;
+}
+
+void CSubtitlesAdapter::ChangeSubtitleStopTime(int subtitleId, double stopTime)
+{
+ if (subtitleId == NO_SUBTITLE_ID)
+ subtitleId = ASS_NO_ID;
+ return m_libass->ChangeEventStopTime(subtitleId, stopTime);
+}
+
+void CSubtitlesAdapter::FlushSubtitles()
+{
+ // Flush events to avoid display duplicates events e.g. on video seek
+ m_libass->FlushEvents();
+}
+
+CDVDOverlay* CSubtitlesAdapter::CreateOverlay()
+{
+ // Warning with Libass the overlay does not contain image or text then is not tied to Libass Events
+ // any variation of the overlay Start/Stop PTS time will cause problems with the rendering.
+ // The better thing is create a single overlay without PTS stop time to avoid side effects,
+ // maybe this situation could be improved in the future.
+
+ // Side effects that happens when you create each overlay based on each Libass Event:
+ // - A small delay when switching on/off the overlay renderer, cause subtitles
+ // flickering when Events have close timing. A possible cause of the
+ // delay could be the async implementation of overlay management chain.
+ // - When an overlay disable the renderer, Libass has no possibility to
+ // complete the rendering of text animations (not sure if related to previous problem)
+ // with the result of displaying broken animations on the screen.
+ CDVDOverlayText* overlay = new CDVDOverlayText(m_libass);
+ overlay->iPTSStartTime = 0.0;
+ overlay->iPTSStopTime = DVD_NOPTS_VALUE;
+ return overlay;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.h b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.h
new file mode 100644
index 0000000..14b2b89
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "SubtitlesStyle.h"
+
+#include <memory>
+#include <string>
+
+class CDVDOverlay;
+class CDVDSubtitlesLibass;
+
+static constexpr int NO_SUBTITLE_ID = -1;
+
+class CSubtitlesAdapter
+{
+public:
+ CSubtitlesAdapter();
+ virtual ~CSubtitlesAdapter();
+
+ /*!
+ * \brief Initialize the subtitles adapter
+ * \return True if success, false if error
+ */
+ bool Initialize();
+
+ /*!
+ * \brief Add a subtitle
+ * \param text The subtitle text
+ * \param startTime The PTS start time of the subtitle
+ * \param stopTime The PTS stop time of the subtitle
+ * \return Return the subtitle ID, otherwise NO_SUBTITLE_ID if fails
+ */
+ int AddSubtitle(std::string& text, double startTime, double stopTime);
+
+ /*!
+ * \brief Add a subtitle with supplementary options
+ * \param text The subtitle text
+ * \param startTime The PTS start time of the subtitle
+ * \param stopTime The PTS stop time of the subtitle
+ * \param opts Subtitle options
+ * \return Return the subtitle ID, otherwise NO_SUBTITLE_ID if fails
+ */
+ int AddSubtitle(std::string& text,
+ double startTime,
+ double stopTime,
+ KODI::SUBTITLES::STYLE::subtitleOpts* opts);
+
+ /*!
+ * \brief Append text to the specified subtitle ID
+ * \param subtitleId The subtitle ID
+ * \param text The text to append
+ */
+ void AppendToSubtitle(int subtitleId, const char* text);
+
+ /*!
+ * \brief Delete old subtitles only if the total number of subtitles added reaches the threshold
+ * \param nSubtitles The number of subtitles to delete
+ * \param threshold Start deleting only when the number of subtitles is reached
+ * \return The updated ID of the last subtitle, otherwise NO_SUBTITLE_ID if error or no subtitles
+ */
+ int DeleteSubtitles(int nSubtitles, int threshold);
+
+ /*!
+ * \brief Change the stop time of a subtitle ID with the specified time
+ * \param subtitleId The subtitle ID
+ * \param stopTime The PTS stop time
+ */
+ void ChangeSubtitleStopTime(int subtitleId, double stopTime);
+
+ void FlushSubtitles();
+
+ CDVDOverlay* CreateOverlay();
+
+protected:
+ /*!
+ * \brief Post processing of subtitle, will be called before processing
+ * AddSubtitle method
+ * \param text The subtitle text
+ */
+ virtual void PostProcess(std::string& text){};
+
+private:
+ std::shared_ptr<CDVDSubtitlesLibass> m_libass;
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h
new file mode 100644
index 0000000..f276012
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/ColorUtils.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace SUBTITLES
+{
+namespace STYLE
+{
+
+constexpr double VIEWPORT_HEIGHT = 1080.0;
+constexpr double VIEWPORT_WIDTH = 1920.0;
+constexpr int MARGIN_VERTICAL = 75;
+
+enum class HorizontalAlign
+{
+ DISABLED = 0,
+ LEFT,
+ CENTER,
+ RIGHT
+};
+
+enum class FontAlign
+{
+ TOP_LEFT = 0,
+ TOP_CENTER,
+ TOP_RIGHT,
+ MIDDLE_LEFT,
+ MIDDLE_CENTER,
+ MIDDLE_RIGHT,
+ SUB_LEFT,
+ SUB_CENTER,
+ SUB_RIGHT
+};
+
+enum class FontStyle
+{
+ NORMAL = 0,
+ BOLD,
+ ITALIC,
+ BOLD_ITALIC
+};
+
+enum class BorderType
+{
+ OUTLINE_NO_SHADOW,
+ OUTLINE,
+ BOX,
+ SQUARE_BOX
+};
+
+enum class OverrideStyles
+{
+ DISABLED = 0,
+ POSITIONS,
+ STYLES,
+ STYLES_POSITIONS
+};
+
+enum class MarginsMode
+{
+ // Use style margins only
+ DEFAULT,
+ // Apply margins to position text within the video area (cropped videos)
+ INSIDE_VIDEO,
+ // Disable any kind of margin
+ DISABLED
+};
+
+struct style
+{
+ std::string fontName; // Font family name
+ double fontSize; // Font size in pixel
+ FontStyle fontStyle = FontStyle::NORMAL;
+ UTILS::COLOR::Color fontColor = UTILS::COLOR::WHITE;
+ int fontBorderSize = 15; // In %
+ UTILS::COLOR::Color fontBorderColor = UTILS::COLOR::BLACK;
+ int fontOpacity = 100; // In %
+ BorderType borderStyle = BorderType::OUTLINE;
+ UTILS::COLOR::Color backgroundColor = UTILS::COLOR::BLACK;
+ int backgroundOpacity = 0; // In %
+ int shadowSize = 0; // In %
+ UTILS::COLOR::Color shadowColor = UTILS::COLOR::BLACK;
+ int shadowOpacity = 100; // In %
+ FontAlign alignment = FontAlign::TOP_LEFT;
+ // Override styles to native ASS/SSA format type only
+ OverrideStyles assOverrideStyles = OverrideStyles::DISABLED;
+ // Override fonts to native ASS/SSA format type only
+ bool assOverrideFont = false;
+ // Vertical margin value in pixels scaled for VIEWPORT_HEIGHT
+ int marginVertical = MARGIN_VERTICAL;
+ int blur = 0; // In %
+};
+
+struct subtitleOpts
+{
+ bool useMargins = false;
+ int marginLeft;
+ int marginRight;
+ int marginVertical;
+};
+
+struct renderOpts
+{
+ float frameWidth;
+ float frameHeight;
+ // Video size width, may be influenced by video settings (e.g. zoom)
+ float videoWidth;
+ // Video size height, may be influenced by video settings (e.g. zoom)
+ float videoHeight;
+ float sourceWidth;
+ float sourceHeight;
+ float m_par; // Set the pixel aspect ratio
+ MarginsMode marginsMode = MarginsMode::DEFAULT;
+ // Vertical line position of subtitles in percentage
+ // only for bottom alignment, 0 = bottom (no change), 100 = on top
+ double position = 0;
+ HorizontalAlign horizontalAlignment = HorizontalAlign::DISABLED;
+};
+
+} // namespace STYLE
+} // namespace SUBTITLES
+} // namespace KODI
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/CMakeLists.txt
new file mode 100644
index 0000000..0d91326
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES WebVTTHandler.cpp
+ WebVTTISOHandler.cpp)
+
+set(HEADERS WebVTTHandler.h
+ WebVTTISOHandler.h)
+
+core_add_library(subtitles_webvtt)
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp
new file mode 100644
index 0000000..5591550
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp
@@ -0,0 +1,1166 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebVTTHandler.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "filesystem/SpecialProtocol.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SubtitlesSettings.h"
+#include "utils/CSSUtils.h"
+#include "utils/CharsetConverter.h"
+#include "utils/HTMLUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cstring>
+
+// This code follow W3C standard https://www.w3.org/TR/webvtt1/
+// Due to some Libass rendering limits some feature are not fully implemented
+// all these have been documented in the code (like CSS parsing)
+// Other special cases are not supported:
+// - Cue region (text will be displayed at the bottom)
+// - Cue "line" setting as number value
+// - Vertical text (used for some specific asian languages only)
+
+using namespace KODI::SUBTITLES::STYLE;
+
+namespace
+{
+// WebVTT signature
+constexpr const char* signatureCharsBOM = "\xEF\xBB\xBF\x57\x45\x42\x56\x54\x54";
+constexpr const char* signatureChars = "\x57\x45\x42\x56\x54\x54";
+constexpr char signatureLastChars[] = {'\x0A', '\x0D', '\x20', '\x09'};
+
+constexpr char tagPattern[] = "<(\\/)?([^a-zA-Z >]+)?([^\\d:. >]+)?(\\.[^ >]+)?(?> ([^>]+))?>";
+
+constexpr char cueTimePattern[] = "^(?>(\\d{2,}):)?(\\d{2}):(\\d{2}\\.\\d{3})"
+ "[ \\t]*-->[ \\t]*"
+ "(?>(\\d{2,}):)?(\\d{2}):(\\d{2}\\.\\d{3})";
+
+constexpr char timePattern[] = "<(?>(\\d{2,}):)?(\\d{2}):(\\d{2}\\.\\d{3})>";
+
+// Regex patterns for cue properties
+const std::map<std::string, std::string> cuePropsPatterns = {
+ {"position", "position\\:(\\d+|\\d+\\.\\d+|auto)%"},
+ {"positionAlign", "position\\:\\d+\\.\\d+%,([a-z]+)"},
+ {"size", "size\\:((\\d+\\.)?\\d+%?)"},
+ {"line", "line\\:(\\d+%|\\d+\\.\\d+%|-?\\d+|auto)(,|\\s|$)"},
+ {"align", "align\\:([a-z]+)"},
+ {"vertical", "vertical\\:(rl|lr)"},
+ {"snapToLines", "snapToLines\\:(true|false)"}};
+
+constexpr char cueCssTagPattern[] = "::cue\\(([^\\(]+)\\)|(?>(::cue)\\(?\\)?) *{";
+
+const std::map<std::string, std::string> cueCssPatterns = {
+ {"colorName", "color:\\s*([a-zA-Z]+)($|;|\\s|})"},
+ {"colorRGB", "color:\\s?rgba?\\((\\d{1,3},\\d{1,3},\\d{1,3})(,\\d{1,3})?\\)"},
+ {"fontStyle", "font-style:\\s*(italic)($|;|\\s|})"},
+ {"fontWeight", "font-weight:\\s*(bold)($|;|\\s|})"},
+ {"textDecoration", "text-decoration:\\s*(underline)($|;|\\s|})"}};
+
+const std::map<std::string, webvttCssStyle> cueCssDefaultColorClasses = {
+ {".white", {WebvttSelector::CLASS, ".white", "FFFFFF"}},
+ {".lime", {WebvttSelector::CLASS, ".lime", "00FF00"}},
+ {".cyan", {WebvttSelector::CLASS, ".cyan", "00FFFF"}},
+ {".red", {WebvttSelector::CLASS, ".red", "FF0000"}},
+ {".yellow", {WebvttSelector::CLASS, ".yellow", "FFFF00"}},
+ {".magenta", {WebvttSelector::CLASS, ".magenta", "FF00FF"}},
+ {".blue", {WebvttSelector::CLASS, ".blue", "0000FF"}},
+ {".black", {WebvttSelector::CLASS, ".black", "000000"}}};
+
+enum FlagTags
+{
+ FLAG_TAG_BOLD = 0,
+ FLAG_TAG_ITALIC,
+ FLAG_TAG_UNDERLINE,
+ FLAG_TAG_CLASS,
+ FLAG_TAG_COLOR,
+ FLAG_TAG_COUNT
+};
+
+bool ValidateSignature(const std::string& data, const char* signature)
+{
+ const size_t signatureLen = std::strlen(signature);
+ if (data.size() > signatureLen)
+ {
+ if (data.compare(0, signatureLen, signature) == 0)
+ {
+ // Check if last char is valid
+ if (std::strchr(signatureLastChars, data[signatureLen]) != nullptr)
+ return true;
+ }
+ }
+ return false;
+}
+
+void InsertTextPos(std::string& text, const std::string& insert, int& pos)
+{
+ text.insert(pos, insert);
+ pos += static_cast<int>(insert.length());
+}
+
+std::string ConvertStyleToOpenTags(int flagTags[], webvttCssStyle& style)
+{
+ std::string tags;
+ if (style.m_isFontBold)
+ {
+ if (flagTags[FLAG_TAG_BOLD] == 0)
+ tags += "{\\b1}";
+ flagTags[FLAG_TAG_BOLD] += 1;
+ }
+ if (style.m_isFontItalic)
+ {
+ if (flagTags[FLAG_TAG_ITALIC] == 0)
+ tags += "{\\i1}";
+ flagTags[FLAG_TAG_ITALIC] += 1;
+ }
+ if (style.m_isFontUnderline)
+ {
+ if (flagTags[FLAG_TAG_UNDERLINE] == 0)
+ tags += "{\\u1}";
+ flagTags[FLAG_TAG_UNDERLINE] += 1;
+ }
+ if (!style.m_color.empty())
+ {
+ if (flagTags[FLAG_TAG_COLOR] > 0)
+ tags += "{\\c}";
+ flagTags[FLAG_TAG_COLOR] += 1;
+ tags += "{\\c&H" + style.m_color + "&}";
+ }
+ return tags;
+}
+
+std::string ConvertStyleToCloseTags(int flagTags[],
+ webvttCssStyle* style,
+ webvttCssStyle* baseStyle)
+{
+ std::string tags;
+ if (style->m_isFontBold)
+ {
+ flagTags[FLAG_TAG_BOLD] = flagTags[FLAG_TAG_BOLD] > 0 ? (flagTags[FLAG_TAG_BOLD] - 1) : 0;
+ if (flagTags[FLAG_TAG_BOLD] == 0)
+ tags += "{\\b0}";
+ }
+ if (style->m_isFontItalic)
+ {
+ flagTags[FLAG_TAG_ITALIC] = flagTags[FLAG_TAG_ITALIC] > 0 ? (flagTags[FLAG_TAG_ITALIC] - 1) : 0;
+ if (flagTags[FLAG_TAG_ITALIC] == 0)
+ tags += "{\\i0}";
+ }
+ if (style->m_isFontUnderline)
+ {
+ flagTags[FLAG_TAG_UNDERLINE] =
+ flagTags[FLAG_TAG_UNDERLINE] > 0 ? (flagTags[FLAG_TAG_UNDERLINE] - 1) : 0;
+ if (flagTags[FLAG_TAG_UNDERLINE] == 0)
+ tags += "{\\u0}";
+ }
+ if (!style->m_color.empty())
+ {
+ flagTags[FLAG_TAG_COLOR] = flagTags[FLAG_TAG_COLOR] > 0 ? (flagTags[FLAG_TAG_COLOR] - 1) : 0;
+ tags += "{\\c}";
+ if (flagTags[FLAG_TAG_COLOR] > 0 && !baseStyle->m_color.empty())
+ tags += "{\\c&H" + baseStyle->m_color + "&}";
+ }
+ return tags;
+}
+
+void TranslateEscapeChars(std::string& text)
+{
+ if (text.find('&') != std::string::npos)
+ {
+ // The specs says to use unicode
+ // U+200E for "&lrm;" and U+200F for "&rlm;"
+ // but libass rendering assume the text as left-to-right,
+ // to display text in the right order we have to use embedded codes
+ StringUtils::Replace(text, "&lrm;", u8"\u202a");
+ StringUtils::Replace(text, "&rlm;", u8"\u202b");
+ StringUtils::Replace(text, "&#x2068;", u8"\u2068");
+ StringUtils::Replace(text, "&#x2069;", u8"\u2069");
+ StringUtils::Replace(text, "&amp;", "&");
+ StringUtils::Replace(text, "&lt;", "<");
+ StringUtils::Replace(text, "&gt;", ">");
+ StringUtils::Replace(text, "&nbsp;", " ");
+ }
+}
+
+constexpr int MICROS_PER_SECOND = 1000000;
+constexpr int MPEG_TIMESCALE = 90000;
+
+} // unnamed namespace
+
+bool CWebVTTHandler::Initialize()
+{
+ m_currentSection = WebvttSection::UNDEFINED;
+
+ // Load default CSS Style values
+ AddDefaultCssClasses();
+
+ // Compile regex patterns
+ if (!m_tagsRegex.RegComp(tagPattern))
+ return false;
+ if (!m_cueTimeRegex.RegComp(cueTimePattern))
+ return false;
+ if (!m_timeRegex.RegComp(timePattern))
+ return false;
+ if (!m_cueCssTagRegex.RegComp(cueCssTagPattern))
+ return false;
+ for (auto const& item : cuePropsPatterns)
+ {
+ CRegExp reg = CRegExp();
+ if (!reg.RegComp(item.second))
+ return false;
+ m_cuePropsMapRegex.insert({item.first, reg});
+ }
+ for (auto const& item : cueCssPatterns)
+ {
+ CRegExp reg = CRegExp();
+ if (!reg.RegComp(item.second))
+ return false;
+ m_cueCssStyleMapRegex.insert({item.first, reg});
+ }
+
+ auto overrideStyles{
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetOverrideStyles()};
+ m_overridePositions = (overrideStyles == KODI::SUBTITLES::OverrideStyles::STYLES_POSITIONS ||
+ overrideStyles == KODI::SUBTITLES::OverrideStyles::POSITIONS);
+ m_overrideStyle = (overrideStyles == KODI::SUBTITLES::OverrideStyles::STYLES_POSITIONS ||
+ overrideStyles == KODI::SUBTITLES::OverrideStyles::STYLES);
+
+ return true;
+}
+
+void CWebVTTHandler::Reset()
+{
+ m_previousLines[0].clear();
+ m_previousLines[1].clear();
+ m_previousLines[2].clear();
+ m_currentSection = WebvttSection::UNDEFINED;
+ m_offset = 0;
+}
+
+bool CWebVTTHandler::CheckSignature(const std::string& data)
+{
+ // Check the sequence of chars to identify WebVTT signature
+ if (ValidateSignature(data, signatureCharsBOM) || ValidateSignature(data, signatureChars))
+ return true;
+
+ CLog::LogF(LOGERROR, "WebVTT signature not valid");
+ return false;
+}
+
+void CWebVTTHandler::DecodeLine(std::string line, std::vector<subtitleData>* subList)
+{
+ // Keep lines values history, needed to identify the cue ID
+ m_previousLines[0] = m_previousLines[1];
+ m_previousLines[1] = m_previousLines[2];
+ m_previousLines[2] = line;
+
+ if (m_currentSection == WebvttSection::UNDEFINED)
+ {
+ if (line == "STYLE" || line == "Style:") // "Style:" Youtube spec
+ {
+ m_currentSection = WebvttSection::STYLE;
+ return;
+ }
+ else if (line == "REGION")
+ {
+ m_currentSection = WebvttSection::REGION;
+ return;
+ }
+ else if (line == "NOTE")
+ {
+ m_currentSection = WebvttSection::NOTE;
+ }
+ else if (StringUtils::StartsWith(line, "X-TIMESTAMP-MAP")) // HLS streaming spec
+ {
+ // Get the HLS timestamp values to sync the subtitles with video
+ CRegExp regLocal;
+ CRegExp regMpegTs;
+
+ if (regLocal.RegComp("LOCAL:((?:(\\d{1,}):)?(\\d{2}):(\\d{2}\\.\\d{3}))") &&
+ regMpegTs.RegComp("MPEGTS:(\\d+)"))
+ {
+ double tsLocalUs{0.0};
+ double tsMpegUs{0.0};
+
+ if ((regLocal.RegFind(line) >= 0 && regLocal.GetSubCount() == 4) &&
+ regMpegTs.RegFind(line) >= 0)
+ {
+ tsLocalUs = GetTimeFromRegexTS(regLocal, 2);
+ // Converts a 90 kHz clock timestamp to a timestamp in microseconds
+ tsMpegUs = std::stod(regMpegTs.GetMatch(1)) * MICROS_PER_SECOND / MPEG_TIMESCALE;
+ }
+ else
+ {
+ CLog::LogF(LOGERROR,
+ "Failed to get X-TIMESTAMP-MAP values, subtitles could be out of sync");
+ }
+
+ // offset = periodStart + tsMpegUs - tsLocalUs
+ m_offset += tsMpegUs - tsLocalUs;
+ }
+ else
+ {
+ CLog::LogF(LOGERROR,
+ "Failed to compile X-TIMESTAMP-MAP regexes, subtitles could be out of sync");
+ }
+ }
+ else if (IsCueLine(line))
+ {
+ // From here we start the cue conversions,
+ // other sections should not be allowed with exception of "NOTE"
+ m_currentSection = WebvttSection::CUE;
+ }
+ }
+
+ if (m_currentSection == WebvttSection::CUE || m_currentSection == WebvttSection::CUE_TEXT)
+ {
+ if (IsCueLine(line))
+ {
+ if (m_currentSection == WebvttSection::CUE_TEXT && !m_subtitleData.text.empty())
+ {
+ CLog::LogF(LOGWARNING,
+ "Malformed cue, is missing the empty line for the end of cue section");
+
+ // Recover the current cue, add the subtitle to the list
+ ConvertAddSubtitle(subList);
+
+ // Change to a new cue section
+ m_currentSection = WebvttSection::CUE;
+ }
+ else if (m_currentSection == WebvttSection::CUE_TEXT)
+ {
+ CLog::LogF(LOGWARNING, "Malformed cue, the cue is within the text area");
+ return; // Continue to try again for possible subtitle text after this line
+ }
+
+ // Create new subtitle data
+ if (m_currentSection == WebvttSection::CUE)
+ m_subtitleData = subtitleData();
+
+ // Get and store the cue data
+ GetCueData(line);
+
+ // The cue ID is optional, can be a number or a text,
+ // used to identify chapters or to apply a specific CSS Style
+ if (m_previousLines[0].empty() && !m_previousLines[1].empty())
+ m_subtitleData.cueSettings.id = m_previousLines[1];
+
+ // From the next line we should have the text area
+ // NOTE: The cue properties will be computed at the first subtitle text line
+ m_currentSection = WebvttSection::CUE_TEXT;
+ return; // Start read the cue text area from the next line
+ }
+
+ if (m_currentSection == WebvttSection::CUE_TEXT)
+ {
+ if (line.empty()) // An empty line means the end of the current cue
+ {
+ if (m_subtitleData.text.empty())
+ {
+ CLog::LogF(LOGWARNING, "Malformed cue, is missing the subtitle text");
+ m_currentSection = WebvttSection::UNDEFINED;
+ return; // This cue will be skipped
+ }
+
+ ConvertAddSubtitle(subList);
+
+ m_currentSection = WebvttSection::CUE;
+ }
+ else
+ {
+ if (line == "{")
+ {
+ // Starting point for JSON metadata
+ m_currentSection = WebvttSection::CUE_METADATA;
+ return;
+ }
+ // Collect and convert all subtitle text lines
+ if (!m_subtitleData.text.empty())
+ m_subtitleData.text += "\n";
+
+ TranslateEscapeChars(line);
+
+ // We need to calculate the text position right here,
+ // when we have the first subtitle text line
+ if (m_subtitleData.text.empty())
+ CalculateTextPosition(line);
+
+ m_subtitleData.text += line;
+ }
+ }
+ }
+ else if (m_currentSection == WebvttSection::CUE_METADATA)
+ {
+ // Cue metadata is not supported (is for scripted apps)
+ if (line.empty()) // End of section
+ m_currentSection = WebvttSection::UNDEFINED;
+ }
+ else if (m_currentSection == WebvttSection::STYLE ||
+ m_currentSection == WebvttSection::STYLE_CONTENT)
+ {
+ // Non-implemented CSS selector features:
+ // - Attribute selector [lang="xx-yy"] for applicable language
+ // - Pseudo-classes
+ // - Cue region
+ // - Cascading classes for color-background (takes only first color)
+
+ if (line.empty()) // End of section
+ {
+ m_feedCssSelectorNames.clear();
+ m_currentSection = WebvttSection::UNDEFINED;
+ }
+
+ if (m_currentSection == WebvttSection::STYLE)
+ {
+ if (m_feedCssSelectorNames.empty())
+ m_feedCssStyle = webvttCssStyle();
+
+ // Collect cue selectors (also handle multiple inline selectors)
+ for (std::string& cueSelector : StringUtils::Split(line, ','))
+ {
+ if (m_cueCssTagRegex.RegFind(cueSelector) >= 0)
+ {
+ std::string selectorName = m_cueCssTagRegex.GetMatch(m_cueCssTagRegex.GetSubCount());
+ UTILS::CSS::Escape(selectorName);
+ m_feedCssSelectorNames.emplace_back(selectorName);
+ }
+ }
+
+ if (line.find('{') != std::string::npos && !m_feedCssSelectorNames.empty())
+ {
+ // Detect the selector type, from the first selector name
+ std::string_view selectorName = m_feedCssSelectorNames[0];
+
+ if (selectorName == "::cue")
+ m_feedCssStyle.m_selectorType = WebvttSelector::ANY;
+ else if (selectorName[0] == '#')
+ m_feedCssStyle.m_selectorType = WebvttSelector::ID;
+ else if (selectorName.find('.') != std::string::npos)
+ m_feedCssStyle.m_selectorType = WebvttSelector::CLASS;
+ else if (selectorName.compare(0, 9, "lang[lang") == 0 ||
+ selectorName.compare(0, 7, "v[voice") == 0)
+ m_feedCssStyle.m_selectorType = WebvttSelector::ATTRIBUTE;
+ else if (selectorName[0] == ':') // Pseudo-classes not implemented
+ m_feedCssStyle.m_selectorType = WebvttSelector::UNSUPPORTED;
+ else
+ m_feedCssStyle.m_selectorType = WebvttSelector::TYPE;
+
+ if (m_feedCssStyle.m_selectorType == WebvttSelector::UNSUPPORTED)
+ {
+ m_feedCssSelectorNames.clear();
+ m_currentSection = WebvttSection::UNDEFINED;
+ return;
+ }
+
+ // Go through to recover possible data inline with the selector
+ m_currentSection = WebvttSection::STYLE_CONTENT;
+ }
+ }
+
+ if (m_currentSection == WebvttSection::STYLE_CONTENT)
+ {
+ // Get and store the CSS Style properties
+ // Font color
+ const std::string colorName = GetCueCssValue("colorName", line);
+ if (!colorName.empty()) // From CSS Color name
+ {
+ if (!m_CSSColorsLoaded) // Load colors in lazy way
+ LoadColors();
+
+ auto colorInfo =
+ std::find_if(m_CSSColors.begin(), m_CSSColors.end(),
+ [&](const std::pair<std::string, UTILS::COLOR::ColorInfo>& item) {
+ return StringUtils::CompareNoCase(item.first, colorName) == 0;
+ });
+ if (colorInfo != m_CSSColors.end())
+ {
+ const uint32_t color = UTILS::COLOR::ConvertToBGR(colorInfo->second.colorARGB);
+ m_feedCssStyle.m_color = StringUtils::Format("{:6x}", color);
+ }
+ }
+ std::string colorRGB = GetCueCssValue("colorRGB", line);
+ if (!colorRGB.empty()) // From CSS Color numeric R,G,B values
+ {
+ const auto intValues = StringUtils::Split(colorRGB, ",");
+ uint32_t color = UTILS::COLOR::ConvertIntToRGB(
+ std::stoi(intValues[2]), std::stoi(intValues[1]), std::stoi(intValues[0]));
+ m_feedCssStyle.m_color = StringUtils::Format("{:6x}", color);
+ }
+ // Font bold
+ if (!GetCueCssValue("fontWeight", line).empty())
+ m_feedCssStyle.m_isFontBold = true;
+ // Font italic
+ if (!GetCueCssValue("fontStyle", line).empty())
+ m_feedCssStyle.m_isFontItalic = true;
+ // Font underline
+ if (!GetCueCssValue("textDecoration", line).empty())
+ m_feedCssStyle.m_isFontUnderline = true;
+
+ if (line.find('}') != std::string::npos || line.empty()) // End of current style
+ {
+ // Store the style
+ // Overwrite existing selectors to allow authors to change default classes
+ auto& selectorTypeMap = m_cssSelectors[m_feedCssStyle.m_selectorType];
+ // For multiple selectors, copy the style for each one
+ for (std::string_view selectorName : m_feedCssSelectorNames)
+ {
+ webvttCssStyle cssStyleCopy = m_feedCssStyle;
+ // Convert CSS syntax to WebVTT syntax selector
+ std::string selectorNameConv{selectorName};
+ if (m_feedCssStyle.m_selectorType == WebvttSelector::ATTRIBUTE)
+ {
+ selectorNameConv = selectorName.substr(0, selectorName.find('['));
+ selectorNameConv += " ";
+ const size_t attribPosStart = selectorName.find('"');
+ const size_t attribPosEnd = selectorName.find_last_of('"');
+ if (attribPosEnd > attribPosStart)
+ {
+ selectorNameConv +=
+ selectorName.substr(attribPosStart + 1, attribPosEnd - 1 - attribPosStart);
+ }
+ }
+ else if (m_feedCssStyle.m_selectorType == WebvttSelector::ID)
+ selectorNameConv.erase(0, 1); // Remove # char
+
+ cssStyleCopy.m_selectorName = selectorNameConv;
+ selectorTypeMap[selectorNameConv] = cssStyleCopy;
+ }
+ m_feedCssSelectorNames.clear();
+
+ // Let's go back to "STYLE" to parse multiple "::cue" on the same section
+ m_currentSection = WebvttSection::STYLE;
+ }
+ }
+ }
+ else if (m_currentSection == WebvttSection::REGION)
+ {
+ // Regions are not supported
+ if (line.empty()) // End of section
+ m_currentSection = WebvttSection::UNDEFINED;
+ }
+ else if (m_currentSection == WebvttSection::NOTE)
+ {
+ if (line.empty()) // End of section
+ m_currentSection = WebvttSection::UNDEFINED;
+ }
+}
+
+bool CWebVTTHandler::IsCueLine(std::string& line)
+{
+ return m_cueTimeRegex.RegFind(line) >= 0;
+}
+
+void CWebVTTHandler::GetCueData(std::string& cueText)
+{
+ std::string cueSettings;
+ // Valid time formats: (included with or without spaces near -->)
+ // 00:00.000 --> 00:00.000
+ // 00:00:00.000 --> 00:00:00.000
+ if (m_cueTimeRegex.GetSubCount() == 6)
+ {
+ m_subtitleData.startTime = GetTimeFromRegexTS(m_cueTimeRegex) + m_offset;
+ m_subtitleData.stopTime = GetTimeFromRegexTS(m_cueTimeRegex, 4) + m_offset;
+ cueSettings =
+ cueText.substr(m_cueTimeRegex.GetFindLen(), cueText.length() - m_cueTimeRegex.GetFindLen());
+ StringUtils::Trim(cueSettings);
+ }
+ else // This should never happen
+ {
+ CLog::LogF(LOGERROR, "Cue timing not found");
+ }
+
+ // Parse the cue settings
+ GetCueSettings(cueSettings);
+}
+
+void CWebVTTHandler::GetCueSettings(std::string& cueSettings)
+{
+ webvttCueSettings settings;
+ // settings.regionId = ""; // "region" is not supported
+
+ const std::string cueVertical =
+ GetCueSettingValue("vertical", cueSettings, ""); // Ref. Writing direction
+ if (cueVertical == "lr")
+ settings.verticalAlign = WebvttVAlign::VERTICAL_LR;
+ else if (cueVertical == "rl")
+ settings.verticalAlign = WebvttVAlign::VERTICAL_RL;
+ else
+ settings.verticalAlign = WebvttVAlign::HORIZONTAL;
+
+ const std::string cuePos = GetCueSettingValue("position", cueSettings, "auto");
+ if (cuePos == "auto")
+ settings.position.isAuto = true;
+ else
+ {
+ settings.position.isAuto = false;
+ settings.position.value = std::stod(cuePos.c_str());
+ if (settings.position.value > 100) // Not valid
+ settings.position.value = 50;
+ }
+
+ const std::string cuePosAlign = GetCueSettingValue("positionAlign", cueSettings, "auto");
+ if (cuePosAlign == "line-left")
+ settings.positionAlign = WebvttAlign::LEFT;
+ else if (cuePosAlign == "line-right")
+ settings.positionAlign = WebvttAlign::RIGHT;
+ else if (cuePosAlign == "center" || cuePosAlign == "middle") // "middle" undocumented
+ settings.positionAlign = WebvttAlign::CENTER;
+ else if (cuePosAlign == "start") // Undocumented
+ settings.positionAlign = WebvttAlign::START;
+ else if (cuePosAlign == "end") // Undocumented
+ settings.positionAlign = WebvttAlign::END;
+ else
+ settings.positionAlign = WebvttAlign::AUTO;
+
+ const std::string cueSize = GetCueSettingValue("size", cueSettings, "100.00");
+ settings.size = std::stod(cueSize.c_str());
+ if (settings.size > 100.0) // Not valid
+ settings.size = 100.0;
+
+ const std::string cueSnapToLines = GetCueSettingValue("snapToLines", cueSettings, "true");
+ settings.snapToLines = cueSnapToLines == "true";
+
+ std::string cueLine = GetCueSettingValue("line", cueSettings, "auto");
+ settings.line.isAuto = false;
+ auto cueLinePercPos = cueLine.find('%');
+ if (cueLine == "auto")
+ {
+ settings.line.isAuto = true;
+ cueLine = "0";
+ }
+ else if (cueLinePercPos != std::string::npos || !settings.snapToLines)
+ {
+ settings.snapToLines = false;
+ if (cueLinePercPos != std::string::npos)
+ cueLine.pop_back(); // Remove % at the end
+ }
+ settings.line.value = std::stod(cueLine.c_str());
+
+ // The optional "alignment" property of "line" setting is not supported.
+
+ const std::string cueAlign = GetCueSettingValue("align", cueSettings, "");
+ if (cueAlign == "left")
+ settings.align = WebvttAlign::LEFT;
+ else if (cueAlign == "right")
+ settings.align = WebvttAlign::RIGHT;
+ else if (cueAlign == "center" || cueAlign == "middle") // Middle undocumented
+ settings.align = WebvttAlign::CENTER;
+ else if (cueAlign == "start")
+ settings.align = WebvttAlign::START;
+ else if (cueAlign == "end")
+ settings.align = WebvttAlign::END;
+ else
+ settings.align = WebvttAlign::CENTER;
+
+ m_subtitleData.cueSettings = settings;
+}
+
+void CWebVTTHandler::CalculateTextPosition(std::string& subtitleText)
+{
+ // Here we cannot handle a kind of cue box, to simulate it we use the margins
+ // to position the text on the screen, the subtitle text (horizontal)
+ // alignment will be also computed.
+
+ // Sidenote: Limits to bidi direction checks, the webvtt doc specifies that
+ // each line of text should be aligned according to the direction of text,
+ // but here we limit this by checking the text direction of the first line
+ // only, and will be used for all lines of the Cue text.
+
+ int marginLeft = 0;
+ int marginRight = 0;
+ int marginVertical = 0;
+ TextAlignment textAlign;
+
+ webvttCueSettings* settings = &m_subtitleData.cueSettings;
+
+ // Compute cue box "align" value
+ if (settings->align == WebvttAlign::START)
+ {
+ // Clean text from tags or bidi check doesn't work
+ std::string textNoTags = subtitleText;
+ HTML::CHTMLUtil::RemoveTags(textNoTags);
+ if (CCharsetConverter::utf8IsRTLBidiDirection(textNoTags))
+ settings->align = WebvttAlign::RIGHT;
+ else
+ settings->align = WebvttAlign::LEFT;
+ }
+ else if (settings->align == WebvttAlign::END)
+ {
+ // Clean text from tags or bidi check doesn't work
+ std::string textNoTags = subtitleText;
+ HTML::CHTMLUtil::RemoveTags(textNoTags);
+ if (CCharsetConverter::utf8IsRTLBidiDirection(textNoTags))
+ settings->align = WebvttAlign::LEFT;
+ else
+ settings->align = WebvttAlign::RIGHT;
+ }
+
+ // Compute cue box "position" value
+ if (settings->position.isAuto)
+ {
+ // Position of cue box depends from text alignment
+ if (settings->align == WebvttAlign::LEFT)
+ settings->position.value = 0;
+ else if (settings->align == WebvttAlign::RIGHT)
+ settings->position.value = 100;
+ else
+ settings->position.value = 50;
+ }
+ int cuePosPixel = static_cast<int>((VIEWPORT_WIDTH / 100) * settings->position.value);
+
+ // Compute cue box "position alignment" value
+ if (settings->positionAlign == WebvttAlign::AUTO)
+ {
+ if (settings->align == WebvttAlign::LEFT)
+ settings->positionAlign = WebvttAlign::LEFT; // line-left
+ else if (settings->align == WebvttAlign::RIGHT)
+ settings->positionAlign = WebvttAlign::RIGHT; // line-right
+ else
+ settings->positionAlign = WebvttAlign::CENTER;
+ }
+ else if (settings->positionAlign == WebvttAlign::START) // Undocumented
+ {
+ // Is not clear if bidi check here is needed
+ // Clean text from tags or bidi check doesn't work
+ std::string textNoTags = subtitleText;
+ HTML::CHTMLUtil::RemoveTags(textNoTags);
+ if (CCharsetConverter::utf8IsRTLBidiDirection(textNoTags))
+ settings->positionAlign = WebvttAlign::RIGHT; // line-right
+ else
+ settings->positionAlign = WebvttAlign::LEFT; // line-left
+ }
+ else if (settings->positionAlign == WebvttAlign::END) // Undocumented
+ {
+ // Is not clear if bidi check here is needed
+ // Clean text from tags or bidi check doesn't work
+ std::string textNoTags = subtitleText;
+ HTML::CHTMLUtil::RemoveTags(textNoTags);
+ if (CCharsetConverter::utf8IsRTLBidiDirection(textNoTags))
+ settings->positionAlign = WebvttAlign::LEFT; // line-left
+ else
+ settings->positionAlign = WebvttAlign::RIGHT; // line-right
+ }
+
+ // Compute cue box "size" value
+ int cueSizePixel = static_cast<int>((VIEWPORT_WIDTH / 100) * settings->size);
+
+ // Calculate Left/Right margins,
+ // by taking into account cue box "position alignment" and cue box "size"
+ if (settings->positionAlign == WebvttAlign::LEFT) // line-left
+ {
+ marginLeft = cuePosPixel;
+ marginRight = static_cast<int>(VIEWPORT_WIDTH - (cuePosPixel + cueSizePixel));
+ }
+ else if (settings->positionAlign == WebvttAlign::RIGHT) // line-right
+ {
+ marginLeft = static_cast<int>(cuePosPixel - cueSizePixel);
+ marginRight = static_cast<int>(VIEWPORT_WIDTH - cuePosPixel);
+ }
+ else if (settings->positionAlign == WebvttAlign::CENTER)
+ {
+ int cueHalfSize = static_cast<int>(static_cast<double>(cueSizePixel) / 2);
+ marginLeft = cuePosPixel - cueHalfSize;
+ marginRight = static_cast<int>(VIEWPORT_WIDTH - (cuePosPixel + cueHalfSize));
+ }
+
+ // Compute cue box "line"
+ double cueLinePerc = 100.00;
+ if (settings->snapToLines) // Numeric line value
+ {
+ // "line" as numeric value is not supported.
+ // From docs is specified to calculate the line position
+ // by the height of the first line of text, but is not clear:
+ // 1) how we can calculate the position when the text size is not fixed
+ // 2) what is the max value and if can change between webvtt files
+ // ref. https://www.w3.org/TR/webvtt1/#webvtt-line-cue-setting
+ }
+ else // Percentage line value
+ {
+ if (settings->line.value < 0.00)
+ cueLinePerc = 0;
+ else if (settings->line.value > 100.00)
+ cueLinePerc = 100;
+ else
+ cueLinePerc = settings->line.value;
+ }
+
+ // The vertical margin should always referred from the top to simulate
+ // the current cue box position without a cue box.
+ // But if the vertical margin is too high and the text size is very large,
+ // in some cases the text could go off-screen.
+ // To try ensure that the text does not go off-screen,
+ // above a certain threshold of vertical margin we align from bottom.
+ bool useAlignBottom = false;
+ if (cueLinePerc >= 90)
+ {
+ useAlignBottom = true;
+ marginVertical = MARGIN_VERTICAL;
+ }
+ else
+ marginVertical = static_cast<int>((VIEWPORT_HEIGHT / 100) * cueLinePerc);
+
+ if (settings->align == WebvttAlign::LEFT)
+ textAlign = useAlignBottom ? TextAlignment::SUB_LEFT : TextAlignment::TOP_LEFT;
+ else if (settings->align == WebvttAlign::RIGHT)
+ textAlign = useAlignBottom ? TextAlignment::SUB_RIGHT : TextAlignment::TOP_RIGHT;
+ else
+ textAlign = useAlignBottom ? TextAlignment::SUB_CENTER : TextAlignment::TOP_CENTER;
+
+ m_subtitleData.textAlign = textAlign;
+ m_subtitleData.useMargins = !m_overridePositions;
+ m_subtitleData.marginLeft = std::max(marginLeft, 0);
+ m_subtitleData.marginRight = std::max(marginRight, 0);
+ m_subtitleData.marginVertical = marginVertical;
+}
+
+std::string CWebVTTHandler::GetCueSettingValue(const std::string& propName,
+ std::string& text,
+ std::string defaultValue)
+{
+ if (m_cuePropsMapRegex[propName].RegFind(text) >= 0)
+ return m_cuePropsMapRegex[propName].GetMatch(1);
+
+ return defaultValue;
+}
+
+std::string CWebVTTHandler::GetCueCssValue(const std::string& cssPropName, std::string& line)
+{
+ if (m_cueCssStyleMapRegex[cssPropName].RegFind(line) >= 0)
+ return m_cueCssStyleMapRegex[cssPropName].GetMatch(1);
+
+ return "";
+}
+
+void CWebVTTHandler::AddDefaultCssClasses()
+{
+ m_cssSelectors[WebvttSelector::CLASS] = cueCssDefaultColorClasses;
+}
+
+bool CWebVTTHandler::GetBaseStyle(webvttCssStyle& style)
+{
+ // Get the style applied to all cue's
+ bool isBaseStyleFound = false;
+ if (!m_overrideStyle)
+ {
+ auto& selectorAnyMap = m_cssSelectors[WebvttSelector::ANY];
+ auto itBaseStyle = selectorAnyMap.find("::cue");
+ if (itBaseStyle != selectorAnyMap.end())
+ {
+ style = itBaseStyle->second;
+ isBaseStyleFound = true;
+ }
+ }
+
+ // Try find the CSS Style by cue ID
+ // and merge it to the base style
+ if (!m_subtitleData.cueSettings.id.empty())
+ {
+ auto& selectorIdMap = m_cssSelectors[WebvttSelector::ID];
+ auto itCssStyle = selectorIdMap.find(m_subtitleData.cueSettings.id);
+ if (itCssStyle != selectorIdMap.end())
+ {
+ webvttCssStyle& idStyle = itCssStyle->second;
+ style.m_isFontBold = style.m_isFontBold || idStyle.m_isFontBold;
+ style.m_isFontItalic = style.m_isFontItalic || idStyle.m_isFontItalic;
+ style.m_isFontUnderline = style.m_isFontUnderline || idStyle.m_isFontUnderline;
+ style.m_color = idStyle.m_color.empty() ? style.m_color : idStyle.m_color;
+ return true;
+ }
+ }
+ return isBaseStyleFound;
+}
+
+void CWebVTTHandler::ConvertSubtitle(std::string& text)
+{
+ int pos = 0;
+ int flagTags[FLAG_TAG_COUNT] = {0};
+
+ std::string textRaw;
+ int lastPos{0};
+ webvttCssStyle baseStyle;
+ bool isBaseStyleSet = GetBaseStyle(baseStyle);
+
+ if (isBaseStyleSet)
+ {
+ const std::string baseStyleTag = ConvertStyleToOpenTags(flagTags, baseStyle);
+ text.insert(0, baseStyleTag);
+ lastPos = baseStyleTag.length();
+ }
+
+ // Map to store opened CSS tags [tag name]+[style selector]
+ std::deque<std::pair<std::string, webvttCssStyle*>> cssTagsOpened;
+ // Scan all tags
+ while ((pos = m_tagsRegex.RegFind(text, pos)) >= 0)
+ {
+ tagToken tag;
+ tag.m_token = StringUtils::ToLower(m_tagsRegex.GetMatch(0));
+ tag.m_isClosing = m_tagsRegex.GetMatch(1) == "/";
+ if (!m_tagsRegex.GetMatch(2).empty())
+ tag.m_timestampTag = tag.m_token;
+ tag.m_tag = StringUtils::ToLower(m_tagsRegex.GetMatch(3));
+ tag.m_classes = StringUtils::Split(m_tagsRegex.GetMatch(4).erase(0, 1), ".");
+ tag.m_annotation = m_tagsRegex.GetMatch(5);
+
+ text.erase(pos, tag.m_token.length());
+ // Keep a copy of the text without tags
+ textRaw += text.substr(lastPos, pos - lastPos);
+
+ if (tag.m_isClosing)
+ InsertCssStyleCloseTag(tag, text, pos, flagTags, cssTagsOpened, baseStyle);
+
+ if (tag.m_tag == "b")
+ {
+ if (!tag.m_isClosing)
+ {
+ if (flagTags[FLAG_TAG_BOLD] == 0)
+ InsertTextPos(text, "{\\b1}", pos);
+ flagTags[FLAG_TAG_BOLD] += 1;
+ }
+ else if (flagTags[FLAG_TAG_BOLD] > 0)
+ { // Closing tag (if previously opened)
+ flagTags[FLAG_TAG_BOLD] = flagTags[FLAG_TAG_BOLD] > 0 ? (flagTags[FLAG_TAG_BOLD] - 1) : 0;
+ if (flagTags[FLAG_TAG_BOLD] == 0)
+ InsertTextPos(text, "{\\b0}", pos);
+ }
+ }
+ else if (tag.m_tag == "i")
+ {
+ if (!tag.m_isClosing)
+ {
+ if (flagTags[FLAG_TAG_ITALIC] == 0)
+ InsertTextPos(text, "{\\i1}", pos);
+ flagTags[FLAG_TAG_ITALIC] += 1;
+ }
+ else if (flagTags[FLAG_TAG_ITALIC] > 0)
+ { // Closing tag (if previously opened)
+ flagTags[FLAG_TAG_ITALIC] =
+ flagTags[FLAG_TAG_ITALIC] > 0 ? (flagTags[FLAG_TAG_ITALIC] - 1) : 0;
+ if (flagTags[FLAG_TAG_ITALIC] == 0)
+ InsertTextPos(text, "{\\i0}", pos);
+ }
+ }
+ else if (tag.m_tag == "u")
+ {
+ if (!tag.m_isClosing)
+ {
+ if (flagTags[FLAG_TAG_UNDERLINE] == 0)
+ InsertTextPos(text, "{\\u1}", pos);
+ flagTags[FLAG_TAG_UNDERLINE] += 1;
+ }
+ else if (flagTags[FLAG_TAG_ITALIC] > 0)
+ { // Closing tag (if previously opened)
+ flagTags[FLAG_TAG_UNDERLINE] =
+ flagTags[FLAG_TAG_UNDERLINE] > 0 ? (flagTags[FLAG_TAG_UNDERLINE] - 1) : 0;
+ if (flagTags[FLAG_TAG_UNDERLINE] == 0)
+ InsertTextPos(text, "{\\u0}", pos);
+ }
+ }
+
+ if (!tag.m_isClosing)
+ InsertCssStyleStartTag(tag, text, pos, flagTags, cssTagsOpened);
+
+ lastPos = pos;
+ }
+ // Keep a copy of the text without tags
+ textRaw += text.substr(lastPos);
+
+ m_subtitleData.textRaw = textRaw;
+
+ if (isBaseStyleSet)
+ {
+ webvttCssStyle emptyStyle{};
+ text += ConvertStyleToCloseTags(flagTags, &baseStyle, &emptyStyle);
+ }
+
+ // Check for malformed tags still opened
+ if (!cssTagsOpened.empty() || flagTags[FLAG_TAG_BOLD] > 0 || flagTags[FLAG_TAG_ITALIC] > 0 ||
+ flagTags[FLAG_TAG_UNDERLINE] > 0)
+ {
+ text += "{\\r}"; // Cancel all opened tags
+ }
+
+ // Add text alignment (based on cue settings)
+ if (!m_overridePositions)
+ {
+ if (m_subtitleData.textAlign == TextAlignment::TOP_LEFT)
+ text.insert(0, "{\\an7}");
+ else if (m_subtitleData.textAlign == TextAlignment::TOP_RIGHT)
+ text.insert(0, "{\\an9}");
+ else if (m_subtitleData.textAlign == TextAlignment::TOP_CENTER)
+ text.insert(0, "{\\an8}");
+ else if (m_subtitleData.textAlign == TextAlignment::SUB_LEFT)
+ text.insert(0, "{\\an1}");
+ else if (m_subtitleData.textAlign == TextAlignment::SUB_RIGHT)
+ text.insert(0, "{\\an3}");
+ else if (m_subtitleData.textAlign == TextAlignment::SUB_CENTER)
+ text.insert(0, "{\\an2}");
+ }
+}
+
+void CWebVTTHandler::InsertCssStyleStartTag(
+ const tagToken& tag,
+ std::string& text,
+ int& pos,
+ int flagTags[],
+ std::deque<std::pair<std::string, webvttCssStyle*>>& cssTagsOpened)
+{
+ if (!tag.m_timestampTag.empty())
+ {
+ // Timestamp tag will be interpreded as karaoke effect
+ if (m_timeRegex.RegFind(tag.m_timestampTag) >= 0)
+ {
+ const double timeStart = GetTimeFromRegexTS(m_timeRegex) + m_offset;
+ // Libass works with duration instead of timestamp
+ // so we need to find the next timestamp
+ double timeEnd = m_subtitleData.stopTime;
+ if (m_timeRegex.RegFind(text) >= 0)
+ timeEnd = GetTimeFromRegexTS(m_timeRegex) + m_offset;
+
+ if (timeStart <= timeEnd)
+ {
+ int duration = static_cast<int>(timeEnd - timeStart) / 10000;
+ std::string assTag = "{\\k" + std::to_string(duration) + "}";
+ text.insert(pos, assTag);
+ pos += static_cast<int>(assTag.length());
+ }
+ else
+ CLog::LogF(LOGERROR, "Unable to get duration from timestamp: {}", tag.m_timestampTag);
+ }
+ else
+ CLog::LogF(LOGERROR, "Error parsing timestamp tag: {}", tag.m_timestampTag);
+
+ return;
+ }
+
+ bool hasAttribute = !tag.m_annotation.empty() && (tag.m_tag == "lang" || tag.m_tag == "v");
+
+ webvttCssStyle* cssStyle{nullptr};
+ if (hasAttribute)
+ {
+ auto& selectorMap = m_cssSelectors[WebvttSelector::ATTRIBUTE];
+ auto itCssStyle = selectorMap.find(tag.m_tag + " " + tag.m_annotation);
+ if (itCssStyle != selectorMap.end())
+ cssStyle = &itCssStyle->second;
+ }
+ else if (!tag.m_classes.empty())
+ {
+ auto& selectorMap = m_cssSelectors[WebvttSelector::CLASS];
+ // Cascading classes not implemented
+ const std::string className = "." + tag.m_classes[0];
+ // Class selector that target a specific element have the priority
+ auto itCssStyle = selectorMap.find(tag.m_tag + className);
+ if (itCssStyle != selectorMap.end())
+ cssStyle = &itCssStyle->second;
+
+ if (!cssStyle)
+ {
+ auto itCssStyle = selectorMap.find(className);
+ if (itCssStyle != selectorMap.end())
+ cssStyle = &itCssStyle->second;
+ }
+ }
+ else
+ {
+ auto& selectorMap = m_cssSelectors[WebvttSelector::TYPE];
+ auto itCssStyle = selectorMap.find(tag.m_tag);
+ if (itCssStyle != selectorMap.end())
+ cssStyle = &itCssStyle->second;
+ }
+
+ if (cssStyle)
+ {
+ // Insert the CSS Style converted as tags
+ const std::string tags = ConvertStyleToOpenTags(flagTags, *cssStyle);
+ text.insert(pos, tags);
+ pos += static_cast<int>(tags.length());
+ // Keep track of the opened tags
+ cssTagsOpened.emplace_front(tag.m_tag, cssStyle);
+ }
+}
+
+void CWebVTTHandler::InsertCssStyleCloseTag(
+ const tagToken& tag,
+ std::string& text,
+ int& pos,
+ int flagTags[],
+ std::deque<std::pair<std::string, webvttCssStyle*>>& cssTagsOpened,
+ webvttCssStyle& baseStyle)
+{
+ if (cssTagsOpened.empty())
+ return;
+
+ std::pair<std::string, webvttCssStyle*> stylePair = cssTagsOpened.front();
+ if (stylePair.first == tag.m_tag)
+ {
+ cssTagsOpened.pop_front();
+ webvttCssStyle* style = &baseStyle;
+ if (!cssTagsOpened.empty())
+ style = cssTagsOpened.front().second;
+ const std::string tags = ConvertStyleToCloseTags(flagTags, stylePair.second, style);
+ text.insert(pos, tags);
+ pos += static_cast<int>(tags.length());
+ }
+}
+
+void CWebVTTHandler::ConvertAddSubtitle(std::vector<subtitleData>* subList)
+{
+ // Convert tags and apply the CSS Styles converted
+ ConvertSubtitle(m_subtitleData.text);
+
+ if (m_lastSubtitleData)
+ {
+ // Check for same subtitle data
+ if (m_lastSubtitleData->startTime == m_subtitleData.startTime &&
+ m_lastSubtitleData->stopTime == m_subtitleData.stopTime &&
+ m_lastSubtitleData->textRaw == m_subtitleData.textRaw &&
+ m_lastSubtitleData->cueSettings == m_subtitleData.cueSettings)
+ {
+ if (subList->empty())
+ {
+ // On segmented WebVTT, it can happen that the last subtitle entry is sent
+ // on consecutive demux packet. Hence we avoid showing overlapping subs.
+ return;
+ }
+ else
+ {
+ // Youtube WebVTT can have multiple cues with same time, text and position
+ // sometimes with different css color but only last cue will be visible
+ // this cause unexpected results on screen, so we keep only the current one
+ // and delete the previous one.
+ subList->pop_back();
+ }
+ }
+ }
+
+ subList->emplace_back(m_subtitleData);
+ m_lastSubtitleData = std::make_unique<subtitleData>(m_subtitleData);
+}
+
+void CWebVTTHandler::LoadColors()
+{
+ CGUIColorManager colorManager;
+ colorManager.LoadColorsListFromXML(
+ CSpecialProtocol::TranslatePathConvertCase("special://xbmc/system/colors.xml"), m_CSSColors,
+ false);
+ m_CSSColorsLoaded = true;
+}
+
+double CWebVTTHandler::GetTimeFromRegexTS(CRegExp& regex, int indexStart /* = 1 */)
+{
+ int sHours = 0;
+ if (!regex.GetMatch(indexStart).empty())
+ sHours = std::stoi(regex.GetMatch(indexStart));
+ int sMinutes = std::stoi(regex.GetMatch(indexStart + 1));
+ double sSeconds = std::stod(regex.GetMatch(indexStart + 2));
+ return (static_cast<double>(sHours * 3600 + sMinutes * 60) + sSeconds) * DVD_TIME_BASE;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h
new file mode 100644
index 0000000..1934a74
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/GUIColorManager.h"
+#include "utils/ColorUtils.h"
+#include "utils/RegExp.h"
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <stdio.h>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+enum class WebvttSection
+{
+ UNDEFINED = 0,
+ STYLE,
+ STYLE_CONTENT,
+ REGION,
+ CUE,
+ CUE_TEXT,
+ CUE_METADATA,
+ NOTE
+};
+
+enum class WebvttAlign
+{
+ AUTO = 0,
+ LEFT,
+ CENTER,
+ RIGHT,
+ START,
+ END
+};
+
+enum class WebvttVAlign
+{
+ HORIZONTAL = 0,
+ VERTICAL_RL,
+ VERTICAL_LR,
+};
+
+enum class TextAlignment
+{
+ TOP_LEFT = 0,
+ TOP_CENTER,
+ TOP_RIGHT,
+ SUB_LEFT,
+ SUB_CENTER,
+ SUB_RIGHT
+};
+
+struct webvttAutoValue
+{
+ double value = 0;
+ bool isAuto = true;
+};
+
+struct webvttCueSettings
+{
+ std::string id;
+ std::string regionId;
+ WebvttVAlign verticalAlign;
+ bool snapToLines;
+ webvttAutoValue line;
+ webvttAutoValue position;
+ WebvttAlign positionAlign;
+ double size;
+ WebvttAlign align;
+
+ bool operator==(webvttCueSettings const& other) const
+ {
+ return this->verticalAlign == other.verticalAlign && this->snapToLines == other.snapToLines &&
+ this->line.isAuto == other.line.isAuto && this->line.value == other.line.value &&
+ this->position.isAuto == other.position.isAuto &&
+ this->position.value == other.position.value &&
+ this->positionAlign == other.positionAlign && this->size == other.size &&
+ this->align == other.align;
+ }
+};
+
+struct subtitleData
+{
+ std::string text;
+ std::string textRaw; // Text without tags
+ webvttCueSettings cueSettings;
+ double startTime;
+ double stopTime;
+ bool useMargins;
+ int marginLeft;
+ int marginRight;
+ int marginVertical;
+ TextAlignment textAlign;
+};
+
+enum class WebvttSelector
+{
+ ANY = 0,
+ ID,
+ TYPE,
+ CLASS,
+ ATTRIBUTE,
+ UNSUPPORTED
+};
+
+struct webvttCssStyle
+{
+ webvttCssStyle() {}
+ webvttCssStyle(WebvttSelector selectorType,
+ const std::string& selectorName,
+ const std::string& colorHexRGB)
+ : m_selectorType{selectorType}, m_selectorName{selectorName}
+ {
+ // Color hex values need to be in BGR format
+ m_color = colorHexRGB.substr(4, 2) + colorHexRGB.substr(2, 2) + colorHexRGB.substr(0, 2);
+ }
+
+ WebvttSelector m_selectorType = WebvttSelector::ANY;
+ std::string m_selectorName;
+ std::string m_color;
+ bool m_isFontBold = false;
+ bool m_isFontItalic = false;
+ bool m_isFontUnderline = false;
+};
+
+struct tagToken
+{
+ std::string m_token; // Entire tag
+ std::string m_tag;
+ std::string m_timestampTag;
+ std::string m_annotation;
+ std::vector<std::string> m_classes;
+ bool m_isClosing;
+};
+
+class CWebVTTHandler
+{
+public:
+ CWebVTTHandler(){};
+ ~CWebVTTHandler(){};
+
+ /*!
+ * \brief Prepare the handler to the decoding
+ */
+ bool Initialize();
+
+ /*
+ * \brief Reset handler data
+ */
+ void Reset();
+
+ /*!
+ * \brief Verify the validity of the WebVTT signature
+ */
+ bool CheckSignature(const std::string& data);
+
+ /*!
+ * \brief Decode a line of the WebVTT text data
+ * \param line The line to decode
+ * \param subList The list to be filled with decoded subtitles
+ */
+ void DecodeLine(std::string line, std::vector<subtitleData>* subList);
+
+ /*
+ * \brief Return true if the margins are handled by the parser.
+ */
+ bool IsForcedMargins() const { return !m_overridePositions; }
+
+ /*
+ * \brief Set the period start pts to sync subtitles
+ */
+ void SetPeriodStart(double pts) { m_offset = pts; }
+
+protected:
+ void CalculateTextPosition(std::string& subtitleText);
+ void ConvertSubtitle(std::string& text);
+ void GetCueSettings(std::string& cueSettings);
+ subtitleData m_subtitleData;
+
+private:
+ bool IsCueLine(std::string& line);
+ void GetCueData(std::string& cueText);
+ std::string GetCueSettingValue(const std::string& propName,
+ std::string& text,
+ std::string defaultValue);
+ std::string GetCueCssValue(const std::string& cssPropName, std::string& line);
+ void AddDefaultCssClasses();
+ void InsertCssStyleStartTag(const tagToken& tag,
+ std::string& text,
+ int& pos,
+ int flagTags[],
+ std::deque<std::pair<std::string, webvttCssStyle*>>& cssTagsOpened);
+ void InsertCssStyleCloseTag(const tagToken& tag,
+ std::string& text,
+ int& pos,
+ int flagTags[],
+ std::deque<std::pair<std::string, webvttCssStyle*>>& cssTagsOpened,
+ webvttCssStyle& baseStyle);
+ bool GetBaseStyle(webvttCssStyle& style);
+ void ConvertAddSubtitle(std::vector<subtitleData>* subList);
+ void LoadColors();
+ double GetTimeFromRegexTS(CRegExp& regex, int indexStart = 1);
+
+ // Last subtitle data added, must persist and be updated between all demuxer packages
+ std::unique_ptr<subtitleData> m_lastSubtitleData;
+
+ std::string m_previousLines[3];
+ bool m_overrideStyle{false};
+ bool m_overridePositions{false};
+ WebvttSection m_currentSection{WebvttSection::UNDEFINED};
+ CRegExp m_cueTimeRegex;
+ CRegExp m_timeRegex;
+ std::map<std::string, CRegExp> m_cuePropsMapRegex;
+ CGUIColorManager m_colorManager;
+ CRegExp m_tagsRegex;
+ CRegExp m_cueCssTagRegex;
+ std::map<std::string, CRegExp> m_cueCssStyleMapRegex;
+ std::vector<std::string> m_feedCssSelectorNames;
+ webvttCssStyle m_feedCssStyle;
+ std::map<WebvttSelector, std::map<std::string, webvttCssStyle>> m_cssSelectors;
+
+ bool m_CSSColorsLoaded{false};
+ std::vector<std::pair<std::string, UTILS::COLOR::ColorInfo>> m_CSSColors;
+ double m_offset{0.0};
+};
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.cpp
new file mode 100644
index 0000000..c19ca35
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebVTTISOHandler.h"
+
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/CharArrayParser.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StreamUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <cstring>
+#include <stdint.h>
+
+// WebVTT in MP4 encapsulated subtitles (ISO/IEC 14496-30:2014)
+// This format type that differ from the text WebVTT, it makes use
+// of ISO BMFF byte stream where the data are enclosed in boxes (first 4 byte
+// specify the size and next 4 byte specify the type).
+// Start/Stop times info are not included, the start time is the current pts
+// provided from the decoder, the stop time is defined from the pts of the next
+// packages (depends from the box type).
+
+namespace
+{
+constexpr int defaultSubDuration = 20 * static_cast<int>(DVD_TIME_BASE);
+// VTTEmptyCueBox
+constexpr uint32_t ISO_BOX_TYPE_VTTE = StreamUtils::MakeFourCC('v', 't', 't', 'e');
+// VTTCueBox
+constexpr uint32_t ISO_BOX_TYPE_VTTC = StreamUtils::MakeFourCC('v', 't', 't', 'c');
+// VTTContinuationCueBox
+constexpr uint32_t ISO_BOX_TYPE_VTTX = StreamUtils::MakeFourCC('v', 't', 't', 'x');
+// CueIDBox
+constexpr uint32_t ISO_BOX_TYPE_IDEN = StreamUtils::MakeFourCC('i', 'd', 'e', 'n');
+// CueSettingsBox
+constexpr uint32_t ISO_BOX_TYPE_STTG = StreamUtils::MakeFourCC('s', 't', 't', 'g');
+// CuePayloadBox
+constexpr uint32_t ISO_BOX_TYPE_PAYL = StreamUtils::MakeFourCC('p', 'a', 'y', 'l');
+} // unnamed namespace
+
+
+void CWebVTTISOHandler::DecodeStream(const char* buffer,
+ int bufferSize,
+ double pts,
+ std::vector<subtitleData>* subList,
+ double& prevSubStopTime)
+{
+ CCharArrayParser sampleData;
+ sampleData.Reset(buffer, bufferSize);
+
+ // A sample data package can contain:
+ // - One VTTE
+ // or
+ // - One or more VTTC and/or VTTX (where all share the same start/stop times)
+
+ while (sampleData.CharsLeft() > 0)
+ {
+ if (sampleData.CharsLeft() < MP4_BOX_HEADER_SIZE)
+ {
+ CLog::Log(LOGWARNING, "{} - Incomplete box header found", __FUNCTION__);
+ break;
+ }
+
+ uint32_t boxSize = sampleData.ReadNextUnsignedInt();
+ uint32_t boxType = sampleData.ReadNextUnsignedInt();
+ if (boxType == ISO_BOX_TYPE_VTTE)
+ {
+ // VTTE is used to set the stop time value to previously subtitles
+ prevSubStopTime = pts;
+ sampleData.SkipChars(boxSize - MP4_BOX_HEADER_SIZE);
+ }
+ else if (boxType == ISO_BOX_TYPE_VTTC)
+ {
+ // VTTC can be used to set the stop time value to previously subtitles
+ prevSubStopTime = pts;
+ m_subtitleData = subtitleData();
+ m_subtitleData.startTime = pts;
+ m_subtitleData.stopTime = pts + defaultSubDuration;
+ if (ParseVTTCueBox(sampleData, boxSize - MP4_BOX_HEADER_SIZE, subList))
+ subList->emplace_back(m_subtitleData);
+ }
+ else if (boxType == ISO_BOX_TYPE_VTTX)
+ {
+ // VTTX could be used to set the stop time value to previously subtitles
+ prevSubStopTime = pts;
+ m_subtitleData = subtitleData();
+ m_subtitleData.startTime = pts;
+ m_subtitleData.stopTime = pts + defaultSubDuration;
+ if (ParseVTTCueBox(sampleData, boxSize - MP4_BOX_HEADER_SIZE, subList))
+ subList->emplace_back(m_subtitleData);
+ }
+ else
+ {
+ // Skip unsupported box types
+ sampleData.SkipChars(boxSize - MP4_BOX_HEADER_SIZE);
+ }
+ }
+}
+
+bool CWebVTTISOHandler::ParseVTTCueBox(CCharArrayParser& sampleData,
+ int remainingCueBoxChars,
+ std::vector<subtitleData>* subList)
+{
+ std::string cueId;
+ std::string cueSettings;
+ std::string subtitleText;
+
+ // No order is imposed between box types,
+ // so we have to process the data after retrieving them.
+ while (remainingCueBoxChars > 0)
+ {
+ if (remainingCueBoxChars < MP4_BOX_HEADER_SIZE)
+ {
+ CLog::Log(LOGWARNING, "{} - Incomplete VTT Cue box header found", __FUNCTION__);
+ return false;
+ }
+ uint32_t boxSize = sampleData.ReadNextUnsignedInt();
+ uint32_t boxType = sampleData.ReadNextUnsignedInt();
+ int payloadLength = boxSize - MP4_BOX_HEADER_SIZE;
+ remainingCueBoxChars -= MP4_BOX_HEADER_SIZE;
+ remainingCueBoxChars -= payloadLength;
+ std::string payload = sampleData.ReadNextString(payloadLength);
+
+ if (boxType == ISO_BOX_TYPE_IDEN) // Optional
+ {
+ cueId = payload;
+ }
+ else if (boxType == ISO_BOX_TYPE_STTG) // Optional
+ {
+ cueSettings = payload;
+ }
+ else if (boxType == ISO_BOX_TYPE_PAYL)
+ {
+ subtitleText = payload;
+ }
+ }
+
+ m_subtitleData.cueSettings.id = cueId;
+ GetCueSettings(cueSettings);
+ CalculateTextPosition(subtitleText);
+ ConvertSubtitle(subtitleText);
+ m_subtitleData.text = subtitleText;
+
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.h b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.h
new file mode 100644
index 0000000..b2eb073
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTISOHandler.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "WebVTTHandler.h"
+
+class CCharArrayParser;
+
+
+class CWebVTTISOHandler : public CWebVTTHandler
+{
+public:
+ CWebVTTISOHandler(){};
+ ~CWebVTTISOHandler(){};
+
+ /*!
+ * \brief Decode a stream package of the WebVTT in MP4 encapsulated subtitles
+ * (ISO/IEC 14496-30:2014)
+ * \param buffer The data buffer
+ * \param bufferSize The buffer size
+ * \param subList The list to be filled with decoded subtitles
+ * \param[out] prevSubStopTime Provide the stop time value (depends on box type)
+ */
+ void DecodeStream(const char* buffer,
+ int bufferSize,
+ double pts,
+ std::vector<subtitleData>* subList,
+ double& prevSubStopTime);
+
+private:
+ bool ParseVTTCueBox(CCharArrayParser& sampleData,
+ int remainingCueBoxChars,
+ std::vector<subtitleData>* subList);
+};
diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp
new file mode 100644
index 0000000..51f7ef7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Edl.cpp
@@ -0,0 +1,1040 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Edl.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "cores/EdlEdit.h"
+#include "filesystem/File.h"
+#include "pvr/PVREdl.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include "PlatformDefs.h"
+
+#define COMSKIP_HEADER "FILE PROCESSING COMPLETE"
+#define VIDEOREDO_HEADER "<Version>2"
+#define VIDEOREDO_TAG_CUT "<Cut>"
+#define VIDEOREDO_TAG_SCENE "<SceneMarker "
+
+using namespace EDL;
+using namespace XFILE;
+
+CEdl::CEdl()
+{
+ Clear();
+}
+
+void CEdl::Clear()
+{
+ m_vecEdits.clear();
+ m_vecSceneMarkers.clear();
+ m_totalCutTime = 0;
+ m_lastEditTime = -1;
+}
+
+bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesPerSecond)
+{
+ bool bFound = false;
+
+ /*
+ * Only check for edit decision lists if the movie is on the local hard drive, or accessed over a
+ * network share.
+ */
+ const std::string& strMovie = fileItem.GetDynPath();
+ if ((URIUtils::IsHD(strMovie) || URIUtils::IsOnLAN(strMovie)) &&
+ !URIUtils::IsInternetStream(strMovie))
+ {
+ CLog::Log(LOGDEBUG,
+ "{} - Checking for edit decision lists (EDL) on local drive or remote share for: {}",
+ __FUNCTION__, CURL::GetRedacted(strMovie));
+
+ /*
+ * Read any available file format until a valid EDL related file is found.
+ */
+ if (!bFound)
+ bFound = ReadVideoReDo(strMovie);
+
+ if (!bFound)
+ bFound = ReadEdl(strMovie, fFramesPerSecond);
+
+ if (!bFound)
+ bFound = ReadComskip(strMovie, fFramesPerSecond);
+
+ if (!bFound)
+ bFound = ReadBeyondTV(strMovie);
+ }
+ else
+ {
+ bFound = ReadPvr(fileItem);
+ }
+
+ if (bFound)
+ {
+ MergeShortCommBreaks();
+ AddSceneMarkersAtStartAndEndOfEdits();
+ }
+
+ return bFound;
+}
+
+bool CEdl::ReadEdl(const std::string& strMovie, const float fFramesPerSecond)
+{
+ Clear();
+
+ std::string edlFilename(URIUtils::ReplaceExtension(strMovie, ".edl"));
+ if (!CFile::Exists(edlFilename))
+ return false;
+
+ CFile edlFile;
+ if (!edlFile.Open(edlFilename))
+ {
+ CLog::Log(LOGERROR, "{} - Could not open EDL file: {}", __FUNCTION__,
+ CURL::GetRedacted(edlFilename));
+ return false;
+ }
+
+ bool bError = false;
+ int iLine = 0;
+ std::string strBuffer;
+ strBuffer.resize(1024);
+ while (edlFile.ReadString(&strBuffer[0], 1024))
+ {
+ // Log any errors from previous run in the loop
+ if (bError)
+ CLog::Log(LOGWARNING, "{} - Error on line {} in EDL file: {}", __FUNCTION__, iLine,
+ CURL::GetRedacted(edlFilename));
+
+ bError = false;
+
+ iLine++;
+
+ char buffer1[513];
+ char buffer2[513];
+ int iAction;
+ int iFieldsRead = sscanf(strBuffer.c_str(), "%512s %512s %i", buffer1,
+ buffer2, &iAction);
+ if (iFieldsRead != 2 && iFieldsRead != 3) // Make sure we read the right number of fields
+ {
+ bError = true;
+ continue;
+ }
+
+ std::vector<std::string> strFields(2);
+ strFields[0] = buffer1;
+ strFields[1] = buffer2;
+
+ if (iFieldsRead == 2) // If only 2 fields read, then assume it's a scene marker.
+ {
+ iAction = atoi(strFields[1].c_str());
+ strFields[1] = strFields[0];
+ }
+
+ if (StringUtils::StartsWith(strFields[0], "##"))
+ {
+ CLog::Log(LOGDEBUG, "Skipping comment line {} in EDL file: {}", iLine,
+ CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ /*
+ * For each of the first two fields read, parse based on whether it is a time string
+ * (HH:MM:SS.sss), frame marker (#12345), or normal seconds string (123.45).
+ */
+ int64_t editStartEnd[2];
+ for (int i = 0; i < 2; i++)
+ {
+ if (strFields[i].find(':') != std::string::npos) // HH:MM:SS.sss format
+ {
+ std::vector<std::string> fieldParts = StringUtils::Split(strFields[i], '.');
+ if (fieldParts.size() == 1) // No ms
+ {
+ editStartEnd[i] = StringUtils::TimeStringToSeconds(fieldParts[0]) *
+ static_cast<int64_t>(1000); // seconds to ms
+ }
+ else if (fieldParts.size() == 2) // Has ms. Everything after the dot (.) is ms
+ {
+ /*
+ * Have to pad or truncate the ms portion to 3 characters before converting to ms.
+ */
+ if (fieldParts[1].length() == 1)
+ {
+ fieldParts[1] = fieldParts[1] + "00";
+ }
+ else if (fieldParts[1].length() == 2)
+ {
+ fieldParts[1] = fieldParts[1] + "0";
+ }
+ else if (fieldParts[1].length() > 3)
+ {
+ fieldParts[1] = fieldParts[1].substr(0, 3);
+ }
+ editStartEnd[i] =
+ static_cast<int64_t>(StringUtils::TimeStringToSeconds(fieldParts[0])) * 1000 +
+ std::atoi(fieldParts[1].c_str()); // seconds to ms
+ }
+ else
+ {
+ bError = true;
+ continue;
+ }
+ }
+ else if (strFields[i][0] == '#') // #12345 format for frame number
+ {
+ if (fFramesPerSecond > 0.0f)
+ {
+ editStartEnd[i] = static_cast<int64_t>(std::atol(strFields[i].substr(1).c_str()) /
+ fFramesPerSecond * 1000); // frame number to ms
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "Edl::ReadEdl - Frame number not supported in EDL files when frame rate is "
+ "unavailable (ts) - supplied frame number: {}",
+ strFields[i].substr(1));
+ return false;
+ }
+ }
+ else // Plain old seconds in float format, e.g. 123.45
+ {
+ editStartEnd[i] = std::lround(std::atof(strFields[i].c_str()) * 1000); // seconds to ms
+ }
+ }
+
+ if (bError) // If there was an error in the for loop, ignore and continue with the next line
+ continue;
+
+ Edit edit;
+ edit.start = editStartEnd[0];
+ edit.end = editStartEnd[1];
+
+ switch (iAction)
+ {
+ case 0:
+ edit.action = Action::CUT;
+ if (!AddEdit(edit))
+ {
+ CLog::Log(LOGWARNING, "{} - Error adding cut from line {} in EDL file: {}", __FUNCTION__,
+ iLine, CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ break;
+ case 1:
+ edit.action = Action::MUTE;
+ if (!AddEdit(edit))
+ {
+ CLog::Log(LOGWARNING, "{} - Error adding mute from line {} in EDL file: {}", __FUNCTION__,
+ iLine, CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ break;
+ case 2:
+ if (!AddSceneMarker(edit.end))
+ {
+ CLog::Log(LOGWARNING, "{} - Error adding scene marker from line {} in EDL file: {}",
+ __FUNCTION__, iLine, CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ break;
+ case 3:
+ edit.action = Action::COMM_BREAK;
+ if (!AddEdit(edit))
+ {
+ CLog::Log(LOGWARNING, "{} - Error adding commercial break from line {} in EDL file: {}",
+ __FUNCTION__, iLine, CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ break;
+ default:
+ CLog::Log(LOGWARNING, "{} - Invalid action on line {} in EDL file: {}", __FUNCTION__, iLine,
+ CURL::GetRedacted(edlFilename));
+ continue;
+ }
+ }
+
+ if (bError) // Log last line warning, if there was one, since while loop will have terminated.
+ CLog::Log(LOGWARNING, "{} - Error on line {} in EDL file: {}", __FUNCTION__, iLine,
+ CURL::GetRedacted(edlFilename));
+
+ edlFile.Close();
+
+ if (HasEdits() || HasSceneMarker())
+ {
+ CLog::Log(LOGDEBUG, "{0} - Read {1} edits and {2} scene markers in EDL file: {3}", __FUNCTION__,
+ m_vecEdits.size(), m_vecSceneMarkers.size(), CURL::GetRedacted(edlFilename));
+ return true;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - No edits or scene markers found in EDL file: {}", __FUNCTION__,
+ CURL::GetRedacted(edlFilename));
+ return false;
+ }
+}
+
+bool CEdl::ReadComskip(const std::string& strMovie, const float fFramesPerSecond)
+{
+ Clear();
+
+ std::string comskipFilename(URIUtils::ReplaceExtension(strMovie, ".txt"));
+ if (!CFile::Exists(comskipFilename))
+ return false;
+
+ CFile comskipFile;
+ if (!comskipFile.Open(comskipFilename))
+ {
+ CLog::Log(LOGERROR, "{} - Could not open Comskip file: {}", __FUNCTION__,
+ CURL::GetRedacted(comskipFilename));
+ return false;
+ }
+
+ char szBuffer[1024];
+ if (comskipFile.ReadString(szBuffer, 1023)
+ && strncmp(szBuffer, COMSKIP_HEADER, strlen(COMSKIP_HEADER)) != 0) // Line 1.
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid Comskip file: {}. Error reading line 1 - expected '{}' at start.",
+ __FUNCTION__, CURL::GetRedacted(comskipFilename), COMSKIP_HEADER);
+ comskipFile.Close();
+ return false;
+ }
+
+ int iFrames;
+ float fFrameRate;
+ if (sscanf(szBuffer, "FILE PROCESSING COMPLETE %i FRAMES AT %f", &iFrames, &fFrameRate) != 2)
+ {
+ /*
+ * Not all generated Comskip files have the frame rate information.
+ */
+ if (fFramesPerSecond > 0.0f)
+ {
+ fFrameRate = fFramesPerSecond;
+ CLog::Log(LOGWARNING,
+ "Edl::ReadComskip - Frame rate not in Comskip file. Using detected frames per "
+ "second: {:.3f}",
+ fFrameRate);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "Edl::ReadComskip - Frame rate is unavailable and also not in Comskip file (ts).");
+ return false;
+ }
+ }
+ else
+ fFrameRate /= 100; // Reduce by factor of 100 to get fps.
+
+ (void)comskipFile.ReadString(szBuffer, 1023); // Line 2. Ignore "-------------"
+
+ bool bValid = true;
+ int iLine = 2;
+ while (bValid && comskipFile.ReadString(szBuffer, 1023)) // Line 3 and onwards.
+ {
+ iLine++;
+ double dStartFrame, dEndFrame;
+ if (sscanf(szBuffer, "%lf %lf", &dStartFrame, &dEndFrame) == 2)
+ {
+ Edit edit;
+ edit.start = std::lround(dStartFrame / static_cast<double>(fFrameRate) * 1000.0);
+ edit.end = std::lround(dEndFrame / static_cast<double>(fFrameRate) * 1000.0);
+ edit.action = Action::COMM_BREAK;
+ bValid = AddEdit(edit);
+ }
+ else
+ bValid = false;
+ }
+ comskipFile.Close();
+
+ if (!bValid)
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid Comskip file: {}. Error on line {}. Clearing any valid commercial "
+ "breaks found.",
+ __FUNCTION__, CURL::GetRedacted(comskipFilename), iLine);
+ Clear();
+ return false;
+ }
+ else if (HasEdits())
+ {
+ CLog::Log(LOGDEBUG, "{0} - Read {1} commercial breaks from Comskip file: {2}", __FUNCTION__,
+ m_vecEdits.size(), CURL::GetRedacted(comskipFilename));
+ return true;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - No commercial breaks found in Comskip file: {}", __FUNCTION__,
+ CURL::GetRedacted(comskipFilename));
+ return false;
+ }
+}
+
+bool CEdl::ReadVideoReDo(const std::string& strMovie)
+{
+ /*
+ * VideoReDo file is strange. Tags are XML like, but it isn't an XML file.
+ *
+ * http://www.videoredo.com/
+ */
+
+ Clear();
+ std::string videoReDoFilename(URIUtils::ReplaceExtension(strMovie, ".Vprj"));
+ if (!CFile::Exists(videoReDoFilename))
+ return false;
+
+ CFile videoReDoFile;
+ if (!videoReDoFile.Open(videoReDoFilename))
+ {
+ CLog::Log(LOGERROR, "{} - Could not open VideoReDo file: {}", __FUNCTION__,
+ CURL::GetRedacted(videoReDoFilename));
+ return false;
+ }
+
+ char szBuffer[1024];
+ if (videoReDoFile.ReadString(szBuffer, 1023)
+ && strncmp(szBuffer, VIDEOREDO_HEADER, strlen(VIDEOREDO_HEADER)) != 0)
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid VideoReDo file: {}. Error reading line 1 - expected {}. Only version 2 "
+ "files are supported.",
+ __FUNCTION__, CURL::GetRedacted(videoReDoFilename), VIDEOREDO_HEADER);
+ videoReDoFile.Close();
+ return false;
+ }
+
+ int iLine = 1;
+ bool bValid = true;
+ while (bValid && videoReDoFile.ReadString(szBuffer, 1023))
+ {
+ iLine++;
+ if (strncmp(szBuffer, VIDEOREDO_TAG_CUT, strlen(VIDEOREDO_TAG_CUT)) == 0) // Found the <Cut> tag
+ {
+ /*
+ * double is used as 32 bit float would overflow.
+ */
+ double dStart, dEnd;
+ if (sscanf(szBuffer + strlen(VIDEOREDO_TAG_CUT), "%lf:%lf", &dStart, &dEnd) == 2)
+ {
+ /*
+ * Times need adjusting by 1/10,000 to get ms.
+ */
+ Edit edit;
+ edit.start = std::lround(dStart / 10000);
+ edit.end = std::lround(dEnd / 10000);
+ edit.action = Action::CUT;
+ bValid = AddEdit(edit);
+ }
+ else
+ bValid = false;
+ }
+ else if (strncmp(szBuffer, VIDEOREDO_TAG_SCENE, strlen(VIDEOREDO_TAG_SCENE)) == 0) // Found the <SceneMarker > tag
+ {
+ int iScene;
+ double dSceneMarker;
+ if (sscanf(szBuffer + strlen(VIDEOREDO_TAG_SCENE), " %i>%lf", &iScene, &dSceneMarker) == 2)
+ bValid = AddSceneMarker(
+ std::lround(dSceneMarker / 10000)); // Times need adjusting by 1/10,000 to get ms.
+ else
+ bValid = false;
+ }
+ /*
+ * Ignore any other tags.
+ */
+ }
+ videoReDoFile.Close();
+
+ if (!bValid)
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid VideoReDo file: {}. Error in line {}. Clearing any valid edits or "
+ "scenes found.",
+ __FUNCTION__, CURL::GetRedacted(videoReDoFilename), iLine);
+ Clear();
+ return false;
+ }
+ else if (HasEdits() || HasSceneMarker())
+ {
+ CLog::Log(LOGDEBUG, "{0} - Read {1} edits and {2} scene markers in VideoReDo file: {3}",
+ __FUNCTION__, m_vecEdits.size(), m_vecSceneMarkers.size(),
+ CURL::GetRedacted(videoReDoFilename));
+ return true;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - No edits or scene markers found in VideoReDo file: {}", __FUNCTION__,
+ CURL::GetRedacted(videoReDoFilename));
+ return false;
+ }
+}
+
+bool CEdl::ReadBeyondTV(const std::string& strMovie)
+{
+ Clear();
+
+ std::string beyondTVFilename(URIUtils::ReplaceExtension(strMovie, URIUtils::GetExtension(strMovie) + ".chapters.xml"));
+ if (!CFile::Exists(beyondTVFilename))
+ return false;
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(beyondTVFilename))
+ {
+ CLog::Log(LOGERROR, "{} - Could not load Beyond TV file: {}. {}", __FUNCTION__,
+ CURL::GetRedacted(beyondTVFilename), xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ if (xmlDoc.Error())
+ {
+ CLog::Log(LOGERROR, "{} - Could not parse Beyond TV file: {}. {}", __FUNCTION__,
+ CURL::GetRedacted(beyondTVFilename), xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement *pRoot = xmlDoc.RootElement();
+ if (!pRoot || strcmp(pRoot->Value(), "cutlist"))
+ {
+ CLog::Log(LOGERROR, "{} - Invalid Beyond TV file: {}. Expected root node to be <cutlist>",
+ __FUNCTION__, CURL::GetRedacted(beyondTVFilename));
+ return false;
+ }
+
+ bool bValid = true;
+ TiXmlElement *pRegion = pRoot->FirstChildElement("Region");
+ while (bValid && pRegion)
+ {
+ TiXmlElement *pStart = pRegion->FirstChildElement("start");
+ TiXmlElement *pEnd = pRegion->FirstChildElement("end");
+ if (pStart && pEnd && pStart->FirstChild() && pEnd->FirstChild())
+ {
+ /*
+ * Need to divide the start and end times by a factor of 10,000 to get msec.
+ * E.g. <start comment="00:02:44.9980867">1649980867</start>
+ *
+ * Use atof so doesn't overflow 32 bit float or integer / long.
+ * E.g. <end comment="0:26:49.0000009">16090090000</end>
+ *
+ * Don't use atoll even though it is more correct as it isn't natively supported by
+ * Visual Studio.
+ *
+ * atof() returns 0 if there were any problems and will subsequently be rejected in AddEdit().
+ */
+ Edit edit;
+ edit.start = std::lround((std::atof(pStart->FirstChild()->Value()) / 10000));
+ edit.end = std::lround((std::atof(pEnd->FirstChild()->Value()) / 10000));
+ edit.action = Action::COMM_BREAK;
+ bValid = AddEdit(edit);
+ }
+ else
+ bValid = false;
+
+ pRegion = pRegion->NextSiblingElement("Region");
+ }
+ if (!bValid)
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid Beyond TV file: {}. Clearing any valid commercial breaks found.",
+ __FUNCTION__, CURL::GetRedacted(beyondTVFilename));
+ Clear();
+ return false;
+ }
+ else if (HasEdits())
+ {
+ CLog::Log(LOGDEBUG, "{0} - Read {1} commercial breaks from Beyond TV file: {2}", __FUNCTION__,
+ m_vecEdits.size(), CURL::GetRedacted(beyondTVFilename));
+ return true;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "{} - No commercial breaks found in Beyond TV file: {}", __FUNCTION__,
+ CURL::GetRedacted(beyondTVFilename));
+ return false;
+ }
+}
+
+bool CEdl::ReadPvr(const CFileItem &fileItem)
+{
+ const std::vector<Edit> editlist = PVR::CPVREdl::GetEdits(fileItem);
+ for (const auto& edit : editlist)
+ {
+ switch (edit.action)
+ {
+ case Action::CUT:
+ case Action::MUTE:
+ case Action::COMM_BREAK:
+ if (AddEdit(edit))
+ {
+ CLog::Log(LOGDEBUG, "{} - Added break [{} - {}] found in PVR item for: {}.", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ CURL::GetRedacted(fileItem.GetDynPath()));
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "{} - Invalid break [{} - {}] found in PVR item for: {}. Continuing anyway.",
+ __FUNCTION__, MillisecondsToTimeString(edit.start),
+ MillisecondsToTimeString(edit.end), CURL::GetRedacted(fileItem.GetDynPath()));
+ }
+ break;
+
+ case Action::SCENE:
+ if (!AddSceneMarker(edit.end))
+ {
+ CLog::Log(LOGWARNING, "{} - Error adding scene marker for PVR item", __FUNCTION__);
+ }
+ break;
+
+ default:
+ CLog::Log(LOGINFO, "{} - Ignoring entry of unknown edit action: {}", __FUNCTION__,
+ static_cast<int>(edit.action));
+ break;
+ }
+ }
+
+ return !editlist.empty();
+}
+
+bool CEdl::AddEdit(const Edit& newEdit)
+{
+ Edit edit = newEdit;
+
+ if (edit.action != Action::CUT && edit.action != Action::MUTE &&
+ edit.action != Action::COMM_BREAK)
+ {
+ CLog::Log(LOGERROR,
+ "{} - Not an Action::CUT, Action::MUTE, or Action::COMM_BREAK! [{} - {}], {}",
+ __FUNCTION__, MillisecondsToTimeString(edit.start),
+ MillisecondsToTimeString(edit.end), static_cast<int>(edit.action));
+ return false;
+ }
+
+ if (edit.start < 0)
+ {
+ CLog::Log(LOGERROR, "{} - Before start! [{} - {}], {}", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ static_cast<int>(edit.action));
+ return false;
+ }
+
+ if (edit.start >= edit.end)
+ {
+ CLog::Log(LOGERROR, "{} - Times are around the wrong way or the same! [{} - {}], {}",
+ __FUNCTION__, MillisecondsToTimeString(edit.start),
+ MillisecondsToTimeString(edit.end), static_cast<int>(edit.action));
+ return false;
+ }
+
+ if (InEdit(edit.start) || InEdit(edit.end))
+ {
+ CLog::Log(LOGERROR, "{} - Start or end is in an existing edit! [{} - {}], {}", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ static_cast<int>(edit.action));
+ return false;
+ }
+
+ for (size_t i = 0; i < m_vecEdits.size(); ++i)
+ {
+ if (edit.start < m_vecEdits[i].start && edit.end > m_vecEdits[i].end)
+ {
+ CLog::Log(LOGERROR, "{} - Edit surrounds an existing edit! [{} - {}], {}", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ static_cast<int>(edit.action));
+ return false;
+ }
+ }
+
+ if (edit.action == Action::COMM_BREAK)
+ {
+ /*
+ * Detection isn't perfect near the edges of commercial breaks so automatically wait for a bit at
+ * the start (autowait) and automatically rewind by a bit (autowind) at the end of the commercial
+ * break.
+ */
+ int autowait = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEdlCommBreakAutowait * 1000; // seconds -> ms
+ int autowind = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEdlCommBreakAutowind * 1000; // seconds -> ms
+
+ if (edit.start > 0) // Only autowait if not at the start.
+ {
+ /* get the edit length so we don't start skipping after the end */
+ int editLength = edit.end - edit.start;
+ /* add the lesser of the edit length or the autowait to the start */
+ edit.start += autowait > editLength ? editLength : autowait;
+ }
+ if (edit.end > edit.start) // Only autowind if there is any edit time remaining.
+ {
+ /* get the remaining edit length so we don't rewind to before the start */
+ int editLength = edit.end - edit.start;
+ /* subtract the lesser of the edit length or the autowind from the end */
+ edit.end -= autowind > editLength ? editLength : autowind;
+ }
+ }
+
+ /*
+ * Insert edit in the list in the right position (ALL algorithms assume edits are in ascending order)
+ */
+ if (m_vecEdits.empty() || edit.start > m_vecEdits.back().start)
+ {
+ CLog::Log(LOGDEBUG, "{} - Pushing new edit to back [{} - {}], {}", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ static_cast<int>(edit.action));
+ m_vecEdits.emplace_back(edit);
+ }
+ else
+ {
+ std::vector<Edit>::iterator pCurrentEdit;
+ for (pCurrentEdit = m_vecEdits.begin(); pCurrentEdit != m_vecEdits.end(); ++pCurrentEdit)
+ {
+ if (edit.start < pCurrentEdit->start)
+ {
+ CLog::Log(LOGDEBUG, "{} - Inserting new edit [{} - {}], {}", __FUNCTION__,
+ MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end),
+ static_cast<int>(edit.action));
+ m_vecEdits.insert(pCurrentEdit, edit);
+ break;
+ }
+ }
+ }
+
+ if (edit.action == Action::CUT)
+ m_totalCutTime += edit.end - edit.start;
+
+ return true;
+}
+
+bool CEdl::AddSceneMarker(const int iSceneMarker)
+{
+ Edit edit;
+
+ if (InEdit(iSceneMarker, &edit) && edit.action == Action::CUT) // Only works for current cuts.
+ return false;
+
+ CLog::Log(LOGDEBUG, "{} - Inserting new scene marker: {}", __FUNCTION__,
+ MillisecondsToTimeString(iSceneMarker));
+ m_vecSceneMarkers.push_back(iSceneMarker); // Unsorted
+
+ return true;
+}
+
+bool CEdl::HasEdits() const
+{
+ return !m_vecEdits.empty();
+}
+
+bool CEdl::HasCuts() const
+{
+ return m_totalCutTime > 0;
+}
+
+int CEdl::GetTotalCutTime() const
+{
+ return m_totalCutTime; // ms
+}
+
+const std::vector<EDL::Edit> CEdl::GetEditList() const
+{
+ // the sum of cut durations while we iterate over them
+ // note: edits are ordered by start time
+ int surpassedSumOfCutDurations{0};
+ std::vector<EDL::Edit> editList;
+
+ // @note we should not modify the original edits since
+ // they are used during playback. However we need to correct
+ // the start and end times to present on the GUI by removing
+ // the already surpassed cut time. The copy here is intentional
+ // \sa Player_Editlist
+ for (EDL::Edit edit : m_vecEdits)
+ {
+ if (edit.action == Action::CUT)
+ {
+ surpassedSumOfCutDurations += edit.end - edit.start;
+ continue;
+ }
+
+ // substract the duration of already surpassed cuts
+ edit.start -= surpassedSumOfCutDurations;
+ edit.end -= surpassedSumOfCutDurations;
+ editList.emplace_back(edit);
+ }
+
+ return editList;
+}
+
+const std::vector<int64_t> CEdl::GetCutMarkers() const
+{
+ int surpassedSumOfCutDurations{0};
+ std::vector<int64_t> cutList;
+ for (const EDL::Edit& edit : m_vecEdits)
+ {
+ if (edit.action != Action::CUT)
+ continue;
+
+ cutList.emplace_back(edit.start - surpassedSumOfCutDurations);
+ surpassedSumOfCutDurations += edit.end - edit.start;
+ }
+ return cutList;
+}
+
+const std::vector<int64_t> CEdl::GetSceneMarkers() const
+{
+ std::vector<int64_t> sceneMarkers;
+ sceneMarkers.reserve(m_vecSceneMarkers.size());
+ for (const int& scene : m_vecSceneMarkers)
+ {
+ sceneMarkers.emplace_back(GetTimeWithoutCuts(scene));
+ }
+ return sceneMarkers;
+}
+
+int CEdl::GetTimeWithoutCuts(int seek) const
+{
+ if (!HasCuts())
+ return seek;
+
+ int cutTime = 0;
+ for (const EDL::Edit& edit : m_vecEdits)
+ {
+ if (edit.action != Action::CUT)
+ continue;
+
+ // inside cut
+ if (seek >= edit.start && seek <= edit.end)
+ {
+ // decrease cut lenght by 1 ms to jump over the end boundary.
+ cutTime += seek - edit.start - 1;
+ }
+ // cut has already been passed over
+ else if (seek >= edit.start)
+ {
+ cutTime += edit.end - edit.start;
+ }
+ }
+ return seek - cutTime;
+}
+
+double CEdl::GetTimeAfterRestoringCuts(double seek) const
+{
+ if (!HasCuts())
+ return seek;
+
+ for (const EDL::Edit& edit : m_vecEdits)
+ {
+ double cutDuration = static_cast<double>(edit.end - edit.start);
+ // add 1 ms to jump over the start boundary
+ if (edit.action == Action::CUT && seek > edit.start + 1)
+ {
+ seek += cutDuration;
+ }
+ }
+ return seek;
+}
+
+bool CEdl::HasSceneMarker() const
+{
+ return !m_vecSceneMarkers.empty();
+}
+
+bool CEdl::InEdit(const int iSeek, Edit* pEdit)
+{
+ for (size_t i = 0; i < m_vecEdits.size(); ++i)
+ {
+ if (iSeek < m_vecEdits[i].start) // Early exit if not even up to the edit start time.
+ return false;
+
+ if (iSeek >= m_vecEdits[i].start && iSeek <= m_vecEdits[i].end) // Inside edit.
+ {
+ if (pEdit)
+ *pEdit = m_vecEdits[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int CEdl::GetLastEditTime() const
+{
+ return m_lastEditTime;
+}
+
+void CEdl::SetLastEditTime(int editTime)
+{
+ m_lastEditTime = editTime;
+}
+
+void CEdl::ResetLastEditTime()
+{
+ m_lastEditTime = -1;
+}
+
+void CEdl::SetLastEditActionType(EDL::Action action)
+{
+ m_lastEditActionType = action;
+}
+
+EDL::Action CEdl::GetLastEditActionType() const
+{
+ return m_lastEditActionType;
+}
+
+bool CEdl::GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker)
+{
+ if (!HasSceneMarker())
+ return false;
+
+ int iSeek = GetTimeAfterRestoringCuts(iClock);
+
+ int iDiff = 10 * 60 * 60 * 1000; // 10 hours to ms.
+ bool bFound = false;
+
+ if (bPlus) // Find closest scene forwards
+ {
+ for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++)
+ {
+ if ((m_vecSceneMarkers[i] > iSeek) && ((m_vecSceneMarkers[i] - iSeek) < iDiff))
+ {
+ iDiff = m_vecSceneMarkers[i] - iSeek;
+ *iSceneMarker = m_vecSceneMarkers[i];
+ bFound = true;
+ }
+ }
+ }
+ else // Find closest scene backwards
+ {
+ for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++)
+ {
+ if ((m_vecSceneMarkers[i] < iSeek) && ((iSeek - m_vecSceneMarkers[i]) < iDiff))
+ {
+ iDiff = iSeek - m_vecSceneMarkers[i];
+ *iSceneMarker = m_vecSceneMarkers[i];
+ bFound = true;
+ }
+ }
+ }
+
+ /*
+ * If the scene marker is in a cut then return the end of the cut. Can't guarantee that this is
+ * picked up when scene markers are added.
+ */
+ Edit edit;
+ if (bFound && InEdit(*iSceneMarker, &edit) && edit.action == Action::CUT)
+ *iSceneMarker = edit.end;
+
+ return bFound;
+}
+
+std::string CEdl::MillisecondsToTimeString(const int iMilliseconds)
+{
+ std::string strTimeString = StringUtils::SecondsToTimeString((long)(iMilliseconds / 1000), TIME_FORMAT_HH_MM_SS); // milliseconds to seconds
+ strTimeString += StringUtils::Format(".{:03}", iMilliseconds % 1000);
+ return strTimeString;
+}
+
+void CEdl::MergeShortCommBreaks()
+{
+ /*
+ * mythcommflag routinely seems to put a 20-40ms commercial break at the start of the recording.
+ *
+ * Remove any spurious short commercial breaks at the very start so they don't interfere with
+ * the algorithms below.
+ */
+ if (!m_vecEdits.empty() && m_vecEdits[0].action == Action::COMM_BREAK &&
+ (m_vecEdits[0].end - m_vecEdits[0].start) < 5 * 1000) // 5 seconds
+ {
+ CLog::Log(LOGDEBUG, "{} - Removing short commercial break at start [{} - {}]. <5 seconds",
+ __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start),
+ MillisecondsToTimeString(m_vecEdits[0].end));
+ m_vecEdits.erase(m_vecEdits.begin());
+ }
+
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings->m_bEdlMergeShortCommBreaks)
+ {
+ for (size_t i = 0; i < m_vecEdits.size() - 1; ++i)
+ {
+ if ((m_vecEdits[i].action == Action::COMM_BREAK &&
+ m_vecEdits[i + 1].action == Action::COMM_BREAK) &&
+ (m_vecEdits[i + 1].end - m_vecEdits[i].start <
+ advancedSettings->m_iEdlMaxCommBreakLength * 1000) // s to ms
+ && (m_vecEdits[i + 1].start - m_vecEdits[i].end <
+ advancedSettings->m_iEdlMaxCommBreakGap * 1000)) // s to ms
+ {
+ Edit commBreak;
+ commBreak.action = Action::COMM_BREAK;
+ commBreak.start = m_vecEdits[i].start;
+ commBreak.end = m_vecEdits[i + 1].end;
+
+ CLog::Log(
+ LOGDEBUG, "{} - Consolidating commercial break [{} - {}] and [{} - {}] to: [{} - {}]",
+ __FUNCTION__, MillisecondsToTimeString(m_vecEdits[i].start),
+ MillisecondsToTimeString(m_vecEdits[i].end),
+ MillisecondsToTimeString(m_vecEdits[i + 1].start),
+ MillisecondsToTimeString(m_vecEdits[i + 1].end),
+ MillisecondsToTimeString(commBreak.start), MillisecondsToTimeString(commBreak.end));
+
+ /*
+ * Erase old edits and insert the new merged one.
+ */
+ m_vecEdits.erase(m_vecEdits.begin() + i, m_vecEdits.begin() + i + 2);
+ m_vecEdits.insert(m_vecEdits.begin() + i, commBreak);
+
+ i--; // Reduce i to see if the next break is also within the max commercial break length.
+ }
+ }
+
+ /*
+ * To cater for recordings that are started early and then have a commercial break identified
+ * before the TV show starts, expand the first commercial break to the very beginning if it
+ * starts within the maximum start gap. This is done outside of the consolidation to prevent
+ * the maximum commercial break length being triggered.
+ */
+ if (!m_vecEdits.empty() && m_vecEdits[0].action == Action::COMM_BREAK &&
+ m_vecEdits[0].start < advancedSettings->m_iEdlMaxStartGap * 1000)
+ {
+ CLog::Log(LOGDEBUG, "{} - Expanding first commercial break back to start [{} - {}].",
+ __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start),
+ MillisecondsToTimeString(m_vecEdits[0].end));
+ m_vecEdits[0].start = 0;
+ }
+
+ /*
+ * Remove any commercial breaks shorter than the minimum (unless at the start)
+ */
+ for (size_t i = 0; i < m_vecEdits.size(); ++i)
+ {
+ if (m_vecEdits[i].action == Action::COMM_BREAK && m_vecEdits[i].start > 0 &&
+ (m_vecEdits[i].end - m_vecEdits[i].start) <
+ advancedSettings->m_iEdlMinCommBreakLength * 1000)
+ {
+ CLog::Log(LOGDEBUG,
+ "{} - Removing short commercial break [{} - {}]. Minimum length: {} seconds",
+ __FUNCTION__, MillisecondsToTimeString(m_vecEdits[i].start),
+ MillisecondsToTimeString(m_vecEdits[i].end),
+ advancedSettings->m_iEdlMinCommBreakLength);
+ m_vecEdits.erase(m_vecEdits.begin() + i);
+
+ i--;
+ }
+ }
+ }
+}
+
+void CEdl::AddSceneMarkersAtStartAndEndOfEdits()
+{
+ for (const EDL::Edit& edit : m_vecEdits)
+ {
+ // Add scene markers at the start and end of commercial breaks
+ if (edit.action == Action::COMM_BREAK)
+ {
+ // Don't add a scene marker at the start.
+ if (edit.start > 0)
+ AddSceneMarker(edit.start);
+ AddSceneMarker(edit.end);
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h
new file mode 100644
index 0000000..faf3e73
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Edl.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/EdlEdit.h"
+
+#include <string>
+#include <vector>
+
+class CFileItem;
+
+class CEdl
+{
+public:
+ CEdl();
+
+ // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesPerSecond);
+ void Clear();
+
+ /*!
+ * @brief Check if there are any parsed edits in EDL for the current item
+ * @return true if EDL has edits, false otherwise
+ */
+ bool HasEdits() const;
+
+ /*!
+ * @brief Check if the edit list has EDL cuts (edits with action CUT)
+ * @return true if EDL has cuts, false otherwise
+ */
+ bool HasCuts() const;
+
+ bool HasSceneMarker() const;
+
+ /*!
+ * @brief Get the total cut time removed from the original item
+ * because of EDL cuts
+ * @return the total cut time
+ */
+ int GetTotalCutTime() const;
+
+ /*!
+ * @brief Providing a given seek time, return the actual time without
+ * considering cut ranges removed from the file
+ * @note VideoPlayer always displays/returns the playback time considering
+ * cut blocks are not part of the playable file
+ * @param seek the desired seek time
+ * @return the seek time without considering EDL cut blocks
+ */
+ int GetTimeWithoutCuts(int seek) const;
+
+ /*!
+ * @brief Provided a given seek time, return the time after correction with
+ * the addition of the already surpassed EDL cut ranges
+ * @note VideoPlayer uses it to restore the correct time after seek since cut blocks
+ * are not part of the playable file
+ * @param seek the desired seek time
+ * @return the seek time after applying the cut blocks already surpassed by the
+ * provided seek time
+ */
+ double GetTimeAfterRestoringCuts(double seek) const;
+
+ /*!
+ * @brief Get the raw EDL edit list.
+ * @return The EDL edits or an empty vector if no edits exist. Edits are
+ * provided with respect to the original media item timeline.
+ */
+ const std::vector<EDL::Edit>& GetRawEditList() const { return m_vecEdits; }
+
+ /*!
+ * @brief Get the EDL edit list.
+ * @return The EDL edits or an empty vector if no edits exist. Edits are
+ * provided with respect to the actual timeline, i.e. considering EDL cuts
+ * are not part of the media item.
+ */
+ const std::vector<EDL::Edit> GetEditList() const;
+
+ /*!
+ * @brief Get the list of EDL cut markers.
+ * @return The list of EDL cut markers or an empty vector if no EDL cuts exist.
+ * The returned values are accurate with respect to cut durations. I.e. if the file
+ * has multiple cuts, the positions of subsquent cuts are automatically corrected by
+ * substracting the previous cut durations.
+ */
+ const std::vector<int64_t> GetCutMarkers() const;
+
+ /*!
+ * @brief Get the list of EDL scene markers.
+ * @return The list of EDL scene markers or an empty vector if no EDL scene exist.
+ * The returned values are accurate with respect to cut durations. I.e. if the file
+ * has multiple cuts, the positions of scene markers are automatically corrected by
+ * substracting the surpassed cut durations until the scene marker point.
+ */
+ const std::vector<int64_t> GetSceneMarkers() const;
+
+ /*!
+ * @brief Check if for the provided seek time is contained within an EDL
+ * edit and fill pEdit with the respective edit struct.
+ * @note seek time refers to the time in the original file timeline (i.e. without
+ * considering cut blocks)
+ * @param iSeek The seek time (on the original timeline)
+ * @param[in,out] pEdit The edit pointer (or nullptr if iSeek not within an edit)
+ * @return true if iSeek is within an edit, false otherwise
+ */
+ bool InEdit(int iSeek, EDL::Edit* pEdit = nullptr);
+
+ /*!
+ * @brief Get the last processed edit time (set during playback when a given
+ * edit is surpassed)
+ * @return The last processed edit time (ms) or -1 if not any
+ */
+ int GetLastEditTime() const;
+
+ /*!
+ * @brief Set the last processed edit time (set during playback when a given
+ * edit is surpassed)
+ * @param editTime The last processed EDL edit time (ms)
+ */
+ void SetLastEditTime(int editTime);
+
+ /*!
+ * @brief Reset the last recorded edit time (-1)
+ */
+ void ResetLastEditTime();
+
+ /*!
+ * @brief Set the last processed edit action type
+ * @param action The action type (e.g. COMM_BREAK)
+ */
+ void SetLastEditActionType(EDL::Action action);
+
+ /*!
+ * @brief Get the last processed edit action type (set during playback when a given
+ * edit is surpassed)
+ * @return The last processed edit action type or -1 if not any
+ */
+ EDL::Action GetLastEditActionType() const;
+
+ // FIXME: remove const modifier for iClock as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker);
+
+ // FIXME: remove const modifier as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ static std::string MillisecondsToTimeString(const int iMilliseconds);
+
+private:
+ // total cut time (edl cuts) in ms
+ int m_totalCutTime;
+ std::vector<EDL::Edit> m_vecEdits;
+ std::vector<int> m_vecSceneMarkers;
+
+ /*!
+ * @brief Last processed EDL edit time (ms)
+ */
+ int m_lastEditTime;
+
+ /*!
+ * @brief Last processed EDL edit action type
+ */
+ EDL::Action m_lastEditActionType{EDL::EDL_ACTION_NONE};
+
+ // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool ReadEdl(const std::string& strMovie, const float fFramesPerSecond);
+ // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool ReadComskip(const std::string& strMovie, const float fFramesPerSecond);
+ // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool ReadVideoReDo(const std::string& strMovie);
+ // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool ReadBeyondTV(const std::string& strMovie);
+ bool ReadPvr(const CFileItem& fileItem);
+
+ /*!
+ * @brief Adds an edit to the list of EDL edits
+ * @param newEdit the edit to add
+ * @return true if the operation succeeds, false otherwise
+ */
+ bool AddEdit(const EDL::Edit& newEdit);
+
+ // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing
+ // for the reader of the interface, but limits the implementation
+ // to not modify the parameter on stack
+ bool AddSceneMarker(const int sceneMarker);
+
+ void MergeShortCommBreaks();
+
+ /*!
+ * @brief Adds scene markers at the start and end of some edits
+ * (currently only for commercial breaks)
+ */
+ void AddSceneMarkersAtStartAndEndOfEdits();
+};
diff --git a/xbmc/cores/VideoPlayer/IVideoPlayer.h b/xbmc/cores/VideoPlayer/IVideoPlayer.h
new file mode 100644
index 0000000..cf30266
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/IVideoPlayer.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDClock.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#define VideoPlayer_AUDIO 1
+#define VideoPlayer_VIDEO 2
+#define VideoPlayer_SUBTITLE 3
+#define VideoPlayer_TELETEXT 4
+#define VideoPlayer_RDS 5
+#define VideoPlayer_ID3 6
+
+class CDVDMsg;
+class CDVDStreamInfo;
+class CProcessInfo;
+
+class IVideoPlayer
+{
+public:
+ virtual int OnDiscNavResult(void* pData, int iMessage) = 0;
+ virtual void GetVideoResolution(unsigned int &width, unsigned int &height) = 0;
+ virtual ~IVideoPlayer() = default;
+};
+
+class IDVDStreamPlayer
+{
+public:
+ explicit IDVDStreamPlayer(CProcessInfo& processInfo) : m_processInfo(processInfo) {}
+ virtual ~IDVDStreamPlayer() = default;
+ virtual bool OpenStream(CDVDStreamInfo hint) = 0;
+ virtual void CloseStream(bool bWaitForBuffers) = 0;
+ virtual void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) = 0;
+ virtual void FlushMessages() = 0;
+ virtual bool IsInited() const = 0;
+ virtual bool AcceptsData() const = 0;
+ virtual bool IsStalled() const = 0;
+
+ enum ESyncState
+ {
+ SYNC_STARTING,
+ SYNC_WAITSYNC,
+ SYNC_INSYNC
+ };
+protected:
+ CProcessInfo &m_processInfo;
+};
+
+struct SStartMsg
+{
+ double timestamp;
+ int player;
+ double cachetime;
+ double cachetotal;
+};
+
+struct SStateMsg
+{
+ IDVDStreamPlayer::ESyncState syncState;
+ int player;
+};
+
+class CDVDVideoCodec;
+
+class IDVDStreamPlayerVideo : public IDVDStreamPlayer
+{
+public:
+ explicit IDVDStreamPlayerVideo(CProcessInfo& processInfo) : IDVDStreamPlayer(processInfo) {}
+ ~IDVDStreamPlayerVideo() override = default;
+ bool OpenStream(CDVDStreamInfo hint) override = 0;
+ void CloseStream(bool bWaitForBuffers) override = 0;
+ virtual void Flush(bool sync) = 0;
+ bool AcceptsData() const override = 0;
+ virtual bool HasData() const = 0;
+ bool IsInited() const override = 0;
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override = 0;
+ virtual void EnableSubtitle(bool bEnable) = 0;
+ virtual bool IsSubtitleEnabled() = 0;
+ virtual double GetSubtitleDelay() = 0;
+ virtual void SetSubtitleDelay(double delay) = 0;
+ bool IsStalled() const override = 0;
+ virtual bool IsRewindStalled() const { return false; }
+ virtual double GetCurrentPts() = 0;
+ virtual double GetOutputDelay() = 0;
+ virtual std::string GetPlayerInfo() = 0;
+ virtual int GetVideoBitrate() = 0;
+ virtual void SetSpeed(int iSpeed) = 0;
+ virtual bool IsEOS() { return false; }
+};
+
+class CDVDAudioCodec;
+class IDVDStreamPlayerAudio : public IDVDStreamPlayer
+{
+public:
+ explicit IDVDStreamPlayerAudio(CProcessInfo& processInfo) : IDVDStreamPlayer(processInfo) {}
+ ~IDVDStreamPlayerAudio() override = default;
+ bool OpenStream(CDVDStreamInfo hints) override = 0;
+ void CloseStream(bool bWaitForBuffers) override = 0;
+ virtual void SetSpeed(int speed) = 0;
+ virtual void Flush(bool sync) = 0;
+ bool AcceptsData() const override = 0;
+ virtual bool HasData() const = 0;
+ virtual int GetLevel() const = 0;
+ bool IsInited() const override = 0;
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override = 0;
+ virtual void SetVolume(float fVolume) {}
+ virtual void SetMute(bool bOnOff) {}
+ virtual void SetDynamicRangeCompression(long drc) = 0;
+ virtual std::string GetPlayerInfo() = 0;
+ virtual int GetAudioChannels() = 0;
+ virtual double GetCurrentPts() = 0;
+ bool IsStalled() const override = 0;
+ virtual bool IsPassthrough() const = 0;
+ virtual float GetDynamicRangeAmplification() const = 0;
+ virtual bool IsEOS() { return false; }
+};
diff --git a/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h b/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h
new file mode 100644
index 0000000..4d2a859
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_crypto.h"
+
+#include <string>
+
+//CryptoSession is usually obtained once per stream, but could change if an key expires
+
+enum CryptoSessionSystem : uint8_t
+{
+ CRYPTO_SESSION_SYSTEM_NONE,
+ CRYPTO_SESSION_SYSTEM_WIDEVINE,
+ CRYPTO_SESSION_SYSTEM_PLAYREADY,
+ CRYPTO_SESSION_SYSTEM_WISEPLAY,
+};
+
+struct DemuxCryptoSession
+{
+ DemuxCryptoSession(const CryptoSessionSystem sys, const char* sData, const uint8_t flags)
+ : sessionId(sData), keySystem(sys), flags(flags)
+ {
+ }
+
+ bool operator == (const DemuxCryptoSession &other) const
+ {
+ return keySystem == other.keySystem && sessionId == other.sessionId;
+ };
+
+ // encryped stream infos
+ std::string sessionId;
+ CryptoSessionSystem keySystem;
+
+ static const uint8_t FLAG_SECURE_DECODER = 1;
+ uint8_t flags;
+private:
+ DemuxCryptoSession(const DemuxCryptoSession&) = delete;
+ DemuxCryptoSession& operator=(const DemuxCryptoSession&) = delete;
+};
+
+//CryptoInfo stores the information to decrypt a sample
+
+struct DemuxCryptoInfo : DEMUX_CRYPTO_INFO
+{
+ explicit DemuxCryptoInfo(const unsigned int numSubs)
+ {
+ numSubSamples = numSubs;
+ flags = 0;
+ clearBytes = new uint16_t[numSubs];
+ cipherBytes = new uint32_t[numSubs];
+ };
+
+ ~DemuxCryptoInfo()
+ {
+ delete[] clearBytes;
+ delete[] cipherBytes;
+ }
+
+private:
+ DemuxCryptoInfo(const DemuxCryptoInfo&) = delete;
+ DemuxCryptoInfo& operator=(const DemuxCryptoInfo&) = delete;
+};
diff --git a/xbmc/cores/VideoPlayer/Interface/DemuxPacket.h b/xbmc/cores/VideoPlayer/Interface/DemuxPacket.h
new file mode 100644
index 0000000..a8ffb98
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Interface/DemuxPacket.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "TimingConstants.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/demux_packet.h"
+
+#define DMX_SPECIALID_STREAMINFO DEMUX_SPECIALID_STREAMINFO
+#define DMX_SPECIALID_STREAMCHANGE DEMUX_SPECIALID_STREAMCHANGE
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+ struct DemuxPacket : DEMUX_PACKET
+ {
+ DemuxPacket()
+ {
+ pData = nullptr;
+ iSize = 0;
+ iStreamId = -1;
+ demuxerId = -1;
+ iGroupId = -1;
+
+ pSideData = nullptr;
+ iSideDataElems = 0;
+
+ pts = DVD_NOPTS_VALUE;
+ dts = DVD_NOPTS_VALUE;
+ duration = 0;
+ dispTime = 0;
+ recoveryPoint = false;
+
+ cryptoInfo = nullptr;
+ }
+
+ //! @brief PTS offset correction applied to the PTS and DTS.
+ double m_ptsOffsetCorrection{0};
+ };
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
diff --git a/xbmc/cores/VideoPlayer/Interface/InputStreamConstants.h b/xbmc/cores/VideoPlayer/Interface/InputStreamConstants.h
new file mode 100644
index 0000000..93eabc5
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Interface/InputStreamConstants.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_constants.h"
diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h
new file mode 100644
index 0000000..df67662
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Geometry.h"
+
+#include <string>
+
+template <typename T> class CRectGen;
+typedef CRectGen<float> CRect;
+
+enum StreamFlags
+{
+ FLAG_NONE = 0x0000,
+ FLAG_DEFAULT = 0x0001,
+ FLAG_DUB = 0x0002,
+ FLAG_ORIGINAL = 0x0004,
+ FLAG_COMMENT = 0x0008,
+ FLAG_LYRICS = 0x0010,
+ FLAG_KARAOKE = 0x0020,
+ FLAG_FORCED = 0x0040,
+ FLAG_HEARING_IMPAIRED = 0x0080,
+ FLAG_VISUAL_IMPAIRED = 0x0100,
+ FLAG_STILL_IMAGES = 0x100000
+};
+
+enum class StreamHdrType
+{
+ HDR_TYPE_NONE, ///< <b>None</b>, returns an empty string when used in infolabels
+ HDR_TYPE_HDR10, ///< <b>HDR10</b>, returns `hdr10` when used in infolabels
+ HDR_TYPE_DOLBYVISION, ///< <b>Dolby Vision</b>, returns `dolbyvision` when used in infolabels
+ HDR_TYPE_HLG ///< <b>HLG</b>, returns `hlg` when used in infolabels
+};
+
+struct StreamInfo
+{
+ bool valid = false;
+ int bitrate = 0;
+ std::string language;
+ std::string name;
+ std::string codecName;
+ StreamFlags flags = StreamFlags::FLAG_NONE;
+
+protected:
+ StreamInfo() = default;
+ virtual ~StreamInfo() = default;
+};
+
+struct AudioStreamInfo : StreamInfo
+{
+ int channels = 0;
+ int samplerate = 0;
+ int bitspersample = 0;
+};
+
+struct SubtitleStreamInfo : StreamInfo
+{};
+
+struct VideoStreamInfo : StreamInfo
+{
+ float videoAspectRatio = 0.0f;
+ int height = 0;
+ int width = 0;
+ CRect SrcRect;
+ CRect DestRect;
+ CRect VideoRect;
+ std::string stereoMode;
+ int angles = 0;
+ StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE;
+};
+
+struct ProgramInfo
+{
+ int id = -1;
+ bool playing = false;
+ std::string name;
+};
diff --git a/xbmc/cores/VideoPlayer/Interface/TimingConstants.h b/xbmc/cores/VideoPlayer/Interface/TimingConstants.h
new file mode 100644
index 0000000..3f526cc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Interface/TimingConstants.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#define DVD_TIME_BASE 1000000
+#define DVD_NOPTS_VALUE 0xFFF0000000000000
+
+constexpr int64_t DVD_TIME_TO_MSEC(double x)
+{
+ return static_cast<int64_t>(x * 1000 / DVD_TIME_BASE);
+}
+constexpr double DVD_SEC_TO_TIME(double x) { return x * DVD_TIME_BASE; }
+constexpr double DVD_MSEC_TO_TIME(double x) { return x * DVD_TIME_BASE / 1000; }
+
+#define DVD_PLAYSPEED_PAUSE 0 // frame stepping
+#define DVD_PLAYSPEED_NORMAL 1000
diff --git a/xbmc/cores/VideoPlayer/PTSTracker.cpp b/xbmc/cores/VideoPlayer/PTSTracker.cpp
new file mode 100644
index 0000000..c5fdbf1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/PTSTracker.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PTSTracker.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cmath>
+
+#define MAXERR DVD_MSEC_TO_TIME(2.5)
+
+CPtsTracker::CPtsTracker()
+{
+ ResetVFRDetection();
+ Flush();
+}
+
+void CPtsTracker::ResetVFRDetection(void)
+{
+ m_minframeduration = DVD_NOPTS_VALUE;
+ m_maxframeduration = DVD_NOPTS_VALUE;
+ m_VFRCounter = 0;
+ m_patternCounter = 0;
+ m_lastPattern.clear();
+}
+
+void CPtsTracker::Flush()
+{
+ m_pattern.clear();
+ m_ringpos = 0;
+ m_prevpts = DVD_NOPTS_VALUE;
+ m_ringfill = 0;
+ m_haspattern = false;
+ m_patternlength = 0;
+ m_frameduration = DVD_NOPTS_VALUE;
+ memset(m_diffring, 0, sizeof(m_diffring));
+}
+
+void CPtsTracker::Add(double pts)
+{
+ //can't get a diff with just one pts
+ if (m_prevpts == DVD_NOPTS_VALUE)
+ {
+ m_prevpts = pts;
+ return;
+ }
+
+ //increase the ringbuffer position
+ m_ringpos = (m_ringpos + 1) % DIFFRINGSIZE;
+ //add the current diff to the ringbuffer
+ m_diffring[m_ringpos] = pts - m_prevpts;
+ //save the pts
+ m_prevpts = pts;
+
+ if (m_ringfill < DIFFRINGSIZE)
+ m_ringfill++;
+
+ //only search for patterns if we have full ringbuffer
+ if (m_ringfill < DIFFRINGSIZE)
+ return;
+
+ //get the current pattern in the ringbuffer
+ std::vector<double> pattern;
+ GetPattern(pattern);
+
+ //check if the pattern is the same as the saved pattern
+ //and if it is actually a pattern
+ if (!CheckPattern(pattern))
+ {
+ if (m_haspattern)
+ {
+ m_VFRCounter++;
+ m_lastPattern = m_pattern;
+ CLog::Log(LOGDEBUG, "CPtsTracker: pattern lost on diff {:f}, number of losses {}", GetDiff(0),
+ m_VFRCounter);
+ Flush();
+ }
+
+ //no pattern detected or current pattern broke/changed
+ //save detected pattern so we can check it with the next iteration
+ m_pattern = pattern;
+
+ return;
+ }
+ else
+ {
+ if (!m_haspattern)
+ {
+ m_haspattern = true;
+ m_patternlength = m_pattern.size();
+
+ if (!m_lastPattern.empty() && !CheckPattern(m_lastPattern))
+ {
+ m_patternCounter++;
+ }
+
+ double frameduration = CalcFrameDuration();
+ CLog::Log(LOGDEBUG, "CPtsTracker: detected pattern of length {}: {}, frameduration: {:f}",
+ (int)pattern.size(), GetPatternStr(), frameduration);
+ }
+ }
+
+ m_frameduration = CalcFrameDuration();
+}
+
+//gets a diff diffnr into the past
+inline double CPtsTracker::GetDiff(int diffnr)
+{
+ //m_ringpos is the last added diff, so if we want to go in the past we have to move back in the ringbuffer
+ int pos = m_ringpos - diffnr;
+ if (pos < 0)
+ pos += DIFFRINGSIZE;
+
+ return m_diffring[pos];
+}
+
+//calculate the current pattern in the ringbuffer
+void CPtsTracker::GetPattern(std::vector<double>& pattern)
+{
+ int difftypesbuff[DIFFRINGSIZE]; //difftypes of the diffs, difftypesbuff[0] is the last added diff,
+ //difftypesbuff[1] the one added before that etc
+
+ //get the difftypes
+ std::vector<double> difftypes;
+ for (int i = 0; i < m_ringfill; i++)
+ {
+ bool hasmatch = false;
+ for (unsigned int j = 0; j < difftypes.size(); j++)
+ {
+ if (MatchDiff(GetDiff(i), difftypes[j]))
+ {
+ hasmatch = true;
+ break;
+ }
+ }
+
+ //if we don't have a match with a saved difftype, we add it as a new one
+ if (!hasmatch)
+ difftypes.push_back(GetDiff(i));
+ }
+
+ //mark each diff with what difftype it is
+ for (int i = 0; i < m_ringfill; i++)
+ {
+ for (unsigned int j = 0; j < difftypes.size(); j++)
+ {
+ if (MatchDiff(GetDiff(i), difftypes[j]))
+ {
+ difftypesbuff[i] = j;
+ break;
+ }
+ }
+ }
+
+ bool checkexisting = !m_pattern.empty();
+
+ //we check for patterns to the length of DIFFRINGSIZE / 2
+ for (int i = 1; i <= m_ringfill / 2; i++)
+ {
+ //check the existing pattern length first
+ int length = checkexisting ? m_pattern.size() : i;
+
+ bool hasmatch = true;
+ for (int j = 1; j <= m_ringfill / length; j++)
+ {
+ int nrdiffs = length;
+ //we want to check the full buffer to see if the pattern repeats
+ //but we can't go beyond the buffer
+ if (j * length + length > m_ringfill)
+ nrdiffs = m_ringfill - j * length;
+
+ if (nrdiffs < 1) //if the buffersize can be cleanly divided by i we're done here
+ break;
+
+ if (!MatchDifftype(difftypesbuff, difftypesbuff + j * length, nrdiffs))
+ {
+ hasmatch = false;
+ break;
+ }
+ }
+
+ if (checkexisting)
+ {
+ checkexisting = false;
+ i--;
+ }
+
+ if (hasmatch)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ double avgdiff = 0.0;
+ for (int j = 0; j < m_ringfill / length; j++)
+ avgdiff += GetDiff(j * length + i);
+
+ avgdiff /= m_ringfill / length;
+ pattern.push_back(avgdiff);
+ }
+ break;
+ }
+ }
+ std::sort(pattern.begin(), pattern.end());
+}
+
+inline bool CPtsTracker::MatchDiff(double diff1, double diff2)
+{
+ return fabs(diff1 - diff2) < MAXERR;
+}
+
+//check if diffs1 is the same as diffs2
+inline bool CPtsTracker::MatchDifftype(int diffs1[], int diffs2[], int nrdiffs)
+{
+ for (int i = 0; i < nrdiffs; i++)
+ {
+ if (diffs1[i] != diffs2[i])
+ return false;
+ }
+ return true;
+}
+
+//check if our current detected pattern is the same as the one we saved
+bool CPtsTracker::CheckPattern(std::vector<double>& pattern)
+{
+ //if no pattern was detected or if the size of the patterns differ we don't have a match
+ if (pattern.empty() || pattern.size() != m_pattern.size())
+ return false;
+
+ if (pattern.size() == 1)
+ {
+ if (pattern[0] < MAXERR)
+ return false; //all diffs are too close to 0, can't use this
+ }
+
+ //check if the current pattern matches the saved pattern, with an offset of 1
+ for (unsigned int i = 0; i < m_pattern.size(); i++)
+ {
+ double diff = pattern[i];
+
+ if (!MatchDiff(diff, m_pattern[i]))
+ return false;
+ }
+
+ return true;
+}
+
+//calculate how long each frame should last from the saved pattern
+//also retrieve information of max and min frame rate duration, for VFR files case
+double CPtsTracker::CalcFrameDuration()
+{
+ if (!m_pattern.empty())
+ {
+ //take the average of all diffs in the pattern
+ double frameduration;
+ double current, currentmin, currentmax;
+
+ currentmin = m_pattern[0];
+ currentmax = currentmin;
+ frameduration = currentmin;
+ for (unsigned int i = 1; i < m_pattern.size(); i++)
+ {
+ current = m_pattern[i];
+ if (current>currentmax)
+ currentmax = current;
+ if (current<currentmin)
+ currentmin = current;
+ frameduration += current;
+ }
+ frameduration /= m_pattern.size();
+
+ // Update min and max frame duration, only if data is valid
+ bool standard = false;
+ double tempduration = CDVDCodecUtils::NormalizeFrameduration(currentmin, &standard);
+ if (m_minframeduration == DVD_NOPTS_VALUE)
+ {
+ if (standard)
+ m_minframeduration = tempduration;
+ }
+ else
+ {
+ if (standard && (tempduration < m_minframeduration))
+ m_minframeduration = tempduration;
+ }
+
+ tempduration = CDVDCodecUtils::NormalizeFrameduration(currentmax, &standard);
+ if (m_maxframeduration == DVD_NOPTS_VALUE)
+ {
+ if (standard)
+ m_maxframeduration = tempduration;
+ }
+ else
+ {
+ if (standard && (tempduration > m_maxframeduration))
+ m_maxframeduration = tempduration;
+ }
+
+ //frameduration is not completely correct, use a common one if it's close
+ return CDVDCodecUtils::NormalizeFrameduration(frameduration);
+ }
+
+ return DVD_NOPTS_VALUE;
+}
+
+//looks pretty in the log
+std::string CPtsTracker::GetPatternStr()
+{
+ std::string patternstr;
+
+ for (unsigned int i = 0; i < m_pattern.size(); i++)
+ patternstr += StringUtils::Format("{:.2f} ", m_pattern[i]);
+
+ StringUtils::Trim(patternstr);
+
+ return patternstr;
+}
diff --git a/xbmc/cores/VideoPlayer/PTSTracker.h b/xbmc/cores/VideoPlayer/PTSTracker.h
new file mode 100644
index 0000000..d7b98b6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/PTSTracker.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#define DIFFRINGSIZE 120
+#define VFR_DETECTION_THRESHOLD 3
+#define VFR_PATTERN_THRESHOLD 2
+
+class CPtsTracker
+{
+ public:
+ CPtsTracker();
+ void Add(double pts);
+ void Flush(); //flush the saved pattern and the ringbuffer
+ void ResetVFRDetection(void);
+
+ int GetPatternLength() { return m_patternlength; }
+ double GetFrameDuration() { return m_frameduration; }
+ double GetMaxFrameDuration(void) { return m_maxframeduration; }
+ double GetMinFrameDuration(void) { return m_minframeduration; }
+ bool HasFullBuffer() { return m_ringfill == DIFFRINGSIZE; }
+ bool VFRDetection(void) { return ((m_VFRCounter >= VFR_DETECTION_THRESHOLD) && (m_patternCounter >= VFR_PATTERN_THRESHOLD)); }
+
+ private:
+ double m_prevpts; //last pts added
+ double m_diffring[DIFFRINGSIZE]; //ringbuffer of differences between pts'
+ int m_ringpos; //position of last diff added to ringbuffer
+ int m_ringfill; //how many diffs we have in the ringbuffer
+ double GetDiff(int diffnr); //gets diffs from now to the past
+
+ void GetPattern(std::vector<double>& pattern); //gets the current pattern
+
+ static bool MatchDiff(double diff1, double diff2); //checks if two diffs match by MAXERR
+ static bool MatchDifftype(int diffs1[], int diffs2[], int nrdiffs); //checks if the difftypes match
+
+ //checks if the current pattern matches with the saved m_pattern with offset m_patternpos
+ bool CheckPattern(std::vector<double>& pattern);
+
+ double CalcFrameDuration(); //calculates the frame duration from m_pattern
+
+ std::vector<double> m_pattern, m_lastPattern; //the last saved pattern
+ double m_frameduration; //frameduration exposed to VideoPlayer, used for calculating the fps
+ double m_maxframeduration; //Max value detected for frame duration (for VFR files case)
+ double m_minframeduration; //Min value detected for frame duration (for VFR files case)
+ bool m_haspattern; //for the log and detecting VFR files case
+ int m_patternlength; //for the codec info
+ int m_VFRCounter; //retry counter for VFR detection
+ int m_patternCounter;
+ std::string GetPatternStr(); //also for the log
+};
diff --git a/xbmc/cores/VideoPlayer/Process/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/CMakeLists.txt
new file mode 100644
index 0000000..3197852
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES ProcessInfo.cpp)
+set(HEADERS ProcessInfo.h)
+
+core_add_library(process)
diff --git a/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp b/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp
new file mode 100644
index 0000000..00c58cb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfo.h"
+
+#include "ServiceBroker.h"
+#include "cores/DataCacheCore.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+
+#include <mutex>
+
+CCriticalSection createSection;
+std::map<std::string, CreateProcessControl> CProcessInfo::m_processControls;
+
+void CProcessInfo::RegisterProcessControl(const std::string& id, CreateProcessControl createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(createSection);
+
+ m_processControls.clear();
+ m_processControls[id] = createFunc;
+}
+
+CProcessInfo* CProcessInfo::CreateInstance()
+{
+ std::unique_lock<CCriticalSection> lock(createSection);
+
+ CProcessInfo *ret = nullptr;
+ for (auto &info : m_processControls)
+ {
+ ret = info.second();
+ if (ret)
+ return ret;
+ }
+ return new CProcessInfo();
+}
+
+CProcessInfo::CProcessInfo()
+{
+ m_videoSettingsLocked.reset(new CVideoSettingsLocked(m_videoSettings, m_settingsSection));
+}
+
+void CProcessInfo::SetDataCache(CDataCacheCore *cache)
+{
+ m_dataCache = cache;;
+
+ ResetVideoCodecInfo();
+ m_renderGuiLayer = false;
+ m_renderVideoLayer = false;
+ m_dataCache->SetGuiRender(m_renderGuiLayer);
+ m_dataCache->SetVideoRender(m_renderVideoLayer);
+}
+
+//******************************************************************************
+// video codec
+//******************************************************************************
+void CProcessInfo::ResetVideoCodecInfo()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoIsHWDecoder = false;
+ m_videoDecoderName = "unknown";
+ m_videoDeintMethod = "unknown";
+ m_videoPixelFormat = "unknown";
+ m_videoStereoMode.clear();
+ m_videoWidth = 0;
+ m_videoHeight = 0;
+ m_videoFPS = 0.0;
+ m_videoDAR = 0.0;
+ m_videoIsInterlaced = false;
+ m_deintMethods.clear();
+ m_deintMethods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE);
+ m_deintMethodDefault = EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE;
+ m_stateSeeking = false;
+
+ if (m_dataCache)
+ {
+ m_dataCache->SetVideoDecoderName(m_videoDecoderName, m_videoIsHWDecoder);
+ m_dataCache->SetVideoDeintMethod(m_videoDeintMethod);
+ m_dataCache->SetVideoPixelFormat(m_videoPixelFormat);
+ m_dataCache->SetVideoDimensions(m_videoWidth, m_videoHeight);
+ m_dataCache->SetVideoFps(m_videoFPS);
+ m_dataCache->SetVideoDAR(m_videoDAR);
+ m_dataCache->SetStateSeeking(m_stateSeeking);
+ m_dataCache->SetVideoStereoMode(m_videoStereoMode);
+ }
+}
+
+void CProcessInfo::SetVideoDecoderName(const std::string &name, bool isHw)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoIsHWDecoder = isHw;
+ m_videoDecoderName = name;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoDecoderName(m_videoDecoderName, m_videoIsHWDecoder);
+}
+
+std::string CProcessInfo::GetVideoDecoderName()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoDecoderName;
+}
+
+bool CProcessInfo::IsVideoHwDecoder()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoIsHWDecoder;
+}
+
+void CProcessInfo::SetVideoDeintMethod(const std::string &method)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoDeintMethod = method;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoDeintMethod(m_videoDeintMethod);
+}
+
+std::string CProcessInfo::GetVideoDeintMethod()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoDeintMethod;
+}
+
+void CProcessInfo::SetVideoPixelFormat(const std::string &pixFormat)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoPixelFormat = pixFormat;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoPixelFormat(m_videoPixelFormat);
+}
+
+std::string CProcessInfo::GetVideoPixelFormat()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoPixelFormat;
+}
+
+void CProcessInfo::SetVideoStereoMode(const std::string &mode)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoStereoMode = mode;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoStereoMode(m_videoStereoMode);
+}
+
+std::string CProcessInfo::GetVideoStereoMode()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoStereoMode;
+}
+
+void CProcessInfo::SetVideoDimensions(int width, int height)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoWidth = width;
+ m_videoHeight = height;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoDimensions(m_videoWidth, m_videoHeight);
+}
+
+void CProcessInfo::GetVideoDimensions(int &width, int &height)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ width = m_videoWidth;
+ height = m_videoHeight;
+}
+
+void CProcessInfo::SetVideoFps(float fps)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoFPS = fps;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoFps(m_videoFPS);
+}
+
+float CProcessInfo::GetVideoFps()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoFPS;
+}
+
+void CProcessInfo::SetVideoDAR(float dar)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoDAR = dar;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoDAR(m_videoDAR);
+}
+
+float CProcessInfo::GetVideoDAR()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoDAR;
+}
+
+void CProcessInfo::SetVideoInterlaced(bool interlaced)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_videoIsInterlaced = interlaced;
+
+ if (m_dataCache)
+ m_dataCache->SetVideoInterlaced(interlaced);
+}
+
+bool CProcessInfo::GetVideoInterlaced()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_videoIsInterlaced;
+}
+
+EINTERLACEMETHOD CProcessInfo::GetFallbackDeintMethod()
+{
+ return VS_INTERLACEMETHOD_DEINTERLACE;
+}
+
+void CProcessInfo::SetSwDeinterlacingMethods()
+{
+ std::list<EINTERLACEMETHOD> methods;
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE);
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE);
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE_HALF);
+
+ UpdateDeinterlacingMethods(methods);
+ SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE);
+}
+
+void CProcessInfo::UpdateDeinterlacingMethods(std::list<EINTERLACEMETHOD> &methods)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_deintMethods = methods;
+
+ for (auto &deint : m_renderInfo.m_deintMethods)
+ {
+ if (!Supports(deint))
+ m_deintMethods.push_back(deint);
+ }
+
+ if (!Supports(EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE))
+ m_deintMethods.push_front(EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE);
+}
+
+bool CProcessInfo::Supports(EINTERLACEMETHOD method) const
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ auto it = std::find(m_deintMethods.begin(), m_deintMethods.end(), method);
+ if (it != m_deintMethods.end())
+ return true;
+
+ return false;
+}
+
+void CProcessInfo::SetDeinterlacingMethodDefault(EINTERLACEMETHOD method)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_deintMethodDefault = method;
+}
+
+EINTERLACEMETHOD CProcessInfo::GetDeinterlacingMethodDefault() const
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ return m_deintMethodDefault;
+}
+
+CVideoBufferManager& CProcessInfo::GetVideoBufferManager()
+{
+ return m_videoBufferManager;
+}
+
+std::vector<AVPixelFormat> CProcessInfo::GetPixFormats()
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ if (m_pixFormats.empty())
+ {
+ return GetRenderFormats();
+ }
+ return m_pixFormats;
+}
+
+void CProcessInfo::SetPixFormats(std::vector<AVPixelFormat> &formats)
+{
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+
+ m_pixFormats = formats;
+}
+
+//******************************************************************************
+// player audio info
+//******************************************************************************
+void CProcessInfo::ResetAudioCodecInfo()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ m_audioDecoderName = "unknown";
+ m_audioChannels = "unknown";
+ m_audioSampleRate = 0;;
+ m_audioBitsPerSample = 0;
+
+ if (m_dataCache)
+ {
+ m_dataCache->SetAudioDecoderName(m_audioDecoderName);
+ m_dataCache->SetAudioChannels(m_audioChannels);
+ m_dataCache->SetAudioSampleRate(m_audioSampleRate);
+ m_dataCache->SetAudioBitsPerSample(m_audioBitsPerSample);
+ }
+}
+
+void CProcessInfo::SetAudioDecoderName(const std::string &name)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ m_audioDecoderName = name;
+
+ if (m_dataCache)
+ m_dataCache->SetAudioDecoderName(m_audioDecoderName);
+}
+
+std::string CProcessInfo::GetAudioDecoderName()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ return m_audioDecoderName;
+}
+
+void CProcessInfo::SetAudioChannels(const std::string &channels)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ m_audioChannels = channels;
+
+ if (m_dataCache)
+ m_dataCache->SetAudioChannels(m_audioChannels);
+}
+
+std::string CProcessInfo::GetAudioChannels()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ return m_audioChannels;
+}
+
+void CProcessInfo::SetAudioSampleRate(int sampleRate)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ m_audioSampleRate = sampleRate;
+
+ if (m_dataCache)
+ m_dataCache->SetAudioSampleRate(m_audioSampleRate);
+}
+
+int CProcessInfo::GetAudioSampleRate()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ return m_audioSampleRate;
+}
+
+void CProcessInfo::SetAudioBitsPerSample(int bitsPerSample)
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ m_audioBitsPerSample = bitsPerSample;
+
+ if (m_dataCache)
+ m_dataCache->SetAudioBitsPerSample(m_audioBitsPerSample);
+}
+
+int CProcessInfo::GetAudioBitsPerSample()
+{
+ std::unique_lock<CCriticalSection> lock(m_audioCodecSection);
+
+ return m_audioBitsPerSample;
+}
+
+bool CProcessInfo::AllowDTSHDDecode()
+{
+ return true;
+}
+
+void CProcessInfo::SetRenderClockSync(bool enabled)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ m_isClockSync = enabled;
+
+ if (m_dataCache)
+ m_dataCache->SetRenderClockSync(enabled);
+}
+
+bool CProcessInfo::IsRenderClockSync()
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ return m_isClockSync;
+}
+
+void CProcessInfo::UpdateRenderInfo(CRenderInfo &info)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ m_renderInfo = info;
+
+ for (auto &deint : m_renderInfo.m_deintMethods)
+ {
+ if (!Supports(deint))
+ m_deintMethods.push_back(deint);
+ }
+}
+
+void CProcessInfo::UpdateRenderBuffers(int queued, int discard, int free)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+ m_renderBufQueued = queued;
+ m_renderBufDiscard = discard;
+ m_renderBufFree = free;
+}
+
+void CProcessInfo::GetRenderBuffers(int &queued, int &discard, int &free)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+ queued = m_renderBufQueued;
+ discard = m_renderBufDiscard;
+ free = m_renderBufFree;
+}
+
+std::vector<AVPixelFormat> CProcessInfo::GetRenderFormats()
+{
+ std::vector<AVPixelFormat> formats;
+ formats.push_back(AV_PIX_FMT_YUV420P);
+ return formats;
+}
+
+//******************************************************************************
+// player states
+//******************************************************************************
+void CProcessInfo::SeekFinished(int64_t offset)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ if (m_dataCache)
+ m_dataCache->SeekFinished(offset);
+}
+
+void CProcessInfo::SetStateSeeking(bool active)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ m_stateSeeking = active;
+
+ if (m_dataCache)
+ m_dataCache->SetStateSeeking(active);
+}
+
+bool CProcessInfo::IsSeeking()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_stateSeeking;
+}
+
+void CProcessInfo::SetStateRealtime(bool state)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderSection);
+
+ m_realTimeStream = state;
+}
+
+bool CProcessInfo::IsRealtimeStream()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_realTimeStream;
+}
+
+void CProcessInfo::SetSpeed(float speed)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_speed = speed;
+ m_newSpeed = speed;
+
+ if (m_dataCache)
+ m_dataCache->SetSpeed(m_newTempo, speed);
+}
+
+void CProcessInfo::SetNewSpeed(float speed)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_newSpeed = speed;
+
+ if (m_dataCache)
+ m_dataCache->SetSpeed(m_tempo, speed);
+}
+
+float CProcessInfo::GetNewSpeed()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_newSpeed;
+}
+
+void CProcessInfo::SetFrameAdvance(bool fa)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_frameAdvance = fa;
+
+ if (m_dataCache)
+ m_dataCache->SetFrameAdvance(fa);
+}
+
+bool CProcessInfo::IsFrameAdvance()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_frameAdvance;
+}
+
+void CProcessInfo::SetTempo(float tempo)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_tempo = tempo;
+ m_newTempo = tempo;
+
+ if (m_dataCache)
+ m_dataCache->SetSpeed(tempo, m_newSpeed);
+}
+
+void CProcessInfo::SetNewTempo(float tempo)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ m_newTempo = tempo;
+
+ if (m_dataCache)
+ m_dataCache->SetSpeed(tempo, m_speed);
+}
+
+float CProcessInfo::GetNewTempo()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_newTempo;
+}
+
+float CProcessInfo::MinTempoPlatform()
+{
+ return 0.75f;
+}
+
+float CProcessInfo::MaxTempoPlatform()
+{
+ return 1.55f;
+}
+
+bool CProcessInfo::IsTempoAllowed(float tempo)
+{
+ if (tempo > MinTempoPlatform() &&
+ (tempo < MaxTempoPlatform() || tempo < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_maxTempo))
+ return true;
+
+ return false;
+}
+
+unsigned int CProcessInfo::GetMaxPassthroughOffSyncDuration() const
+{
+ return CServiceBroker::GetSettingsComponent()
+ ->GetAdvancedSettings()
+ ->m_maxPassthroughOffSyncDuration;
+}
+
+void CProcessInfo::SetLevelVQ(int level)
+{
+ m_levelVQ = level;
+}
+
+int CProcessInfo::GetLevelVQ()
+{
+ return m_levelVQ;
+}
+
+void CProcessInfo::SetGuiRender(bool gui)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ bool change = (m_renderGuiLayer != gui);
+ m_renderGuiLayer = gui;
+ if (change)
+ {
+ if (m_dataCache)
+ m_dataCache->SetGuiRender(gui);
+ }
+}
+
+bool CProcessInfo::GetGuiRender()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_renderGuiLayer;
+}
+
+void CProcessInfo::SetVideoRender(bool video)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ bool change = (m_renderVideoLayer != video);
+ m_renderVideoLayer = video;
+ if (change)
+ {
+ if (m_dataCache)
+ m_dataCache->SetVideoRender(video);
+ }
+}
+
+bool CProcessInfo::GetVideoRender()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+
+ return m_renderVideoLayer;
+}
+
+void CProcessInfo::SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max)
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ m_startTime = start;
+ m_time = current;
+ m_timeMin = min;
+ m_timeMax = max;
+
+ if (m_dataCache)
+ {
+ m_dataCache->SetPlayTimes(start, current, min, max);
+ }
+}
+
+int64_t CProcessInfo::GetMaxTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_stateSection);
+ return m_timeMax;
+}
+
+//******************************************************************************
+// settings
+//******************************************************************************
+CVideoSettings CProcessInfo::GetVideoSettings()
+{
+ std::unique_lock<CCriticalSection> lock(m_settingsSection);
+ return m_videoSettings;
+}
+
+CVideoSettingsLocked& CProcessInfo::GetVideoSettingsLocked()
+{
+ std::unique_lock<CCriticalSection> lock(m_settingsSection);
+ return *m_videoSettingsLocked;
+}
+
+void CProcessInfo::SetVideoSettings(CVideoSettings &settings)
+{
+ std::unique_lock<CCriticalSection> lock(m_settingsSection);
+ m_videoSettings = settings;
+}
diff --git a/xbmc/cores/VideoPlayer/Process/ProcessInfo.h b/xbmc/cores/VideoPlayer/Process/ProcessInfo.h
new file mode 100644
index 0000000..adac47b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/ProcessInfo.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderInfo.h"
+#include "cores/VideoSettings.h"
+#include "threads/CriticalSection.h"
+
+#include <atomic>
+#include <list>
+#include <map>
+#include <string>
+
+class CProcessInfo;
+class CDataCacheCore;
+
+using CreateProcessControl = CProcessInfo* (*)();
+
+class CProcessInfo
+{
+public:
+ static CProcessInfo* CreateInstance();
+ static void RegisterProcessControl(const std::string& id, CreateProcessControl createFunc);
+ virtual ~CProcessInfo() = default;
+ void SetDataCache(CDataCacheCore *cache);
+
+ // player video
+ void ResetVideoCodecInfo();
+ void SetVideoDecoderName(const std::string &name, bool isHw);
+ std::string GetVideoDecoderName();
+ bool IsVideoHwDecoder();
+ void SetVideoDeintMethod(const std::string &method);
+ std::string GetVideoDeintMethod();
+ void SetVideoPixelFormat(const std::string &pixFormat);
+ std::string GetVideoPixelFormat();
+ void SetVideoStereoMode(const std::string &mode);
+ std::string GetVideoStereoMode();
+ void SetVideoDimensions(int width, int height);
+ void GetVideoDimensions(int &width, int &height);
+ void SetVideoFps(float fps);
+ float GetVideoFps();
+ void SetVideoDAR(float dar);
+ float GetVideoDAR();
+ void SetVideoInterlaced(bool interlaced);
+ bool GetVideoInterlaced();
+ virtual EINTERLACEMETHOD GetFallbackDeintMethod();
+ virtual void SetSwDeinterlacingMethods();
+ void UpdateDeinterlacingMethods(std::list<EINTERLACEMETHOD> &methods);
+ bool Supports(EINTERLACEMETHOD method) const;
+ void SetDeinterlacingMethodDefault(EINTERLACEMETHOD method);
+ EINTERLACEMETHOD GetDeinterlacingMethodDefault() const;
+ CVideoBufferManager& GetVideoBufferManager();
+ std::vector<AVPixelFormat> GetPixFormats();
+ void SetPixFormats(std::vector<AVPixelFormat> &formats);
+
+ // player audio info
+ void ResetAudioCodecInfo();
+ void SetAudioDecoderName(const std::string &name);
+ std::string GetAudioDecoderName();
+ void SetAudioChannels(const std::string &channels);
+ std::string GetAudioChannels();
+ void SetAudioSampleRate(int sampleRate);
+ int GetAudioSampleRate();
+ void SetAudioBitsPerSample(int bitsPerSample);
+ int GetAudioBitsPerSample();
+ virtual bool AllowDTSHDDecode();
+ virtual bool WantsRawPassthrough() { return false; }
+
+ // render info
+ void SetRenderClockSync(bool enabled);
+ bool IsRenderClockSync();
+ void UpdateRenderInfo(CRenderInfo &info);
+ void UpdateRenderBuffers(int queued, int discard, int free);
+ void GetRenderBuffers(int &queued, int &discard, int &free);
+ virtual std::vector<AVPixelFormat> GetRenderFormats();
+
+ // player states
+ /*!
+ * @brief Notifies that a seek operation has finished
+ * @param offset - the seek offset
+ */
+ void SeekFinished(int64_t offset);
+
+ void SetStateSeeking(bool active);
+ bool IsSeeking();
+ void SetStateRealtime(bool state);
+ bool IsRealtimeStream();
+ void SetSpeed(float speed);
+ void SetNewSpeed(float speed);
+ float GetNewSpeed();
+ void SetFrameAdvance(bool fa);
+ bool IsFrameAdvance();
+ void SetTempo(float tempo);
+ void SetNewTempo(float tempo);
+ float GetNewTempo();
+ bool IsTempoAllowed(float tempo);
+ virtual float MinTempoPlatform();
+ virtual float MaxTempoPlatform();
+ void SetLevelVQ(int level);
+ int GetLevelVQ();
+ void SetGuiRender(bool gui);
+ bool GetGuiRender();
+ void SetVideoRender(bool video);
+ bool GetVideoRender();
+ unsigned int GetMaxPassthroughOffSyncDuration() const;
+
+ void SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max);
+ int64_t GetMaxTime();
+
+ // settings
+ CVideoSettings GetVideoSettings();
+ void SetVideoSettings(CVideoSettings &settings);
+ CVideoSettingsLocked& GetVideoSettingsLocked();
+
+protected:
+ CProcessInfo();
+ static std::map<std::string, CreateProcessControl> m_processControls;
+ CDataCacheCore *m_dataCache = nullptr;
+
+ // player video info
+ bool m_videoIsHWDecoder;
+ std::string m_videoDecoderName;
+ std::string m_videoDeintMethod;
+ std::string m_videoPixelFormat;
+ std::string m_videoStereoMode;
+ int m_videoWidth;
+ int m_videoHeight;
+ float m_videoFPS;
+ float m_videoDAR;
+ bool m_videoIsInterlaced;
+ std::list<EINTERLACEMETHOD> m_deintMethods;
+ EINTERLACEMETHOD m_deintMethodDefault;
+ mutable CCriticalSection m_videoCodecSection;
+ CVideoBufferManager m_videoBufferManager;
+ std::vector<AVPixelFormat> m_pixFormats;
+
+ // player audio info
+ std::string m_audioDecoderName;
+ std::string m_audioChannels;
+ int m_audioSampleRate;
+ int m_audioBitsPerSample;
+ CCriticalSection m_audioCodecSection;
+
+ // render info
+ CCriticalSection m_renderSection;
+ bool m_isClockSync;
+ CRenderInfo m_renderInfo;
+ int m_renderBufQueued = 0;
+ int m_renderBufFree = 0;
+ int m_renderBufDiscard = 0;
+
+ // player states
+ CCriticalSection m_stateSection;
+ bool m_stateSeeking;
+ std::atomic_int m_levelVQ;
+ std::atomic_bool m_renderGuiLayer;
+ std::atomic_bool m_renderVideoLayer;
+ float m_tempo;
+ float m_newTempo;
+ float m_speed;
+ float m_newSpeed;
+ bool m_frameAdvance;
+ time_t m_startTime;
+ int64_t m_time;
+ int64_t m_timeMax;
+ int64_t m_timeMin;
+ bool m_realTimeStream;
+
+ // settings
+ CCriticalSection m_settingsSection;
+ CVideoSettings m_videoSettings;
+ std::unique_ptr<CVideoSettingsLocked> m_videoSettingsLocked;
+};
diff --git a/xbmc/cores/VideoPlayer/Process/X11/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/X11/CMakeLists.txt
new file mode 100644
index 0000000..d30d213
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/X11/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ProcessInfoX11.cpp)
+
+set(HEADERS ProcessInfoX11.h)
+
+core_add_library(processX11)
diff --git a/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.cpp b/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.cpp
new file mode 100644
index 0000000..3fd91f4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoX11.h"
+
+#include <mutex>
+
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoX11::Create()
+{
+ return new CProcessInfoX11();
+}
+
+void CProcessInfoX11::Register()
+{
+ CProcessInfo::RegisterProcessControl("X11", CProcessInfoX11::Create);
+}
+
+void CProcessInfoX11::SetSwDeinterlacingMethods()
+{
+ // first populate with the defaults from base implementation
+ CProcessInfo::SetSwDeinterlacingMethods();
+
+ std::list<EINTERLACEMETHOD> methods;
+ {
+ // get the current methods
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+ methods = m_deintMethods;
+ }
+ // add bob and blend deinterlacer for osx
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BOB);
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BLEND);
+
+ // update with the new methods list
+ UpdateDeinterlacingMethods(methods);
+}
+
+std::vector<AVPixelFormat> CProcessInfoX11::GetRenderFormats()
+{
+ return
+ {
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUV420P9,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_YUV420P12,
+ AV_PIX_FMT_YUV420P14,
+ AV_PIX_FMT_YUV420P16,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_YUYV422,
+ AV_PIX_FMT_UYVY422
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.h b/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.h
new file mode 100644
index 0000000..72beefa
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/X11/ProcessInfoX11.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoX11 : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ void SetSwDeinterlacingMethods() override;
+ std::vector<AVPixelFormat> GetRenderFormats() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/Process/android/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/android/CMakeLists.txt
new file mode 100644
index 0000000..c1505b1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/android/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ProcessInfoAndroid.cpp)
+
+set(HEADERS ProcessInfoAndroid.h)
+
+core_add_library(processAndroid)
diff --git a/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.cpp b/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.cpp
new file mode 100644
index 0000000..88e9a1e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoAndroid.h"
+
+#include "ServiceBroker.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoAndroid::Create()
+{
+ return new CProcessInfoAndroid();
+}
+
+void CProcessInfoAndroid::Register()
+{
+ CProcessInfo::RegisterProcessControl("android", CProcessInfoAndroid::Create);
+}
+
+EINTERLACEMETHOD CProcessInfoAndroid::GetFallbackDeintMethod()
+{
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE_HALF;
+}
+
+bool CProcessInfoAndroid::WantsRawPassthrough()
+{
+ const std::string device = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE);
+
+ if (std::string::npos != device.find("(RAW)"))
+ return true;
+
+ return false;
+}
diff --git a/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.h b/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.h
new file mode 100644
index 0000000..4499c8d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/android/ProcessInfoAndroid.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoAndroid : public CProcessInfo
+{
+public:
+ CProcessInfoAndroid() = default;
+ static CProcessInfo* Create();
+ static void Register();
+ EINTERLACEMETHOD GetFallbackDeintMethod() override;
+ bool WantsRawPassthrough() override;
+};
+
+} // namespace VIDEOPLAYER
diff --git a/xbmc/cores/VideoPlayer/Process/gbm/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/gbm/CMakeLists.txt
new file mode 100644
index 0000000..852fc96
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/gbm/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES ProcessInfoGBM.cpp)
+set(HEADERS ProcessInfoGBM.h)
+
+core_add_library(processGBM)
diff --git a/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.cpp b/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.cpp
new file mode 100644
index 0000000..be97902
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoGBM.h"
+
+#include "cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h"
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoGBM::Create()
+{
+ return new CProcessInfoGBM();
+}
+
+void CProcessInfoGBM::Register()
+{
+ CProcessInfo::RegisterProcessControl("gbm", CProcessInfoGBM::Create);
+}
+
+CProcessInfoGBM::CProcessInfoGBM()
+{
+ m_videoBufferManager.RegisterPool(std::make_shared<CVideoBufferPoolDMA>());
+}
+
+EINTERLACEMETHOD CProcessInfoGBM::GetFallbackDeintMethod()
+{
+#if defined(__arm__)
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE_HALF;
+#else
+ return CProcessInfo::GetFallbackDeintMethod();
+#endif
+}
diff --git a/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h b/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h
new file mode 100644
index 0000000..0524d6e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoGBM : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ CProcessInfoGBM();
+ EINTERLACEMETHOD GetFallbackDeintMethod() override;
+};
+
+} // namespace VIDEOPLAYER
diff --git a/xbmc/cores/VideoPlayer/Process/ios/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/ios/CMakeLists.txt
new file mode 100644
index 0000000..999b2da
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/ios/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ProcessInfoIOS.cpp)
+
+set(HEADERS ProcessInfoIOS.h)
+
+core_add_library(processios)
diff --git a/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIOS.h b/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIOS.h
new file mode 100644
index 0000000..2b1824d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIOS.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoIOS : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ void SetSwDeinterlacingMethods() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIos.cpp b/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIos.cpp
new file mode 100644
index 0000000..ee3bc3e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/ios/ProcessInfoIos.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoIOS.h"
+
+#include <mutex>
+
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoIOS::Create()
+{
+ return new CProcessInfoIOS();
+}
+
+void CProcessInfoIOS::Register()
+{
+ CProcessInfo::RegisterProcessControl("ios", CProcessInfoIOS::Create);
+}
+
+void CProcessInfoIOS::SetSwDeinterlacingMethods()
+{
+ // first populate with the defaults from base implementation
+ CProcessInfo::SetSwDeinterlacingMethods();
+
+ std::list<EINTERLACEMETHOD> methods;
+ {
+ // get the current methods
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+ methods = m_deintMethods;
+ }
+ // add bob deinterlacer for ios
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BOB);
+
+ // update with the new methods list
+ UpdateDeinterlacingMethods(methods);
+}
+
diff --git a/xbmc/cores/VideoPlayer/Process/osx/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/osx/CMakeLists.txt
new file mode 100644
index 0000000..b4c0da5
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/osx/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ProcessInfoOSX.cpp)
+
+set(HEADERS ProcessInfoOSX.h)
+
+core_add_library(processosx)
diff --git a/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.cpp b/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.cpp
new file mode 100644
index 0000000..b301961
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoOSX.h"
+
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+#include <mutex>
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoOSX::Create()
+{
+ return new CProcessInfoOSX();
+}
+
+void CProcessInfoOSX::Register()
+{
+ CProcessInfo::RegisterProcessControl("osx", CProcessInfoOSX::Create);
+}
+
+void CProcessInfoOSX::SetSwDeinterlacingMethods()
+{
+ // first populate with the defaults from base implementation
+ CProcessInfo::SetSwDeinterlacingMethods();
+
+ std::list<EINTERLACEMETHOD> methods;
+ {
+ // get the current methods
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+ methods = m_deintMethods;
+ }
+ // add bob and blend deinterlacer for osx
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BOB);
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BLEND);
+
+ // update with the new methods list
+ UpdateDeinterlacingMethods(methods);
+}
+
+std::vector<AVPixelFormat> CProcessInfoOSX::GetRenderFormats()
+{
+ std::vector<AVPixelFormat> formats;
+ formats.push_back(AV_PIX_FMT_YUV420P);
+ formats.push_back(AV_PIX_FMT_YUV420P10);
+ formats.push_back(AV_PIX_FMT_YUV420P16);
+ formats.push_back(AV_PIX_FMT_NV12);
+ formats.push_back(AV_PIX_FMT_YUYV422);
+ formats.push_back(AV_PIX_FMT_UYVY422);
+
+ return formats;
+}
+
diff --git a/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.h b/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.h
new file mode 100644
index 0000000..2013d6d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/osx/ProcessInfoOSX.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+#include "cores/IPlayer.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoOSX : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ void SetSwDeinterlacingMethods() override;
+ std::vector<AVPixelFormat> GetRenderFormats() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/Process/wayland/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/wayland/CMakeLists.txt
new file mode 100644
index 0000000..bbc6cbd
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/wayland/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES ProcessInfoWayland.cpp)
+
+set(HEADERS ProcessInfoWayland.h)
+
+core_add_library(processWayland)
diff --git a/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.cpp b/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.cpp
new file mode 100644
index 0000000..b47a93a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoWayland.h"
+
+#include "cores/VideoPlayer/Buffers/VideoBufferPoolDMA.h"
+
+#include <mutex>
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoWayland::Create()
+{
+ return new CProcessInfoWayland();
+}
+
+void CProcessInfoWayland::Register()
+{
+ CProcessInfo::RegisterProcessControl("Wayland", CProcessInfoWayland::Create);
+}
+
+CProcessInfoWayland::CProcessInfoWayland()
+{
+ m_videoBufferManager.RegisterPool(std::make_shared<CVideoBufferPoolDMA>());
+}
+
+void CProcessInfoWayland::SetSwDeinterlacingMethods()
+{
+ // first populate with the defaults from base implementation
+ CProcessInfo::SetSwDeinterlacingMethods();
+
+ std::list<EINTERLACEMETHOD> methods;
+ {
+ // get the current methods
+ std::unique_lock<CCriticalSection> lock(m_videoCodecSection);
+ methods = m_deintMethods;
+ }
+ // add bob and blend deinterlacer
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BOB);
+ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_RENDER_BLEND);
+
+ // update with the new methods list
+ UpdateDeinterlacingMethods(methods);
+}
+
+std::vector<AVPixelFormat> CProcessInfoWayland::GetRenderFormats()
+{
+ return
+ {
+ // GL & GLES
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_NV12,
+
+#if defined(HAS_GL)
+ // Full GL only at the moment
+ // TODO YUV420Pxx need runtime-checking for GL_ALPHA16/GL_LUMINANCE16 support
+ AV_PIX_FMT_YUV420P9,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_YUV420P12,
+ AV_PIX_FMT_YUV420P14,
+ AV_PIX_FMT_YUV420P16,
+ AV_PIX_FMT_YUYV422,
+ AV_PIX_FMT_UYVY422
+#endif
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h b/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h
new file mode 100644
index 0000000..91df3f7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoWayland : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ CProcessInfoWayland();
+ void SetSwDeinterlacingMethods() override;
+ std::vector<AVPixelFormat> GetRenderFormats() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/Process/windows/CMakeLists.txt b/xbmc/cores/VideoPlayer/Process/windows/CMakeLists.txt
new file mode 100644
index 0000000..bb5047f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/windows/CMakeLists.txt
@@ -0,0 +1,9 @@
+if(CORE_SYSTEM_NAME STREQUAL windows)
+ set(SOURCES ProcessInfoWin.cpp)
+ set(HEADERS ProcessInfoWin.h)
+elseif(CORE_SYSTEM_NAME STREQUAL windowsstore)
+ set(SOURCES ProcessInfoWin10.cpp)
+ set(HEADERS ProcessInfoWin10.h)
+endif()
+
+core_add_library(processwin)
diff --git a/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.cpp b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.cpp
new file mode 100644
index 0000000..eaa6d77
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoWin.h"
+
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoWin::Create()
+{
+ return new CProcessInfoWin();
+}
+
+void CProcessInfoWin::Register()
+{
+ RegisterProcessControl("win", Create);
+}
+
+EINTERLACEMETHOD CProcessInfoWin::GetFallbackDeintMethod()
+{
+ return VS_INTERLACEMETHOD_AUTO;
+}
+
+std::vector<AVPixelFormat> CProcessInfoWin::GetRenderFormats()
+{
+ return {
+ AV_PIX_FMT_D3D11VA_VLD,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_P010,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_P016,
+ AV_PIX_FMT_YUV420P16
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.h b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.h
new file mode 100644
index 0000000..4393a35
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+#include "cores/IPlayer.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoWin : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ EINTERLACEMETHOD GetFallbackDeintMethod() override;
+ std::vector<AVPixelFormat> GetRenderFormats() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.cpp b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.cpp
new file mode 100644
index 0000000..00b6790
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ProcessInfoWin10.h"
+
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+
+#include <set>
+
+using namespace VIDEOPLAYER;
+
+CProcessInfo* CProcessInfoWin10::Create()
+{
+ return new CProcessInfoWin10();
+}
+
+void CProcessInfoWin10::Register()
+{
+ CProcessInfo::RegisterProcessControl("win10", CProcessInfoWin10::Create);
+}
+
+EINTERLACEMETHOD CProcessInfoWin10::GetFallbackDeintMethod()
+{
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_AUTO;
+}
+
+std::vector<AVPixelFormat> CProcessInfoWin10::GetRenderFormats()
+{
+ return {
+ AV_PIX_FMT_D3D11VA_VLD,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_P010,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_P016,
+ AV_PIX_FMT_YUV420P16
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.h b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.h
new file mode 100644
index 0000000..acb06e7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/Process/windows/ProcessInfoWin10.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../ProcessInfo.h"
+#include "cores/IPlayer.h"
+
+namespace VIDEOPLAYER
+{
+
+class CProcessInfoWin10 : public CProcessInfo
+{
+public:
+ static CProcessInfo* Create();
+ static void Register();
+
+ EINTERLACEMETHOD GetFallbackDeintMethod() override;
+ std::vector<AVPixelFormat> GetRenderFormats() override;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp
new file mode 100644
index 0000000..fcba11c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp
@@ -0,0 +1,5364 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayer.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "DVDDemuxers/DVDDemux.h"
+#include "DVDDemuxers/DVDDemuxCC.h"
+#include "DVDDemuxers/DVDDemuxFFmpeg.h"
+#include "DVDDemuxers/DVDDemuxUtils.h"
+#include "DVDDemuxers/DVDDemuxVobsub.h"
+#include "DVDDemuxers/DVDFactoryDemuxer.h"
+#include "DVDInputStreams/DVDFactoryInputStream.h"
+#include "DVDInputStreams/DVDInputStream.h"
+#include "DVDMessage.h"
+#include "VideoPlayerVideo.h"
+#include "application/Application.h"
+
+#include <mutex>
+#if defined(HAVE_LIBBLURAY)
+#include "DVDInputStreams/DVDInputStreamBluray.h"
+#endif
+#include "DVDInputStreams/DVDInputStreamNavigator.h"
+#include "DVDInputStreams/InputStreamPVRBase.h"
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "VideoPlayerAudio.h"
+#include "VideoPlayerRadioRDS.h"
+#include "cores/DataCacheCore.h"
+#include "cores/EdlEdit.h"
+#include "cores/FFmpeg.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/StereoscopicsManager.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "storage/MediaManager.h"
+#include "threads/SingleLock.h"
+#include "utils/FontUtils.h"
+#include "utils/JobManager.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/StreamDetails.h"
+#include "utils/StreamUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "video/Bookmark.h"
+#include "video/VideoInfoTag.h"
+#include "windowing/WinSystem.h"
+
+#include <iterator>
+#include <utility>
+
+using namespace std::chrono_literals;
+
+//------------------------------------------------------------------------------
+// selection streams
+//------------------------------------------------------------------------------
+
+#define PREDICATE_RETURN(lh, rh) \
+ do { \
+ if((lh) != (rh)) \
+ return (lh) > (rh); \
+ } while(0)
+
+class PredicateSubtitleFilter
+{
+private:
+ std::string audiolang;
+ bool original;
+ bool nosub;
+ bool onlyforced;
+ int currentSubStream;
+public:
+ /** \brief The class' operator() decides if the given (subtitle) SelectionStream is relevant wrt.
+ * preferred subtitle language and audio language. If the subtitle is relevant <B>false</B> false is returned.
+ *
+ * A subtitle is relevant if
+ * - it was previously selected, or
+ * - it's an external sub, or
+ * - it's a forced sub and "original stream's language" was selected and audio stream language matches, or
+ * - it's a default and a forced sub (could lead to users seeing forced subs in a foreign language!), or
+ * - its language matches the preferred subtitle's language (unequal to "original stream's language")
+ */
+ explicit PredicateSubtitleFilter(const std::string& lang, int subStream)
+ : audiolang(lang),
+ currentSubStream(subStream)
+ {
+ const std::string subtitleLang = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE);
+ original = StringUtils::EqualsNoCase(subtitleLang, "original");
+ nosub = StringUtils::EqualsNoCase(subtitleLang, "none");
+ onlyforced = StringUtils::EqualsNoCase(subtitleLang, "forced_only");
+ };
+
+ bool operator()(const SelectionStream& ss) const
+ {
+ if (ss.type_index == currentSubStream)
+ return false;
+
+ if (nosub)
+ return true;
+
+ if (onlyforced)
+ {
+ if ((ss.flags & StreamFlags::FLAG_FORCED) && g_LangCodeExpander.CompareISO639Codes(ss.language, audiolang))
+ return false;
+ else
+ return true;
+ }
+
+ if(STREAM_SOURCE_MASK(ss.source) == STREAM_SOURCE_DEMUX_SUB || STREAM_SOURCE_MASK(ss.source) == STREAM_SOURCE_TEXT)
+ return false;
+
+ if ((ss.flags & StreamFlags::FLAG_FORCED) && g_LangCodeExpander.CompareISO639Codes(ss.language, audiolang))
+ return false;
+
+ if ((ss.flags & StreamFlags::FLAG_FORCED) && (ss.flags & StreamFlags::FLAG_DEFAULT))
+ return false;
+
+ if (ss.language == "cc" && ss.flags & StreamFlags::FLAG_HEARING_IMPAIRED)
+ return false;
+
+ if(!original)
+ {
+ std::string subtitle_language = g_langInfo.GetSubtitleLanguage();
+ if (g_LangCodeExpander.CompareISO639Codes(subtitle_language, ss.language))
+ return false;
+ }
+ else if (ss.flags & StreamFlags::FLAG_DEFAULT)
+ return false;
+
+ return true;
+ }
+};
+
+class PredicateAudioFilter
+{
+private:
+ int currentAudioStream;
+ bool preferStereo;
+public:
+ explicit PredicateAudioFilter(int audioStream, bool preferStereo)
+ : currentAudioStream(audioStream)
+ , preferStereo(preferStereo)
+ {
+ };
+ bool operator()(const SelectionStream& lh, const SelectionStream& rh)
+ {
+ PREDICATE_RETURN(lh.type_index == currentAudioStream
+ , rh.type_index == currentAudioStream);
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ if (!StringUtils::EqualsNoCase(settings->GetString(CSettings::SETTING_LOCALE_AUDIOLANGUAGE), "mediadefault"))
+ {
+ if (!StringUtils::EqualsNoCase(settings->GetString(CSettings::SETTING_LOCALE_AUDIOLANGUAGE), "original"))
+ {
+ std::string audio_language = g_langInfo.GetAudioLanguage();
+ PREDICATE_RETURN(g_LangCodeExpander.CompareISO639Codes(audio_language, lh.language)
+ , g_LangCodeExpander.CompareISO639Codes(audio_language, rh.language));
+ }
+ else
+ {
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_ORIGINAL,
+ rh.flags & StreamFlags::FLAG_ORIGINAL);
+ }
+
+ bool hearingimp = settings->GetBool(CSettings::SETTING_ACCESSIBILITY_AUDIOHEARING);
+ PREDICATE_RETURN(!hearingimp ? !(lh.flags & StreamFlags::FLAG_HEARING_IMPAIRED) : lh.flags & StreamFlags::FLAG_HEARING_IMPAIRED
+ , !hearingimp ? !(rh.flags & StreamFlags::FLAG_HEARING_IMPAIRED) : rh.flags & StreamFlags::FLAG_HEARING_IMPAIRED);
+
+ bool visualimp = settings->GetBool(CSettings::SETTING_ACCESSIBILITY_AUDIOVISUAL);
+ PREDICATE_RETURN(!visualimp ? !(lh.flags & StreamFlags::FLAG_VISUAL_IMPAIRED) : lh.flags & StreamFlags::FLAG_VISUAL_IMPAIRED
+ , !visualimp ? !(rh.flags & StreamFlags::FLAG_VISUAL_IMPAIRED) : rh.flags & StreamFlags::FLAG_VISUAL_IMPAIRED);
+ }
+
+ if (settings->GetBool(CSettings::SETTING_VIDEOPLAYER_PREFERDEFAULTFLAG))
+ {
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_DEFAULT,
+ rh.flags & StreamFlags::FLAG_DEFAULT);
+ }
+
+ if (preferStereo)
+ PREDICATE_RETURN(lh.channels == 2,
+ rh.channels == 2);
+ else
+ PREDICATE_RETURN(lh.channels,
+ rh.channels);
+
+ PREDICATE_RETURN(StreamUtils::GetCodecPriority(lh.codec),
+ StreamUtils::GetCodecPriority(rh.codec));
+
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_DEFAULT,
+ rh.flags & StreamFlags::FLAG_DEFAULT);
+ return false;
+ };
+};
+
+/** \brief The class' operator() decides if the given (subtitle) SelectionStream lh is 'better than' the given (subtitle) SelectionStream rh.
+* If lh is 'better than' rh the return value is true, false otherwise.
+*
+* A subtitle lh is 'better than' a subtitle rh (in evaluation order) if
+* - lh was previously selected, or
+* - lh is an external sub and rh not, or
+* - lh is a forced sub and ("original stream's language" was selected or subtitles are off) and audio stream language matches sub language and rh not, or
+* - lh is a default sub and ("original stream's language" was selected or subtitles are off) and audio stream language matches sub language and rh not, or
+* - lh is a sub where audio stream language matches sub language and (original stream's language" was selected or subtitles are off) and rh not, or
+* - lh is a forced sub and a default sub ("original stream's language" was selected or subtitles are off)
+* - lh is an external sub and its language matches the preferred subtitle's language (unequal to "original stream's language") and rh not, or
+* - lh is language matches the preferred subtitle's language (unequal to "original stream's language") and rh not, or
+* - lh is a default sub and rh not
+*/
+class PredicateSubtitlePriority
+{
+private:
+ std::string audiolang;
+ bool original;
+ bool subson;
+ PredicateSubtitleFilter filter;
+ int subStream;
+public:
+ explicit PredicateSubtitlePriority(const std::string& lang, int stream, bool ison)
+ : audiolang(lang),
+ original(StringUtils::EqualsNoCase(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE), "original")),
+ subson(ison),
+ filter(lang, stream),
+ subStream(stream)
+ {
+ };
+
+ bool relevant(const SelectionStream& ss) const
+ {
+ return !filter(ss);
+ }
+
+ bool operator()(const SelectionStream& lh, const SelectionStream& rh) const
+ {
+ PREDICATE_RETURN(relevant(lh)
+ , relevant(rh));
+
+ PREDICATE_RETURN(lh.type_index == subStream
+ , rh.type_index == subStream);
+
+ // prefer external subs
+ PREDICATE_RETURN(STREAM_SOURCE_MASK(lh.source) == STREAM_SOURCE_DEMUX_SUB || STREAM_SOURCE_MASK(lh.source) == STREAM_SOURCE_TEXT
+ , STREAM_SOURCE_MASK(rh.source) == STREAM_SOURCE_DEMUX_SUB || STREAM_SOURCE_MASK(rh.source) == STREAM_SOURCE_TEXT);
+
+ if (!subson || original)
+ {
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_FORCED && g_LangCodeExpander.CompareISO639Codes(lh.language, audiolang)
+ , rh.flags & StreamFlags::FLAG_FORCED && g_LangCodeExpander.CompareISO639Codes(rh.language, audiolang));
+
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_DEFAULT && g_LangCodeExpander.CompareISO639Codes(lh.language, audiolang)
+ , rh.flags & StreamFlags::FLAG_DEFAULT && g_LangCodeExpander.CompareISO639Codes(rh.language, audiolang));
+
+ PREDICATE_RETURN(g_LangCodeExpander.CompareISO639Codes(lh.language, audiolang)
+ , g_LangCodeExpander.CompareISO639Codes(rh.language, audiolang));
+
+ PREDICATE_RETURN((lh.flags & (StreamFlags::FLAG_FORCED | StreamFlags::FLAG_DEFAULT)) == (StreamFlags::FLAG_FORCED | StreamFlags::FLAG_DEFAULT)
+ , (rh.flags & (StreamFlags::FLAG_FORCED | StreamFlags::FLAG_DEFAULT)) == (StreamFlags::FLAG_FORCED | StreamFlags::FLAG_DEFAULT));
+
+ }
+
+ std::string subtitle_language = g_langInfo.GetSubtitleLanguage();
+ if (!original)
+ {
+ PREDICATE_RETURN((STREAM_SOURCE_MASK(lh.source) == STREAM_SOURCE_DEMUX_SUB || STREAM_SOURCE_MASK(lh.source) == STREAM_SOURCE_TEXT) && g_LangCodeExpander.CompareISO639Codes(subtitle_language, lh.language)
+ , (STREAM_SOURCE_MASK(rh.source) == STREAM_SOURCE_DEMUX_SUB || STREAM_SOURCE_MASK(rh.source) == STREAM_SOURCE_TEXT) && g_LangCodeExpander.CompareISO639Codes(subtitle_language, rh.language));
+ }
+
+ if (!original)
+ {
+ PREDICATE_RETURN(g_LangCodeExpander.CompareISO639Codes(subtitle_language, lh.language)
+ , g_LangCodeExpander.CompareISO639Codes(subtitle_language, rh.language));
+
+ bool hearingimp = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_ACCESSIBILITY_SUBHEARING);
+ PREDICATE_RETURN(!hearingimp ? !(lh.flags & StreamFlags::FLAG_HEARING_IMPAIRED) : lh.flags & StreamFlags::FLAG_HEARING_IMPAIRED
+ , !hearingimp ? !(rh.flags & StreamFlags::FLAG_HEARING_IMPAIRED) : rh.flags & StreamFlags::FLAG_HEARING_IMPAIRED);
+ }
+
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_DEFAULT
+ , rh.flags & StreamFlags::FLAG_DEFAULT);
+
+ return false;
+ }
+};
+
+class PredicateVideoFilter
+{
+private:
+ int currentVideoStream;
+public:
+ explicit PredicateVideoFilter(int videoStream) : currentVideoStream(videoStream)
+ {
+ };
+ bool operator()(const SelectionStream& lh, const SelectionStream& rh)
+ {
+ PREDICATE_RETURN(lh.type_index == currentVideoStream,
+ rh.type_index == currentVideoStream);
+
+ PREDICATE_RETURN(lh.flags & StreamFlags::FLAG_DEFAULT,
+ rh.flags & StreamFlags::FLAG_DEFAULT);
+ return false;
+ }
+};
+
+void CSelectionStreams::Clear(StreamType type, StreamSource source)
+{
+ auto new_end = std::remove_if(m_Streams.begin(), m_Streams.end(),
+ [type, source](const SelectionStream &stream)
+ {
+ return (type == STREAM_NONE || stream.type == type) &&
+ (source == 0 || stream.source == source);
+ });
+ m_Streams.erase(new_end, m_Streams.end());
+}
+
+SelectionStream& CSelectionStreams::Get(StreamType type, int index)
+{
+ return const_cast<SelectionStream&>(std::as_const(*this).Get(type, index));
+}
+
+const SelectionStream& CSelectionStreams::Get(StreamType type, int index) const
+{
+ int count = -1;
+ for (size_t i = 0; i < m_Streams.size(); ++i)
+ {
+ if (m_Streams[i].type != type)
+ continue;
+ count++;
+ if (count == index)
+ return m_Streams[i];
+ }
+ return m_invalid;
+}
+
+std::vector<SelectionStream> CSelectionStreams::Get(StreamType type)
+{
+ std::vector<SelectionStream> streams;
+ std::copy_if(m_Streams.begin(), m_Streams.end(), std::back_inserter(streams),
+ [type](const SelectionStream &stream)
+ {
+ return stream.type == type;
+ });
+ return streams;
+}
+
+bool CSelectionStreams::Get(StreamType type, StreamFlags flag, SelectionStream& out)
+{
+ for(size_t i=0;i<m_Streams.size();i++)
+ {
+ if(m_Streams[i].type != type)
+ continue;
+ if((m_Streams[i].flags & flag) != flag)
+ continue;
+ out = m_Streams[i];
+ return true;
+ }
+ return false;
+}
+
+int CSelectionStreams::TypeIndexOf(StreamType type, int source, int64_t demuxerId, int id) const
+{
+ if (id < 0)
+ return -1;
+
+ auto it = std::find_if(m_Streams.begin(), m_Streams.end(),
+ [&](const SelectionStream& stream) {return stream.type == type
+ && stream.source == source && stream.id == id
+ && stream.demuxerId == demuxerId;});
+
+ if (it != m_Streams.end())
+ return it->type_index;
+ else
+ return -1;
+}
+
+int CSelectionStreams::Source(StreamSource source, const std::string& filename)
+{
+ int index = source - 1;
+ for (size_t i=0; i<m_Streams.size(); i++)
+ {
+ SelectionStream &s = m_Streams[i];
+ if (STREAM_SOURCE_MASK(s.source) != source)
+ continue;
+ // if it already exists, return same
+ if (s.filename == filename)
+ return s.source;
+ if (index < s.source)
+ index = s.source;
+ }
+ // return next index
+ return index + 1;
+}
+
+void CSelectionStreams::Update(SelectionStream& s)
+{
+ int index = TypeIndexOf(s.type, s.source, s.demuxerId, s.id);
+ if(index >= 0)
+ {
+ SelectionStream& o = Get(s.type, index);
+ s.type_index = o.type_index;
+ o = s;
+ }
+ else
+ {
+ s.type_index = CountType(s.type);
+ m_Streams.push_back(s);
+ }
+}
+
+void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input,
+ CDVDDemux* demuxer,
+ const std::string& filename2)
+{
+ if(input && input->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> nav = std::static_pointer_cast<CDVDInputStreamNavigator>(input);
+ std::string filename = nav->GetFileName();
+ int source = Source(STREAM_SOURCE_NAV, filename);
+
+ int count;
+ count = nav->GetAudioStreamCount();
+ for(int i=0;i<count;i++)
+ {
+ SelectionStream s;
+ s.source = source;
+ s.type = STREAM_AUDIO;
+ s.id = i;
+ s.flags = StreamFlags::FLAG_NONE;
+ s.filename = filename;
+
+ AudioStreamInfo info = nav->GetAudioStreamInfo(i);
+ s.name = info.name;
+ s.codec = info.codecName;
+ s.language = g_LangCodeExpander.ConvertToISO6392B(info.language);
+ s.channels = info.channels;
+ s.flags = info.flags;
+ Update(s);
+ }
+
+ count = nav->GetSubTitleStreamCount();
+ for(int i=0;i<count;i++)
+ {
+ SelectionStream s;
+ s.source = source;
+ s.type = STREAM_SUBTITLE;
+ s.id = i;
+ s.filename = filename;
+ s.channels = 0;
+
+ SubtitleStreamInfo info = nav->GetSubtitleStreamInfo(i);
+ s.name = info.name;
+ s.flags = info.flags;
+ s.language = g_LangCodeExpander.ConvertToISO6392B(info.language);
+ Update(s);
+ }
+
+ VideoStreamInfo info = nav->GetVideoStreamInfo();
+ for (int i = 1; i <= info.angles; i++)
+ {
+ SelectionStream s;
+ s.source = source;
+ s.type = STREAM_VIDEO;
+ s.id = i;
+ s.flags = StreamFlags::FLAG_NONE;
+ s.filename = filename;
+ s.channels = 0;
+ s.aspect_ratio = info.videoAspectRatio;
+ s.width = info.width;
+ s.height = info.height;
+ s.codec = info.codecName;
+ s.name = StringUtils::Format("{} {}", g_localizeStrings.Get(38032), i);
+ Update(s);
+ }
+ }
+ else if(demuxer)
+ {
+ std::string filename = demuxer->GetFileName();
+ int source;
+ if(input) /* hack to know this is sub decoder */
+ source = Source(STREAM_SOURCE_DEMUX, filename);
+ else if (!filename2.empty())
+ source = Source(STREAM_SOURCE_DEMUX_SUB, filename);
+ else
+ source = Source(STREAM_SOURCE_VIDEOMUX, filename);
+
+ for (auto stream : demuxer->GetStreams())
+ {
+ /* skip streams with no type */
+ if (stream->type == STREAM_NONE)
+ continue;
+ /* make sure stream is marked with right source */
+ stream->source = source;
+
+ SelectionStream s;
+ s.source = source;
+ s.type = stream->type;
+ s.id = stream->uniqueId;
+ s.demuxerId = stream->demuxerId;
+ s.language = g_LangCodeExpander.ConvertToISO6392B(stream->language);
+ s.flags = stream->flags;
+ s.filename = demuxer->GetFileName();
+ s.filename2 = filename2;
+ s.name = stream->GetStreamName();
+ s.codec = demuxer->GetStreamCodecName(stream->demuxerId, stream->uniqueId);
+ s.channels = 0; // Default to 0. Overwrite if STREAM_AUDIO below.
+ if(stream->type == STREAM_VIDEO)
+ {
+ CDemuxStreamVideo* vstream = static_cast<CDemuxStreamVideo*>(stream);
+ s.width = vstream->iWidth;
+ s.height = vstream->iHeight;
+ s.aspect_ratio = vstream->fAspect;
+ s.stereo_mode = vstream->stereo_mode;
+ s.bitrate = vstream->iBitRate;
+ s.hdrType = vstream->hdr_type;
+ }
+ if(stream->type == STREAM_AUDIO)
+ {
+ std::string type;
+ type = static_cast<CDemuxStreamAudio*>(stream)->GetStreamType();
+ if(type.length() > 0)
+ {
+ if(s.name.length() > 0)
+ s.name += " - ";
+ s.name += type;
+ }
+ s.channels = static_cast<CDemuxStreamAudio*>(stream)->iChannels;
+ s.bitrate = static_cast<CDemuxStreamAudio*>(stream)->iBitRate;
+ }
+ Update(s);
+ }
+ }
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalVideoInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalSubtitleInfoChange();
+}
+
+void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, CDVDDemux* demuxer)
+{
+ Update(input, demuxer, "");
+}
+
+int CSelectionStreams::CountTypeOfSource(StreamType type, StreamSource source) const
+{
+ return std::count_if(m_Streams.begin(), m_Streams.end(),
+ [&](const SelectionStream& stream) {return (stream.type == type) && (stream.source == source);});
+}
+
+int CSelectionStreams::CountType(StreamType type) const
+{
+ return std::count_if(m_Streams.begin(), m_Streams.end(),
+ [&](const SelectionStream& stream) { return stream.type == type; });
+}
+
+//------------------------------------------------------------------------------
+// main class
+//------------------------------------------------------------------------------
+
+void CVideoPlayer::CreatePlayers()
+{
+ if (m_players_created)
+ return;
+
+ m_VideoPlayerVideo = new CVideoPlayerVideo(&m_clock, &m_overlayContainer, m_messenger, m_renderManager, *m_processInfo);
+ m_VideoPlayerAudio = new CVideoPlayerAudio(&m_clock, m_messenger, *m_processInfo);
+ m_VideoPlayerSubtitle = new CVideoPlayerSubtitle(&m_overlayContainer, *m_processInfo);
+ m_VideoPlayerTeletext = new CDVDTeletextData(*m_processInfo);
+ m_VideoPlayerRadioRDS = new CDVDRadioRDSData(*m_processInfo);
+ m_VideoPlayerAudioID3 = std::make_unique<CVideoPlayerAudioID3>(*m_processInfo);
+ m_players_created = true;
+}
+
+void CVideoPlayer::DestroyPlayers()
+{
+ if (!m_players_created)
+ return;
+
+ delete m_VideoPlayerVideo;
+ delete m_VideoPlayerAudio;
+ delete m_VideoPlayerSubtitle;
+ delete m_VideoPlayerTeletext;
+ delete m_VideoPlayerRadioRDS;
+ m_VideoPlayerAudioID3.reset();
+
+ m_players_created = false;
+}
+
+CVideoPlayer::CVideoPlayer(IPlayerCallback& callback)
+ : IPlayer(callback),
+ CThread("VideoPlayer"),
+ m_CurrentAudio(STREAM_AUDIO, VideoPlayer_AUDIO),
+ m_CurrentVideo(STREAM_VIDEO, VideoPlayer_VIDEO),
+ m_CurrentSubtitle(STREAM_SUBTITLE, VideoPlayer_SUBTITLE),
+ m_CurrentTeletext(STREAM_TELETEXT, VideoPlayer_TELETEXT),
+ m_CurrentRadioRDS(STREAM_RADIO_RDS, VideoPlayer_RDS),
+ m_CurrentAudioID3(STREAM_AUDIO_ID3, VideoPlayer_ID3),
+ m_messenger("player"),
+ m_renderManager(m_clock, this)
+{
+ m_outboundEvents.reset(new CJobQueue(false, 1, CJob::PRIORITY_NORMAL));
+ m_players_created = false;
+ m_pDemuxer = nullptr;
+ m_pSubtitleDemuxer = nullptr;
+ m_pCCDemuxer = nullptr;
+ m_pInputStream = nullptr;
+
+ m_dvd.Clear();
+ m_State.Clear();
+
+ m_bAbortRequest = false;
+ m_offset_pts = 0.0;
+ m_playSpeed = DVD_PLAYSPEED_NORMAL;
+ m_streamPlayerSpeed = DVD_PLAYSPEED_NORMAL;
+ m_caching = CACHESTATE_DONE;
+ m_HasVideo = false;
+ m_HasAudio = false;
+ m_UpdateStreamDetails = false;
+
+ memset(&m_SpeedState, 0, sizeof(m_SpeedState));
+
+ m_SkipCommercials = true;
+
+ m_processInfo.reset(CProcessInfo::CreateInstance());
+ // if we have a gui, register the cache
+ m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore());
+ m_processInfo->SetSpeed(1.0);
+ m_processInfo->SetTempo(1.0);
+ m_processInfo->SetFrameAdvance(false);
+
+ CreatePlayers();
+
+ m_displayLost = false;
+ m_error = false;
+ m_bCloseRequest = false;
+ CServiceBroker::GetWinSystem()->Register(this);
+}
+
+CVideoPlayer::~CVideoPlayer()
+{
+ CServiceBroker::GetWinSystem()->Unregister(this);
+
+ CloseFile();
+ DestroyPlayers();
+
+ while (m_outboundEvents->IsProcessing())
+ {
+ CThread::Sleep(10ms);
+ }
+}
+
+bool CVideoPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
+{
+ CLog::Log(LOGINFO, "VideoPlayer::OpenFile: {}", CURL::GetRedacted(file.GetPath()));
+
+ if (IsRunning())
+ {
+ CDVDMsgOpenFile::FileParams params;
+ params.m_item = file;
+ params.m_options = options;
+ params.m_item.SetMimeTypeForInternetFile();
+ m_messenger.Put(std::make_shared<CDVDMsgOpenFile>(params), 1);
+
+ return true;
+ }
+
+ m_item = file;
+ m_playerOptions = options;
+
+ m_processInfo->SetPlayTimes(0,0,0,0);
+ m_bAbortRequest = false;
+ m_error = false;
+ m_bCloseRequest = false;
+ m_renderManager.PreInit();
+
+ Create();
+ m_messenger.Init();
+
+ m_callback.OnPlayBackStarted(m_item);
+
+ return true;
+}
+
+bool CVideoPlayer::CloseFile(bool reopen)
+{
+ CLog::Log(LOGINFO, "CVideoPlayer::CloseFile()");
+
+ // set the abort request so that other threads can finish up
+ m_bAbortRequest = true;
+ m_bCloseRequest = true;
+
+ // tell demuxer to abort
+ if(m_pDemuxer)
+ m_pDemuxer->Abort();
+
+ if(m_pSubtitleDemuxer)
+ m_pSubtitleDemuxer->Abort();
+
+ if(m_pInputStream)
+ m_pInputStream->Abort();
+
+ m_renderManager.UnInit();
+
+ CLog::Log(LOGINFO, "VideoPlayer: waiting for threads to exit");
+
+ // wait for the main thread to finish up
+ // since this main thread cleans up all other resources and threads
+ // we are done after the StopThread call
+ {
+ CSingleExit exitlock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ StopThread();
+ }
+
+ m_Edl.Clear();
+ CServiceBroker::GetDataCacheCore().SetEditList(m_Edl.GetEditList());
+ CServiceBroker::GetDataCacheCore().SetCuts(m_Edl.GetCutMarkers());
+ CServiceBroker::GetDataCacheCore().SetSceneMarkers(m_Edl.GetSceneMarkers());
+
+ m_HasVideo = false;
+ m_HasAudio = false;
+
+ CLog::Log(LOGINFO, "VideoPlayer: finished waiting");
+ return true;
+}
+
+bool CVideoPlayer::IsPlaying() const
+{
+ return !m_bStop;
+}
+
+void CVideoPlayer::OnStartup()
+{
+ m_CurrentVideo.Clear();
+ m_CurrentAudio.Clear();
+ m_CurrentSubtitle.Clear();
+ m_CurrentTeletext.Clear();
+ m_CurrentRadioRDS.Clear();
+ m_CurrentAudioID3.Clear();
+
+ UTILS::FONT::ClearTemporaryFonts();
+}
+
+bool CVideoPlayer::OpenInputStream()
+{
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+
+ CLog::Log(LOGINFO, "Creating InputStream");
+
+ // correct the filename if needed
+ const CURL url{m_item.GetPath()};
+ if (url.GetProtocol() == "dvd")
+ {
+ // FIXME: we should deprecate this when more than one device drive is supported
+ m_item.SetPath(CServiceBroker::GetMediaManager().TranslateDevicePath(""));
+ }
+ else if (url.GetProtocol() == "iso9660" && !url.GetHostName().empty() &&
+ url.GetFileName() == "VIDEO_TS/video_ts.ifo")
+ {
+ m_item.SetPath(url.GetHostName());
+ }
+
+ m_pInputStream = CDVDFactoryInputStream::CreateInputStream(this, m_item, true);
+ if (m_pInputStream == nullptr)
+ {
+ CLog::Log(LOGERROR, "CVideoPlayer::OpenInputStream - unable to create input stream for [{}]",
+ CURL::GetRedacted(m_item.GetPath()));
+ return false;
+ }
+
+ if (!m_pInputStream->Open())
+ {
+ CLog::Log(LOGERROR, "CVideoPlayer::OpenInputStream - error opening [{}]",
+ CURL::GetRedacted(m_item.GetPath()));
+ return false;
+ }
+
+ // find any available external subtitles for non dvd files
+ if (!m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) &&
+ !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER))
+ {
+ // find any available external subtitles
+ std::vector<std::string> filenames;
+ CUtil::ScanForExternalSubtitles(m_item.GetDynPath(), filenames);
+
+ // load any subtitles from file item
+ std::string key("subtitle:1");
+ for (unsigned s = 1; m_item.HasProperty(key); key = StringUtils::Format("subtitle:{}", ++s))
+ filenames.push_back(m_item.GetProperty(key).asString());
+
+ for (unsigned int i=0;i<filenames.size();i++)
+ {
+ // if vobsub subtitle:
+ if (URIUtils::HasExtension(filenames[i], ".idx"))
+ {
+ std::string strSubFile;
+ if (CUtil::FindVobSubPair( filenames, filenames[i], strSubFile))
+ AddSubtitleFile(filenames[i], strSubFile);
+ }
+ else
+ {
+ if (!CUtil::IsVobSub(filenames, filenames[i] ))
+ {
+ AddSubtitleFile(filenames[i]);
+ }
+ }
+ } // end loop over all subtitle files
+ }
+
+ m_clock.Reset();
+ m_dvd.Clear();
+
+ return true;
+}
+
+bool CVideoPlayer::OpenDemuxStream()
+{
+ CloseDemuxer();
+
+ CLog::Log(LOGINFO, "Creating Demuxer");
+
+ int attempts = 10;
+ while (!m_bStop && attempts-- > 0)
+ {
+ m_pDemuxer.reset(CDVDFactoryDemuxer::CreateDemuxer(m_pInputStream));
+ if(!m_pDemuxer && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER))
+ {
+ continue;
+ }
+ else if(!m_pDemuxer && m_pInputStream->NextStream() != CDVDInputStream::NEXTSTREAM_NONE)
+ {
+ CLog::Log(LOGDEBUG, "{} - New stream available from input, retry open", __FUNCTION__);
+ continue;
+ }
+ break;
+ }
+
+ if (!m_pDemuxer)
+ {
+ CLog::Log(LOGERROR, "{} - Error creating demuxer", __FUNCTION__);
+ return false;
+ }
+
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX);
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NAV);
+ m_SelectionStreams.Update(m_pInputStream, m_pDemuxer.get());
+ m_pDemuxer->GetPrograms(m_programs);
+ UpdateContent();
+ m_demuxerSpeed = DVD_PLAYSPEED_NORMAL;
+ m_processInfo->SetStateRealtime(false);
+
+ int64_t len = m_pInputStream->GetLength();
+ int64_t tim = m_pDemuxer->GetStreamLength();
+ if (len > 0 && tim > 0)
+ m_pInputStream->SetReadRate(static_cast<uint32_t>(len * 1000 / tim));
+
+ m_offset_pts = 0;
+
+ return true;
+}
+
+void CVideoPlayer::CloseDemuxer()
+{
+ m_pDemuxer.reset();
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX);
+
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalVideoInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalSubtitleInfoChange();
+}
+
+void CVideoPlayer::OpenDefaultStreams(bool reset)
+{
+ // if input stream dictate, we will open later
+ if (m_dvd.iSelectedAudioStream >= 0 ||
+ m_dvd.iSelectedSPUStream >= 0)
+ return;
+
+ bool valid;
+
+ // open video stream
+ valid = false;
+
+ PredicateVideoFilter vf(m_processInfo->GetVideoSettings().m_VideoStream);
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_VIDEO, vf))
+ {
+ if (OpenStream(m_CurrentVideo, stream.demuxerId, stream.id, stream.source, reset))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid)
+ {
+ CloseStream(m_CurrentVideo, true);
+ m_processInfo->ResetVideoCodecInfo();
+ }
+
+ // open audio stream
+ valid = false;
+ if (!m_playerOptions.videoOnly)
+ {
+ PredicateAudioFilter af(m_processInfo->GetVideoSettings().m_AudioStream, m_playerOptions.preferStereo);
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_AUDIO, af))
+ {
+ if(OpenStream(m_CurrentAudio, stream.demuxerId, stream.id, stream.source, reset))
+ {
+ valid = true;
+ break;
+ }
+ }
+ }
+
+ if(!valid)
+ {
+ CloseStream(m_CurrentAudio, true);
+ m_processInfo->ResetAudioCodecInfo();
+ }
+
+ // enable or disable subtitles
+ bool visible = m_processInfo->GetVideoSettings().m_SubtitleOn;
+
+ // open subtitle stream
+ SelectionStream as = m_SelectionStreams.Get(STREAM_AUDIO, GetAudioStream());
+ PredicateSubtitlePriority psp(as.language,
+ m_processInfo->GetVideoSettings().m_SubtitleStream,
+ m_processInfo->GetVideoSettings().m_SubtitleOn);
+ valid = false;
+ // We need to close CC subtitles to avoid conflicts with external sub stream
+ if (m_CurrentSubtitle.source == STREAM_SOURCE_VIDEOMUX)
+ CloseStream(m_CurrentSubtitle, false);
+
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_SUBTITLE, psp))
+ {
+ if (OpenStream(m_CurrentSubtitle, stream.demuxerId, stream.id, stream.source))
+ {
+ valid = true;
+ if(!psp.relevant(stream))
+ visible = false;
+ else if(stream.flags & StreamFlags::FLAG_FORCED)
+ visible = true;
+ break;
+ }
+ }
+ if(!valid)
+ CloseStream(m_CurrentSubtitle, false);
+
+ if (!std::dynamic_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream) || m_playerOptions.state.empty())
+ SetSubtitleVisibleInternal(visible); // only set subtitle visibility if state not stored by dvd navigator, because navigator will restore it (if visible)
+
+ // open teletext stream
+ valid = false;
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_TELETEXT))
+ {
+ if (OpenStream(m_CurrentTeletext, stream.demuxerId, stream.id, stream.source))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if(!valid)
+ CloseStream(m_CurrentTeletext, false);
+
+ // open RDS stream
+ valid = false;
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_RADIO_RDS))
+ {
+ if (OpenStream(m_CurrentRadioRDS, stream.demuxerId, stream.id, stream.source))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if(!valid)
+ CloseStream(m_CurrentRadioRDS, false);
+
+ // open ID3 stream
+ valid = false;
+ for (const auto& stream : m_SelectionStreams.Get(STREAM_AUDIO_ID3))
+ {
+ if (OpenStream(m_CurrentAudioID3, stream.demuxerId, stream.id, stream.source))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid)
+ CloseStream(m_CurrentAudioID3, false);
+
+ // disable demux streams
+ if (m_item.IsRemote() && m_pDemuxer)
+ {
+ for (auto &stream : m_SelectionStreams.m_Streams)
+ {
+ if (STREAM_SOURCE_MASK(stream.source) == STREAM_SOURCE_DEMUX)
+ {
+ if (stream.id != m_CurrentVideo.id && stream.id != m_CurrentAudio.id &&
+ stream.id != m_CurrentSubtitle.id && stream.id != m_CurrentTeletext.id &&
+ stream.id != m_CurrentRadioRDS.id && stream.id != m_CurrentAudioID3.id)
+ {
+ m_pDemuxer->EnableStream(stream.demuxerId, stream.id, false);
+ }
+ }
+ }
+ }
+}
+
+bool CVideoPlayer::ReadPacket(DemuxPacket*& packet, CDemuxStream*& stream)
+{
+
+ // check if we should read from subtitle demuxer
+ if (m_pSubtitleDemuxer && m_VideoPlayerSubtitle->AcceptsData())
+ {
+ packet = m_pSubtitleDemuxer->Read();
+
+ if(packet)
+ {
+ UpdateCorrection(packet, m_offset_pts);
+ if(packet->iStreamId < 0)
+ return true;
+
+ stream = m_pSubtitleDemuxer->GetStream(packet->demuxerId, packet->iStreamId);
+ if (!stream)
+ {
+ CLog::Log(LOGERROR, "{} - Error demux packet doesn't belong to a valid stream",
+ __FUNCTION__);
+ return false;
+ }
+ if (stream->source == STREAM_SOURCE_NONE)
+ {
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX_SUB);
+ m_SelectionStreams.Update(NULL, m_pSubtitleDemuxer.get());
+ UpdateContent();
+ }
+ return true;
+ }
+ }
+
+ // read a data frame from stream.
+ if (m_pDemuxer)
+ packet = m_pDemuxer->Read();
+
+ if (packet)
+ {
+ // stream changed, update and open defaults
+ if (packet->iStreamId == DMX_SPECIALID_STREAMCHANGE)
+ {
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX);
+ m_SelectionStreams.Update(m_pInputStream, m_pDemuxer.get());
+ m_pDemuxer->GetPrograms(m_programs);
+ UpdateContent();
+ OpenDefaultStreams(false);
+
+ // reevaluate HasVideo/Audio, we may have switched from/to a radio channel
+ if(m_CurrentVideo.id < 0)
+ m_HasVideo = false;
+ if(m_CurrentAudio.id < 0)
+ m_HasAudio = false;
+
+ return true;
+ }
+
+ UpdateCorrection(packet, m_offset_pts);
+
+ if(packet->iStreamId < 0)
+ return true;
+
+ if(m_pDemuxer)
+ {
+ stream = m_pDemuxer->GetStream(packet->demuxerId, packet->iStreamId);
+ if (!stream)
+ {
+ CLog::Log(LOGERROR, "{} - Error demux packet doesn't belong to a valid stream",
+ __FUNCTION__);
+ return false;
+ }
+ if(stream->source == STREAM_SOURCE_NONE)
+ {
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX);
+ m_SelectionStreams.Update(m_pInputStream, m_pDemuxer.get());
+ UpdateContent();
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CVideoPlayer::IsValidStream(const CCurrentStream& stream)
+{
+ if(stream.id<0)
+ return true; // we consider non selected as valid
+
+ int source = STREAM_SOURCE_MASK(stream.source);
+ if(source == STREAM_SOURCE_TEXT)
+ return true;
+ if (source == STREAM_SOURCE_DEMUX_SUB)
+ {
+ CDemuxStream* st = m_pSubtitleDemuxer->GetStream(stream.demuxerId, stream.id);
+ if(st == NULL || st->disabled)
+ return false;
+ if(st->type != stream.type)
+ return false;
+ return true;
+ }
+ if (source == STREAM_SOURCE_DEMUX)
+ {
+ CDemuxStream* st = m_pDemuxer->GetStream(stream.demuxerId, stream.id);
+ if(st == NULL || st->disabled)
+ return false;
+ if(st->type != stream.type)
+ return false;
+
+ if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ if (stream.type == STREAM_AUDIO && st->dvdNavId != m_dvd.iSelectedAudioStream)
+ return false;
+ if(stream.type == STREAM_SUBTITLE && st->dvdNavId != m_dvd.iSelectedSPUStream)
+ return false;
+ }
+
+ return true;
+ }
+ if (source == STREAM_SOURCE_VIDEOMUX)
+ {
+ CDemuxStream* st = m_pCCDemuxer->GetStream(stream.id);
+ if (st == NULL || st->disabled)
+ return false;
+ if (st->type != stream.type)
+ return false;
+ return true;
+ }
+
+ return false;
+}
+
+bool CVideoPlayer::IsBetterStream(const CCurrentStream& current, CDemuxStream* stream)
+{
+ // Do not reopen non-video streams if we're in video-only mode
+ if (m_playerOptions.videoOnly && current.type != STREAM_VIDEO)
+ return false;
+
+ if(stream->disabled)
+ return false;
+
+ if (m_pInputStream && (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) ||
+ m_pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY)))
+ {
+ int source_type;
+
+ source_type = STREAM_SOURCE_MASK(current.source);
+ if (source_type != STREAM_SOURCE_DEMUX &&
+ source_type != STREAM_SOURCE_NONE)
+ return false;
+
+ source_type = STREAM_SOURCE_MASK(stream->source);
+ if(source_type != STREAM_SOURCE_DEMUX ||
+ stream->type != current.type ||
+ stream->uniqueId == current.id)
+ return false;
+
+ if(current.type == STREAM_AUDIO && stream->dvdNavId == m_dvd.iSelectedAudioStream)
+ return true;
+ if(current.type == STREAM_SUBTITLE && stream->dvdNavId == m_dvd.iSelectedSPUStream)
+ return true;
+ if(current.type == STREAM_VIDEO && current.id < 0)
+ return true;
+ }
+ else
+ {
+ if(stream->source == current.source &&
+ stream->uniqueId == current.id &&
+ stream->demuxerId == current.demuxerId)
+ return false;
+
+ if(stream->type != current.type)
+ return false;
+
+ if(current.type == STREAM_SUBTITLE)
+ return false;
+
+ if(current.id < 0)
+ return true;
+ }
+ return false;
+}
+
+void CVideoPlayer::CheckBetterStream(CCurrentStream& current, CDemuxStream* stream)
+{
+ IDVDStreamPlayer* player = GetStreamPlayer(current.player);
+ if (!IsValidStream(current) && (player == NULL || player->IsStalled()))
+ CloseStream(current, true);
+
+ if (IsBetterStream(current, stream))
+ OpenStream(current, stream->demuxerId, stream->uniqueId, stream->source);
+}
+
+void CVideoPlayer::Prepare()
+{
+ CFFmpegLog::SetLogLevel(1);
+ SetPlaySpeed(DVD_PLAYSPEED_NORMAL);
+ m_processInfo->SetSpeed(1.0);
+ m_processInfo->SetTempo(1.0);
+ m_processInfo->SetFrameAdvance(false);
+ m_State.Clear();
+ m_CurrentVideo.hint.Clear();
+ m_CurrentAudio.hint.Clear();
+ m_CurrentSubtitle.hint.Clear();
+ m_CurrentTeletext.hint.Clear();
+ m_CurrentRadioRDS.hint.Clear();
+ m_CurrentAudioID3.hint.Clear();
+ memset(&m_SpeedState, 0, sizeof(m_SpeedState));
+ m_offset_pts = 0;
+ m_CurrentAudio.lastdts = DVD_NOPTS_VALUE;
+ m_CurrentVideo.lastdts = DVD_NOPTS_VALUE;
+
+ IPlayerCallback *cb = &m_callback;
+ CFileItem fileItem = m_item;
+ m_outboundEvents->Submit([=]() {
+ cb->RequestVideoSettings(fileItem);
+ });
+
+ if (!OpenInputStream())
+ {
+ m_bAbortRequest = true;
+ m_error = true;
+ return;
+ }
+
+ bool discStateRestored = false;
+ if (std::shared_ptr<CDVDInputStream::IMenus> ptr = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream))
+ {
+ CLog::Log(LOGINFO, "VideoPlayer: playing a file with menu's");
+
+ if (!m_playerOptions.state.empty())
+ {
+ discStateRestored = ptr->SetState(m_playerOptions.state);
+ }
+ else if(std::shared_ptr<CDVDInputStreamNavigator> nav = std::dynamic_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream))
+ {
+ nav->EnableSubtitleStream(m_processInfo->GetVideoSettings().m_SubtitleOn);
+ }
+ }
+
+ if (!OpenDemuxStream())
+ {
+ m_bAbortRequest = true;
+ m_error = true;
+ return;
+ }
+ // give players a chance to reconsider now codecs are known
+ CreatePlayers();
+
+ if (!discStateRestored)
+ OpenDefaultStreams();
+
+ /*
+ * Check to see if the demuxer should start at something other than time 0. This will be the case
+ * if there was a start time specified as part of the "Start from where last stopped" (aka
+ * auto-resume) feature or if there is an EDL cut or commercial break that starts at time 0.
+ */
+ EDL::Edit edit;
+ int starttime = 0;
+ if (m_playerOptions.starttime > 0 || m_playerOptions.startpercent > 0)
+ {
+ if (m_playerOptions.startpercent > 0 && m_pDemuxer)
+ {
+ int playerStartTime = static_cast<int>((static_cast<double>(
+ m_pDemuxer->GetStreamLength() * (m_playerOptions.startpercent / 100.0))));
+ starttime = m_Edl.GetTimeAfterRestoringCuts(playerStartTime);
+ }
+ else
+ {
+ starttime = m_Edl.GetTimeAfterRestoringCuts(
+ static_cast<int>(m_playerOptions.starttime * 1000)); // s to ms
+ }
+ CLog::Log(LOGDEBUG, "{} - Start position set to last stopped position: {}", __FUNCTION__,
+ starttime);
+ }
+ else if (m_Edl.InEdit(starttime, &edit))
+ {
+ // save last edit times
+ m_Edl.SetLastEditTime(edit.start);
+ m_Edl.SetLastEditActionType(edit.action);
+
+ if (edit.action == EDL::Action::CUT)
+ {
+ starttime = edit.end;
+ CLog::Log(LOGDEBUG, "{} - Start position set to end of first cut: {}", __FUNCTION__,
+ starttime);
+ }
+ else if (edit.action == EDL::Action::COMM_BREAK)
+ {
+ if (m_SkipCommercials)
+ {
+ starttime = edit.end;
+ CLog::Log(LOGDEBUG, "{} - Start position set to end of first commercial break: {}",
+ __FUNCTION__, starttime);
+ }
+
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications)
+ {
+ const std::string timeString =
+ StringUtils::SecondsToTimeString(edit.end / 1000, TIME_FORMAT_MM_SS);
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString);
+ }
+ }
+ }
+
+ if (starttime > 0)
+ {
+ double startpts = DVD_NOPTS_VALUE;
+ if (m_pDemuxer)
+ {
+ if (m_pDemuxer->SeekTime(starttime, true, &startpts))
+ {
+ FlushBuffers(starttime / 1000 * AV_TIME_BASE, true, true);
+ CLog::Log(LOGDEBUG, "{} - starting demuxer from: {}", __FUNCTION__, starttime);
+ }
+ else
+ CLog::Log(LOGDEBUG, "{} - failed to start demuxing from: {}", __FUNCTION__, starttime);
+ }
+
+ if (m_pSubtitleDemuxer)
+ {
+ if(m_pSubtitleDemuxer->SeekTime(starttime, true, &startpts))
+ CLog::Log(LOGDEBUG, "{} - starting subtitle demuxer from: {}", __FUNCTION__, starttime);
+ else
+ CLog::Log(LOGDEBUG, "{} - failed to start subtitle demuxing from: {}", __FUNCTION__,
+ starttime);
+ }
+
+ m_clock.Discontinuity(DVD_MSEC_TO_TIME(starttime));
+ }
+
+ UpdatePlayState(0);
+
+ SetCaching(CACHESTATE_FLUSH);
+}
+
+void CVideoPlayer::Process()
+{
+ // Try to resolve the correct mime type. This can take some time, for example if a requested
+ // item is located at a slow/not reachable remote source. So, do mime type detection in vp worker
+ // thread, not directly when initalizing the player to keep GUI responsible.
+ m_item.SetMimeTypeForInternetFile();
+
+ CServiceBroker::GetWinSystem()->RegisterRenderLoop(this);
+
+ Prepare();
+
+ while (!m_bAbortRequest)
+ {
+ // check display lost
+ if (m_displayLost)
+ {
+ CThread::Sleep(50ms);
+ continue;
+ }
+
+ // check if in an edit (cut or commercial break) that should be automatically skipped
+ CheckAutoSceneSkip();
+
+ // handle messages send to this thread, like seek or demuxer reset requests
+ HandleMessages();
+
+ if (m_bAbortRequest)
+ break;
+
+ // should we open a new input stream?
+ if (!m_pInputStream)
+ {
+ if (OpenInputStream() == false)
+ {
+ m_bAbortRequest = true;
+ break;
+ }
+ }
+
+ // should we open a new demuxer?
+ if (!m_pDemuxer)
+ {
+ if (m_pInputStream->NextStream() == CDVDInputStream::NEXTSTREAM_NONE)
+ break;
+
+ if (m_pInputStream->IsEOF())
+ break;
+
+ if (OpenDemuxStream() == false)
+ {
+ m_bAbortRequest = true;
+ break;
+ }
+
+ // on channel switch we don't want to close stream players at this
+ // time. we'll get the stream change event later
+ if (!m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER) ||
+ !m_SelectionStreams.m_Streams.empty())
+ OpenDefaultStreams();
+
+ UpdatePlayState(0);
+ }
+
+ // handle eventual seeks due to playspeed
+ HandlePlaySpeed();
+
+ // update player state
+ UpdatePlayState(200);
+
+ // make sure we run subtitle process here
+ m_VideoPlayerSubtitle->Process(m_clock.GetClock() + m_State.time_offset - m_VideoPlayerVideo->GetSubtitleDelay(), m_State.time_offset);
+
+ // tell demuxer if we want to fill buffers
+ if (m_demuxerSpeed != DVD_PLAYSPEED_PAUSE)
+ {
+ int audioLevel = 90;
+ int videoLevel = 90;
+ bool fillBuffer = false;
+ if (m_CurrentAudio.id >= 0)
+ audioLevel = m_VideoPlayerAudio->GetLevel();
+ if (m_CurrentVideo.id >= 0)
+ videoLevel = m_processInfo->GetLevelVQ();
+ if (videoLevel < 85 && audioLevel < 85)
+ {
+ fillBuffer = true;
+ }
+ if (m_pDemuxer)
+ m_pDemuxer->FillBuffer(fillBuffer);
+ }
+
+ // if the queues are full, no need to read more
+ if ((!m_VideoPlayerAudio->AcceptsData() && m_CurrentAudio.id >= 0) ||
+ (!m_VideoPlayerVideo->AcceptsData() && m_CurrentVideo.id >= 0))
+ {
+ if (m_playSpeed == DVD_PLAYSPEED_PAUSE &&
+ m_demuxerSpeed != DVD_PLAYSPEED_PAUSE)
+ {
+ if (m_pDemuxer)
+ m_pDemuxer->SetSpeed(DVD_PLAYSPEED_PAUSE);
+ m_demuxerSpeed = DVD_PLAYSPEED_PAUSE;
+ }
+ CThread::Sleep(10ms);
+ continue;
+ }
+
+ // adjust demuxer speed; some rtsp servers wants to know for i.e. ff
+ // delay pause until queue is full
+ if (m_playSpeed != DVD_PLAYSPEED_PAUSE &&
+ m_demuxerSpeed != m_playSpeed)
+ {
+ if (m_pDemuxer)
+ m_pDemuxer->SetSpeed(m_playSpeed);
+ m_demuxerSpeed = m_playSpeed;
+ }
+
+ DemuxPacket* pPacket = NULL;
+ CDemuxStream *pStream = NULL;
+ ReadPacket(pPacket, pStream);
+ if (pPacket && !pStream)
+ {
+ /* probably a empty packet, just free it and move on */
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ continue;
+ }
+
+ if (!pPacket)
+ {
+ // when paused, demuxer could be be returning empty
+ if (m_playSpeed == DVD_PLAYSPEED_PAUSE)
+ continue;
+
+ // check for a still frame state
+ if (std::shared_ptr<CDVDInputStream::IMenus> pStream = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream))
+ {
+ // stills will be skipped
+ if(m_dvd.state == DVDSTATE_STILL)
+ {
+ if (m_dvd.iDVDStillTime > 0ms)
+ {
+ const auto now = std::chrono::steady_clock::now();
+ const auto duration = now - m_dvd.iDVDStillStartTime;
+
+ if (duration >= m_dvd.iDVDStillTime)
+ {
+ m_dvd.iDVDStillTime = 0ms;
+ m_dvd.iDVDStillStartTime = {};
+ m_dvd.state = DVDSTATE_NORMAL;
+ pStream->SkipStill();
+ continue;
+ }
+ }
+ }
+ }
+
+ // if there is another stream available, reopen demuxer
+ CDVDInputStream::ENextStream next = m_pInputStream->NextStream();
+ if(next == CDVDInputStream::NEXTSTREAM_OPEN)
+ {
+ CloseDemuxer();
+
+ SetCaching(CACHESTATE_DONE);
+ CLog::Log(LOGINFO, "VideoPlayer: next stream, wait for old streams to be finished");
+ CloseStream(m_CurrentAudio, true);
+ CloseStream(m_CurrentVideo, true);
+
+ m_CurrentAudio.Clear();
+ m_CurrentVideo.Clear();
+ m_CurrentSubtitle.Clear();
+ continue;
+ }
+
+ // input stream asked us to just retry
+ if(next == CDVDInputStream::NEXTSTREAM_RETRY)
+ {
+ CThread::Sleep(100ms);
+ continue;
+ }
+
+ if (m_CurrentVideo.inited)
+ {
+ m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::VIDEO_DRAIN));
+ }
+
+ m_CurrentAudio.inited = false;
+ m_CurrentVideo.inited = false;
+ m_CurrentSubtitle.inited = false;
+ m_CurrentTeletext.inited = false;
+ m_CurrentRadioRDS.inited = false;
+ m_CurrentAudioID3.inited = false;
+
+ // if we are caching, start playing it again
+ SetCaching(CACHESTATE_DONE);
+
+ // while players are still playing, keep going to allow seekbacks
+ if (m_VideoPlayerAudio->HasData() ||
+ m_VideoPlayerVideo->HasData())
+ {
+ CThread::Sleep(100ms);
+ continue;
+ }
+
+ if (!m_pInputStream->IsEOF())
+ CLog::Log(LOGINFO, "{} - eof reading from demuxer", __FUNCTION__);
+
+ break;
+ }
+
+ // see if we can find something better to play
+ CheckBetterStream(m_CurrentAudio, pStream);
+ CheckBetterStream(m_CurrentVideo, pStream);
+ CheckBetterStream(m_CurrentSubtitle, pStream);
+ CheckBetterStream(m_CurrentTeletext, pStream);
+ CheckBetterStream(m_CurrentRadioRDS, pStream);
+ CheckBetterStream(m_CurrentAudioID3, pStream);
+
+ // demux video stream
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SUBTITLES_PARSECAPTIONS) && CheckIsCurrent(m_CurrentVideo, pStream, pPacket))
+ {
+ if (m_pCCDemuxer)
+ {
+ bool first = true;
+ while (!m_bAbortRequest)
+ {
+ DemuxPacket *pkt = m_pCCDemuxer->Read(first ? pPacket : NULL);
+ if (!pkt)
+ break;
+
+ first = false;
+ if (m_pCCDemuxer->GetNrOfStreams() != m_SelectionStreams.CountTypeOfSource(STREAM_SUBTITLE, STREAM_SOURCE_VIDEOMUX))
+ {
+ m_SelectionStreams.Clear(STREAM_SUBTITLE, STREAM_SOURCE_VIDEOMUX);
+ m_SelectionStreams.Update(NULL, m_pCCDemuxer.get(), "");
+ UpdateContent();
+ OpenDefaultStreams(false);
+ }
+ CDemuxStream *pSubStream = m_pCCDemuxer->GetStream(pkt->iStreamId);
+ if (pSubStream && m_CurrentSubtitle.id == pkt->iStreamId && m_CurrentSubtitle.source == STREAM_SOURCE_VIDEOMUX)
+ ProcessSubData(pSubStream, pkt);
+ else
+ CDVDDemuxUtils::FreeDemuxPacket(pkt);
+ }
+ }
+ }
+
+ if (IsInMenuInternal())
+ {
+ if (std::shared_ptr<CDVDInputStream::IMenus> menu = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream))
+ {
+ double correction = menu->GetTimeStampCorrection();
+ if (pPacket->dts != DVD_NOPTS_VALUE && pPacket->dts > correction)
+ pPacket->dts -= correction;
+ if (pPacket->pts != DVD_NOPTS_VALUE && pPacket->pts > correction)
+ pPacket->pts -= correction;
+ }
+ if (m_dvd.syncClock)
+ {
+ m_clock.Discontinuity(pPacket->dts);
+ m_dvd.syncClock = false;
+ }
+ }
+
+ // process the packet
+ ProcessPacket(pStream, pPacket);
+ }
+}
+
+bool CVideoPlayer::CheckIsCurrent(const CCurrentStream& current,
+ CDemuxStream* stream,
+ DemuxPacket* pkg)
+{
+ if(current.id == pkg->iStreamId &&
+ current.demuxerId == stream->demuxerId &&
+ current.source == stream->source &&
+ current.type == stream->type)
+ return true;
+ else
+ return false;
+}
+
+void CVideoPlayer::ProcessPacket(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ // process packet if it belongs to selected stream.
+ // for dvd's don't allow automatic opening of streams*/
+
+ if (CheckIsCurrent(m_CurrentAudio, pStream, pPacket))
+ ProcessAudioData(pStream, pPacket);
+ else if (CheckIsCurrent(m_CurrentVideo, pStream, pPacket))
+ ProcessVideoData(pStream, pPacket);
+ else if (CheckIsCurrent(m_CurrentSubtitle, pStream, pPacket))
+ ProcessSubData(pStream, pPacket);
+ else if (CheckIsCurrent(m_CurrentTeletext, pStream, pPacket))
+ ProcessTeletextData(pStream, pPacket);
+ else if (CheckIsCurrent(m_CurrentRadioRDS, pStream, pPacket))
+ ProcessRadioRDSData(pStream, pPacket);
+ else if (CheckIsCurrent(m_CurrentAudioID3, pStream, pPacket))
+ ProcessAudioID3Data(pStream, pPacket);
+ else
+ {
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket); // free it since we won't do anything with it
+ }
+}
+
+void CVideoPlayer::CheckStreamChanges(CCurrentStream& current, CDemuxStream* stream)
+{
+ if (current.stream != (void*)stream
+ || current.changes != stream->changes)
+ {
+ /* check so that dmuxer hints or extra data hasn't changed */
+ /* if they have, reopen stream */
+
+ if (current.hint != CDVDStreamInfo(*stream, true))
+ {
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX);
+ m_SelectionStreams.Update(m_pInputStream, m_pDemuxer.get());
+ UpdateContent();
+ OpenDefaultStreams(false);
+ }
+
+ current.stream = (void*)stream;
+ current.changes = stream->changes;
+ }
+}
+
+void CVideoPlayer::ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentAudio, pStream);
+
+ bool checkcont = CheckContinuity(m_CurrentAudio, pPacket);
+ UpdateTimestamps(m_CurrentAudio, pPacket);
+
+ if (checkcont && (m_CurrentAudio.avsync == CCurrentStream::AV_SYNC_CHECK))
+ m_CurrentAudio.avsync = CCurrentStream::AV_SYNC_NONE;
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentAudio))
+ drop = true;
+
+ /*
+ * If CheckSceneSkip() returns true then demux point is inside an EDL cut and the packets are dropped.
+ */
+ EDL::Edit edit;
+ if (CheckSceneSkip(m_CurrentAudio))
+ drop = true;
+ else if (m_Edl.InEdit(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts), &edit) &&
+ edit.action == EDL::Action::MUTE)
+ {
+ drop = true;
+ }
+
+ m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+
+ if (!drop)
+ m_CurrentAudio.packets++;
+}
+
+void CVideoPlayer::ProcessVideoData(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentVideo, pStream);
+ bool checkcont = false;
+
+ if( pPacket->iSize != 4) //don't check the EOF_SEQUENCE of stillframes
+ {
+ checkcont = CheckContinuity(m_CurrentVideo, pPacket);
+ UpdateTimestamps(m_CurrentVideo, pPacket);
+ }
+ if (checkcont && (m_CurrentVideo.avsync == CCurrentStream::AV_SYNC_CHECK))
+ m_CurrentVideo.avsync = CCurrentStream::AV_SYNC_NONE;
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentVideo))
+ drop = true;
+
+ if (CheckSceneSkip(m_CurrentVideo))
+ drop = true;
+
+ m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+
+ if (!drop)
+ m_CurrentVideo.packets++;
+}
+
+void CVideoPlayer::ProcessSubData(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentSubtitle, pStream);
+
+ UpdateTimestamps(m_CurrentSubtitle, pPacket);
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentSubtitle))
+ drop = true;
+
+ if (CheckSceneSkip(m_CurrentSubtitle))
+ drop = true;
+
+ m_VideoPlayerSubtitle->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+
+ if(m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ m_VideoPlayerSubtitle->UpdateOverlayInfo(std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream), LIBDVDNAV_BUTTON_NORMAL);
+}
+
+void CVideoPlayer::ProcessTeletextData(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentTeletext, pStream);
+
+ UpdateTimestamps(m_CurrentTeletext, pPacket);
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentTeletext))
+ drop = true;
+
+ if (CheckSceneSkip(m_CurrentTeletext))
+ drop = true;
+
+ m_VideoPlayerTeletext->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+}
+
+void CVideoPlayer::ProcessRadioRDSData(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentRadioRDS, pStream);
+
+ UpdateTimestamps(m_CurrentRadioRDS, pPacket);
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentRadioRDS))
+ drop = true;
+
+ if (CheckSceneSkip(m_CurrentRadioRDS))
+ drop = true;
+
+ m_VideoPlayerRadioRDS->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+}
+
+void CVideoPlayer::ProcessAudioID3Data(CDemuxStream* pStream, DemuxPacket* pPacket)
+{
+ CheckStreamChanges(m_CurrentAudioID3, pStream);
+
+ UpdateTimestamps(m_CurrentAudioID3, pPacket);
+
+ bool drop = false;
+ if (CheckPlayerInit(m_CurrentAudioID3))
+ drop = true;
+
+ if (CheckSceneSkip(m_CurrentAudioID3))
+ drop = true;
+
+ m_VideoPlayerAudioID3->SendMessage(std::make_shared<CDVDMsgDemuxerPacket>(pPacket, drop));
+}
+
+bool CVideoPlayer::GetCachingTimes(double& level, double& delay, double& offset)
+{
+ if (!m_pInputStream || !m_pDemuxer)
+ return false;
+
+ XFILE::SCacheStatus status;
+ if (!m_pInputStream->GetCacheStatus(&status))
+ return false;
+
+ const uint64_t& cached = status.forward;
+ const uint32_t& currate = status.currate;
+ const uint32_t& maxrate = status.maxrate;
+ const uint32_t& lowrate = status.lowrate;
+
+ int64_t length = m_pInputStream->GetLength();
+ int64_t remain = length - m_pInputStream->Seek(0, SEEK_CUR);
+
+ if (length <= 0 || remain < 0)
+ return false;
+
+ double play_sbp = DVD_MSEC_TO_TIME(m_pDemuxer->GetStreamLength()) / length;
+ double queued = 1000.0 * GetQueueTime() / play_sbp;
+
+ delay = 0.0;
+ level = 0.0;
+ offset = (cached + queued) / length;
+
+ if (currate == 0)
+ return true;
+
+ double cache_sbp = 1.1 * (double)DVD_TIME_BASE / currate; /* underestimate by 10 % */
+ double play_left = play_sbp * (remain + queued); /* time to play out all remaining bytes */
+ double cache_left = cache_sbp * (remain - cached); /* time to cache the remaining bytes */
+ double cache_need = std::max(0.0, remain - play_left / cache_sbp); /* bytes needed until play_left == cache_left */
+
+ delay = cache_left - play_left;
+
+ if (lowrate > 0)
+ {
+ CLog::Log(LOGDEBUG, "Readrate {} was too low with {} required", lowrate, maxrate);
+ level = -1.0; /* buffer is full & our read rate is too low */
+ }
+ else
+ level = (cached + queued) / (cache_need + queued);
+
+ return true;
+}
+
+void CVideoPlayer::HandlePlaySpeed()
+{
+ const bool isInMenu = IsInMenuInternal();
+ const bool tolerateStall =
+ isInMenu || (m_CurrentVideo.hint.flags & StreamFlags::FLAG_STILL_IMAGES);
+
+ if (tolerateStall && m_caching != CACHESTATE_DONE)
+ SetCaching(CACHESTATE_DONE);
+
+ if (m_caching == CACHESTATE_FULL)
+ {
+ double level, delay, offset;
+ if (GetCachingTimes(level, delay, offset))
+ {
+ if (level < 0.0)
+ {
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(21454), g_localizeStrings.Get(21455));
+ SetCaching(CACHESTATE_INIT);
+ }
+ if (level >= 1.0)
+ SetCaching(CACHESTATE_INIT);
+ }
+ else
+ {
+ if ((!m_VideoPlayerAudio->AcceptsData() && m_CurrentAudio.id >= 0) ||
+ (!m_VideoPlayerVideo->AcceptsData() && m_CurrentVideo.id >= 0))
+ SetCaching(CACHESTATE_INIT);
+ }
+
+ // if audio stream stalled, wait until demux queue filled 10%
+ if (m_pInputStream->IsRealtime() &&
+ (m_CurrentAudio.id < 0 || m_VideoPlayerAudio->GetLevel() > 10))
+ {
+ SetCaching(CACHESTATE_INIT);
+ }
+ }
+
+ if (m_caching == CACHESTATE_INIT)
+ {
+ // if all enabled streams have been inited we are done
+ if ((m_CurrentVideo.id >= 0 || m_CurrentAudio.id >= 0) &&
+ (m_CurrentVideo.id < 0 || m_CurrentVideo.syncState != IDVDStreamPlayer::SYNC_STARTING) &&
+ (m_CurrentAudio.id < 0 || m_CurrentAudio.syncState != IDVDStreamPlayer::SYNC_STARTING))
+ SetCaching(CACHESTATE_PLAY);
+
+ // handle exceptions
+ if (m_CurrentAudio.id >= 0 && m_CurrentVideo.id >= 0)
+ {
+ if ((!m_VideoPlayerAudio->AcceptsData() || !m_VideoPlayerVideo->AcceptsData()) &&
+ m_cachingTimer.IsTimePast())
+ {
+ SetCaching(CACHESTATE_DONE);
+ }
+ }
+ }
+
+ if (m_caching == CACHESTATE_PLAY)
+ {
+ // if all enabled streams have started playing we are done
+ if ((m_CurrentVideo.id < 0 || !m_VideoPlayerVideo->IsStalled()) &&
+ (m_CurrentAudio.id < 0 || !m_VideoPlayerAudio->IsStalled()))
+ SetCaching(CACHESTATE_DONE);
+ }
+
+ if (m_caching == CACHESTATE_DONE)
+ {
+ if (m_playSpeed == DVD_PLAYSPEED_NORMAL && !tolerateStall)
+ {
+ // take action if audio or video stream is stalled
+ if (((m_VideoPlayerAudio->IsStalled() && m_CurrentAudio.inited) ||
+ (m_VideoPlayerVideo->IsStalled() && m_CurrentVideo.inited)) &&
+ m_syncTimer.IsTimePast())
+ {
+ if (m_pInputStream->IsRealtime())
+ {
+ if ((m_CurrentAudio.id >= 0 && m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_INSYNC &&
+ m_VideoPlayerAudio->IsStalled()) ||
+ (m_CurrentVideo.id >= 0 && m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_INSYNC &&
+ m_processInfo->GetLevelVQ() == 0))
+ {
+ CLog::Log(LOGDEBUG, "Stream stalled, start buffering. Audio: {} - Video: {}",
+ m_VideoPlayerAudio->GetLevel(), m_processInfo->GetLevelVQ());
+
+ if (m_VideoPlayerAudio->AcceptsData() && m_VideoPlayerVideo->AcceptsData())
+ SetCaching(CACHESTATE_FULL);
+ else
+ FlushBuffers(DVD_NOPTS_VALUE, false, true);
+ }
+ }
+ else
+ {
+ // start caching if audio and video have run dry
+ if (m_VideoPlayerAudio->GetLevel() <= 50 &&
+ m_processInfo->GetLevelVQ() <= 50)
+ {
+ SetCaching(CACHESTATE_FULL);
+ }
+ else if (m_CurrentAudio.id >= 0 && m_CurrentAudio.inited &&
+ m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_INSYNC &&
+ m_VideoPlayerAudio->GetLevel() == 0)
+ {
+ CLog::Log(LOGDEBUG,"CVideoPlayer::HandlePlaySpeed - audio stream stalled, triggering re-sync");
+ FlushBuffers(DVD_NOPTS_VALUE, true, true);
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)GetUpdatedTime();
+ mode.backward = false;
+ mode.accurate = true;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ }
+ // care for live streams
+ else if (m_pInputStream->IsRealtime())
+ {
+ if (m_CurrentAudio.id >= 0)
+ {
+ double adjust = -1.0; // a unique value
+ if (m_clock.GetSpeedAdjust() >= 0 && m_VideoPlayerAudio->GetLevel() < 5)
+ adjust = -0.05;
+
+ if (m_clock.GetSpeedAdjust() < 0 && m_VideoPlayerAudio->GetLevel() > 10)
+ adjust = 0.0;
+
+ if (adjust != -1.0)
+ {
+ m_clock.SetSpeedAdjust(adjust);
+ }
+ }
+ }
+ }
+ }
+
+ // sync streams to clock
+ if ((m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_WAITSYNC) ||
+ (m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_WAITSYNC))
+ {
+ unsigned int threshold = 20;
+ if (m_pInputStream->IsRealtime())
+ threshold = 40;
+
+ bool video = m_CurrentVideo.id < 0 || (m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_WAITSYNC) ||
+ (m_CurrentVideo.packets == 0 && m_CurrentAudio.packets > threshold) ||
+ (!m_VideoPlayerAudio->AcceptsData() && m_processInfo->GetLevelVQ() < 10);
+ bool audio = m_CurrentAudio.id < 0 || (m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_WAITSYNC) ||
+ (m_CurrentAudio.packets == 0 && m_CurrentVideo.packets > threshold) ||
+ (!m_VideoPlayerVideo->AcceptsData() && m_VideoPlayerAudio->GetLevel() < 10);
+
+ if (m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_WAITSYNC &&
+ (m_CurrentAudio.avsync == CCurrentStream::AV_SYNC_CONT ||
+ m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_INSYNC))
+ {
+ m_CurrentAudio.syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_CurrentAudio.avsync = CCurrentStream::AV_SYNC_NONE;
+ m_VideoPlayerAudio->SendMessage(
+ std::make_shared<CDVDMsgDouble>(CDVDMsg::GENERAL_RESYNC, m_clock.GetClock()), 1);
+ }
+ else if (m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_WAITSYNC &&
+ (m_CurrentVideo.avsync == CCurrentStream::AV_SYNC_CONT ||
+ m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_INSYNC))
+ {
+ m_CurrentVideo.syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_CurrentVideo.avsync = CCurrentStream::AV_SYNC_NONE;
+ m_VideoPlayerVideo->SendMessage(
+ std::make_shared<CDVDMsgDouble>(CDVDMsg::GENERAL_RESYNC, m_clock.GetClock()), 1);
+ }
+ else if (video && audio)
+ {
+ double clock = 0;
+ if (m_CurrentAudio.syncState == IDVDStreamPlayer::SYNC_WAITSYNC)
+ CLog::Log(LOGDEBUG, "VideoPlayer::Sync - Audio - pts: {:f}, cache: {:f}, totalcache: {:f}",
+ m_CurrentAudio.starttime, m_CurrentAudio.cachetime, m_CurrentAudio.cachetotal);
+ if (m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_WAITSYNC)
+ CLog::Log(LOGDEBUG, "VideoPlayer::Sync - Video - pts: {:f}, cache: {:f}, totalcache: {:f}",
+ m_CurrentVideo.starttime, m_CurrentVideo.cachetime, m_CurrentVideo.cachetotal);
+
+ if (m_CurrentVideo.starttime != DVD_NOPTS_VALUE && m_CurrentVideo.packets > 0 &&
+ m_playSpeed == DVD_PLAYSPEED_PAUSE)
+ {
+ clock = m_CurrentVideo.starttime;
+ }
+ else if (m_CurrentAudio.starttime != DVD_NOPTS_VALUE && m_CurrentAudio.packets > 0)
+ {
+ if (m_pInputStream->IsRealtime())
+ clock = m_CurrentAudio.starttime - m_CurrentAudio.cachetotal - DVD_MSEC_TO_TIME(400);
+ else
+ clock = m_CurrentAudio.starttime - m_CurrentAudio.cachetime;
+
+ if (m_CurrentVideo.starttime != DVD_NOPTS_VALUE && (m_CurrentVideo.packets > 0))
+ {
+ if (m_CurrentVideo.starttime - m_CurrentVideo.cachetotal < clock)
+ {
+ clock = m_CurrentVideo.starttime - m_CurrentVideo.cachetotal;
+ }
+ else if (m_CurrentVideo.starttime > m_CurrentAudio.starttime &&
+ !m_pInputStream->IsRealtime())
+ {
+ int audioLevel = m_VideoPlayerAudio->GetLevel();
+ //@todo hardcoded 8 seconds in message queue
+ double maxAudioTime = clock + DVD_MSEC_TO_TIME(80 * audioLevel);
+ if ((m_CurrentVideo.starttime - m_CurrentVideo.cachetotal) > maxAudioTime)
+ clock = maxAudioTime;
+ else
+ clock = m_CurrentVideo.starttime - m_CurrentVideo.cachetotal;
+ }
+ }
+ }
+ else if (m_CurrentVideo.starttime != DVD_NOPTS_VALUE && m_CurrentVideo.packets > 0)
+ {
+ clock = m_CurrentVideo.starttime - m_CurrentVideo.cachetotal;
+ }
+
+ m_clock.Discontinuity(clock);
+ m_CurrentAudio.syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_CurrentAudio.avsync = CCurrentStream::AV_SYNC_NONE;
+ m_CurrentVideo.syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_CurrentVideo.avsync = CCurrentStream::AV_SYNC_NONE;
+ m_VideoPlayerAudio->SendMessage(
+ std::make_shared<CDVDMsgDouble>(CDVDMsg::GENERAL_RESYNC, clock), 1);
+ m_VideoPlayerVideo->SendMessage(
+ std::make_shared<CDVDMsgDouble>(CDVDMsg::GENERAL_RESYNC, clock), 1);
+ SetCaching(CACHESTATE_DONE);
+ UpdatePlayState(0);
+
+ m_syncTimer.Set(3000ms);
+
+ if (!m_State.streamsReady)
+ {
+ if (m_playerOptions.fullscreen)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
+ }
+
+ IPlayerCallback *cb = &m_callback;
+ CFileItem fileItem = m_item;
+ m_outboundEvents->Submit([=]() {
+ cb->OnAVStarted(fileItem);
+ });
+ m_State.streamsReady = true;
+ }
+ }
+ else
+ {
+ // exceptions for which stream players won't start properly
+ // 1. videoplayer has not detected a keyframe within length of demux buffers
+ if (m_CurrentAudio.id >= 0 && m_CurrentVideo.id >= 0 &&
+ !m_VideoPlayerAudio->AcceptsData() &&
+ m_CurrentVideo.syncState == IDVDStreamPlayer::SYNC_STARTING &&
+ m_VideoPlayerVideo->IsStalled() &&
+ m_CurrentVideo.packets > 10)
+ {
+ m_VideoPlayerAudio->AcceptsData();
+ CLog::Log(LOGWARNING, "VideoPlayer::Sync - stream player video does not start, flushing buffers");
+ FlushBuffers(DVD_NOPTS_VALUE, true, true);
+ }
+ }
+ }
+
+ // handle ff/rw
+ if (m_playSpeed != DVD_PLAYSPEED_NORMAL && m_playSpeed != DVD_PLAYSPEED_PAUSE)
+ {
+ if (isInMenu)
+ {
+ // this can't be done in menu
+ SetPlaySpeed(DVD_PLAYSPEED_NORMAL);
+
+ }
+ else
+ {
+ bool check = true;
+
+ // only check if we have video
+ if (m_CurrentVideo.id < 0 || m_CurrentVideo.syncState != IDVDStreamPlayer::SYNC_INSYNC)
+ check = false;
+ // video message queue either initiated or already seen eof
+ else if (m_CurrentVideo.inited == false && m_playSpeed >= 0)
+ check = false;
+ // don't check if time has not advanced since last check
+ else if (m_SpeedState.lasttime == GetTime())
+ check = false;
+ // skip if frame at screen has no valid timestamp
+ else if (m_VideoPlayerVideo->GetCurrentPts() == DVD_NOPTS_VALUE)
+ check = false;
+ // skip if frame on screen has not changed
+ else if (m_SpeedState.lastpts == m_VideoPlayerVideo->GetCurrentPts() &&
+ (m_SpeedState.lastpts > m_State.dts || m_playSpeed > 0))
+ check = false;
+
+ if (check)
+ {
+ m_SpeedState.lastpts = m_VideoPlayerVideo->GetCurrentPts();
+ m_SpeedState.lasttime = GetTime();
+ m_SpeedState.lastabstime = m_clock.GetAbsoluteClock();
+ // check how much off clock video is when ff/rw:ing
+ // a problem here is that seeking isn't very accurate
+ // and since the clock will be resynced after seek
+ // we might actually not really be playing at the wanted
+ // speed. we'd need to have some way to not resync the clock
+ // after a seek to remember timing. still need to handle
+ // discontinuities somehow
+
+ double error;
+ error = m_clock.GetClock() - m_SpeedState.lastpts;
+ error *= m_playSpeed / abs(m_playSpeed);
+
+ // allow a bigger error when going ff, the faster we go
+ // the the bigger is the error we allow
+ if (m_playSpeed > DVD_PLAYSPEED_NORMAL)
+ {
+ int errorwin = m_playSpeed / DVD_PLAYSPEED_NORMAL;
+ if (errorwin > 8)
+ errorwin = 8;
+ error /= errorwin;
+ }
+
+ if (error > DVD_MSEC_TO_TIME(1000))
+ {
+ error = (m_clock.GetClock() - m_SpeedState.lastseekpts) / 1000;
+
+ if (std::abs(error) > 1000 || (m_VideoPlayerVideo->IsRewindStalled() && std::abs(error) > 100))
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayer::Process - Seeking to catch up, error was: {:f}",
+ error);
+ m_SpeedState.lastseekpts = m_clock.GetClock();
+ int direction = (m_playSpeed > 0) ? 1 : -1;
+ double iTime = (m_clock.GetClock() + m_State.time_offset + 1000000.0 * direction) / 1000;
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = iTime;
+ mode.backward = (m_playSpeed < 0);
+ mode.accurate = false;
+ mode.restore = false;
+ mode.trickplay = true;
+ mode.sync = false;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ }
+ }
+ }
+
+ // reset tempo
+ if (!m_State.cantempo)
+ {
+ float currentTempo = m_processInfo->GetNewTempo();
+ if (currentTempo != 1.0f)
+ {
+ SetTempo(1.0f);
+ }
+ }
+}
+
+bool CVideoPlayer::CheckPlayerInit(CCurrentStream& current)
+{
+ if (current.inited)
+ return false;
+
+ if (current.startpts != DVD_NOPTS_VALUE)
+ {
+ if(current.dts == DVD_NOPTS_VALUE)
+ {
+ CLog::Log(LOGDEBUG, "{} - dropping packet type:{} dts:{:f} to get to start point at {:f}",
+ __FUNCTION__, current.player, current.dts, current.startpts);
+ return true;
+ }
+
+ if ((current.startpts - current.dts) > DVD_SEC_TO_TIME(20))
+ {
+ CLog::Log(LOGDEBUG, "{} - too far to decode before finishing seek", __FUNCTION__);
+ if(m_CurrentAudio.startpts != DVD_NOPTS_VALUE)
+ m_CurrentAudio.startpts = current.dts;
+ if(m_CurrentVideo.startpts != DVD_NOPTS_VALUE)
+ m_CurrentVideo.startpts = current.dts;
+ if(m_CurrentSubtitle.startpts != DVD_NOPTS_VALUE)
+ m_CurrentSubtitle.startpts = current.dts;
+ if(m_CurrentTeletext.startpts != DVD_NOPTS_VALUE)
+ m_CurrentTeletext.startpts = current.dts;
+ if(m_CurrentRadioRDS.startpts != DVD_NOPTS_VALUE)
+ m_CurrentRadioRDS.startpts = current.dts;
+ if (m_CurrentAudioID3.startpts != DVD_NOPTS_VALUE)
+ m_CurrentAudioID3.startpts = current.dts;
+ }
+
+ if(current.dts < current.startpts)
+ {
+ CLog::Log(LOGDEBUG, "{} - dropping packet type:{} dts:{:f} to get to start point at {:f}",
+ __FUNCTION__, current.player, current.dts, current.startpts);
+ return true;
+ }
+ }
+
+ if (current.dts != DVD_NOPTS_VALUE)
+ {
+ current.inited = true;
+ current.startpts = current.dts;
+ }
+ return false;
+}
+
+void CVideoPlayer::UpdateCorrection(DemuxPacket* pkt, double correction)
+{
+ pkt->m_ptsOffsetCorrection = correction;
+
+ if(pkt->dts != DVD_NOPTS_VALUE)
+ pkt->dts -= correction;
+ if(pkt->pts != DVD_NOPTS_VALUE)
+ pkt->pts -= correction;
+}
+
+void CVideoPlayer::UpdateTimestamps(CCurrentStream& current, DemuxPacket* pPacket)
+{
+ double dts = current.dts;
+ /* update stored values */
+ if(pPacket->dts != DVD_NOPTS_VALUE)
+ dts = pPacket->dts;
+ else if(pPacket->pts != DVD_NOPTS_VALUE)
+ dts = pPacket->pts;
+
+ /* calculate some average duration */
+ if(pPacket->duration != DVD_NOPTS_VALUE)
+ current.dur = pPacket->duration;
+ else if(dts != DVD_NOPTS_VALUE && current.dts != DVD_NOPTS_VALUE)
+ current.dur = 0.1 * (current.dur * 9 + (dts - current.dts));
+
+ current.dts = dts;
+
+ current.dispTime = pPacket->dispTime;
+}
+
+static void UpdateLimits(double& minimum, double& maximum, double dts)
+{
+ if(dts == DVD_NOPTS_VALUE)
+ return;
+ if(minimum == DVD_NOPTS_VALUE || minimum > dts) minimum = dts;
+ if(maximum == DVD_NOPTS_VALUE || maximum < dts) maximum = dts;
+}
+
+bool CVideoPlayer::CheckContinuity(CCurrentStream& current, DemuxPacket* pPacket)
+{
+ if (m_playSpeed < DVD_PLAYSPEED_PAUSE)
+ return false;
+
+ if( pPacket->dts == DVD_NOPTS_VALUE || current.dts == DVD_NOPTS_VALUE)
+ return false;
+
+ double mindts = DVD_NOPTS_VALUE, maxdts = DVD_NOPTS_VALUE;
+ UpdateLimits(mindts, maxdts, m_CurrentAudio.dts);
+ UpdateLimits(mindts, maxdts, m_CurrentVideo.dts);
+ UpdateLimits(mindts, maxdts, m_CurrentAudio.dts_end());
+ UpdateLimits(mindts, maxdts, m_CurrentVideo.dts_end());
+
+ /* if we don't have max and min, we can't do anything more */
+ if( mindts == DVD_NOPTS_VALUE || maxdts == DVD_NOPTS_VALUE )
+ return false;
+
+ double correction = 0.0;
+ if( pPacket->dts > maxdts + DVD_MSEC_TO_TIME(1000))
+ {
+ CLog::Log(LOGDEBUG,
+ "CVideoPlayer::CheckContinuity - resync forward :{}, prev:{:f}, curr:{:f}, diff:{:f}",
+ current.type, current.dts, pPacket->dts, pPacket->dts - maxdts);
+ correction = pPacket->dts - maxdts;
+ }
+
+ /* if it's large scale jump, correct for it after having confirmed the jump */
+ if(pPacket->dts + DVD_MSEC_TO_TIME(500) < current.dts_end())
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CVideoPlayer::CheckContinuity - resync backward :{}, prev:{:f}, curr:{:f}, diff:{:f}",
+ current.type, current.dts, pPacket->dts, pPacket->dts - current.dts);
+ correction = pPacket->dts - current.dts_end();
+ }
+ else if(pPacket->dts < current.dts)
+ {
+ CLog::Log(LOGDEBUG,
+ "CVideoPlayer::CheckContinuity - wrapback :{}, prev:{:f}, curr:{:f}, diff:{:f}",
+ current.type, current.dts, pPacket->dts, pPacket->dts - current.dts);
+ }
+
+ double lastdts = pPacket->dts;
+ if(correction != 0.0)
+ {
+ // we want the dts values of two streams to close, or for one to be invalid (e.g. from a missing audio stream)
+ double this_dts = pPacket->dts;
+ double that_dts = current.type == STREAM_AUDIO ? m_CurrentVideo.lastdts : m_CurrentAudio.lastdts;
+
+ if (m_CurrentAudio.id == -1 || m_CurrentVideo.id == -1 ||
+ current.lastdts == DVD_NOPTS_VALUE ||
+ fabs(this_dts - that_dts) < DVD_MSEC_TO_TIME(1000))
+ {
+ m_offset_pts += correction;
+ UpdateCorrection(pPacket, correction);
+ lastdts = pPacket->dts;
+ CLog::Log(LOGDEBUG, "CVideoPlayer::CheckContinuity - update correction: {:f}", correction);
+ if (current.avsync == CCurrentStream::AV_SYNC_CHECK)
+ current.avsync = CCurrentStream::AV_SYNC_CONT;
+ }
+ else
+ {
+ // not sure yet - flags the packets as unknown until we get confirmation on another audio/video packet
+ pPacket->dts = DVD_NOPTS_VALUE;
+ pPacket->pts = DVD_NOPTS_VALUE;
+ }
+ }
+ else
+ {
+ if (current.avsync == CCurrentStream::AV_SYNC_CHECK)
+ current.avsync = CCurrentStream::AV_SYNC_CONT;
+ }
+ current.lastdts = lastdts;
+ return true;
+}
+
+bool CVideoPlayer::CheckSceneSkip(const CCurrentStream& current)
+{
+ if (!m_Edl.HasEdits())
+ return false;
+
+ if(current.dts == DVD_NOPTS_VALUE)
+ return false;
+
+ if(current.inited == false)
+ return false;
+
+ EDL::Edit edit;
+ return m_Edl.InEdit(DVD_TIME_TO_MSEC(current.dts + m_offset_pts), &edit) &&
+ edit.action == EDL::Action::CUT;
+}
+
+void CVideoPlayer::CheckAutoSceneSkip()
+{
+ if (!m_Edl.HasEdits())
+ return;
+
+ // Check that there is an audio and video stream.
+ if((m_CurrentAudio.id < 0 || m_CurrentAudio.syncState != IDVDStreamPlayer::SYNC_INSYNC) ||
+ (m_CurrentVideo.id < 0 || m_CurrentVideo.syncState != IDVDStreamPlayer::SYNC_INSYNC))
+ return;
+
+ // If there is a startpts defined for either the audio or video stream then VideoPlayer is still
+ // still decoding frames to get to the previously requested seek point.
+ if (m_CurrentAudio.inited == false ||
+ m_CurrentVideo.inited == false)
+ return;
+
+ const int64_t clock = GetTime();
+
+ const double correctClock = m_Edl.GetTimeAfterRestoringCuts(clock);
+ EDL::Edit edit;
+ if (!m_Edl.InEdit(correctClock, &edit))
+ {
+ // @note: Users are allowed to jump back into EDL commercial breaks
+ // do not reset the last edit time if the last surpassed edit is a commercial break
+ if (m_Edl.GetLastEditActionType() != EDL::Action::COMM_BREAK)
+ {
+ m_Edl.ResetLastEditTime();
+ }
+ return;
+ }
+
+ if (edit.action == EDL::Action::CUT)
+ {
+ if ((m_playSpeed > 0 && correctClock < (edit.start + 1000)) ||
+ (m_playSpeed < 0 && correctClock < (edit.end - 1000)))
+ {
+ CLog::Log(LOGDEBUG, "{} - Clock in EDL cut [{} - {}]: {}. Automatically skipping over.",
+ __FUNCTION__, CEdl::MillisecondsToTimeString(edit.start),
+ CEdl::MillisecondsToTimeString(edit.end), CEdl::MillisecondsToTimeString(clock));
+
+ // Seeking either goes to the start or the end of the cut depending on the play direction.
+ int seek = m_playSpeed >= 0 ? edit.end : edit.start;
+ if (m_Edl.GetLastEditTime() != seek)
+ {
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = seek;
+ mode.backward = true;
+ mode.accurate = true;
+ mode.restore = false;
+ mode.trickplay = false;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+
+ m_Edl.SetLastEditTime(seek);
+ m_Edl.SetLastEditActionType(edit.action);
+ }
+ }
+ }
+ else if (edit.action == EDL::Action::COMM_BREAK)
+ {
+ // marker for commbreak may be inaccurate. allow user to skip into break from the back
+ if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit.start && clock < edit.end - 1000)
+ {
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications)
+ {
+ const std::string timeString =
+ StringUtils::SecondsToTimeString((edit.end - edit.start) / 1000, TIME_FORMAT_MM_SS);
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString);
+ }
+
+ m_Edl.SetLastEditTime(edit.start);
+ m_Edl.SetLastEditActionType(edit.action);
+
+ if (m_SkipCommercials)
+ {
+ CLog::Log(LOGDEBUG,
+ "{} - Clock in commercial break [{} - {}]: {}. Automatically skipping to end of "
+ "commercial break",
+ __FUNCTION__, CEdl::MillisecondsToTimeString(edit.start),
+ CEdl::MillisecondsToTimeString(edit.end), CEdl::MillisecondsToTimeString(clock));
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = edit.end;
+ mode.backward = true;
+ mode.accurate = true;
+ mode.restore = false;
+ mode.trickplay = false;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ }
+}
+
+
+void CVideoPlayer::SynchronizeDemuxer()
+{
+ if(IsCurrentThread())
+ return;
+ if(!m_messenger.IsInited())
+ return;
+
+ auto message = std::make_shared<CDVDMsgGeneralSynchronize>(500ms, SYNCSOURCE_PLAYER);
+ m_messenger.Put(message);
+ message->Wait(m_bStop, 0);
+}
+
+IDVDStreamPlayer* CVideoPlayer::GetStreamPlayer(unsigned int target)
+{
+ if(target == VideoPlayer_AUDIO)
+ return m_VideoPlayerAudio;
+ if(target == VideoPlayer_VIDEO)
+ return m_VideoPlayerVideo;
+ if(target == VideoPlayer_SUBTITLE)
+ return m_VideoPlayerSubtitle;
+ if(target == VideoPlayer_TELETEXT)
+ return m_VideoPlayerTeletext;
+ if(target == VideoPlayer_RDS)
+ return m_VideoPlayerRadioRDS;
+ if (target == VideoPlayer_ID3)
+ return m_VideoPlayerAudioID3.get();
+ return NULL;
+}
+
+void CVideoPlayer::SendPlayerMessage(std::shared_ptr<CDVDMsg> pMsg, unsigned int target)
+{
+ IDVDStreamPlayer* player = GetStreamPlayer(target);
+ if(player)
+ player->SendMessage(std::move(pMsg), 0);
+}
+
+void CVideoPlayer::OnExit()
+{
+ CLog::Log(LOGINFO, "CVideoPlayer::OnExit()");
+
+ // set event to inform openfile something went wrong in case openfile is still waiting for this event
+ SetCaching(CACHESTATE_DONE);
+
+ // close each stream
+ if (!m_bAbortRequest)
+ CLog::Log(LOGINFO, "VideoPlayer: eof, waiting for queues to empty");
+
+ CFileItem fileItem(m_item);
+ UpdateFileItemStreamDetails(fileItem);
+
+ CloseStream(m_CurrentAudio, !m_bAbortRequest);
+ CloseStream(m_CurrentVideo, !m_bAbortRequest);
+ CloseStream(m_CurrentTeletext,!m_bAbortRequest);
+ CloseStream(m_CurrentRadioRDS, !m_bAbortRequest);
+ CloseStream(m_CurrentAudioID3, !m_bAbortRequest);
+ // the generalization principle was abused for subtitle player. actually it is not a stream player like
+ // video and audio. subtitle player does not run on its own thread, hence waitForBuffers makes
+ // no sense here. waitForBuffers is abused to clear overlay container (false clears container)
+ // subtitles are added from video player. after video player has finished, overlays have to be cleared.
+ CloseStream(m_CurrentSubtitle, false); // clear overlay container
+
+ CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this);
+
+ IPlayerCallback *cb = &m_callback;
+ CVideoSettings vs = m_processInfo->GetVideoSettings();
+ m_outboundEvents->Submit([=]() {
+ cb->StoreVideoSettings(fileItem, vs);
+ });
+
+ CBookmark bookmark;
+ bookmark.totalTimeInSeconds = 0;
+ bookmark.timeInSeconds = 0;
+ if (m_State.startTime == 0)
+ {
+ bookmark.totalTimeInSeconds = m_State.timeMax / 1000;
+ bookmark.timeInSeconds = m_State.time / 1000;
+ }
+ bookmark.player = m_name;
+ bookmark.playerState = GetPlayerState();
+ m_outboundEvents->Submit([=]() {
+ cb->OnPlayerCloseFile(fileItem, bookmark);
+ });
+
+ // destroy objects
+ m_renderManager.Flush(false, false);
+ m_pDemuxer.reset();
+ m_pSubtitleDemuxer.reset();
+ m_subtitleDemuxerMap.clear();
+ m_pCCDemuxer.reset();
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+
+ // clean up all selection streams
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NONE);
+
+ m_messenger.End();
+
+ CFFmpegLog::ClearLogLevel();
+ m_bStop = true;
+
+ bool error = m_error;
+ bool close = m_bCloseRequest;
+ m_outboundEvents->Submit([=]() {
+ if (close)
+ cb->OnPlayBackStopped();
+ else if (error)
+ cb->OnPlayBackError();
+ else
+ cb->OnPlayBackEnded();
+ });
+}
+
+void CVideoPlayer::HandleMessages()
+{
+ std::shared_ptr<CDVDMsg> pMsg = nullptr;
+
+ while (m_messenger.Get(pMsg, 0) == MSGQ_OK)
+ {
+ if (pMsg->IsType(CDVDMsg::PLAYER_OPENFILE) &&
+ m_messenger.GetPacketCount(CDVDMsg::PLAYER_OPENFILE) == 0)
+ {
+ CDVDMsgOpenFile& msg(*std::static_pointer_cast<CDVDMsgOpenFile>(pMsg));
+
+ IPlayerCallback *cb = &m_callback;
+ CFileItem fileItem(m_item);
+ UpdateFileItemStreamDetails(fileItem);
+ CVideoSettings vs = m_processInfo->GetVideoSettings();
+ m_outboundEvents->Submit([=]() {
+ cb->StoreVideoSettings(fileItem, vs);
+ });
+
+ CBookmark bookmark;
+ bookmark.totalTimeInSeconds = 0;
+ bookmark.timeInSeconds = 0;
+ if (m_State.startTime == 0)
+ {
+ bookmark.totalTimeInSeconds = m_State.timeMax / 1000;
+ bookmark.timeInSeconds = m_State.time / 1000;
+ }
+ bookmark.player = m_name;
+ bookmark.playerState = GetPlayerState();
+ m_outboundEvents->Submit([=]() {
+ cb->OnPlayerCloseFile(fileItem, bookmark);
+ });
+
+ m_item = msg.GetItem();
+ m_playerOptions = msg.GetOptions();
+
+ m_processInfo->SetPlayTimes(0,0,0,0);
+
+ m_outboundEvents->Submit([this]() {
+ m_callback.OnPlayBackStarted(m_item);
+ });
+
+ FlushBuffers(DVD_NOPTS_VALUE, true, true);
+ m_renderManager.Flush(false, false);
+ m_pDemuxer.reset();
+ m_pSubtitleDemuxer.reset();
+ m_subtitleDemuxerMap.clear();
+ m_pCCDemuxer.reset();
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NONE);
+
+ Prepare();
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SEEK) &&
+ m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK) == 0 &&
+ m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK_CHAPTER) == 0)
+ {
+ CDVDMsgPlayerSeek& msg(*std::static_pointer_cast<CDVDMsgPlayerSeek>(pMsg));
+
+ if (!m_State.canseek)
+ {
+ m_processInfo->SetStateSeeking(false);
+ continue;
+ }
+
+ // skip seeks if player has not finished the last seek
+ if (m_CurrentVideo.id >= 0 &&
+ m_CurrentVideo.syncState != IDVDStreamPlayer::SYNC_INSYNC)
+ {
+ double now = m_clock.GetAbsoluteClock();
+ if (m_playSpeed == DVD_PLAYSPEED_NORMAL &&
+ (now - m_State.lastSeek)/1000 < 2000 &&
+ !msg.GetAccurate())
+ {
+ m_processInfo->SetStateSeeking(false);
+ continue;
+ }
+ }
+
+ if (!msg.GetTrickPlay())
+ {
+ m_processInfo->SeekFinished(0);
+ SetCaching(CACHESTATE_FLUSH);
+ }
+
+ double start = DVD_NOPTS_VALUE;
+
+ double time = msg.GetTime();
+ if (msg.GetRelative())
+ time = (m_clock.GetClock() + m_State.time_offset) / 1000l + time;
+
+ time = msg.GetRestore() ? m_Edl.GetTimeAfterRestoringCuts(time) : time;
+
+ // if input stream doesn't support ISeekTime, convert back to pts
+ //! @todo
+ //! After demuxer we add an offset to input pts so that displayed time and clock are
+ //! increasing steadily. For seeking we need to determine the boundaries and offset
+ //! of the desired segment. With the current approach calculated time may point
+ //! to nirvana
+ if (m_pInputStream->GetIPosTime() == nullptr)
+ time -= m_State.time_offset/1000l;
+
+ CLog::Log(LOGDEBUG, "demuxer seek to: {:f}", time);
+ if (m_pDemuxer && m_pDemuxer->SeekTime(time, msg.GetBackward(), &start))
+ {
+ CLog::Log(LOGDEBUG, "demuxer seek to: {:f}, success", time);
+ if(m_pSubtitleDemuxer)
+ {
+ if(!m_pSubtitleDemuxer->SeekTime(time, msg.GetBackward()))
+ CLog::Log(LOGDEBUG, "failed to seek subtitle demuxer: {:f}, success", time);
+ }
+ // dts after successful seek
+ if (start == DVD_NOPTS_VALUE)
+ start = DVD_MSEC_TO_TIME(time) - m_State.time_offset;
+
+ m_State.dts = start;
+ m_State.lastSeek = m_clock.GetAbsoluteClock();
+
+ FlushBuffers(start, msg.GetAccurate(), msg.GetSync());
+ }
+ else if (m_pDemuxer)
+ {
+ CLog::Log(LOGDEBUG, "VideoPlayer: seek failed or hit end of stream");
+ // dts after successful seek
+ if (start == DVD_NOPTS_VALUE)
+ start = DVD_MSEC_TO_TIME(time) - m_State.time_offset;
+
+ m_State.dts = start;
+
+ FlushBuffers(start, false, true);
+ if (m_playSpeed != DVD_PLAYSPEED_PAUSE)
+ {
+ SetPlaySpeed(DVD_PLAYSPEED_NORMAL);
+ }
+ }
+
+ // set flag to indicate we have finished a seeking request
+ if(!msg.GetTrickPlay())
+ {
+ m_processInfo->SeekFinished(0);
+ }
+
+ // dvd's will issue a HOP_CHANNEL that we need to skip
+ if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ m_dvd.state = DVDSTATE_SEEK;
+
+ m_processInfo->SetStateSeeking(false);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SEEK_CHAPTER) &&
+ m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK) == 0 &&
+ m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK_CHAPTER) == 0)
+ {
+ m_processInfo->SeekFinished(0);
+ SetCaching(CACHESTATE_FLUSH);
+
+ CDVDMsgPlayerSeekChapter& msg(*std::static_pointer_cast<CDVDMsgPlayerSeekChapter>(pMsg));
+ double start = DVD_NOPTS_VALUE;
+ int offset = 0;
+
+ // This should always be the case.
+ if(m_pDemuxer && m_pDemuxer->SeekChapter(msg.GetChapter(), &start))
+ {
+ FlushBuffers(start, true, true);
+ int64_t beforeSeek = GetTime();
+ offset = DVD_TIME_TO_MSEC(start) - static_cast<int>(beforeSeek);
+ m_callback.OnPlayBackSeekChapter(msg.GetChapter());
+ }
+ else if (m_pInputStream)
+ {
+ CDVDInputStream::IChapter* pChapter = m_pInputStream->GetIChapter();
+ if (pChapter && pChapter->SeekChapter(msg.GetChapter()))
+ {
+ FlushBuffers(start, true, true);
+ int64_t beforeSeek = GetTime();
+ offset = DVD_TIME_TO_MSEC(start) - static_cast<int>(beforeSeek);
+ m_callback.OnPlayBackSeekChapter(msg.GetChapter());
+ }
+ }
+ m_processInfo->SeekFinished(offset);
+ }
+ else if (pMsg->IsType(CDVDMsg::DEMUXER_RESET))
+ {
+ m_CurrentAudio.stream = NULL;
+ m_CurrentVideo.stream = NULL;
+ m_CurrentSubtitle.stream = NULL;
+
+ // we need to reset the demuxer, probably because the streams have changed
+ if(m_pDemuxer)
+ m_pDemuxer->Reset();
+ if(m_pSubtitleDemuxer)
+ m_pSubtitleDemuxer->Reset();
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_AUDIOSTREAM))
+ {
+ auto pMsg2 = std::static_pointer_cast<CDVDMsgPlayerSetAudioStream>(pMsg);
+
+ SelectionStream& st = m_SelectionStreams.Get(STREAM_AUDIO, pMsg2->GetStreamId());
+ if(st.source != STREAM_SOURCE_NONE)
+ {
+ if(st.source == STREAM_SOURCE_NAV && m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> pStream = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+ if(pStream->SetActiveAudioStream(st.id))
+ {
+ m_dvd.iSelectedAudioStream = -1;
+ CloseStream(m_CurrentAudio, false);
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)GetUpdatedTime();
+ mode.backward = true;
+ mode.accurate = true;
+ mode.trickplay = true;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ else
+ {
+ CloseStream(m_CurrentAudio, false);
+ OpenStream(m_CurrentAudio, st.demuxerId, st.id, st.source);
+ AdaptForcedSubtitles();
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)GetUpdatedTime();
+ mode.backward = true;
+ mode.accurate = true;
+ mode.trickplay = true;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_VIDEOSTREAM))
+ {
+ auto pMsg2 = std::static_pointer_cast<CDVDMsgPlayerSetVideoStream>(pMsg);
+
+ SelectionStream& st = m_SelectionStreams.Get(STREAM_VIDEO, pMsg2->GetStreamId());
+ if (st.source != STREAM_SOURCE_NONE)
+ {
+ if (st.source == STREAM_SOURCE_NAV && m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> pStream = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+ if (pStream->SetAngle(st.id))
+ {
+ m_dvd.iSelectedVideoStream = st.id;
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)GetUpdatedTime();
+ mode.backward = true;
+ mode.accurate = true;
+ mode.trickplay = true;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ else
+ {
+ CloseStream(m_CurrentVideo, false);
+ OpenStream(m_CurrentVideo, st.demuxerId, st.id, st.source);
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)GetUpdatedTime();
+ mode.backward = true;
+ mode.accurate = true;
+ mode.trickplay = true;
+ mode.sync = true;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_SUBTITLESTREAM))
+ {
+ auto pMsg2 = std::static_pointer_cast<CDVDMsgPlayerSetSubtitleStream>(pMsg);
+
+ SelectionStream& st = m_SelectionStreams.Get(STREAM_SUBTITLE, pMsg2->GetStreamId());
+ if(st.source != STREAM_SOURCE_NONE)
+ {
+ if(st.source == STREAM_SOURCE_NAV && m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> pStream = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+ if(pStream->SetActiveSubtitleStream(st.id))
+ {
+ m_dvd.iSelectedSPUStream = -1;
+ CloseStream(m_CurrentSubtitle, false);
+ }
+ }
+ else
+ {
+ CloseStream(m_CurrentSubtitle, false);
+ OpenStream(m_CurrentSubtitle, st.demuxerId, st.id, st.source);
+ }
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_SUBTITLESTREAM_VISIBLE))
+ {
+ auto pValue = std::static_pointer_cast<CDVDMsgBool>(pMsg);
+ SetSubtitleVisibleInternal(pValue->m_value);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_PROGRAM))
+ {
+ auto msg = std::static_pointer_cast<CDVDMsgInt>(pMsg);
+ if (m_pDemuxer)
+ {
+ m_pDemuxer->SetProgram(msg->m_value);
+ FlushBuffers(DVD_NOPTS_VALUE, false, true);
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_STATE))
+ {
+ SetCaching(CACHESTATE_FLUSH);
+
+ auto pMsgPlayerSetState = std::static_pointer_cast<CDVDMsgPlayerSetState>(pMsg);
+
+ if (std::shared_ptr<CDVDInputStream::IMenus> ptr = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream))
+ {
+ if(ptr->SetState(pMsgPlayerSetState->GetState()))
+ {
+ m_dvd.state = DVDSTATE_NORMAL;
+ m_dvd.iDVDStillStartTime = {};
+ m_dvd.iDVDStillTime = 0ms;
+ }
+ }
+
+ m_processInfo->SeekFinished(0);
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH))
+ {
+ FlushBuffers(DVD_NOPTS_VALUE, true, true);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ int speed = std::static_pointer_cast<CDVDMsgPlayerSetSpeed>(pMsg)->GetSpeed();
+
+ // correct our current clock, as it would start going wrong otherwise
+ if (m_State.timestamp > 0)
+ {
+ double offset;
+ offset = m_clock.GetAbsoluteClock() - m_State.timestamp;
+ offset *= m_playSpeed / DVD_PLAYSPEED_NORMAL;
+ offset = DVD_TIME_TO_MSEC(offset);
+ if (offset > 1000)
+ offset = 1000;
+ if (offset < -1000)
+ offset = -1000;
+ m_State.time += offset;
+ m_State.timestamp = m_clock.GetAbsoluteClock();
+ }
+
+ if (speed != DVD_PLAYSPEED_PAUSE && m_playSpeed != DVD_PLAYSPEED_PAUSE && speed != m_playSpeed)
+ {
+ m_callback.OnPlayBackSpeedChanged(speed / DVD_PLAYSPEED_NORMAL);
+ m_processInfo->SeekFinished(0);
+ }
+
+ if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER) && speed != m_playSpeed)
+ {
+ std::shared_ptr<CInputStreamPVRBase> pvrinputstream = std::static_pointer_cast<CInputStreamPVRBase>(m_pInputStream);
+ pvrinputstream->Pause(speed == 0);
+ }
+
+ // do a seek after rewind, clock is not in sync with current pts
+ if ((speed == DVD_PLAYSPEED_NORMAL) &&
+ (m_playSpeed != DVD_PLAYSPEED_NORMAL) &&
+ (m_playSpeed != DVD_PLAYSPEED_PAUSE))
+ {
+ double iTime = m_VideoPlayerVideo->GetCurrentPts();
+ if (iTime == DVD_NOPTS_VALUE)
+ iTime = m_clock.GetClock();
+ iTime = (iTime + m_State.time_offset) / 1000;
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = iTime;
+ mode.backward = m_playSpeed < 0;
+ mode.accurate = true;
+ mode.trickplay = true;
+ mode.sync = true;
+ mode.restore = false;
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ }
+
+ if (std::static_pointer_cast<CDVDMsgPlayerSetSpeed>(pMsg)->IsTempo())
+ m_processInfo->SetTempo(static_cast<float>(speed) / DVD_PLAYSPEED_NORMAL);
+ else
+ m_processInfo->SetSpeed(static_cast<float>(speed) / DVD_PLAYSPEED_NORMAL);
+
+ m_processInfo->SetFrameAdvance(false);
+
+ m_playSpeed = speed;
+
+ m_caching = CACHESTATE_DONE;
+ m_clock.SetSpeed(speed);
+ m_VideoPlayerAudio->SetSpeed(speed);
+ m_VideoPlayerVideo->SetSpeed(speed);
+ m_streamPlayerSpeed = speed;
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_FRAME_ADVANCE))
+ {
+ if (m_playSpeed == DVD_PLAYSPEED_PAUSE)
+ {
+ int frames = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+ double time = DVD_TIME_BASE / static_cast<double>(m_processInfo->GetVideoFps()) * frames;
+ m_processInfo->SetFrameAdvance(true);
+ m_clock.Advance(time);
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_GUI_ACTION))
+ OnAction(std::static_pointer_cast<CDVDMsgType<CAction>>(pMsg)->m_value);
+ else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED))
+ {
+ SStartMsg& msg = std::static_pointer_cast<CDVDMsgType<SStartMsg>>(pMsg)->m_value;
+ if (msg.player == VideoPlayer_AUDIO)
+ {
+ m_CurrentAudio.syncState = IDVDStreamPlayer::SYNC_WAITSYNC;
+ m_CurrentAudio.cachetime = msg.cachetime;
+ m_CurrentAudio.cachetotal = msg.cachetotal;
+ m_CurrentAudio.starttime = msg.timestamp;
+ }
+ if (msg.player == VideoPlayer_VIDEO)
+ {
+ m_CurrentVideo.syncState = IDVDStreamPlayer::SYNC_WAITSYNC;
+ m_CurrentVideo.cachetime = msg.cachetime;
+ m_CurrentVideo.cachetotal = msg.cachetotal;
+ m_CurrentVideo.starttime = msg.timestamp;
+ }
+ CLog::Log(LOGDEBUG, "CVideoPlayer::HandleMessages - player started {}", msg.player);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_REPORT_STATE))
+ {
+ SStateMsg& msg = std::static_pointer_cast<CDVDMsgType<SStateMsg>>(pMsg)->m_value;
+ if (msg.player == VideoPlayer_AUDIO)
+ {
+ m_CurrentAudio.syncState = msg.syncState;
+ }
+ if (msg.player == VideoPlayer_VIDEO)
+ {
+ m_CurrentVideo.syncState = msg.syncState;
+ }
+ CLog::Log(LOGDEBUG, "CVideoPlayer::HandleMessages - player {} reported state: {}", msg.player,
+ msg.syncState);
+ }
+ else if (pMsg->IsType(CDVDMsg::SUBTITLE_ADDFILE))
+ {
+ int id = AddSubtitleFile(std::static_pointer_cast<CDVDMsgType<std::string>>(pMsg)->m_value);
+ if (id >= 0)
+ {
+ SetSubtitle(id);
+ SetSubtitleVisibleInternal(true);
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE))
+ {
+ if (std::static_pointer_cast<CDVDMsgGeneralSynchronize>(pMsg)->Wait(100ms, SYNCSOURCE_PLAYER))
+ CLog::Log(LOGDEBUG, "CVideoPlayer - CDVDMsg::GENERAL_SYNCHRONIZE");
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_AVCHANGE))
+ {
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalVideoInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalSubtitleInfoChange();
+ IPlayerCallback *cb = &m_callback;
+ m_outboundEvents->Submit([=]() {
+ cb->OnAVChange();
+ });
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_ABORT))
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayer - CDVDMsg::PLAYER_ABORT");
+ m_bAbortRequest = true;
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SET_UPDATE_STREAM_DETAILS))
+ m_UpdateStreamDetails = true;
+ }
+}
+
+void CVideoPlayer::SetCaching(ECacheState state)
+{
+ if(state == CACHESTATE_FLUSH)
+ {
+ double level, delay, offset;
+ if(GetCachingTimes(level, delay, offset))
+ state = CACHESTATE_FULL;
+ else
+ state = CACHESTATE_INIT;
+ }
+
+ if(m_caching == state)
+ return;
+
+ CLog::Log(LOGDEBUG, "CVideoPlayer::SetCaching - caching state {}", state);
+ if (state == CACHESTATE_FULL ||
+ state == CACHESTATE_INIT)
+ {
+ m_clock.SetSpeed(DVD_PLAYSPEED_PAUSE);
+
+ m_VideoPlayerAudio->SetSpeed(DVD_PLAYSPEED_PAUSE);
+ m_VideoPlayerVideo->SetSpeed(DVD_PLAYSPEED_PAUSE);
+ m_streamPlayerSpeed = DVD_PLAYSPEED_PAUSE;
+
+ m_cachingTimer.Set(5000ms);
+ }
+
+ if (state == CACHESTATE_PLAY ||
+ (state == CACHESTATE_DONE && m_caching != CACHESTATE_PLAY))
+ {
+ m_clock.SetSpeed(m_playSpeed);
+ m_VideoPlayerAudio->SetSpeed(m_playSpeed);
+ m_VideoPlayerVideo->SetSpeed(m_playSpeed);
+ m_streamPlayerSpeed = m_playSpeed;
+ }
+ m_caching = state;
+
+ m_clock.SetSpeedAdjust(0);
+}
+
+void CVideoPlayer::SetPlaySpeed(int speed)
+{
+ if (IsPlaying())
+ {
+ CDVDMsgPlayerSetSpeed::SpeedParams params = { speed, false };
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetSpeed>(params));
+ }
+ else
+ {
+ m_playSpeed = speed;
+ m_streamPlayerSpeed = speed;
+ }
+}
+
+bool CVideoPlayer::CanPause() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.canpause;
+}
+
+void CVideoPlayer::Pause()
+{
+ // toggle between pause and normal speed
+ if (m_processInfo->GetNewSpeed() == 0)
+ {
+ SetSpeed(1);
+ }
+ else
+ {
+ SetSpeed(0);
+ }
+}
+
+bool CVideoPlayer::HasVideo() const
+{
+ return m_HasVideo;
+}
+
+bool CVideoPlayer::HasAudio() const
+{
+ return m_HasAudio;
+}
+
+bool CVideoPlayer::HasRDS() const
+{
+ return m_CurrentRadioRDS.id >= 0;
+}
+
+bool CVideoPlayer::HasID3() const
+{
+ return m_CurrentAudioID3.id >= 0;
+}
+
+bool CVideoPlayer::IsPassthrough() const
+{
+ return m_VideoPlayerAudio->IsPassthrough();
+}
+
+bool CVideoPlayer::CanSeek() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.canseek;
+}
+
+void CVideoPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
+{
+ if (!m_State.canseek)
+ return;
+
+ if (bLargeStep && bChapterOverride && GetChapter() > 0 && GetChapterCount() > 1)
+ {
+ if (!bPlus)
+ {
+ SeekChapter(GetChapter() - 1);
+ return;
+ }
+ else if (GetChapter() < GetChapterCount())
+ {
+ SeekChapter(GetChapter() + 1);
+ return;
+ }
+ }
+
+ int64_t seekTarget;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings->m_videoUseTimeSeeking && m_processInfo->GetMaxTime() > 2000 * advancedSettings->m_videoTimeSeekForwardBig)
+ {
+ if (bLargeStep)
+ seekTarget = bPlus ? advancedSettings->m_videoTimeSeekForwardBig :
+ advancedSettings->m_videoTimeSeekBackwardBig;
+ else
+ seekTarget = bPlus ? advancedSettings->m_videoTimeSeekForward :
+ advancedSettings->m_videoTimeSeekBackward;
+ seekTarget *= 1000;
+ seekTarget += GetTime();
+ }
+ else
+ {
+ int percent;
+ if (bLargeStep)
+ percent = bPlus ? advancedSettings->m_videoPercentSeekForwardBig : advancedSettings->m_videoPercentSeekBackwardBig;
+ else
+ percent = bPlus ? advancedSettings->m_videoPercentSeekForward : advancedSettings->m_videoPercentSeekBackward;
+ seekTarget = static_cast<int64_t>(m_processInfo->GetMaxTime() * (GetPercentage() + percent) / 100);
+ }
+
+ bool restore = true;
+
+ int64_t time = GetTime();
+ if(g_application.CurrentFileItem().IsStack() &&
+ (seekTarget > m_processInfo->GetMaxTime() || seekTarget < 0))
+ {
+ g_application.SeekTime((seekTarget - time) * 0.001 + g_application.GetTime());
+ // warning, don't access any VideoPlayer variables here as
+ // the VideoPlayer object may have been destroyed
+ return;
+ }
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)seekTarget;
+ mode.backward = !bPlus;
+ mode.accurate = false;
+ mode.restore = restore;
+ mode.trickplay = false;
+ mode.sync = true;
+
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ SynchronizeDemuxer();
+ if (seekTarget < 0)
+ seekTarget = 0;
+ m_callback.OnPlayBackSeek(seekTarget, seekTarget - time);
+}
+
+bool CVideoPlayer::SeekScene(bool bPlus)
+{
+ if (!m_Edl.HasSceneMarker())
+ return false;
+
+ /*
+ * There is a 5 second grace period applied when seeking for scenes backwards. If there is no
+ * grace period applied it is impossible to go backwards past a scene marker.
+ */
+ int64_t clock = GetTime();
+ if (!bPlus && clock > 5 * 1000) // 5 seconds
+ clock -= 5 * 1000;
+
+ int iScenemarker;
+ if (m_Edl.GetNextSceneMarker(bPlus, clock, &iScenemarker))
+ {
+ /*
+ * Seeking is flushed and inaccurate, just like Seek()
+ */
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = iScenemarker;
+ mode.backward = !bPlus;
+ mode.accurate = false;
+ mode.restore = false;
+ mode.trickplay = false;
+ mode.sync = true;
+
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ SynchronizeDemuxer();
+ return true;
+ }
+ return false;
+}
+
+void CVideoPlayer::GetGeneralInfo(std::string& strGeneralInfo)
+{
+ if (!m_bStop)
+ {
+ double apts = m_VideoPlayerAudio->GetCurrentPts();
+ double vpts = m_VideoPlayerVideo->GetCurrentPts();
+ double dDiff = 0;
+
+ if (apts != DVD_NOPTS_VALUE && vpts != DVD_NOPTS_VALUE)
+ dDiff = (apts - vpts) / DVD_TIME_BASE;
+
+ std::string strBuf;
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ if (m_State.cache_bytes >= 0)
+ {
+ strBuf += StringUtils::Format(" forward:{} {:2.0f}%",
+ StringUtils::SizeToString(m_State.cache_bytes),
+ m_State.cache_level * 100);
+ if (m_playSpeed == 0 || m_caching == CACHESTATE_FULL)
+ strBuf += StringUtils::Format(" {} msec", DVD_TIME_TO_MSEC(m_State.cache_delay));
+ }
+
+ strGeneralInfo = StringUtils::Format("Player: a/v:{: 6.3f}, {}", dDiff, strBuf);
+ }
+}
+
+void CVideoPlayer::SeekPercentage(float iPercent)
+{
+ int64_t iTotalTime = m_processInfo->GetMaxTime();
+
+ if (!iTotalTime)
+ return;
+
+ SeekTime((int64_t)(iTotalTime * iPercent / 100));
+}
+
+float CVideoPlayer::GetPercentage()
+{
+ int64_t iTotalTime = m_processInfo->GetMaxTime();
+
+ if (!iTotalTime)
+ return 0.0f;
+
+ return GetTime() * 100 / (float)iTotalTime;
+}
+
+float CVideoPlayer::GetCachePercentage() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return (float) (m_State.cache_offset * 100); // NOTE: Percentage returned is relative
+}
+
+void CVideoPlayer::SetAVDelay(float fValue)
+{
+ m_processInfo->GetVideoSettingsLocked().SetAudioDelay(fValue);
+ m_renderManager.SetDelay(static_cast<int>(fValue * 1000.0f));
+}
+
+float CVideoPlayer::GetAVDelay()
+{
+ return static_cast<float>(m_renderManager.GetDelay()) / 1000.0f;
+}
+
+void CVideoPlayer::SetSubTitleDelay(float fValue)
+{
+ m_processInfo->GetVideoSettingsLocked().SetSubtitleDelay(fValue);
+ m_VideoPlayerVideo->SetSubtitleDelay(static_cast<double>(-fValue) * DVD_TIME_BASE);
+}
+
+float CVideoPlayer::GetSubTitleDelay()
+{
+ return (float) -m_VideoPlayerVideo->GetSubtitleDelay() / DVD_TIME_BASE;
+}
+
+bool CVideoPlayer::GetSubtitleVisible() const
+{
+ if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> pStream = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+ return pStream->IsSubtitleStreamEnabled();
+ }
+
+ return m_VideoPlayerVideo->IsSubtitleEnabled();
+}
+
+void CVideoPlayer::SetSubtitleVisible(bool bVisible)
+{
+ m_messenger.Put(
+ std::make_shared<CDVDMsgBool>(CDVDMsg::PLAYER_SET_SUBTITLESTREAM_VISIBLE, bVisible));
+ m_processInfo->GetVideoSettingsLocked().SetSubtitleVisible(bVisible);
+}
+
+void CVideoPlayer::SetSubtitleVisibleInternal(bool bVisible)
+{
+ m_VideoPlayerVideo->EnableSubtitle(bVisible);
+
+ if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream)->EnableSubtitleStream(bVisible);
+
+ CServiceBroker::GetDataCacheCore().SignalSubtitleInfoChange();
+}
+
+void CVideoPlayer::SetSubtitleVerticalPosition(int value, bool save)
+{
+ m_processInfo->GetVideoSettingsLocked().SetSubtitleVerticalPosition(value, save);
+ m_renderManager.SetSubtitleVerticalPosition(value, save);
+}
+
+std::shared_ptr<TextCacheStruct_t> CVideoPlayer::GetTeletextCache()
+{
+ if (m_CurrentTeletext.id < 0)
+ return nullptr;
+
+ return m_VideoPlayerTeletext->GetTeletextCache();
+}
+
+bool CVideoPlayer::HasTeletextCache() const
+{
+ return m_CurrentTeletext.id >= 0;
+}
+
+void CVideoPlayer::LoadPage(int p, int sp, unsigned char* buffer)
+{
+ if (m_CurrentTeletext.id < 0)
+ return;
+
+ return m_VideoPlayerTeletext->LoadPage(p, sp, buffer);
+}
+
+void CVideoPlayer::SeekTime(int64_t iTime)
+{
+ int64_t seekOffset = iTime - GetTime();
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = static_cast<double>(iTime);
+ mode.backward = true;
+ mode.accurate = true;
+ mode.trickplay = false;
+ mode.sync = true;
+
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ SynchronizeDemuxer();
+ m_callback.OnPlayBackSeek(iTime, seekOffset);
+ m_processInfo->SeekFinished(seekOffset);
+}
+
+bool CVideoPlayer::SeekTimeRelative(int64_t iTime)
+{
+ int64_t abstime = GetTime() + iTime;
+
+ // if the file has EDL cuts we can't rely on m_clock for relative seeks
+ // EDL cuts remove time from the original file, hence we might seek to
+ // positions too far from the current m_clock position. Seek to absolute
+ // time instead
+ if (m_Edl.HasCuts())
+ {
+ SeekTime(abstime);
+ return true;
+ }
+
+ CDVDMsgPlayerSeek::CMode mode;
+ mode.time = (int)iTime;
+ mode.relative = true;
+ mode.backward = (iTime < 0) ? true : false;
+ mode.accurate = false;
+ mode.trickplay = false;
+ mode.sync = true;
+
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeek>(mode));
+ m_processInfo->SetStateSeeking(true);
+
+ m_callback.OnPlayBackSeek(abstime, iTime);
+ m_processInfo->SeekFinished(iTime);
+ return true;
+}
+
+// return the time in milliseconds
+int64_t CVideoPlayer::GetTime()
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return llrint(m_State.time);
+}
+
+void CVideoPlayer::SetSpeed(float speed)
+{
+ // can't rewind in menu as seeking isn't possible
+ // forward is fine
+ if (speed < 0 && IsInMenu())
+ return;
+
+ if (!CanSeek() && !CanPause())
+ return;
+
+ int iSpeed = static_cast<int>(speed * DVD_PLAYSPEED_NORMAL);
+
+ if (!CanSeek())
+ {
+ if ((iSpeed != DVD_PLAYSPEED_NORMAL) && (iSpeed != DVD_PLAYSPEED_PAUSE))
+ return;
+ }
+
+ float currentSpeed = m_processInfo->GetNewSpeed();
+ m_processInfo->SetNewSpeed(speed);
+ if (iSpeed != currentSpeed)
+ {
+ if (iSpeed == DVD_PLAYSPEED_NORMAL)
+ m_callback.OnPlayBackResumed();
+ else if (iSpeed == DVD_PLAYSPEED_PAUSE)
+ m_callback.OnPlayBackPaused();
+
+ if (iSpeed == DVD_PLAYSPEED_NORMAL)
+ {
+ float currentTempo = m_processInfo->GetNewTempo();
+ if (currentTempo != 1.0f)
+ {
+ SetTempo(currentTempo);
+ return;
+ }
+ }
+ SetPlaySpeed(iSpeed);
+ }
+}
+
+void CVideoPlayer::SetTempo(float tempo)
+{
+ tempo = floor(tempo * 100.0f + 0.5f) / 100.0f;
+ if (m_processInfo->IsTempoAllowed(tempo))
+ {
+ int speed = tempo * DVD_PLAYSPEED_NORMAL;
+ CDVDMsgPlayerSetSpeed::SpeedParams params = { speed, true };
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetSpeed>(params));
+
+ m_processInfo->SetNewTempo(tempo);
+ }
+}
+
+void CVideoPlayer::FrameAdvance(int frames)
+{
+ float currentSpeed = m_processInfo->GetNewSpeed();
+ if (currentSpeed != DVD_PLAYSPEED_PAUSE)
+ return;
+
+ m_messenger.Put(std::make_shared<CDVDMsgInt>(CDVDMsg::PLAYER_FRAME_ADVANCE, frames));
+}
+
+bool CVideoPlayer::SupportsTempo() const
+{
+ return m_State.cantempo;
+}
+
+bool CVideoPlayer::OpenStream(CCurrentStream& current, int64_t demuxerId, int iStream, int source, bool reset /*= true*/)
+{
+ CDemuxStream* stream = NULL;
+ CDVDStreamInfo hint;
+
+ CLog::Log(LOGINFO, "Opening stream: {} source: {}", iStream, source);
+
+ if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_DEMUX_SUB)
+ {
+ int index = m_SelectionStreams.TypeIndexOf(current.type, source, demuxerId, iStream);
+ if (index < 0)
+ return false;
+ const SelectionStream& st = m_SelectionStreams.Get(current.type, index);
+
+ CLog::Log(LOGINFO, "Opening Subtitle file: {}", CURL::GetRedacted(st.filename));
+ m_pSubtitleDemuxer.reset();
+ const auto demux = m_subtitleDemuxerMap.find(demuxerId);
+ if (demux == m_subtitleDemuxerMap.end())
+ {
+ CLog::Log(LOGINFO, "No demuxer found for file {}", CURL::GetRedacted(st.filename));
+ return false;
+ }
+
+ m_pSubtitleDemuxer = demux->second;
+
+ double pts = m_VideoPlayerVideo->GetCurrentPts();
+ if(pts == DVD_NOPTS_VALUE)
+ pts = m_CurrentVideo.dts;
+ if(pts == DVD_NOPTS_VALUE)
+ pts = 0;
+ pts += m_offset_pts;
+ if (!m_pSubtitleDemuxer->SeekTime((int)(1000.0 * pts / (double)DVD_TIME_BASE)))
+ CLog::Log(LOGDEBUG, "{} - failed to start subtitle demuxing from: {:f}", __FUNCTION__, pts);
+ stream = m_pSubtitleDemuxer->GetStream(demuxerId, iStream);
+ if(!stream || stream->disabled)
+ return false;
+
+ m_pSubtitleDemuxer->EnableStream(demuxerId, iStream, true);
+
+ hint.Assign(*stream, true);
+ }
+ else if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_TEXT)
+ {
+ int index = m_SelectionStreams.TypeIndexOf(current.type, source, demuxerId, iStream);
+ if(index < 0)
+ return false;
+
+ hint.Clear();
+ hint.filename = m_SelectionStreams.Get(current.type, index).filename;
+ hint.fpsscale = m_CurrentVideo.hint.fpsscale;
+ hint.fpsrate = m_CurrentVideo.hint.fpsrate;
+ }
+ else if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_DEMUX)
+ {
+ if(!m_pDemuxer)
+ return false;
+
+ m_pDemuxer->OpenStream(demuxerId, iStream);
+
+ stream = m_pDemuxer->GetStream(demuxerId, iStream);
+ if (!stream || stream->disabled)
+ return false;
+
+ hint.Assign(*stream, true);
+
+ if(m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ hint.filename = "dvd";
+ }
+ else if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_VIDEOMUX)
+ {
+ if(!m_pCCDemuxer)
+ return false;
+
+ stream = m_pCCDemuxer->GetStream(iStream);
+ if(!stream || stream->disabled)
+ return false;
+
+ hint.Assign(*stream, false);
+ }
+
+ bool res;
+ switch(current.type)
+ {
+ case STREAM_AUDIO:
+ res = OpenAudioStream(hint, reset);
+ break;
+ case STREAM_VIDEO:
+ res = OpenVideoStream(hint, reset);
+ break;
+ case STREAM_SUBTITLE:
+ res = OpenSubtitleStream(hint);
+ break;
+ case STREAM_TELETEXT:
+ res = OpenTeletextStream(hint);
+ break;
+ case STREAM_RADIO_RDS:
+ res = OpenRadioRDSStream(hint);
+ break;
+ case STREAM_AUDIO_ID3:
+ res = OpenAudioID3Stream(hint);
+ break;
+ default:
+ res = false;
+ break;
+ }
+
+ if (res)
+ {
+ int oldId = current.id;
+ current.id = iStream;
+ current.demuxerId = demuxerId;
+ current.source = source;
+ current.hint = hint;
+ current.stream = (void*)stream;
+ current.lastdts = DVD_NOPTS_VALUE;
+ if (oldId >= 0 && current.avsync != CCurrentStream::AV_SYNC_FORCE)
+ current.avsync = CCurrentStream::AV_SYNC_CHECK;
+ if(stream)
+ current.changes = stream->changes;
+ }
+ else
+ {
+ if(stream)
+ {
+ /* mark stream as disabled, to disallow further attempts*/
+ CLog::Log(LOGWARNING, "{} - Unsupported stream {}. Stream disabled.", __FUNCTION__,
+ stream->uniqueId);
+ stream->disabled = true;
+ }
+ }
+
+ UpdateContentState();
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalVideoInfoChange();
+ CServiceBroker::GetDataCacheCore().SignalSubtitleInfoChange();
+
+ return res;
+}
+
+bool CVideoPlayer::OpenAudioStream(CDVDStreamInfo& hint, bool reset)
+{
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentAudio.player);
+ if(player == nullptr)
+ return false;
+
+ if(m_CurrentAudio.id < 0 ||
+ m_CurrentAudio.hint != hint)
+ {
+ if (!player->OpenStream(hint))
+ return false;
+
+ player->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, m_displayLost), 1);
+
+ static_cast<IDVDStreamPlayerAudio*>(player)->SetSpeed(m_streamPlayerSpeed);
+ m_CurrentAudio.syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_CurrentAudio.packets = 0;
+ }
+ else if (reset)
+ player->SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_RESET), 0);
+
+ m_HasAudio = true;
+
+ static_cast<IDVDStreamPlayerAudio*>(player)->SendMessage(
+ std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_REQUEST_STATE), 1);
+
+ return true;
+}
+
+bool CVideoPlayer::OpenVideoStream(CDVDStreamInfo& hint, bool reset)
+{
+ if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ /* set aspect ratio as requested by navigator for dvd's */
+ float aspect = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream)->GetVideoAspectRatio();
+ if (aspect != 0.0f)
+ {
+ hint.aspect = static_cast<double>(aspect);
+ hint.forced_aspect = true;
+ }
+ hint.dvd = true;
+ }
+ else if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER))
+ {
+ // set framerate if not set by demuxer
+ if (hint.fpsrate == 0 || hint.fpsscale == 0)
+ {
+ int fpsidx = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PVRPLAYBACK_FPS);
+ if (fpsidx == 1)
+ {
+ hint.fpsscale = 1000;
+ hint.fpsrate = 50000;
+ }
+ else if (fpsidx == 2)
+ {
+ hint.fpsscale = 1001;
+ hint.fpsrate = 60000;
+ }
+ }
+ }
+
+ std::shared_ptr<CDVDInputStream::IMenus> pMenus = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream);
+ if(pMenus && pMenus->IsInMenu())
+ hint.stills = true;
+
+ if (hint.stereo_mode.empty())
+ {
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui != nullptr)
+ {
+ const CStereoscopicsManager &stereoscopicsManager = gui->GetStereoscopicsManager();
+ hint.stereo_mode = stereoscopicsManager.DetectStereoModeByString(m_item.GetPath());
+ }
+ }
+
+ if (hint.flags & AV_DISPOSITION_ATTACHED_PIC)
+ return false;
+
+ // set desired refresh rate
+ if (m_CurrentVideo.id < 0 && m_playerOptions.fullscreen &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot() && hint.fpsrate != 0 &&
+ hint.fpsscale != 0)
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE) != ADJUST_REFRESHRATE_OFF)
+ {
+ double framerate = DVD_TIME_BASE / CDVDCodecUtils::NormalizeFrameduration((double)DVD_TIME_BASE * hint.fpsscale / hint.fpsrate);
+ RESOLUTION res = CResolutionUtils::ChooseBestResolution(static_cast<float>(framerate), hint.width, hint.height, !hint.stereo_mode.empty());
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(res, false);
+ m_renderManager.TriggerUpdateResolution(framerate, hint.width, hint.height, hint.stereo_mode);
+ }
+ }
+
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentVideo.player);
+ if(player == nullptr)
+ return false;
+
+ if(m_CurrentVideo.id < 0 ||
+ m_CurrentVideo.hint != hint)
+ {
+ if (hint.codec == AV_CODEC_ID_MPEG2VIDEO || hint.codec == AV_CODEC_ID_H264)
+ m_pCCDemuxer.reset();
+
+ if (!player->OpenStream(hint))
+ return false;
+
+ player->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, m_displayLost), 1);
+
+ // look for any EDL files
+ m_Edl.Clear();
+ float fFramesPerSecond = 0.0f;
+ if (m_CurrentVideo.hint.fpsscale > 0.0f)
+ fFramesPerSecond = static_cast<float>(m_CurrentVideo.hint.fpsrate) / static_cast<float>(m_CurrentVideo.hint.fpsscale);
+ m_Edl.ReadEditDecisionLists(m_item, fFramesPerSecond);
+ CServiceBroker::GetDataCacheCore().SetEditList(m_Edl.GetEditList());
+ CServiceBroker::GetDataCacheCore().SetCuts(m_Edl.GetCutMarkers());
+ CServiceBroker::GetDataCacheCore().SetSceneMarkers(m_Edl.GetSceneMarkers());
+
+ static_cast<IDVDStreamPlayerVideo*>(player)->SetSpeed(m_streamPlayerSpeed);
+ m_CurrentVideo.syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_CurrentVideo.packets = 0;
+ }
+ else if (reset)
+ player->SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_RESET), 0);
+
+ m_HasVideo = true;
+
+ static_cast<IDVDStreamPlayerVideo*>(player)->SendMessage(
+ std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_REQUEST_STATE), 1);
+
+ // open CC demuxer if video is mpeg2
+ if ((hint.codec == AV_CODEC_ID_MPEG2VIDEO || hint.codec == AV_CODEC_ID_H264) && !m_pCCDemuxer)
+ {
+ m_pCCDemuxer = std::make_unique<CDVDDemuxCC>(hint.codec);
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_VIDEOMUX);
+ }
+
+ return true;
+}
+
+bool CVideoPlayer::OpenSubtitleStream(const CDVDStreamInfo& hint)
+{
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentSubtitle.player);
+ if(player == nullptr)
+ return false;
+
+ if(m_CurrentSubtitle.id < 0 ||
+ m_CurrentSubtitle.hint != hint)
+ {
+ if (!player->OpenStream(hint))
+ return false;
+ }
+
+ return true;
+}
+
+void CVideoPlayer::AdaptForcedSubtitles()
+{
+ SelectionStream ss = m_SelectionStreams.Get(STREAM_SUBTITLE, GetSubtitle());
+ if (ss.flags & StreamFlags::FLAG_FORCED)
+ {
+ SelectionStream as = m_SelectionStreams.Get(STREAM_AUDIO, GetAudioStream());
+ bool found = false;
+ for (const auto &stream : m_SelectionStreams.Get(STREAM_SUBTITLE))
+ {
+ if (stream.flags & StreamFlags::FLAG_FORCED && g_LangCodeExpander.CompareISO639Codes(stream.language, as.language))
+ {
+ if (OpenStream(m_CurrentSubtitle, stream.demuxerId, stream.id, stream.source))
+ {
+ found = true;
+ SetSubtitleVisibleInternal(true);
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ SetSubtitleVisibleInternal(false);
+ }
+ }
+}
+
+bool CVideoPlayer::OpenTeletextStream(CDVDStreamInfo& hint)
+{
+ if (!m_VideoPlayerTeletext->CheckStream(hint))
+ return false;
+
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentTeletext.player);
+ if(player == nullptr)
+ return false;
+
+ if(m_CurrentTeletext.id < 0 ||
+ m_CurrentTeletext.hint != hint)
+ {
+ if (!player->OpenStream(hint))
+ return false;
+ }
+
+ return true;
+}
+
+bool CVideoPlayer::OpenRadioRDSStream(CDVDStreamInfo& hint)
+{
+ if (!m_VideoPlayerRadioRDS->CheckStream(hint))
+ return false;
+
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentRadioRDS.player);
+ if(player == nullptr)
+ return false;
+
+ if(m_CurrentRadioRDS.id < 0 ||
+ m_CurrentRadioRDS.hint != hint)
+ {
+ if (!player->OpenStream(hint))
+ return false;
+ }
+
+ return true;
+}
+
+bool CVideoPlayer::OpenAudioID3Stream(CDVDStreamInfo& hint)
+{
+ if (!m_VideoPlayerAudioID3->CheckStream(hint))
+ return false;
+
+ IDVDStreamPlayer* player = GetStreamPlayer(m_CurrentAudioID3.player);
+ if (player == nullptr)
+ return false;
+
+ if (m_CurrentAudioID3.id < 0 || m_CurrentAudioID3.hint != hint)
+ {
+ if (!player->OpenStream(hint))
+ return false;
+ }
+
+ return true;
+}
+
+bool CVideoPlayer::CloseStream(CCurrentStream& current, bool bWaitForBuffers)
+{
+ if (current.id < 0)
+ return false;
+
+ CLog::Log(LOGINFO, "Closing stream player {}", current.player);
+
+ if(bWaitForBuffers)
+ SetCaching(CACHESTATE_DONE);
+
+ if (m_pDemuxer && STREAM_SOURCE_MASK(current.source) == STREAM_SOURCE_DEMUX)
+ m_pDemuxer->EnableStream(current.demuxerId, current.id, false);
+
+ IDVDStreamPlayer* player = GetStreamPlayer(current.player);
+ if (player)
+ {
+ if ((current.type == STREAM_AUDIO && current.syncState != IDVDStreamPlayer::SYNC_INSYNC) ||
+ (current.type == STREAM_VIDEO && current.syncState != IDVDStreamPlayer::SYNC_INSYNC) ||
+ m_bAbortRequest)
+ bWaitForBuffers = false;
+ player->CloseStream(bWaitForBuffers);
+ }
+
+ current.Clear();
+ return true;
+}
+
+void CVideoPlayer::FlushBuffers(double pts, bool accurate, bool sync)
+{
+ CLog::Log(LOGDEBUG, "CVideoPlayer::FlushBuffers - flushing buffers");
+
+ double startpts;
+ if (accurate)
+ startpts = pts;
+ else
+ startpts = DVD_NOPTS_VALUE;
+
+ if (sync)
+ {
+ m_CurrentAudio.inited = false;
+ m_CurrentAudio.avsync = CCurrentStream::AV_SYNC_FORCE;
+ m_CurrentVideo.inited = false;
+ m_CurrentVideo.avsync = CCurrentStream::AV_SYNC_FORCE;
+ m_CurrentSubtitle.inited = false;
+ m_CurrentTeletext.inited = false;
+ m_CurrentRadioRDS.inited = false;
+ }
+
+ m_CurrentAudio.dts = DVD_NOPTS_VALUE;
+ m_CurrentAudio.startpts = startpts;
+ m_CurrentAudio.packets = 0;
+
+ m_CurrentVideo.dts = DVD_NOPTS_VALUE;
+ m_CurrentVideo.startpts = startpts;
+ m_CurrentVideo.packets = 0;
+
+ m_CurrentSubtitle.dts = DVD_NOPTS_VALUE;
+ m_CurrentSubtitle.startpts = startpts;
+ m_CurrentSubtitle.packets = 0;
+
+ m_CurrentTeletext.dts = DVD_NOPTS_VALUE;
+ m_CurrentTeletext.startpts = startpts;
+ m_CurrentTeletext.packets = 0;
+
+ m_CurrentRadioRDS.dts = DVD_NOPTS_VALUE;
+ m_CurrentRadioRDS.startpts = startpts;
+ m_CurrentRadioRDS.packets = 0;
+
+ m_CurrentAudioID3.dts = DVD_NOPTS_VALUE;
+ m_CurrentAudioID3.startpts = startpts;
+ m_CurrentAudioID3.packets = 0;
+
+ m_VideoPlayerAudio->Flush(sync);
+ m_VideoPlayerVideo->Flush(sync);
+ m_VideoPlayerSubtitle->Flush();
+ m_VideoPlayerTeletext->Flush();
+ m_VideoPlayerRadioRDS->Flush();
+ m_VideoPlayerAudioID3->Flush();
+
+ if (m_playSpeed == DVD_PLAYSPEED_NORMAL ||
+ m_playSpeed == DVD_PLAYSPEED_PAUSE)
+ {
+ // make sure players are properly flushed, should put them in stalled state
+ auto msg = std::make_shared<CDVDMsgGeneralSynchronize>(1s, SYNCSOURCE_AUDIO | SYNCSOURCE_VIDEO);
+ m_VideoPlayerAudio->SendMessage(msg, 1);
+ m_VideoPlayerVideo->SendMessage(msg, 1);
+ msg->Wait(m_bStop, 0);
+
+ // purge any pending PLAYER_STARTED messages
+ m_messenger.Flush(CDVDMsg::PLAYER_STARTED);
+
+ // we should now wait for init cache
+ SetCaching(CACHESTATE_FLUSH);
+ if (sync)
+ {
+ m_CurrentAudio.syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_CurrentVideo.syncState = IDVDStreamPlayer::SYNC_STARTING;
+ }
+ }
+
+ if(pts != DVD_NOPTS_VALUE && sync)
+ m_clock.Discontinuity(pts);
+ UpdatePlayState(0);
+
+ m_demuxerSpeed = DVD_PLAYSPEED_NORMAL;
+ if (m_pDemuxer)
+ m_pDemuxer->SetSpeed(DVD_PLAYSPEED_NORMAL);
+}
+
+// since we call ffmpeg functions to decode, this is being called in the same thread as ::Process() is
+int CVideoPlayer::OnDiscNavResult(void* pData, int iMessage)
+{
+ if (!m_pInputStream)
+ return 0;
+
+#if defined(HAVE_LIBBLURAY)
+ if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
+ {
+ switch (iMessage)
+ {
+ case BD_EVENT_MENU_OVERLAY:
+ m_overlayContainer.ProcessAndAddOverlayIfValid(static_cast<CDVDOverlay*>(pData));
+ break;
+ case BD_EVENT_PLAYLIST_STOP:
+ m_dvd.state = DVDSTATE_NORMAL;
+ m_dvd.iDVDStillTime = 0ms;
+ m_messenger.Put(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_FLUSH));
+ break;
+ case BD_EVENT_AUDIO_STREAM:
+ m_dvd.iSelectedAudioStream = *static_cast<int*>(pData);
+ break;
+
+ case BD_EVENT_PG_TEXTST_STREAM:
+ m_dvd.iSelectedSPUStream = *static_cast<int*>(pData);
+ break;
+ case BD_EVENT_PG_TEXTST:
+ {
+ bool enable = (*static_cast<int*>(pData) != 0);
+ m_VideoPlayerVideo->EnableSubtitle(enable);
+ }
+ break;
+ case BD_EVENT_STILL_TIME:
+ {
+ if (m_dvd.state != DVDSTATE_STILL)
+ {
+ // else notify the player we have received a still frame
+
+ m_dvd.iDVDStillTime = std::chrono::milliseconds(*static_cast<int*>(pData));
+ m_dvd.iDVDStillStartTime = std::chrono::steady_clock::now();
+
+ if (m_dvd.iDVDStillTime > 0ms)
+ m_dvd.iDVDStillTime *= 1000;
+
+ /* adjust for the output delay in the video queue */
+ std::chrono::milliseconds time = 0ms;
+ if (m_CurrentVideo.stream && m_dvd.iDVDStillTime > 0ms)
+ {
+ time = std::chrono::milliseconds(
+ static_cast<int>(m_VideoPlayerVideo->GetOutputDelay() / (DVD_TIME_BASE / 1000)));
+ if (time < 10000ms && time > 0ms)
+ m_dvd.iDVDStillTime += time;
+ }
+ m_dvd.state = DVDSTATE_STILL;
+ CLog::Log(LOGDEBUG, "BD_EVENT_STILL_TIME - waiting {} msec, with delay of {} msec",
+ m_dvd.iDVDStillTime.count(), time.count());
+ }
+ }
+ break;
+ case BD_EVENT_STILL:
+ {
+ bool on = static_cast<bool>(*static_cast<int*>(pData));
+ if (on && m_dvd.state != DVDSTATE_STILL)
+ {
+ m_dvd.state = DVDSTATE_STILL;
+ m_dvd.iDVDStillStartTime = std::chrono::steady_clock::now();
+ m_dvd.iDVDStillTime = 0ms;
+ CLog::Log(LOGDEBUG, "CDVDPlayer::OnDVDNavResult - libbluray DVDSTATE_STILL start");
+ }
+ else if (!on && m_dvd.state == DVDSTATE_STILL)
+ {
+ m_dvd.state = DVDSTATE_NORMAL;
+ m_dvd.iDVDStillStartTime = {};
+ m_dvd.iDVDStillTime = 0ms;
+ CLog::Log(LOGDEBUG, "CDVDPlayer::OnDVDNavResult - libbluray DVDSTATE_STILL end");
+ }
+ }
+ break;
+ case BD_EVENT_MENU_ERROR:
+ {
+ m_dvd.state = DVDSTATE_NORMAL;
+ CLog::Log(LOGDEBUG, "CVideoPlayer::OnDiscNavResult - libbluray menu not supported (DVDSTATE_NORMAL)");
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25008), g_localizeStrings.Get(25009));
+ }
+ break;
+ case BD_EVENT_ENC_ERROR:
+ {
+ m_dvd.state = DVDSTATE_NORMAL;
+ CLog::Log(LOGDEBUG, "CVideoPlayer::OnDiscNavResult - libbluray the disc/file is encrypted and can't be played (DVDSTATE_NORMAL)");
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026), g_localizeStrings.Get(29805));
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+ }
+#endif
+
+ if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> pStream = std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+
+ switch (iMessage)
+ {
+ case DVDNAV_STILL_FRAME:
+ {
+ //CLog::Log(LOGDEBUG, "DVDNAV_STILL_FRAME");
+
+ dvdnav_still_event_t *still_event = static_cast<dvdnav_still_event_t*>(pData);
+ // should wait the specified time here while we let the player running
+ // after that call dvdnav_still_skip(m_dvdnav);
+
+ if (m_dvd.state != DVDSTATE_STILL)
+ {
+ // else notify the player we have received a still frame
+
+ if(still_event->length < 0xff)
+ m_dvd.iDVDStillTime = std::chrono::seconds(still_event->length);
+ else
+ m_dvd.iDVDStillTime = 0ms;
+
+ m_dvd.iDVDStillStartTime = std::chrono::steady_clock::now();
+
+ /* adjust for the output delay in the video queue */
+ std::chrono::milliseconds time = 0ms;
+ if (m_CurrentVideo.stream && m_dvd.iDVDStillTime > 0ms)
+ {
+ time = std::chrono::milliseconds(
+ static_cast<int>(m_VideoPlayerVideo->GetOutputDelay() / (DVD_TIME_BASE / 1000)));
+ if (time < 10000ms && time > 0ms)
+ m_dvd.iDVDStillTime += time;
+ }
+ m_dvd.state = DVDSTATE_STILL;
+ CLog::Log(LOGDEBUG, "DVDNAV_STILL_FRAME - waiting {} sec, with delay of {} msec",
+ still_event->length, time.count());
+ }
+ return NAVRESULT_HOLD;
+ }
+ break;
+ case DVDNAV_SPU_CLUT_CHANGE:
+ {
+ m_VideoPlayerSubtitle->SendMessage(
+ std::make_shared<CDVDMsgSubtitleClutChange>((uint8_t*)pData));
+ }
+ break;
+ case DVDNAV_SPU_STREAM_CHANGE:
+ {
+ dvdnav_spu_stream_change_event_t* event = static_cast<dvdnav_spu_stream_change_event_t*>(pData);
+
+ int iStream = event->physical_wide;
+ bool visible = !(iStream & 0x80);
+
+ SetSubtitleVisibleInternal(visible);
+
+ if (iStream >= 0)
+ m_dvd.iSelectedSPUStream = (iStream & ~0x80);
+ else
+ m_dvd.iSelectedSPUStream = -1;
+
+ m_CurrentSubtitle.stream = NULL;
+ }
+ break;
+ case DVDNAV_AUDIO_STREAM_CHANGE:
+ {
+ dvdnav_audio_stream_change_event_t* event = static_cast<dvdnav_audio_stream_change_event_t*>(pData);
+ // Tell system what audiostream should be opened by default
+ m_dvd.iSelectedAudioStream = event->physical;
+ m_CurrentAudio.stream = NULL;
+ }
+ break;
+ case DVDNAV_HIGHLIGHT:
+ {
+ //dvdnav_highlight_event_t* pInfo = (dvdnav_highlight_event_t*)pData;
+ int iButton = pStream->GetCurrentButton();
+ CLog::Log(LOGDEBUG, "DVDNAV_HIGHLIGHT: Highlight button {}", iButton);
+ m_VideoPlayerSubtitle->UpdateOverlayInfo(std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream), LIBDVDNAV_BUTTON_NORMAL);
+ }
+ break;
+ case DVDNAV_VTS_CHANGE:
+ {
+ //dvdnav_vts_change_event_t* vts_change_event = (dvdnav_vts_change_event_t*)pData;
+ CLog::Log(LOGDEBUG, "DVDNAV_VTS_CHANGE");
+
+ //Make sure we clear all the old overlays here, or else old forced items are left.
+ m_overlayContainer.Clear();
+
+ //Force an aspect ratio that is set in the dvdheaders if available
+ m_CurrentVideo.hint.aspect = static_cast<double>(pStream->GetVideoAspectRatio());
+ if( m_VideoPlayerVideo->IsInited() )
+ m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgDouble>(
+ CDVDMsg::VIDEO_SET_ASPECT, m_CurrentVideo.hint.aspect));
+
+ m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NAV);
+ m_SelectionStreams.Update(m_pInputStream, m_pDemuxer.get());
+ UpdateContent();
+
+ return NAVRESULT_HOLD;
+ }
+ break;
+ case DVDNAV_CELL_CHANGE:
+ {
+ //dvdnav_cell_change_event_t* cell_change_event = (dvdnav_cell_change_event_t*)pData;
+ CLog::Log(LOGDEBUG, "DVDNAV_CELL_CHANGE");
+
+ if (m_dvd.state != DVDSTATE_STILL)
+ m_dvd.state = DVDSTATE_NORMAL;
+ }
+ break;
+ case DVDNAV_NAV_PACKET:
+ {
+ //pci_t* pci = (pci_t*)pData;
+
+ // this should be possible to use to make sure we get
+ // seamless transitions over these boundaries
+ // if we remember the old vobunits boundaries
+ // when a packet comes out of demuxer that has
+ // pts values outside that boundary, it belongs
+ // to the new vobunit, which has new timestamps
+ UpdatePlayState(0);
+ }
+ break;
+ case DVDNAV_HOP_CHANNEL:
+ {
+ // This event is issued whenever a non-seamless operation has been executed.
+ // Applications with fifos should drop the fifos content to speed up responsiveness.
+ CLog::Log(LOGDEBUG, "DVDNAV_HOP_CHANNEL");
+ if(m_dvd.state == DVDSTATE_SEEK)
+ m_dvd.state = DVDSTATE_NORMAL;
+ else
+ {
+ bool sync = !IsInMenuInternal();
+ FlushBuffers(DVD_NOPTS_VALUE, false, sync);
+ m_dvd.syncClock = true;
+ m_dvd.state = DVDSTATE_NORMAL;
+ if (m_pDemuxer)
+ m_pDemuxer->Flush();
+ }
+
+ return NAVRESULT_ERROR;
+ }
+ break;
+ case DVDNAV_STOP:
+ {
+ CLog::Log(LOGDEBUG, "DVDNAV_STOP");
+ m_dvd.state = DVDSTATE_NORMAL;
+ }
+ break;
+ case DVDNAV_ERROR:
+ {
+ CLog::Log(LOGDEBUG, "DVDNAV_ERROR");
+ m_dvd.state = DVDSTATE_NORMAL;
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026),
+ g_localizeStrings.Get(16029));
+ }
+ break;
+ default:
+ {}
+ break;
+ }
+ }
+ return NAVRESULT_NOP;
+}
+
+void CVideoPlayer::GetVideoResolution(unsigned int &width, unsigned int &height)
+{
+ RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ width = res.iWidth;
+ height = res.iHeight;
+}
+
+bool CVideoPlayer::OnAction(const CAction &action)
+{
+#define THREAD_ACTION(action) \
+ do \
+ { \
+ if (!IsCurrentThread()) \
+ { \
+ m_messenger.Put( \
+ std::make_shared<CDVDMsgType<CAction>>(CDVDMsg::GENERAL_GUI_ACTION, action)); \
+ return true; \
+ } \
+ } while (false)
+
+ std::shared_ptr<CDVDInputStream::IMenus> pMenus = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream);
+ if (pMenus)
+ {
+ if (m_dvd.state == DVDSTATE_STILL && m_dvd.iDVDStillTime != 0ms &&
+ pMenus->GetTotalButtons() == 0)
+ {
+ switch(action.GetID())
+ {
+ case ACTION_NEXT_ITEM:
+ case ACTION_MOVE_RIGHT:
+ case ACTION_MOVE_UP:
+ case ACTION_SELECT_ITEM:
+ {
+ THREAD_ACTION(action);
+ /* this will force us out of the stillframe */
+ CLog::Log(LOGDEBUG, "{} - User asked to exit stillframe", __FUNCTION__);
+ m_dvd.iDVDStillStartTime = {};
+ m_dvd.iDVDStillTime = 1ms;
+ }
+ return true;
+ }
+ }
+
+
+ switch (action.GetID())
+ {
+/* this code is disabled to allow switching playlist items (dvdimage "stacks") */
+#if 0
+ case ACTION_PREV_ITEM: // SKIP-:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - pushed prev");
+ pMenus->OnPrevious();
+ m_processInfo->SeekFinished(0);
+ return true;
+ }
+ break;
+ case ACTION_NEXT_ITEM: // SKIP+:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - pushed next");
+ pMenus->OnNext();
+ m_processInfo->SeekFinished(0);
+ return true;
+ }
+ break;
+#endif
+ case ACTION_SHOW_VIDEOMENU: // start button
+ {
+ THREAD_ACTION(action);
+ CLog::LogF(LOGDEBUG, "Trying to go to the menu");
+ if (pMenus->OnMenu())
+ {
+ if (m_playSpeed == DVD_PLAYSPEED_PAUSE)
+ {
+ SetPlaySpeed(DVD_PLAYSPEED_NORMAL);
+ m_callback.OnPlayBackResumed();
+ }
+
+ // send a message to everyone that we've gone to the menu
+ CGUIMessage msg(GUI_MSG_VIDEO_MENU_STARTED, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+ return true;
+ }
+ break;
+ }
+
+ if (pMenus->IsInMenu())
+ {
+ switch (action.GetID())
+ {
+ case ACTION_NEXT_ITEM:
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - pushed next in menu, stream will decide");
+ if (pMenus->CanSeek() && GetChapterCount() > 0 && GetChapter() < GetChapterCount())
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeekChapter>(GetChapter() + 1));
+ else
+ pMenus->OnNext();
+
+ m_processInfo->SeekFinished(0);
+ return true;
+ case ACTION_PREV_ITEM:
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - pushed prev in menu, stream will decide");
+ if (pMenus->CanSeek() && GetChapterCount() > 0 && GetChapter() > 0)
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeekChapter>(GetChapter() - 1));
+ else
+ pMenus->OnPrevious();
+
+ m_processInfo->SeekFinished(0);
+ return true;
+ case ACTION_PREVIOUS_MENU:
+ case ACTION_NAV_BACK:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - menu back");
+ pMenus->OnBack();
+ }
+ break;
+ case ACTION_MOVE_LEFT:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - move left");
+ pMenus->OnLeft();
+ }
+ break;
+ case ACTION_MOVE_RIGHT:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - move right");
+ pMenus->OnRight();
+ }
+ break;
+ case ACTION_MOVE_UP:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - move up");
+ pMenus->OnUp();
+ }
+ break;
+ case ACTION_MOVE_DOWN:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - move down");
+ pMenus->OnDown();
+ }
+ break;
+
+ case ACTION_MOUSE_MOVE:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ CRect rs, rd, rv;
+ m_renderManager.GetVideoRect(rs, rd, rv);
+ CPoint pt(action.GetAmount(), action.GetAmount(1));
+ if (!rd.PtInRect(pt))
+ return false; // out of bounds
+ THREAD_ACTION(action);
+ // convert to video coords...
+ pt -= CPoint(rd.x1, rd.y1);
+ pt.x *= rs.Width() / rd.Width();
+ pt.y *= rs.Height() / rd.Height();
+ pt += CPoint(rs.x1, rs.y1);
+ if (action.GetID() == ACTION_MOUSE_LEFT_CLICK)
+ {
+ if (pMenus->OnMouseClick(pt))
+ return true;
+ else
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(
+ TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(ACTION_TRIGGER_OSD)));
+ return false;
+ }
+ }
+ return pMenus->OnMouseMove(pt);
+ }
+ break;
+ case ACTION_SELECT_ITEM:
+ {
+ THREAD_ACTION(action);
+ CLog::Log(LOGDEBUG, " - button select");
+ // show button pushed overlay
+ if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
+ m_VideoPlayerSubtitle->UpdateOverlayInfo(std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream), LIBDVDNAV_BUTTON_CLICKED);
+
+ pMenus->ActivateButton();
+ }
+ break;
+ case REMOTE_0:
+ case REMOTE_1:
+ case REMOTE_2:
+ case REMOTE_3:
+ case REMOTE_4:
+ case REMOTE_5:
+ case REMOTE_6:
+ case REMOTE_7:
+ case REMOTE_8:
+ case REMOTE_9:
+ {
+ THREAD_ACTION(action);
+ // Offset from key codes back to button number
+ int button = action.GetID() - REMOTE_0;
+ CLog::Log(LOGDEBUG, " - button pressed {}", button);
+ pMenus->SelectButton(button);
+ }
+ break;
+ default:
+ return false;
+ break;
+ }
+ return true; // message is handled
+ }
+ }
+
+ pMenus.reset();
+
+ switch (action.GetID())
+ {
+ case ACTION_NEXT_ITEM:
+ if (GetChapter() > 0 && GetChapter() < GetChapterCount())
+ {
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeekChapter>(GetChapter() + 1));
+ m_processInfo->SeekFinished(0);
+ return true;
+ }
+ else if (SeekScene(true))
+ return true;
+ else
+ break;
+ case ACTION_PREV_ITEM:
+ if (GetChapter() > 0)
+ {
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeekChapter>(GetChapter() - 1));
+ m_processInfo->SeekFinished(0);
+ return true;
+ }
+ else if (SeekScene(false))
+ return true;
+ else
+ break;
+ case ACTION_TOGGLE_COMMSKIP:
+ m_SkipCommercials = !m_SkipCommercials;
+ CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011),
+ g_localizeStrings.Get(m_SkipCommercials ? 25013 : 25012));
+ break;
+ case ACTION_PLAYER_DEBUG:
+ m_renderManager.ToggleDebug();
+ break;
+ case ACTION_PLAYER_DEBUG_VIDEO:
+ m_renderManager.ToggleDebugVideo();
+ break;
+
+ case ACTION_PLAYER_PROCESS_INFO:
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_DIALOG_PLAYER_PROCESS_INFO)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_PLAYER_PROCESS_INFO);
+ return true;
+ }
+ break;
+ }
+
+ // return false to inform the caller we didn't handle the message
+ return false;
+}
+
+bool CVideoPlayer::IsInMenuInternal() const
+{
+ std::shared_ptr<CDVDInputStream::IMenus> pStream = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream);
+ if (pStream)
+ {
+ if (m_dvd.state == DVDSTATE_STILL)
+ return true;
+ else
+ return pStream->IsInMenu();
+ }
+ return false;
+}
+
+
+bool CVideoPlayer::IsInMenu() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.isInMenu;
+}
+
+MenuType CVideoPlayer::GetSupportedMenuType() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.menuType;
+}
+
+std::string CVideoPlayer::GetPlayerState()
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.player_state;
+}
+
+bool CVideoPlayer::SetPlayerState(const std::string& state)
+{
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetState>(state));
+ return true;
+}
+
+int CVideoPlayer::GetChapterCount() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.chapters.size();
+}
+
+int CVideoPlayer::GetChapter() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return m_State.chapter;
+}
+
+void CVideoPlayer::GetChapterName(std::string& strChapterName, int chapterIdx) const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ if (chapterIdx == -1 && m_State.chapter > 0 && m_State.chapter <= (int) m_State.chapters.size())
+ strChapterName = m_State.chapters[m_State.chapter - 1].first;
+ else if (chapterIdx > 0 && chapterIdx <= (int) m_State.chapters.size())
+ strChapterName = m_State.chapters[chapterIdx - 1].first;
+}
+
+int CVideoPlayer::SeekChapter(int iChapter)
+{
+ if (GetChapter() > 0)
+ {
+ if (iChapter < 0)
+ iChapter = 0;
+ if (iChapter > GetChapterCount())
+ return 0;
+
+ // Seek to the chapter.
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSeekChapter>(iChapter));
+ SynchronizeDemuxer();
+ }
+
+ return 0;
+}
+
+int64_t CVideoPlayer::GetChapterPos(int chapterIdx) const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ if (chapterIdx > 0 && chapterIdx <= (int) m_State.chapters.size())
+ return m_State.chapters[chapterIdx - 1].second;
+
+ return -1;
+}
+
+void CVideoPlayer::AddSubtitle(const std::string& strSubPath)
+{
+ m_messenger.Put(
+ std::make_shared<CDVDMsgType<std::string>>(CDVDMsg::SUBTITLE_ADDFILE, strSubPath));
+}
+
+bool CVideoPlayer::IsCaching() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return !m_State.isInMenu && m_State.caching;
+}
+
+int CVideoPlayer::GetCacheLevel() const
+{
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ return (int)(m_State.cache_level * 100);
+}
+
+double CVideoPlayer::GetQueueTime()
+{
+ int a = m_VideoPlayerAudio->GetLevel();
+ int v = m_processInfo->GetLevelVQ();
+ return std::max(a, v) * 8000.0 / 100;
+}
+
+int CVideoPlayer::AddSubtitleFile(const std::string& filename, const std::string& subfilename)
+{
+ std::string ext = URIUtils::GetExtension(filename);
+ std::string vobsubfile = subfilename;
+ if (ext == ".idx" || ext == ".sup")
+ {
+ std::shared_ptr<CDVDDemux> pDemux;
+ if (ext == ".idx")
+ {
+ if (vobsubfile.empty())
+ {
+ // find corresponding .sub (e.g. in case of manually selected .idx sub)
+ vobsubfile = CUtil::GetVobSubSubFromIdx(filename);
+ if (vobsubfile.empty())
+ return -1;
+ }
+
+ auto pDemuxVobsub = std::make_shared<CDVDDemuxVobsub>();
+ if (!pDemuxVobsub->Open(filename, STREAM_SOURCE_NONE, vobsubfile))
+ return -1;
+
+ m_SelectionStreams.Update(nullptr, pDemuxVobsub.get(), vobsubfile);
+ pDemux = pDemuxVobsub;
+ }
+ else // .sup file
+ {
+ CFileItem item(filename, false);
+ std::shared_ptr<CDVDInputStream> pInput;
+ pInput = CDVDFactoryInputStream::CreateInputStream(nullptr, item);
+ if (!pInput || !pInput->Open())
+ return -1;
+
+ auto pDemuxFFmpeg = std::make_shared<CDVDDemuxFFmpeg>();
+ if (!pDemuxFFmpeg->Open(pInput, false))
+ return -1;
+
+ m_SelectionStreams.Update(nullptr, pDemuxFFmpeg.get(), filename);
+ pDemux = pDemuxFFmpeg;
+ }
+
+ ExternalStreamInfo info =
+ CUtil::GetExternalStreamDetailsFromFilename(m_item.GetDynPath(), filename);
+
+ for (auto sub : pDemux->GetStreams())
+ {
+ if (sub->type != STREAM_SUBTITLE)
+ continue;
+
+ int index = m_SelectionStreams.TypeIndexOf(STREAM_SUBTITLE,
+ m_SelectionStreams.Source(STREAM_SOURCE_DEMUX_SUB, filename),
+ sub->demuxerId, sub->uniqueId);
+ SelectionStream& stream = m_SelectionStreams.Get(STREAM_SUBTITLE, index);
+
+ if (stream.name.empty())
+ stream.name = info.name;
+
+ if (stream.language.empty())
+ stream.language = info.language;
+
+ if (static_cast<StreamFlags>(info.flag) != StreamFlags::FLAG_NONE)
+ stream.flags = static_cast<StreamFlags>(info.flag);
+ }
+
+ UpdateContent();
+ // the demuxer id is unique
+ m_subtitleDemuxerMap[pDemux->GetDemuxerId()] = pDemux;
+ return m_SelectionStreams.TypeIndexOf(
+ STREAM_SUBTITLE, m_SelectionStreams.Source(STREAM_SOURCE_DEMUX_SUB, filename),
+ pDemux->GetDemuxerId(), 0);
+ }
+
+ if(ext == ".sub")
+ {
+ // if this looks like vobsub file (i.e. .idx found), add it as such
+ std::string vobsubidx = CUtil::GetVobSubIdxFromSub(filename);
+ if (!vobsubidx.empty())
+ return AddSubtitleFile(vobsubidx, filename);
+ }
+
+ SelectionStream s;
+ s.source = m_SelectionStreams.Source(STREAM_SOURCE_TEXT, filename);
+ s.type = STREAM_SUBTITLE;
+ s.id = 0;
+ s.filename = filename;
+ ExternalStreamInfo info = CUtil::GetExternalStreamDetailsFromFilename(m_item.GetDynPath(), filename);
+ s.name = info.name;
+ s.language = info.language;
+ if (static_cast<StreamFlags>(info.flag) != StreamFlags::FLAG_NONE)
+ s.flags = static_cast<StreamFlags>(info.flag);
+
+ m_SelectionStreams.Update(s);
+ UpdateContent();
+ return m_SelectionStreams.TypeIndexOf(STREAM_SUBTITLE, s.source, s.demuxerId, s.id);
+}
+
+void CVideoPlayer::UpdatePlayState(double timeout)
+{
+ if (m_State.timestamp != 0 &&
+ m_State.timestamp + DVD_MSEC_TO_TIME(timeout) > m_clock.GetAbsoluteClock())
+ return;
+
+ SPlayerState state(m_State);
+
+ state.dts = DVD_NOPTS_VALUE;
+ if (m_CurrentVideo.dts != DVD_NOPTS_VALUE)
+ state.dts = m_CurrentVideo.dts;
+ else if (m_CurrentAudio.dts != DVD_NOPTS_VALUE)
+ state.dts = m_CurrentAudio.dts;
+ else if (m_CurrentVideo.startpts != DVD_NOPTS_VALUE)
+ state.dts = m_CurrentVideo.startpts;
+ else if (m_CurrentAudio.startpts != DVD_NOPTS_VALUE)
+ state.dts = m_CurrentAudio.startpts;
+
+ state.startTime = 0;
+ state.timeMin = 0;
+
+ std::shared_ptr<CDVDInputStream::IMenus> pMenu = std::dynamic_pointer_cast<CDVDInputStream::IMenus>(m_pInputStream);
+
+ if (m_pDemuxer)
+ {
+ if (IsInMenuInternal() && pMenu && !pMenu->CanSeek())
+ state.chapter = 0;
+ else
+ state.chapter = m_pDemuxer->GetChapter();
+
+ state.chapters.clear();
+ if (m_pDemuxer->GetChapterCount() > 0)
+ {
+ for (int i = 0, ie = m_pDemuxer->GetChapterCount(); i < ie; ++i)
+ {
+ std::string name;
+ m_pDemuxer->GetChapterName(name, i + 1);
+ state.chapters.emplace_back(name, m_pDemuxer->GetChapterPos(i + 1));
+ }
+ }
+ CServiceBroker::GetDataCacheCore().SetChapters(state.chapters);
+
+ state.time = m_clock.GetClock(false) * 1000 / DVD_TIME_BASE;
+ state.timeMax = m_pDemuxer->GetStreamLength();
+ }
+
+ state.canpause = false;
+ state.canseek = false;
+ state.cantempo = false;
+ state.isInMenu = false;
+ state.menuType = MenuType::NONE;
+
+ if (m_pInputStream)
+ {
+ CDVDInputStream::IChapter* pChapter = m_pInputStream->GetIChapter();
+ if (pChapter)
+ {
+ if (IsInMenuInternal() && pMenu && !pMenu->CanSeek())
+ state.chapter = 0;
+ else
+ state.chapter = pChapter->GetChapter();
+
+ state.chapters.clear();
+ if (pChapter->GetChapterCount() > 0)
+ {
+ for (int i = 0, ie = pChapter->GetChapterCount(); i < ie; ++i)
+ {
+ std::string name;
+ pChapter->GetChapterName(name, i + 1);
+ state.chapters.push_back(make_pair(name, pChapter->GetChapterPos(i + 1)));
+ }
+ }
+ CServiceBroker::GetDataCacheCore().SetChapters(state.chapters);
+ }
+
+ CDVDInputStream::ITimes* pTimes = m_pInputStream->GetITimes();
+ CDVDInputStream::IDisplayTime* pDisplayTime = m_pInputStream->GetIDisplayTime();
+
+ CDVDInputStream::ITimes::Times times;
+ if (pTimes && pTimes->GetTimes(times))
+ {
+ state.startTime = times.startTime;
+ state.time = (m_clock.GetClock(false) - times.ptsStart) * 1000 / DVD_TIME_BASE;
+ state.timeMax = (times.ptsEnd - times.ptsStart) * 1000 / DVD_TIME_BASE;
+ state.timeMin = (times.ptsBegin - times.ptsStart) * 1000 / DVD_TIME_BASE;
+ state.time_offset = -times.ptsStart;
+ }
+ else if (pDisplayTime && pDisplayTime->GetTotalTime() > 0)
+ {
+ if (state.dts != DVD_NOPTS_VALUE)
+ {
+ int dispTime = 0;
+ if (m_CurrentVideo.id >= 0 && m_CurrentVideo.dispTime)
+ dispTime = m_CurrentVideo.dispTime;
+ else if (m_CurrentAudio.dispTime)
+ dispTime = m_CurrentAudio.dispTime;
+
+ state.time_offset = DVD_MSEC_TO_TIME(dispTime) - state.dts;
+ }
+ state.time += state.time_offset * 1000 / DVD_TIME_BASE;
+ state.timeMax = pDisplayTime->GetTotalTime();
+ }
+ else
+ {
+ state.time_offset = 0;
+ }
+
+ if (pMenu)
+ {
+ if (!pMenu->GetState(state.player_state))
+ state.player_state = "";
+
+ if (m_dvd.state == DVDSTATE_STILL)
+ {
+ const auto now = std::chrono::steady_clock::now();
+ const auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - m_dvd.iDVDStillStartTime);
+ state.time = duration.count();
+ state.timeMax = m_dvd.iDVDStillTime.count();
+ state.isInMenu = true;
+ }
+ else if (IsInMenuInternal())
+ {
+ state.time = pDisplayTime->GetTime();
+ state.isInMenu = true;
+ if (!pMenu->CanSeek())
+ state.time_offset = 0;
+ }
+ state.menuType = pMenu->GetSupportedMenuType();
+ }
+
+ state.canpause = m_pInputStream->CanPause();
+
+ bool realtime = m_pInputStream->IsRealtime();
+
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK) &&
+ !realtime)
+ {
+ state.cantempo = true;
+ }
+ else
+ {
+ state.cantempo = false;
+ }
+
+ m_processInfo->SetStateRealtime(realtime);
+ }
+
+ if (m_Edl.HasCuts())
+ {
+ state.time = static_cast<double>(m_Edl.GetTimeWithoutCuts(state.time));
+ state.timeMax = state.timeMax - static_cast<double>(m_Edl.GetTotalCutTime());
+ }
+
+ if (m_caching > CACHESTATE_DONE && m_caching < CACHESTATE_PLAY)
+ state.caching = true;
+ else
+ state.caching = false;
+
+ double level, delay, offset;
+ if (GetCachingTimes(level, delay, offset))
+ {
+ state.cache_delay = std::max(0.0, delay);
+ state.cache_level = std::max(0.0, std::min(1.0, level));
+ state.cache_offset = offset;
+ }
+ else
+ {
+ state.cache_delay = 0.0;
+ state.cache_level = std::min(1.0, GetQueueTime() / 8000.0);
+ state.cache_offset = GetQueueTime() / state.timeMax;
+ }
+
+ XFILE::SCacheStatus status;
+ if (m_pInputStream && m_pInputStream->GetCacheStatus(&status))
+ {
+ state.cache_bytes = status.forward;
+ if(state.timeMax)
+ state.cache_bytes += m_pInputStream->GetLength() * (int64_t) (GetQueueTime() / state.timeMax);
+ }
+ else
+ state.cache_bytes = 0;
+
+ state.timestamp = m_clock.GetAbsoluteClock();
+
+ if (state.timeMax <= 0)
+ {
+ state.timeMax = state.time;
+ state.timeMin = state.time;
+ }
+ if (state.timeMin == state.timeMax)
+ {
+ state.canseek = false;
+ state.cantempo = false;
+ }
+ else
+ {
+ state.canseek = true;
+ state.canpause = true;
+ }
+
+ m_processInfo->SetPlayTimes(state.startTime, state.time, state.timeMin, state.timeMax);
+
+ std::unique_lock<CCriticalSection> lock(m_StateSection);
+ m_State = state;
+}
+
+int64_t CVideoPlayer::GetUpdatedTime()
+{
+ UpdatePlayState(0);
+ return llrint(m_State.time);
+}
+
+void CVideoPlayer::SetDynamicRangeCompression(long drc)
+{
+ m_processInfo->GetVideoSettingsLocked().SetVolumeAmplification(static_cast<float>(drc) / 100);
+ m_VideoPlayerAudio->SetDynamicRangeCompression(drc);
+}
+
+CVideoSettings CVideoPlayer::GetVideoSettings() const
+{
+ return m_processInfo->GetVideoSettings();
+}
+
+void CVideoPlayer::SetVideoSettings(CVideoSettings& settings)
+{
+ m_processInfo->SetVideoSettings(settings);
+ m_renderManager.SetVideoSettings(settings);
+ m_renderManager.SetDelay(static_cast<int>(settings.m_AudioDelay * 1000.0f));
+ m_renderManager.SetSubtitleVerticalPosition(settings.m_subtitleVerticalPosition,
+ settings.m_subtitleVerticalPositionSave);
+ m_VideoPlayerVideo->EnableSubtitle(settings.m_SubtitleOn);
+ m_VideoPlayerVideo->SetSubtitleDelay(static_cast<int>(-settings.m_SubtitleDelay * DVD_TIME_BASE));
+}
+
+void CVideoPlayer::FrameMove()
+{
+ m_renderManager.FrameMove();
+}
+
+void CVideoPlayer::Render(bool clear, uint32_t alpha, bool gui)
+{
+ m_renderManager.Render(clear, 0, alpha, gui);
+}
+
+void CVideoPlayer::FlushRenderer()
+{
+ m_renderManager.Flush(true, true);
+}
+
+void CVideoPlayer::SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch)
+{
+ m_processInfo->GetVideoSettingsLocked().SetViewMode(mode, zoom, par, shift, stretch);
+ m_renderManager.SetVideoSettings(m_processInfo->GetVideoSettings());
+ m_renderManager.SetViewMode(mode);
+}
+
+float CVideoPlayer::GetRenderAspectRatio() const
+{
+ return m_renderManager.GetAspectRatio();
+}
+
+void CVideoPlayer::TriggerUpdateResolution()
+{
+ std::string stereomode;
+ m_renderManager.TriggerUpdateResolution(0, 0, 0, stereomode);
+}
+
+bool CVideoPlayer::IsRenderingVideo() const
+{
+ return m_renderManager.IsConfigured();
+}
+
+bool CVideoPlayer::Supports(EINTERLACEMETHOD method) const
+{
+ if (!m_processInfo)
+ return false;
+ return m_processInfo->Supports(method);
+}
+
+EINTERLACEMETHOD CVideoPlayer::GetDeinterlacingMethodDefault() const
+{
+ if (!m_processInfo)
+ return EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE;
+ return m_processInfo->GetDeinterlacingMethodDefault();
+}
+
+bool CVideoPlayer::Supports(ESCALINGMETHOD method) const
+{
+ return m_renderManager.Supports(method);
+}
+
+bool CVideoPlayer::Supports(ERENDERFEATURE feature) const
+{
+ return m_renderManager.Supports(feature);
+}
+
+unsigned int CVideoPlayer::RenderCaptureAlloc()
+{
+ return m_renderManager.AllocRenderCapture();
+}
+
+void CVideoPlayer::RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags)
+{
+ m_renderManager.StartRenderCapture(captureId, width, height, flags);
+}
+
+void CVideoPlayer::RenderCaptureRelease(unsigned int captureId)
+{
+ m_renderManager.ReleaseRenderCapture(captureId);
+}
+
+bool CVideoPlayer::RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size)
+{
+ return m_renderManager.RenderCaptureGetPixels(captureId, millis, buffer, size);
+}
+
+void CVideoPlayer::VideoParamsChange()
+{
+ m_messenger.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_AVCHANGE));
+}
+
+void CVideoPlayer::GetDebugInfo(std::string &audio, std::string &video, std::string &general)
+{
+ audio = m_VideoPlayerAudio->GetPlayerInfo();
+ video = m_VideoPlayerVideo->GetPlayerInfo();
+ GetGeneralInfo(general);
+}
+
+void CVideoPlayer::UpdateClockSync(bool enabled)
+{
+ m_processInfo->SetRenderClockSync(enabled);
+}
+
+void CVideoPlayer::UpdateRenderInfo(CRenderInfo &info)
+{
+ m_processInfo->UpdateRenderInfo(info);
+}
+
+void CVideoPlayer::UpdateRenderBuffers(int queued, int discard, int free)
+{
+ m_processInfo->UpdateRenderBuffers(queued, discard, free);
+}
+
+void CVideoPlayer::UpdateGuiRender(bool gui)
+{
+ m_processInfo->SetGuiRender(gui);
+}
+
+void CVideoPlayer::UpdateVideoRender(bool video)
+{
+ m_processInfo->SetVideoRender(video);
+}
+
+// IDispResource interface
+void CVideoPlayer::OnLostDisplay()
+{
+ CLog::Log(LOGINFO, "VideoPlayer: OnLostDisplay received");
+ m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1);
+ m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1);
+ m_clock.Pause(true);
+ m_displayLost = true;
+ FlushRenderer();
+}
+
+void CVideoPlayer::OnResetDisplay()
+{
+ if (!m_displayLost)
+ return;
+
+ CLog::Log(LOGINFO, "VideoPlayer: OnResetDisplay received");
+ m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, false), 1);
+ m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, false), 1);
+ m_clock.Pause(false);
+ m_displayLost = false;
+ m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_DISPLAY_RESET), 1);
+}
+
+void CVideoPlayer::UpdateFileItemStreamDetails(CFileItem& item)
+{
+ if (!m_UpdateStreamDetails)
+ return;
+ m_UpdateStreamDetails = false;
+
+ CLog::Log(LOGDEBUG, "CVideoPlayer: updating file item stream details with available streams");
+
+ VideoStreamInfo videoInfo;
+ AudioStreamInfo audioInfo;
+ SubtitleStreamInfo subtitleInfo;
+ CVideoInfoTag* info = item.GetVideoInfoTag();
+ GetVideoStreamInfo(CURRENT_STREAM, videoInfo);
+ info->m_streamDetails.SetStreams(videoInfo, m_processInfo->GetMaxTime() / 1000, audioInfo,
+ subtitleInfo);
+
+ //grab all the audio and subtitle info and save it
+
+ for (int i = 0; i < GetAudioStreamCount(); i++)
+ {
+ GetAudioStreamInfo(i, audioInfo);
+ info->m_streamDetails.AddStream(new CStreamDetailAudio(audioInfo));
+ }
+
+ for (int i = 0; i < GetSubtitleCount(); i++)
+ {
+ GetSubtitleStreamInfo(i, subtitleInfo);
+ info->m_streamDetails.AddStream(new CStreamDetailSubtitle(subtitleInfo));
+ }
+}
+
+//------------------------------------------------------------------------------
+// content related methods
+//------------------------------------------------------------------------------
+
+void CVideoPlayer::UpdateContent()
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ m_content.m_selectionStreams = m_SelectionStreams;
+ m_content.m_programs = m_programs;
+}
+
+void CVideoPlayer::UpdateContentState()
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+
+ m_content.m_videoIndex = m_SelectionStreams.TypeIndexOf(STREAM_VIDEO, m_CurrentVideo.source,
+ m_CurrentVideo.demuxerId, m_CurrentVideo.id);
+ m_content.m_audioIndex = m_SelectionStreams.TypeIndexOf(STREAM_AUDIO, m_CurrentAudio.source,
+ m_CurrentAudio.demuxerId, m_CurrentAudio.id);
+ m_content.m_subtitleIndex = m_SelectionStreams.TypeIndexOf(STREAM_SUBTITLE, m_CurrentSubtitle.source,
+ m_CurrentSubtitle.demuxerId, m_CurrentSubtitle.id);
+
+ if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) && m_content.m_videoIndex == -1 &&
+ m_content.m_audioIndex == -1)
+ {
+ std::shared_ptr<CDVDInputStreamNavigator> nav =
+ std::static_pointer_cast<CDVDInputStreamNavigator>(m_pInputStream);
+
+ m_content.m_videoIndex = m_SelectionStreams.TypeIndexOf(STREAM_VIDEO, STREAM_SOURCE_NAV, -1,
+ nav->GetActiveAngle());
+ m_content.m_audioIndex = m_SelectionStreams.TypeIndexOf(STREAM_AUDIO, STREAM_SOURCE_NAV, -1,
+ nav->GetActiveAudioStream());
+
+ // only update the subtitle index in libdvdnav if the subtitle is provided by the dvd itself,
+ // i.e. for external subtitles the index is always greater than the subtitlecount in dvdnav
+ if (m_content.m_subtitleIndex < nav->GetSubTitleStreamCount())
+ {
+ m_content.m_subtitleIndex = m_SelectionStreams.TypeIndexOf(
+ STREAM_SUBTITLE, STREAM_SOURCE_NAV, -1, nav->GetActiveSubtitleStream());
+ }
+ }
+}
+
+void CVideoPlayer::GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+
+ if (streamId == CURRENT_STREAM)
+ streamId = m_content.m_videoIndex;
+
+ if (streamId < 0 || streamId > GetVideoStreamCount() - 1)
+ {
+ info.valid = false;
+ return;
+ }
+
+ const SelectionStream& s = m_content.m_selectionStreams.Get(STREAM_VIDEO, streamId);
+ if (s.language.length() > 0)
+ info.language = s.language;
+
+ if (s.name.length() > 0)
+ info.name = s.name;
+
+ m_renderManager.GetVideoRect(info.SrcRect, info.DestRect, info.VideoRect);
+
+ info.valid = true;
+ info.bitrate = s.bitrate;
+ info.width = s.width;
+ info.height = s.height;
+ info.codecName = s.codec;
+ info.videoAspectRatio = s.aspect_ratio;
+ info.stereoMode = s.stereo_mode;
+ info.flags = s.flags;
+ info.hdrType = s.hdrType;
+}
+
+int CVideoPlayer::GetVideoStreamCount() const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_selectionStreams.CountType(STREAM_VIDEO);
+}
+
+int CVideoPlayer::GetVideoStream() const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_videoIndex;
+}
+
+void CVideoPlayer::SetVideoStream(int iStream)
+{
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetVideoStream>(iStream));
+ m_processInfo->GetVideoSettingsLocked().SetVideoStream(iStream);
+ SynchronizeDemuxer();
+}
+
+void CVideoPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+
+ if (index == CURRENT_STREAM)
+ index = m_content.m_audioIndex;
+
+ if (index < 0 || index > GetAudioStreamCount() - 1)
+ {
+ info.valid = false;
+ return;
+ }
+
+ const SelectionStream& s = m_content.m_selectionStreams.Get(STREAM_AUDIO, index);
+ info.language = s.language;
+ info.name = s.name;
+
+ if (s.type == STREAM_NONE)
+ info.name += " (Invalid)";
+
+ info.valid = true;
+ info.bitrate = s.bitrate;
+ info.channels = s.channels;
+ info.codecName = s.codec;
+ info.flags = s.flags;
+}
+
+int CVideoPlayer::GetAudioStreamCount() const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_selectionStreams.CountType(STREAM_AUDIO);
+}
+
+int CVideoPlayer::GetAudioStream()
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_audioIndex;
+}
+
+void CVideoPlayer::SetAudioStream(int iStream)
+{
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetAudioStream>(iStream));
+ m_processInfo->GetVideoSettingsLocked().SetAudioStream(iStream);
+ SynchronizeDemuxer();
+}
+
+void CVideoPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+
+ if (index == CURRENT_STREAM)
+ index = m_content.m_subtitleIndex;
+
+ if (index < 0 || index > GetSubtitleCount() - 1)
+ {
+ info.valid = false;
+ info.language.clear();
+ info.flags = StreamFlags::FLAG_NONE;
+ return;
+ }
+
+ const SelectionStream& s = m_content.m_selectionStreams.Get(STREAM_SUBTITLE, index);
+ info.name = s.name;
+
+ if (s.type == STREAM_NONE)
+ info.name += "(Invalid)";
+
+ info.language = s.language;
+ info.flags = s.flags;
+}
+
+void CVideoPlayer::SetSubtitle(int iStream)
+{
+ m_messenger.Put(std::make_shared<CDVDMsgPlayerSetSubtitleStream>(iStream));
+ m_processInfo->GetVideoSettingsLocked().SetSubtitleStream(iStream);
+}
+
+int CVideoPlayer::GetSubtitleCount() const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_selectionStreams.CountType(STREAM_SUBTITLE);
+}
+
+int CVideoPlayer::GetSubtitle()
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_content.m_subtitleIndex;
+}
+
+int CVideoPlayer::GetPrograms(std::vector<ProgramInfo>& programs)
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ programs = m_programs;
+ return programs.size();
+}
+
+void CVideoPlayer::SetProgram(int progId)
+{
+ m_messenger.Put(std::make_shared<CDVDMsgInt>(CDVDMsg::PLAYER_SET_PROGRAM, progId));
+}
+
+int CVideoPlayer::GetProgramsCount() const
+{
+ std::unique_lock<CCriticalSection> lock(m_content.m_section);
+ return m_programs.size();
+}
+
+void CVideoPlayer::SetUpdateStreamDetails()
+{
+ m_messenger.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_SET_UPDATE_STREAM_DETAILS));
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h
new file mode 100644
index 0000000..e40fb7a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayer.h
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDClock.h"
+#include "DVDMessageQueue.h"
+#include "Edl.h"
+#include "FileItem.h"
+#include "IVideoPlayer.h"
+#include "VideoPlayerAudioID3.h"
+#include "VideoPlayerRadioRDS.h"
+#include "VideoPlayerSubtitle.h"
+#include "VideoPlayerTeletext.h"
+#include "cores/IPlayer.h"
+#include "cores/MenuType.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "guilib/DispResource.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+struct SPlayerState
+{
+ SPlayerState() { Clear(); }
+ void Clear()
+ {
+ timestamp = 0;
+ time = 0;
+ startTime = 0;
+ timeMin = 0;
+ timeMax = 0;
+ time_offset = 0;
+ dts = DVD_NOPTS_VALUE;
+ player_state = "";
+ isInMenu = false;
+ menuType = MenuType::NONE;
+ chapter = 0;
+ chapters.clear();
+ canpause = false;
+ canseek = false;
+ cantempo = false;
+ caching = false;
+ cache_bytes = 0;
+ cache_level = 0.0;
+ cache_delay = 0.0;
+ cache_offset = 0.0;
+ lastSeek = 0;
+ streamsReady = false;
+ }
+
+ double timestamp; // last time of update
+ double lastSeek; // time of last seek
+ double time_offset; // difference between time and pts
+
+ double time; // current playback time
+ double timeMax;
+ double timeMin;
+ time_t startTime;
+ double dts; // last known dts
+
+ std::string player_state; // full player state
+ bool isInMenu;
+ MenuType menuType;
+ bool streamsReady;
+
+ int chapter; // current chapter
+ std::vector<std::pair<std::string, int64_t>> chapters; // name and position for chapters
+
+ bool canpause; // pvr: can pause the current playing item
+ bool canseek; // pvr: can seek in the current playing item
+ bool cantempo;
+ bool caching;
+
+ int64_t cache_bytes; // number of bytes current's cached
+ double cache_level; // current estimated required cache level
+ double cache_delay; // time until cache is expected to reach estimated level
+ double cache_offset; // percentage of file ahead of current position
+};
+
+class CDVDInputStream;
+
+class CDVDDemux;
+class CDemuxStreamVideo;
+class CDemuxStreamAudio;
+class CStreamInfo;
+class CDVDDemuxCC;
+class CVideoPlayer;
+
+#define DVDSTATE_NORMAL 0x00000001 // normal dvd state
+#define DVDSTATE_STILL 0x00000002 // currently displaying a still frame
+#define DVDSTATE_WAIT 0x00000003 // waiting for demuxer read error
+#define DVDSTATE_SEEK 0x00000004 // we are finishing a seek request
+
+class CCurrentStream
+{
+public:
+ int64_t demuxerId; // demuxer's id of current playing stream
+ int id; // id of current playing stream
+ int source;
+ double dts; // last dts from demuxer, used to find discontinuities
+ double dur; // last frame expected duration
+ int dispTime; // display time from input stream
+ CDVDStreamInfo hint; // stream hints, used to notice stream changes
+ void* stream; // pointer or integer, identifying stream playing. if it changes stream changed
+ int changes; // remembered counter from stream to track codec changes
+ bool inited;
+ unsigned int packets;
+ IDVDStreamPlayer::ESyncState syncState;
+ double starttime;
+ double cachetime;
+ double cachetotal;
+ const StreamType type;
+ const int player;
+ // stuff to handle starting after seek
+ double startpts;
+ double lastdts;
+
+ enum
+ {
+ AV_SYNC_NONE,
+ AV_SYNC_CHECK,
+ AV_SYNC_CONT,
+ AV_SYNC_FORCE
+ } avsync;
+
+ CCurrentStream(StreamType t, int i)
+ : type(t)
+ , player(i)
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ id = -1;
+ demuxerId = -1;
+ source = STREAM_SOURCE_NONE;
+ dts = DVD_NOPTS_VALUE;
+ dur = DVD_NOPTS_VALUE;
+ hint.Clear();
+ stream = NULL;
+ changes = 0;
+ inited = false;
+ packets = 0;
+ syncState = IDVDStreamPlayer::SYNC_STARTING;
+ starttime = DVD_NOPTS_VALUE;
+ startpts = DVD_NOPTS_VALUE;
+ lastdts = DVD_NOPTS_VALUE;
+ avsync = AV_SYNC_FORCE;
+ }
+
+ double dts_end()
+ {
+ if(dts == DVD_NOPTS_VALUE)
+ return DVD_NOPTS_VALUE;
+ if(dur == DVD_NOPTS_VALUE)
+ return dts;
+ return dts + dur;
+ }
+};
+
+//------------------------------------------------------------------------------
+// selection streams
+//------------------------------------------------------------------------------
+struct SelectionStream
+{
+ StreamType type = STREAM_NONE;
+ int type_index = 0;
+ std::string filename;
+ std::string filename2; // for vobsub subtitles, 2 files are necessary (idx/sub)
+ std::string language;
+ std::string name;
+ StreamFlags flags = StreamFlags::FLAG_NONE;
+ int source = 0;
+ int id = 0;
+ int64_t demuxerId = -1;
+ std::string codec;
+ int channels = 0;
+ int bitrate = 0;
+ int width = 0;
+ int height = 0;
+ CRect SrcRect;
+ CRect DestRect;
+ CRect VideoRect;
+ std::string stereo_mode;
+ float aspect_ratio = 0.0f;
+ StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE;
+};
+
+class CSelectionStreams
+{
+public:
+ CSelectionStreams() = default;
+
+ int TypeIndexOf(StreamType type, int source, int64_t demuxerId, int id) const;
+ int CountTypeOfSource(StreamType type, StreamSource source) const;
+ int CountType(StreamType type) const;
+ SelectionStream& Get(StreamType type, int index);
+ const SelectionStream& Get(StreamType type, int index) const;
+ bool Get(StreamType type, StreamFlags flag, SelectionStream& out);
+ void Clear(StreamType type, StreamSource source);
+ int Source(StreamSource source, const std::string& filename);
+ void Update(SelectionStream& s);
+ void Update(const std::shared_ptr<CDVDInputStream>& input, CDVDDemux* demuxer);
+ void Update(const std::shared_ptr<CDVDInputStream>& input,
+ CDVDDemux* demuxer,
+ const std::string& filename2);
+
+ std::vector<SelectionStream> Get(StreamType type);
+ template<typename Compare> std::vector<SelectionStream> Get(StreamType type, Compare compare)
+ {
+ std::vector<SelectionStream> streams = Get(type);
+ std::stable_sort(streams.begin(), streams.end(), compare);
+ return streams;
+ }
+
+ std::vector<SelectionStream> m_Streams;
+
+protected:
+ SelectionStream m_invalid;
+};
+
+//------------------------------------------------------------------------------
+// main class
+//------------------------------------------------------------------------------
+
+class CProcessInfo;
+class CJobQueue;
+
+class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer,
+ public IDispResource, public IRenderLoop, public IRenderMsg
+{
+public:
+ explicit CVideoPlayer(IPlayerCallback& callback);
+ ~CVideoPlayer() override;
+ bool OpenFile(const CFileItem& file, const CPlayerOptions &options) override;
+ bool CloseFile(bool reopen = false) override;
+ bool IsPlaying() const override;
+ void Pause() override;
+ bool HasVideo() const override;
+ bool HasAudio() const override;
+ bool HasRDS() const override;
+ bool HasID3() const override;
+ bool IsPassthrough() const override;
+ bool CanSeek() const override;
+ void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) override;
+ bool SeekScene(bool bPlus = true) override;
+ void SeekPercentage(float iPercent) override;
+ float GetCachePercentage() const override;
+
+ void SetDynamicRangeCompression(long drc) override;
+ bool CanPause() const override;
+ void SetAVDelay(float fValue = 0.0f) override;
+ float GetAVDelay() override;
+ bool IsInMenu() const override;
+
+ /*!
+ * \brief Get the supported menu type
+ * \return The supported menu type
+ */
+ MenuType GetSupportedMenuType() const override;
+
+ void SetSubTitleDelay(float fValue = 0.0f) override;
+ float GetSubTitleDelay() override;
+ int GetSubtitleCount() const override;
+ int GetSubtitle() override;
+ void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const override;
+ void SetSubtitle(int iStream) override;
+ bool GetSubtitleVisible() const override;
+ void SetSubtitleVisible(bool bVisible) override;
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ void SetSubtitleVerticalPosition(const int value, bool save) override;
+
+ void AddSubtitle(const std::string& strSubPath) override;
+
+ int GetAudioStreamCount() const override;
+ int GetAudioStream() override;
+ void SetAudioStream(int iStream) override;
+
+ int GetVideoStream() const override;
+ int GetVideoStreamCount() const override;
+ void GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const override;
+ void SetVideoStream(int iStream) override;
+
+ int GetPrograms(std::vector<ProgramInfo>& programs) override;
+ void SetProgram(int progId) override;
+ int GetProgramsCount() const override;
+
+ std::shared_ptr<TextCacheStruct_t> GetTeletextCache() override;
+ bool HasTeletextCache() const override;
+ void LoadPage(int p, int sp, unsigned char* buffer) override;
+
+ int GetChapterCount() const override;
+ int GetChapter() const override;
+ void GetChapterName(std::string& strChapterName, int chapterIdx = -1) const override;
+ int64_t GetChapterPos(int chapterIdx = -1) const override;
+ int SeekChapter(int iChapter) override;
+
+ void SeekTime(int64_t iTime) override;
+ bool SeekTimeRelative(int64_t iTime) override;
+ void SetSpeed(float speed) override;
+ void SetTempo(float tempo) override;
+ bool SupportsTempo() const override;
+ void FrameAdvance(int frames) override;
+ bool OnAction(const CAction &action) override;
+
+ void GetAudioStreamInfo(int index, AudioStreamInfo& info) const override;
+
+ std::string GetPlayerState() override;
+ bool SetPlayerState(const std::string& state) override;
+
+ void FrameMove() override;
+ void Render(bool clear, uint32_t alpha = 255, bool gui = true) override;
+ void FlushRenderer() override;
+ void SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch) override;
+ float GetRenderAspectRatio() const override;
+ void TriggerUpdateResolution() override;
+ bool IsRenderingVideo() const override;
+ bool Supports(EINTERLACEMETHOD method) const override;
+ EINTERLACEMETHOD GetDeinterlacingMethodDefault() const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+ bool Supports(ERENDERFEATURE feature) const override;
+
+ unsigned int RenderCaptureAlloc() override;
+ void RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags) override;
+ void RenderCaptureRelease(unsigned int captureId) override;
+ bool RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size) override;
+
+ // IDispResource interface
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+
+ bool IsCaching() const override;
+ int GetCacheLevel() const override;
+
+ int OnDiscNavResult(void* pData, int iMessage) override;
+ void GetVideoResolution(unsigned int &width, unsigned int &height) override;
+
+ CVideoSettings GetVideoSettings() const override;
+ void SetVideoSettings(CVideoSettings& settings) override;
+
+ void SetUpdateStreamDetails();
+
+protected:
+ friend class CSelectionStreams;
+
+ void OnStartup() override;
+ void OnExit() override;
+ void Process() override;
+ void VideoParamsChange() override;
+ void GetDebugInfo(std::string &audio, std::string &video, std::string &general) override;
+ void UpdateClockSync(bool enabled) override;
+ void UpdateRenderInfo(CRenderInfo &info) override;
+ void UpdateRenderBuffers(int queued, int discard, int free) override;
+ void UpdateGuiRender(bool gui) override;
+ void UpdateVideoRender(bool video) override;
+
+ void CreatePlayers();
+ void DestroyPlayers();
+
+ void Prepare();
+ bool OpenStream(CCurrentStream& current, int64_t demuxerId, int iStream, int source, bool reset = true);
+ bool OpenAudioStream(CDVDStreamInfo& hint, bool reset = true);
+ bool OpenVideoStream(CDVDStreamInfo& hint, bool reset = true);
+ bool OpenSubtitleStream(const CDVDStreamInfo& hint);
+ bool OpenTeletextStream(CDVDStreamInfo& hint);
+ bool OpenRadioRDSStream(CDVDStreamInfo& hint);
+ bool OpenAudioID3Stream(CDVDStreamInfo& hint);
+
+ /** \brief Switches forced subtitles to forced subtitles matching the language of the current audio track.
+ * If these are not available, subtitles are disabled.
+ */
+ void AdaptForcedSubtitles();
+ bool CloseStream(CCurrentStream& current, bool bWaitForBuffers);
+
+ bool CheckIsCurrent(const CCurrentStream& current, CDemuxStream* stream, DemuxPacket* pkg);
+ void ProcessPacket(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessVideoData(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessSubData(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessTeletextData(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessRadioRDSData(CDemuxStream* pStream, DemuxPacket* pPacket);
+ void ProcessAudioID3Data(CDemuxStream* pStream, DemuxPacket* pPacket);
+
+ int AddSubtitleFile(const std::string& filename, const std::string& subfilename = "");
+ void SetSubtitleVisibleInternal(bool bVisible);
+
+ /**
+ * one of the DVD_PLAYSPEED defines
+ */
+ void SetPlaySpeed(int iSpeed);
+
+ enum ECacheState
+ {
+ CACHESTATE_DONE = 0,
+ CACHESTATE_FULL, // player is filling up the demux queue
+ CACHESTATE_INIT, // player is waiting for first packet of each stream
+ CACHESTATE_PLAY, // player is waiting for players to not be stalled
+ CACHESTATE_FLUSH, // temporary state player will choose startup between init or full
+ };
+
+ void SetCaching(ECacheState state);
+
+ double GetQueueTime();
+ bool GetCachingTimes(double& play_left, double& cache_left, double& file_offset);
+
+ void FlushBuffers(double pts, bool accurate, bool sync);
+
+ void HandleMessages();
+ void HandlePlaySpeed();
+ bool IsInMenuInternal() const;
+ void SynchronizeDemuxer();
+ void CheckAutoSceneSkip();
+ bool CheckContinuity(CCurrentStream& current, DemuxPacket* pPacket);
+ bool CheckSceneSkip(const CCurrentStream& current);
+ bool CheckPlayerInit(CCurrentStream& current);
+ void UpdateCorrection(DemuxPacket* pkt, double correction);
+ void UpdateTimestamps(CCurrentStream& current, DemuxPacket* pPacket);
+ IDVDStreamPlayer* GetStreamPlayer(unsigned int player);
+ void SendPlayerMessage(std::shared_ptr<CDVDMsg> pMsg, unsigned int target);
+
+ bool ReadPacket(DemuxPacket*& packet, CDemuxStream*& stream);
+ bool IsValidStream(const CCurrentStream& stream);
+ bool IsBetterStream(const CCurrentStream& current, CDemuxStream* stream);
+ void CheckBetterStream(CCurrentStream& current, CDemuxStream* stream);
+ void CheckStreamChanges(CCurrentStream& current, CDemuxStream* stream);
+
+ bool OpenInputStream();
+ bool OpenDemuxStream();
+ void CloseDemuxer();
+ void OpenDefaultStreams(bool reset = true);
+
+ void UpdatePlayState(double timeout);
+ void GetGeneralInfo(std::string& strVideoInfo);
+ int64_t GetUpdatedTime();
+ int64_t GetTime();
+ float GetPercentage();
+
+ void UpdateContent();
+ void UpdateContentState();
+
+ void UpdateFileItemStreamDetails(CFileItem& item);
+
+ bool m_players_created;
+
+ CFileItem m_item;
+ CPlayerOptions m_playerOptions;
+ bool m_bAbortRequest;
+ bool m_error;
+ bool m_bCloseRequest;
+
+ ECacheState m_caching;
+ XbmcThreads::EndTime<> m_cachingTimer;
+
+ std::unique_ptr<CProcessInfo> m_processInfo;
+
+ CCurrentStream m_CurrentAudio;
+ CCurrentStream m_CurrentVideo;
+ CCurrentStream m_CurrentSubtitle;
+ CCurrentStream m_CurrentTeletext;
+ CCurrentStream m_CurrentRadioRDS;
+ CCurrentStream m_CurrentAudioID3;
+
+ CSelectionStreams m_SelectionStreams;
+ std::vector<ProgramInfo> m_programs;
+
+ struct SContent
+ {
+ mutable CCriticalSection m_section;
+ CSelectionStreams m_selectionStreams;
+ std::vector<ProgramInfo> m_programs;
+ int m_videoIndex{-1};
+ int m_audioIndex{-1};
+ int m_subtitleIndex{-1};
+ } m_content;
+
+ int m_playSpeed;
+ int m_streamPlayerSpeed;
+ int m_demuxerSpeed = DVD_PLAYSPEED_NORMAL;
+ struct SSpeedState
+ {
+ double lastpts; // holds last display pts during ff/rw operations
+ int64_t lasttime;
+ double lastseekpts;
+ double lastabstime;
+ } m_SpeedState;
+
+ double m_offset_pts;
+
+ CDVDMessageQueue m_messenger;
+ std::unique_ptr<CJobQueue> m_outboundEvents;
+
+ IDVDStreamPlayerVideo *m_VideoPlayerVideo;
+ IDVDStreamPlayerAudio *m_VideoPlayerAudio;
+ CVideoPlayerSubtitle *m_VideoPlayerSubtitle;
+ CDVDTeletextData *m_VideoPlayerTeletext;
+ CDVDRadioRDSData *m_VideoPlayerRadioRDS;
+ std::unique_ptr<CVideoPlayerAudioID3> m_VideoPlayerAudioID3;
+
+ CDVDClock m_clock;
+ CDVDOverlayContainer m_overlayContainer;
+
+ std::shared_ptr<CDVDInputStream> m_pInputStream;
+ std::unique_ptr<CDVDDemux> m_pDemuxer;
+ std::shared_ptr<CDVDDemux> m_pSubtitleDemuxer;
+ std::unordered_map<int64_t, std::shared_ptr<CDVDDemux>> m_subtitleDemuxerMap;
+ std::unique_ptr<CDVDDemuxCC> m_pCCDemuxer;
+
+ CRenderManager m_renderManager;
+
+ struct SDVDInfo
+ {
+ void Clear()
+ {
+ state = DVDSTATE_NORMAL;
+ iSelectedSPUStream = -1;
+ iSelectedAudioStream = -1;
+ iSelectedVideoStream = -1;
+ iDVDStillTime = std::chrono::milliseconds::zero();
+ iDVDStillStartTime = {};
+ syncClock = false;
+ }
+
+ int state; // current dvdstate
+ bool syncClock;
+ std::chrono::milliseconds
+ iDVDStillTime; // total time in ticks we should display the still before continuing
+ std::chrono::time_point<std::chrono::steady_clock>
+ iDVDStillStartTime; // time in ticks when we started the still
+ int iSelectedSPUStream; // mpeg stream id, or -1 if disabled
+ int iSelectedAudioStream; // mpeg stream id, or -1 if disabled
+ int iSelectedVideoStream; // mpeg stream id or angle, -1 if disabled
+ } m_dvd;
+
+ SPlayerState m_State;
+ mutable CCriticalSection m_StateSection;
+ XbmcThreads::EndTime<> m_syncTimer;
+
+ CEdl m_Edl;
+ bool m_SkipCommercials;
+
+ bool m_HasVideo;
+ bool m_HasAudio;
+
+ bool m_UpdateStreamDetails;
+
+ std::atomic<bool> m_displayLost;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp
new file mode 100644
index 0000000..6ab5b5d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerAudio.h"
+
+#include "DVDCodecs/Audio/DVDAudioCodec.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "ServiceBroker.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#ifdef TARGET_RASPBERRY_PI
+#include "platform/linux/RBP.h"
+#endif
+
+#include <sstream>
+#include <iomanip>
+#include <math.h>
+
+using namespace std::chrono_literals;
+
+class CDVDMsgAudioCodecChange : public CDVDMsg
+{
+public:
+ CDVDMsgAudioCodecChange(const CDVDStreamInfo& hints, std::unique_ptr<CDVDAudioCodec> codec)
+ : CDVDMsg(GENERAL_STREAMCHANGE), m_codec(std::move(codec)), m_hints(hints)
+ {}
+ ~CDVDMsgAudioCodecChange() override = default;
+
+ std::unique_ptr<CDVDAudioCodec> m_codec;
+ CDVDStreamInfo m_hints;
+};
+
+
+CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent, CProcessInfo &processInfo)
+: CThread("VideoPlayerAudio"), IDVDStreamPlayerAudio(processInfo)
+, m_messageQueue("audio")
+, m_messageParent(parent)
+, m_audioSink(pClock)
+{
+ m_pClock = pClock;
+ m_audioClock = 0;
+ m_speed = DVD_PLAYSPEED_NORMAL;
+ m_stalled = true;
+ m_paused = false;
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_synctype = SYNC_DISCON;
+ m_prevsynctype = -1;
+ m_prevskipped = false;
+ m_maxspeedadjust = 0.0;
+
+ m_messageQueue.SetMaxDataSize(6 * 1024 * 1024);
+ m_messageQueue.SetMaxTimeSize(8.0);
+ m_disconAdjustTimeMs = processInfo.GetMaxPassthroughOffSyncDuration();
+}
+
+CVideoPlayerAudio::~CVideoPlayerAudio()
+{
+ StopThread();
+
+ // close the stream, and don't wait for the audio to be finished
+ // CloseStream(true);
+}
+
+bool CVideoPlayerAudio::OpenStream(CDVDStreamInfo hints)
+{
+ CLog::Log(LOGINFO, "Finding audio codec for: {}", hints.codec);
+ bool allowpassthrough = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK);
+ if (m_processInfo.IsRealtimeStream())
+ allowpassthrough = false;
+
+ CAEStreamInfo::DataType streamType =
+ m_audioSink.GetPassthroughStreamType(hints.codec, hints.samplerate, hints.profile);
+ std::unique_ptr<CDVDAudioCodec> codec = CDVDFactoryCodec::CreateAudioCodec(
+ hints, m_processInfo, allowpassthrough, m_processInfo.AllowDTSHDDecode(), streamType);
+ if(!codec)
+ {
+ CLog::Log(LOGERROR, "Unsupported audio codec");
+ return false;
+ }
+
+ if(m_messageQueue.IsInited())
+ m_messageQueue.Put(std::make_shared<CDVDMsgAudioCodecChange>(hints, std::move(codec)), 0);
+ else
+ {
+ OpenStream(hints, std::move(codec));
+ m_messageQueue.Init();
+ CLog::Log(LOGINFO, "Creating audio thread");
+ Create();
+ }
+ return true;
+}
+
+void CVideoPlayerAudio::OpenStream(CDVDStreamInfo& hints, std::unique_ptr<CDVDAudioCodec> codec)
+{
+ m_pAudioCodec = std::move(codec);
+
+ m_processInfo.ResetAudioCodecInfo();
+
+ /* store our stream hints */
+ m_streaminfo = hints;
+
+ /* update codec information from what codec gave out, if any */
+ int channelsFromCodec = m_pAudioCodec->GetFormat().m_channelLayout.Count();
+ int samplerateFromCodec = m_pAudioCodec->GetFormat().m_sampleRate;
+
+ if (channelsFromCodec > 0)
+ m_streaminfo.channels = channelsFromCodec;
+ if (samplerateFromCodec > 0)
+ m_streaminfo.samplerate = samplerateFromCodec;
+
+ /* check if we only just got sample rate, in which case the previous call
+ * to CreateAudioCodec() couldn't have started passthrough */
+ if (hints.samplerate != m_streaminfo.samplerate)
+ SwitchCodecIfNeeded();
+
+ m_audioClock = 0;
+ m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0;
+
+ m_prevsynctype = -1;
+ m_synctype = SYNC_DISCON;
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK))
+ m_synctype = SYNC_RESAMPLE;
+ else if (m_processInfo.IsRealtimeStream())
+ m_synctype = SYNC_RESAMPLE;
+
+ if (m_synctype == SYNC_DISCON)
+ CLog::LogF(LOGINFO, "Allowing max Out-Of-Sync Value of {} ms", m_disconAdjustTimeMs);
+
+ m_prevskipped = false;
+
+ m_maxspeedadjust = 5.0;
+
+ m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_AVCHANGE));
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+}
+
+void CVideoPlayerAudio::CloseStream(bool bWaitForBuffers)
+{
+ bool bWait = bWaitForBuffers && m_speed > 0 && !CServiceBroker::GetActiveAE()->IsSuspended();
+
+ // wait until buffers are empty
+ if (bWait)
+ m_messageQueue.WaitUntilEmpty();
+
+ // send abort message to the audio queue
+ m_messageQueue.Abort();
+
+ CLog::Log(LOGINFO, "Waiting for audio thread to exit");
+
+ // shut down the adio_decode thread and wait for it
+ StopThread(); // will set this->m_bStop to true
+
+ // destroy audio device
+ CLog::Log(LOGINFO, "Closing audio device");
+ if (bWait)
+ {
+ m_bStop = false;
+ m_audioSink.Drain();
+ m_bStop = true;
+ }
+ else
+ {
+ m_audioSink.Flush();
+ }
+
+ m_audioSink.Destroy(true);
+
+ // uninit queue
+ m_messageQueue.End();
+
+ CLog::Log(LOGINFO, "Deleting audio codec");
+ if (m_pAudioCodec)
+ {
+ m_pAudioCodec->Dispose();
+ m_pAudioCodec.reset();
+ }
+}
+
+void CVideoPlayerAudio::OnStartup()
+{
+}
+
+void CVideoPlayerAudio::UpdatePlayerInfo()
+{
+ std::ostringstream s;
+ s << "aq:" << std::setw(2) << std::min(99,m_messageQueue.GetLevel()) << "%";
+ s << ", Kb/s:" << std::fixed << std::setprecision(2) << m_audioStats.GetBitrate() / 1024.0;
+
+ // print a/v discontinuity adjustments counter when audio is not resampled (passthrough mode)
+ if (m_synctype == SYNC_DISCON)
+ s << ", a/v corrections (" << m_disconAdjustTimeMs << "ms): " << m_disconAdjustCounter;
+
+ //print the inverse of the resample ratio, since that makes more sense
+ //if the resample ratio is 0.5, then we're playing twice as fast
+ else if (m_synctype == SYNC_RESAMPLE)
+ s << ", rr:" << std::fixed << std::setprecision(5) << 1.0 / m_audioSink.GetResampleRatio();
+
+ SInfo info;
+ info.info = s.str();
+ info.pts = m_audioSink.GetPlayingPts();
+ info.passthrough = m_pAudioCodec && m_pAudioCodec->NeedPassthrough();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_info_section);
+ m_info = info;
+ }
+}
+
+void CVideoPlayerAudio::Process()
+{
+ CLog::Log(LOGINFO, "running thread: CVideoPlayerAudio::Process()");
+
+ DVDAudioFrame audioframe;
+ audioframe.nb_frames = 0;
+ audioframe.framesOut = 0;
+ m_audioStats.Start();
+ m_disconAdjustCounter = 0;
+
+ // Only enable "learning" if advancedsettings m_maxPassthroughOffSyncDuration
+ // not exists or has it's default 10 ms value, otherwise use advancedsettings value
+ if (m_disconAdjustTimeMs == 10)
+ {
+ m_disconTimer.Set(30s);
+ m_disconLearning = true;
+ }
+ else
+ {
+ m_disconLearning = false;
+ }
+
+ bool onlyPrioMsgs = false;
+
+ while (!m_bStop)
+ {
+ std::shared_ptr<CDVDMsg> pMsg;
+ int timeout = (int)(1000 * m_audioSink.GetCacheTime());
+
+ // read next packet and return -1 on error
+ int priority = 1;
+ //Do we want a new audio frame?
+ if (m_syncState == IDVDStreamPlayer::SYNC_STARTING || /* when not started */
+ m_processInfo.IsTempoAllowed(static_cast<float>(m_speed)/DVD_PLAYSPEED_NORMAL) ||
+ m_speed < DVD_PLAYSPEED_PAUSE || /* when rewinding */
+ (m_speed > DVD_PLAYSPEED_NORMAL && m_audioClock < m_pClock->GetClock())) /* when behind clock in ff */
+ priority = 0;
+
+ if (m_syncState == IDVDStreamPlayer::SYNC_WAITSYNC)
+ priority = 1;
+
+ if (m_paused)
+ priority = 1;
+
+ if (onlyPrioMsgs)
+ {
+ priority = 1;
+ timeout = 0;
+ }
+
+ MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, timeout, priority);
+
+ onlyPrioMsgs = false;
+
+ if (MSGQ_IS_ERROR(ret))
+ {
+ if (!m_messageQueue.ReceivedAbortRequest())
+ CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret);
+
+ break;
+ }
+ else if (ret == MSGQ_TIMEOUT)
+ {
+ if (ProcessDecoderOutput(audioframe))
+ {
+ onlyPrioMsgs = true;
+ continue;
+ }
+
+ // if we only wanted priority messages, this isn't a stall
+ if (priority)
+ continue;
+
+ if (m_processInfo.IsTempoAllowed(static_cast<float>(m_speed)/DVD_PLAYSPEED_NORMAL) &&
+ !m_stalled && m_syncState == IDVDStreamPlayer::SYNC_INSYNC)
+ {
+ // while AE sync is active, we still have time to fill buffers
+ if (m_syncTimer.IsTimePast())
+ {
+ CLog::Log(LOGINFO, "CVideoPlayerAudio::Process - stream stalled");
+ m_stalled = true;
+ }
+ }
+ if (timeout == 0)
+ CThread::Sleep(10ms);
+
+ continue;
+ }
+
+ // handle messages
+ if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE))
+ {
+ if (std::static_pointer_cast<CDVDMsgGeneralSynchronize>(pMsg)->Wait(100ms, SYNCSOURCE_AUDIO))
+ CLog::Log(LOGDEBUG, "CVideoPlayerAudio - CDVDMsg::GENERAL_SYNCHRONIZE");
+ else
+ m_messageQueue.Put(pMsg, 1); // push back as prio message, to process other prio messages
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC))
+ { //player asked us to set internal clock
+ double pts = std::static_pointer_cast<CDVDMsgDouble>(pMsg)->m_value;
+ CLog::Log(LOGDEBUG,
+ "CVideoPlayerAudio - CDVDMsg::GENERAL_RESYNC({:f}), level: {}, cache: {:f}", pts,
+ m_messageQueue.GetLevel(), m_audioSink.GetDelay());
+
+ double delay = m_audioSink.GetDelay();
+ if (pts > m_audioClock - delay + 0.5 * DVD_TIME_BASE)
+ {
+ m_audioSink.Flush();
+ }
+ m_audioClock = pts + delay;
+ if (m_speed != DVD_PLAYSPEED_PAUSE)
+ m_audioSink.Resume();
+ m_syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_syncTimer.Set(3000ms);
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_RESET))
+ {
+ if (m_pAudioCodec)
+ m_pAudioCodec->Reset();
+ m_audioSink.Flush();
+ m_stalled = true;
+ m_audioClock = 0;
+ audioframe.nb_frames = 0;
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH))
+ {
+ bool sync = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value;
+ m_audioSink.Flush();
+ m_stalled = true;
+ m_audioClock = 0;
+ audioframe.nb_frames = 0;
+
+ if (sync)
+ {
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_audioSink.Pause();
+ }
+
+ if (m_pAudioCodec)
+ m_pAudioCodec->Reset();
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_EOF))
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerAudio - CDVDMsg::GENERAL_EOF");
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ double speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+
+ if (m_processInfo.IsTempoAllowed(static_cast<float>(speed)/DVD_PLAYSPEED_NORMAL))
+ {
+ if (speed != m_speed)
+ {
+ if (m_syncState == IDVDStreamPlayer::SYNC_INSYNC)
+ {
+ m_audioSink.Resume();
+ m_stalled = false;
+ }
+ }
+ }
+ else
+ {
+ m_audioSink.Pause();
+ }
+ m_speed = (int)speed;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE))
+ {
+ auto msg = std::static_pointer_cast<CDVDMsgAudioCodecChange>(pMsg);
+ OpenStream(msg->m_hints, std::move(msg->m_codec));
+ msg->m_codec = NULL;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_PAUSE))
+ {
+ m_paused = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value;
+ CLog::Log(LOGDEBUG, "CVideoPlayerAudio - CDVDMsg::GENERAL_PAUSE: {}", m_paused);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_REQUEST_STATE))
+ {
+ SStateMsg msg;
+ msg.player = VideoPlayer_AUDIO;
+ msg.syncState = m_syncState;
+ m_messageParent.Put(
+ std::make_shared<CDVDMsgType<SStateMsg>>(CDVDMsg::PLAYER_REPORT_STATE, msg));
+ }
+ else if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket();
+ bool bPacketDrop = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacketDrop();
+
+ if (bPacketDrop)
+ {
+ if (m_syncState != IDVDStreamPlayer::SYNC_STARTING)
+ {
+ m_audioSink.Drain();
+ m_audioSink.Flush();
+ audioframe.nb_frames = 0;
+ }
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ continue;
+ }
+
+ if (!m_processInfo.IsTempoAllowed(static_cast<float>(m_speed) / DVD_PLAYSPEED_NORMAL) &&
+ m_syncState == IDVDStreamPlayer::SYNC_INSYNC)
+ {
+ continue;
+ }
+
+ if (!m_pAudioCodec->AddData(*pPacket))
+ {
+ m_messageQueue.PutBack(pMsg);
+ onlyPrioMsgs = true;
+ continue;
+ }
+
+ m_audioStats.AddSampleBytes(pPacket->iSize);
+ UpdatePlayerInfo();
+
+ if (ProcessDecoderOutput(audioframe))
+ {
+ onlyPrioMsgs = true;
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_DISPLAY_RESET))
+ {
+ m_displayReset = true;
+ }
+ }
+}
+
+bool CVideoPlayerAudio::ProcessDecoderOutput(DVDAudioFrame &audioframe)
+{
+ if (audioframe.nb_frames <= audioframe.framesOut)
+ {
+ audioframe.hasDownmix = false;
+
+ m_pAudioCodec->GetData(audioframe);
+
+ if (audioframe.nb_frames == 0)
+ {
+ return false;
+ }
+
+ audioframe.hasTimestamp = true;
+ if (audioframe.pts == DVD_NOPTS_VALUE)
+ {
+ audioframe.pts = m_audioClock;
+ audioframe.hasTimestamp = false;
+ }
+ else
+ {
+ m_audioClock = audioframe.pts;
+ }
+
+ if (audioframe.format.m_sampleRate && m_streaminfo.samplerate != (int) audioframe.format.m_sampleRate)
+ {
+ // The sample rate has changed or we just got it for the first time
+ // for this stream. See if we should enable/disable passthrough due
+ // to it.
+ m_streaminfo.samplerate = audioframe.format.m_sampleRate;
+ if (SwitchCodecIfNeeded())
+ {
+ audioframe.nb_frames = 0;
+ return false;
+ }
+ }
+
+ // if stream switches to realtime, disable pass through
+ // or switch to resample
+ if (m_processInfo.IsRealtimeStream() && m_synctype != SYNC_RESAMPLE)
+ {
+ m_synctype = SYNC_RESAMPLE;
+ if (SwitchCodecIfNeeded())
+ {
+ audioframe.nb_frames = 0;
+ return false;
+ }
+ }
+
+ // Display reset event has occurred
+ // See if we should enable passthrough
+ if (m_displayReset)
+ {
+ if (SwitchCodecIfNeeded())
+ {
+ audioframe.nb_frames = 0;
+ return false;
+ }
+ }
+
+ // demuxer reads metatags that influence channel layout
+ if (m_streaminfo.codec == AV_CODEC_ID_FLAC && m_streaminfo.channellayout)
+ audioframe.format.m_channelLayout = CAEUtil::GetAEChannelLayout(m_streaminfo.channellayout);
+
+ // we have successfully decoded an audio frame, setup renderer to match
+ if (!m_audioSink.IsValidFormat(audioframe))
+ {
+ if (m_speed)
+ m_audioSink.Drain();
+
+ m_audioSink.Destroy(false);
+
+ if (!m_audioSink.Create(audioframe, m_streaminfo.codec, m_synctype == SYNC_RESAMPLE))
+ CLog::Log(LOGERROR, "{} - failed to create audio renderer", __FUNCTION__);
+
+ m_prevsynctype = -1;
+
+ if (m_syncState == IDVDStreamPlayer::SYNC_INSYNC)
+ m_audioSink.Resume();
+ }
+
+ m_audioSink.SetDynamicRangeCompression(
+ static_cast<long>(m_processInfo.GetVideoSettings().m_VolumeAmplification * 100));
+
+ SetSyncType(audioframe.passthrough);
+
+ // downmix
+ double clev = audioframe.hasDownmix ? audioframe.centerMixLevel : M_SQRT1_2;
+ double curDB = 20 * log10(clev);
+ audioframe.centerMixLevel = pow(10, (curDB + m_processInfo.GetVideoSettings().m_CenterMixLevel) / 20);
+ audioframe.hasDownmix = true;
+ }
+
+ if (m_synctype == SYNC_DISCON)
+ {
+ double syncerror = m_audioSink.GetSyncError();
+
+ if (m_disconLearning)
+ {
+ const double syncErr = std::abs(syncerror);
+ if (syncErr > DVD_MSEC_TO_TIME(m_disconAdjustTimeMs))
+ m_disconAdjustTimeMs = DVD_TIME_TO_MSEC(syncErr);
+ if (m_disconTimer.IsTimePast())
+ {
+ m_disconLearning = false;
+ m_disconAdjustTimeMs = (static_cast<double>(m_disconAdjustTimeMs) * 1.15) + 5.0;
+ if (m_disconAdjustTimeMs > 100) // sanity check
+ m_disconAdjustTimeMs = 100;
+
+ CLog::LogF(LOGINFO, "Changed max allowed Out-Of-Sync value to {} ms due self-learning",
+ m_disconAdjustTimeMs);
+ }
+ }
+ else if (std::abs(syncerror) > DVD_MSEC_TO_TIME(m_disconAdjustTimeMs))
+ {
+ double correction = m_pClock->ErrorAdjust(syncerror, "CVideoPlayerAudio::OutputPacket");
+ if (correction != 0)
+ {
+ m_audioSink.SetSyncErrorCorrection(-correction);
+ m_disconAdjustCounter++;
+ }
+ }
+ }
+
+ int framesOutput = m_audioSink.AddPackets(audioframe);
+
+ // guess next pts
+ m_audioClock += audioframe.duration * ((double)framesOutput / audioframe.nb_frames);
+
+ audioframe.framesOut += framesOutput;
+
+ // signal to our parent that we have initialized
+ if (m_syncState == IDVDStreamPlayer::SYNC_STARTING)
+ {
+ double cachetotal = m_audioSink.GetCacheTotal();
+ double cachetime = m_audioSink.GetCacheTime();
+ if (cachetime >= cachetotal * 0.75)
+ {
+ m_syncState = IDVDStreamPlayer::SYNC_WAITSYNC;
+ m_stalled = false;
+ SStartMsg msg;
+ msg.player = VideoPlayer_AUDIO;
+ msg.cachetotal = m_audioSink.GetMaxDelay() * DVD_TIME_BASE;
+ msg.cachetime = m_audioSink.GetDelay();
+ msg.timestamp = audioframe.hasTimestamp ? audioframe.pts : DVD_NOPTS_VALUE;
+ m_messageParent.Put(std::make_shared<CDVDMsgType<SStartMsg>>(CDVDMsg::PLAYER_STARTED, msg));
+
+ m_streaminfo.channels = audioframe.format.m_channelLayout.Count();
+ m_processInfo.SetAudioChannels(audioframe.format.m_channelLayout);
+ m_processInfo.SetAudioSampleRate(audioframe.format.m_sampleRate);
+ m_processInfo.SetAudioBitsPerSample(audioframe.bits_per_sample);
+ m_processInfo.SetAudioDecoderName(m_pAudioCodec->GetName());
+ m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_AVCHANGE));
+ }
+ }
+
+ return true;
+}
+
+void CVideoPlayerAudio::SetSyncType(bool passthrough)
+{
+ if (passthrough && m_synctype == SYNC_RESAMPLE)
+ m_synctype = SYNC_DISCON;
+
+ //if SetMaxSpeedAdjust returns false, it means no video is played and we need to use clock feedback
+ double maxspeedadjust = 0.0;
+ if (m_synctype == SYNC_RESAMPLE)
+ maxspeedadjust = m_maxspeedadjust;
+
+ m_pClock->SetMaxSpeedAdjust(maxspeedadjust);
+
+ if (m_synctype != m_prevsynctype)
+ {
+ const char *synctypes[] = {"clock feedback", "resample", "invalid"};
+ int synctype = (m_synctype >= 0 && m_synctype <= 1) ? m_synctype : 2;
+ CLog::Log(LOGDEBUG, "CVideoPlayerAudio:: synctype set to {}: {}", m_synctype,
+ synctypes[synctype]);
+ m_prevsynctype = m_synctype;
+ if (m_synctype == SYNC_RESAMPLE)
+ m_audioSink.SetResampleMode(1);
+ else
+ m_audioSink.SetResampleMode(0);
+ }
+}
+
+void CVideoPlayerAudio::OnExit()
+{
+#ifdef TARGET_WINDOWS
+ CoUninitialize();
+#endif
+
+ CLog::Log(LOGINFO, "thread end: CVideoPlayerAudio::OnExit()");
+}
+
+void CVideoPlayerAudio::SetSpeed(int speed)
+{
+ if(m_messageQueue.IsInited())
+ m_messageQueue.Put(std::make_shared<CDVDMsgInt>(CDVDMsg::PLAYER_SETSPEED, speed), 1);
+ else
+ m_speed = speed;
+}
+
+void CVideoPlayerAudio::Flush(bool sync)
+{
+ m_messageQueue.Flush();
+ m_messageQueue.Put(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_FLUSH, sync), 1);
+
+ m_audioSink.AbortAddPackets();
+}
+
+bool CVideoPlayerAudio::AcceptsData() const
+{
+ bool full = m_messageQueue.IsFull();
+ return !full;
+}
+
+bool CVideoPlayerAudio::SwitchCodecIfNeeded()
+{
+ if (m_displayReset)
+ CLog::Log(LOGINFO, "CVideoPlayerAudio: display reset occurred, checking for passthrough");
+ else
+ CLog::Log(LOGDEBUG, "CVideoPlayerAudio: stream props changed, checking for passthrough");
+
+ m_displayReset = false;
+
+ bool allowpassthrough = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK);
+ if (m_processInfo.IsRealtimeStream() || m_synctype == SYNC_RESAMPLE)
+ allowpassthrough = false;
+
+ CAEStreamInfo::DataType streamType = m_audioSink.GetPassthroughStreamType(
+ m_streaminfo.codec, m_streaminfo.samplerate, m_streaminfo.profile);
+ std::unique_ptr<CDVDAudioCodec> codec = CDVDFactoryCodec::CreateAudioCodec(
+ m_streaminfo, m_processInfo, allowpassthrough, m_processInfo.AllowDTSHDDecode(), streamType);
+
+ if (!codec || codec->NeedPassthrough() == m_pAudioCodec->NeedPassthrough())
+ {
+ // passthrough state has not changed
+ return false;
+ }
+
+ m_pAudioCodec = std::move(codec);
+
+ return true;
+}
+
+std::string CVideoPlayerAudio::GetPlayerInfo()
+{
+ std::unique_lock<CCriticalSection> lock(m_info_section);
+ return m_info.info;
+}
+
+int CVideoPlayerAudio::GetAudioChannels()
+{
+ return m_streaminfo.channels;
+}
+
+bool CVideoPlayerAudio::IsPassthrough() const
+{
+ std::unique_lock<CCriticalSection> lock(m_info_section);
+ return m_info.passthrough;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.h b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h
new file mode 100644
index 0000000..4ed12c5
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AudioSinkAE.h"
+#include "DVDClock.h"
+#include "DVDMessageQueue.h"
+#include "DVDStreamInfo.h"
+#include "IVideoPlayer.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "threads/SystemClock.h"
+#include "threads/Thread.h"
+#include "utils/BitstreamStats.h"
+
+#include <list>
+#include <mutex>
+#include <utility>
+
+
+class CVideoPlayer;
+class CDVDAudioCodec;
+class CDVDAudioCodec;
+
+class CVideoPlayerAudio : public CThread, public IDVDStreamPlayerAudio
+{
+public:
+ CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent, CProcessInfo &processInfo);
+ ~CVideoPlayerAudio() override;
+
+ bool OpenStream(CDVDStreamInfo hints) override;
+ void CloseStream(bool bWaitForBuffers) override;
+
+ void SetSpeed(int speed) override;
+ void Flush(bool sync) override;
+
+ // waits until all available data has been rendered
+ bool AcceptsData() const override;
+ bool HasData() const override { return m_messageQueue.GetDataSize() > 0; }
+ int GetLevel() const override { return m_messageQueue.GetLevel(); }
+ bool IsInited() const override { return m_messageQueue.IsInited(); }
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override
+ {
+ m_messageQueue.Put(pMsg, priority);
+ }
+ void FlushMessages() override { m_messageQueue.Flush(); }
+
+ void SetDynamicRangeCompression(long drc) override { m_audioSink.SetDynamicRangeCompression(drc); }
+ float GetDynamicRangeAmplification() const override { return 0.0f; }
+
+ std::string GetPlayerInfo() override;
+ int GetAudioChannels() override;
+
+ double GetCurrentPts() override
+ {
+ std::unique_lock<CCriticalSection> lock(m_info_section);
+ return m_info.pts;
+ }
+
+ bool IsStalled() const override { return m_stalled; }
+ bool IsPassthrough() const override;
+
+protected:
+
+ void OnStartup() override;
+ void OnExit() override;
+ void Process() override;
+
+ bool ProcessDecoderOutput(DVDAudioFrame &audioframe);
+ void UpdatePlayerInfo();
+ void OpenStream(CDVDStreamInfo& hints, std::unique_ptr<CDVDAudioCodec> codec);
+ //! Switch codec if needed. Called when the sample rate gotten from the
+ //! codec changes, in which case we may want to switch passthrough on/off.
+ bool SwitchCodecIfNeeded();
+ void SetSyncType(bool passthrough);
+
+ CDVDMessageQueue m_messageQueue;
+ CDVDMessageQueue& m_messageParent;
+
+ // holds stream information for current playing stream
+ CDVDStreamInfo m_streaminfo;
+
+ double m_audioClock;
+
+ CAudioSinkAE m_audioSink; // audio output device
+ CDVDClock* m_pClock; // dvd master clock
+ std::unique_ptr<CDVDAudioCodec> m_pAudioCodec; // audio codec
+ BitstreamStats m_audioStats;
+
+ int m_speed;
+ bool m_stalled;
+ bool m_paused;
+ IDVDStreamPlayer::ESyncState m_syncState;
+ XbmcThreads::EndTime<> m_syncTimer;
+
+ int m_synctype;
+ int m_prevsynctype;
+
+ bool m_prevskipped;
+ double m_maxspeedadjust;
+
+ struct SInfo
+ {
+ std::string info;
+ double pts = DVD_NOPTS_VALUE;
+ bool passthrough = false;
+ };
+
+ mutable CCriticalSection m_info_section;
+ SInfo m_info;
+
+ bool m_displayReset = false;
+ unsigned int m_disconAdjustTimeMs = 10; // maximum sync-off before adjusting
+ int m_disconAdjustCounter = 0;
+ XbmcThreads::EndTime<> m_disconTimer;
+ bool m_disconLearning = false;
+};
+
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.cpp
new file mode 100644
index 0000000..b4db5bf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerAudioID3.h"
+
+#include "DVDStreamInfo.h"
+#include "GUIInfoManager.h"
+#include "Interface/DemuxPacket.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "music/tags/MusicInfoTag.h"
+#include "utils/log.h"
+
+#include <taglib/attachedpictureframe.h>
+#include <taglib/commentsframe.h>
+#include <taglib/id3v1genres.h>
+#include <taglib/id3v2framefactory.h>
+#include <taglib/mpegfile.h>
+#include <taglib/tbytevectorstream.h>
+#include <taglib/textidentificationframe.h>
+
+using namespace TagLib;
+
+CVideoPlayerAudioID3::CVideoPlayerAudioID3(CProcessInfo& processInfo)
+ : IDVDStreamPlayer(processInfo), CThread("VideoPlayerAudioID3"), m_messageQueue("id3")
+{
+ CLog::Log(LOGDEBUG, "Audio ID3 tag processor - new {}", __FUNCTION__);
+}
+
+CVideoPlayerAudioID3::~CVideoPlayerAudioID3()
+{
+ CLog::Log(LOGDEBUG, "Audio ID3 tag processor - delete {}", __FUNCTION__);
+ StopThread();
+}
+
+bool CVideoPlayerAudioID3::CheckStream(const CDVDStreamInfo& hints)
+{
+ return hints.type == STREAM_AUDIO_ID3;
+}
+
+void CVideoPlayerAudioID3::Flush()
+{
+ if (m_messageQueue.IsInited())
+ {
+ m_messageQueue.Flush();
+ m_messageQueue.Put(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_FLUSH));
+ }
+}
+
+void CVideoPlayerAudioID3::WaitForBuffers()
+{
+ m_messageQueue.WaitUntilEmpty();
+}
+
+bool CVideoPlayerAudioID3::OpenStream(CDVDStreamInfo hints)
+{
+ CloseStream(true);
+ m_messageQueue.Init();
+
+ if (hints.type == STREAM_AUDIO_ID3)
+ {
+ Flush();
+ CLog::Log(LOGINFO, "Creating Audio ID3 tag processor data thread");
+ Create();
+ return true;
+ }
+
+ return false;
+}
+
+void CVideoPlayerAudioID3::CloseStream(bool bWaitForBuffers)
+{
+ m_messageQueue.Abort();
+
+ CLog::Log(LOGINFO, "Audio ID3 tag processor - waiting for data thread to exit");
+ StopThread();
+
+ m_messageQueue.End();
+}
+
+void CVideoPlayerAudioID3::SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority)
+{
+ if (m_messageQueue.IsInited())
+ m_messageQueue.Put(pMsg, priority);
+}
+
+void CVideoPlayerAudioID3::FlushMessages()
+{
+ m_messageQueue.Flush();
+}
+
+bool CVideoPlayerAudioID3::IsInited() const
+{
+ return true;
+}
+
+bool CVideoPlayerAudioID3::AcceptsData() const
+{
+ return !m_messageQueue.IsFull();
+}
+
+bool CVideoPlayerAudioID3::IsStalled() const
+{
+ return true;
+}
+
+void CVideoPlayerAudioID3::OnExit()
+{
+ CLog::Log(LOGINFO, "Audio ID3 tag processor - thread end");
+}
+
+void CVideoPlayerAudioID3::Process()
+{
+ CLog::Log(LOGINFO, "Audio ID3 tag processor - running thread");
+
+ while (!m_bStop)
+ {
+ std::shared_ptr<CDVDMsg> pMsg;
+ int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE) ? 1 : 0;
+ MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, 2000, iPriority);
+
+ // Timeout for ID3 tag data is not a bad thing, so we continue without error
+ if (ret == MSGQ_TIMEOUT)
+ continue;
+
+ if (MSGQ_IS_ERROR(ret))
+ {
+ if (!m_messageQueue.ReceivedAbortRequest())
+ CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret);
+
+ break;
+ }
+
+ if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket();
+ if (pPacket)
+ ProcessID3(pPacket->pData, pPacket->iSize);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ m_speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+ }
+ }
+}
+
+void CVideoPlayerAudioID3::ProcessID3(const unsigned char* data, unsigned int length) const
+{
+ if (data && length > 0)
+ {
+ ByteVectorStream tagStream(ByteVector(reinterpret_cast<const char*>(data), length));
+ if (tagStream.isOpen())
+ {
+ MPEG::File tagFile = MPEG::File(&tagStream, ID3v2::FrameFactory::instance());
+ if (tagFile.isOpen())
+ {
+ if (tagFile.hasID3v1Tag())
+ ProcessID3v1(tagFile.ID3v1Tag(false));
+
+ else if (tagFile.hasID3v2Tag())
+ ProcessID3v2(tagFile.ID3v2Tag(false));
+ }
+ }
+ }
+}
+
+void CVideoPlayerAudioID3::ProcessID3v1(const ID3v1::Tag* tag) const
+{
+ if (tag != nullptr && !tag->isEmpty())
+ {
+ MUSIC_INFO::CMusicInfoTag* currentMusic = g_application.CurrentFileItem().GetMusicInfoTag();
+ if (currentMusic)
+ {
+ bool changed = false;
+
+ const String title = tag->title();
+ if (!title.isEmpty())
+ {
+ currentMusic->SetTitle(title.to8Bit(true));
+ changed = true;
+ }
+
+ const String artist = tag->artist();
+ if (!artist.isEmpty())
+ {
+ currentMusic->SetArtist(artist.to8Bit(true));
+ changed = true;
+ }
+
+ const String album = tag->album();
+ if (!album.isEmpty())
+ {
+ currentMusic->SetAlbum(album.to8Bit(true));
+ changed = true;
+ }
+
+ const String comment = tag->comment();
+ if (!comment.isEmpty())
+ {
+ currentMusic->SetComment(comment.to8Bit(true));
+ changed = true;
+ }
+
+ const String genre = tag->genre();
+ if (!genre.isEmpty())
+ {
+ currentMusic->SetGenre(genre.to8Bit(true));
+ changed = true;
+ }
+
+ const unsigned int year = tag->year();
+ if (year != 0)
+ {
+ currentMusic->SetYear(year);
+ changed = true;
+ }
+
+ const unsigned int track = tag->track();
+ if (track != 0)
+ {
+ currentMusic->SetTrackNumber(track);
+ changed = true;
+ }
+
+ if (changed)
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(g_application.CurrentFileItem());
+ }
+ }
+}
+
+void CVideoPlayerAudioID3::ProcessID3v2(const ID3v2::Tag* tag) const
+{
+ if (tag != nullptr && !tag->isEmpty())
+ {
+ MUSIC_INFO::CMusicInfoTag* currentMusic = g_application.CurrentFileItem().GetMusicInfoTag();
+ if (currentMusic)
+ {
+ bool changed = false;
+
+ const ID3v2::FrameListMap& frameListMap = tag->frameListMap();
+ for (const auto& it : frameListMap)
+ {
+ if (!it.second.isEmpty())
+ {
+ if (it.first == "TIT2")
+ {
+ currentMusic->SetTitle(it.second.front()->toString().to8Bit(true));
+ changed = true;
+ }
+
+ else if (it.first == "TPE1")
+ {
+ currentMusic->SetArtist(GetID3v2StringList(it.second));
+ changed = true;
+ }
+
+ else if (it.first == "TALB")
+ {
+ currentMusic->SetAlbum(it.second.front()->toString().to8Bit(true));
+ changed = true;
+ }
+
+ else if (it.first == "COMM")
+ {
+ // Loop through and look for the main (no description) comment
+ for (const auto& ct : it.second)
+ {
+ auto commentsFrame = dynamic_cast<const ID3v2::CommentsFrame*>(ct);
+ if (commentsFrame && commentsFrame->description().isEmpty())
+ {
+ currentMusic->SetComment(commentsFrame->text().to8Bit(true));
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ else if (it.first == "TCON")
+ {
+ currentMusic->SetGenre(GetID3v2StringList(it.second));
+ changed = true;
+ }
+
+ else if (it.first == "TYER")
+ {
+ currentMusic->SetYear(static_cast<int>(
+ strtol(it.second.front()->toString().toCString(true), nullptr, 10)));
+ changed = true;
+ }
+
+ else if (it.first == "TRCK")
+ {
+ currentMusic->SetTrackNumber(static_cast<int>(
+ strtol(it.second.front()->toString().toCString(true), nullptr, 10)));
+ changed = true;
+ }
+
+ // Support for setting the cover art image via CMusicInfoTag does not currently exist,
+ // the code sample below would check for an ID3v2 "APIC" tag of the proper type and
+ // convert the information into an EmbeddedArt object instance
+ //
+ // else if (it.first == "APIC")
+ // {
+ // // Loop through and look for the FrontCover picture frame
+ // for (const auto& pi : it.second)
+ // {
+ // auto pictureFrame = dynamic_cast<ID3v2::AttachedPictureFrame*>(pi);
+ // if (pictureFrame && pictureFrame->type() == ID3v2::AttachedPictureFrame::FrontCover)
+ // {
+ // EmbeddedArt coverArt(
+ // reinterpret_cast<const uint8_t*>(pictureFrame->picture().data()),
+ // pictureFrame->size(), pictureFrame->mimeType().to8Bit(true));
+
+ // // Assumes "void CMusicInfoTag::SetCoverArt(const EmbeddedArt& art)" exists
+ // currentMusic->SetCoverArt(coverArt);
+ // changed = true;
+ // }
+ // }
+ // }
+ }
+ }
+
+ if (changed)
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(g_application.CurrentFileItem());
+ }
+ }
+}
+
+std::vector<std::string> CVideoPlayerAudioID3::GetID3v2StringList(const ID3v2::FrameList& frameList)
+{
+ auto frame = dynamic_cast<const ID3v2::TextIdentificationFrame*>(frameList.front());
+ if (frame)
+ return StringListToVectorString(frame->fieldList());
+ return {};
+}
+
+std::vector<std::string> CVideoPlayerAudioID3::StringListToVectorString(
+ const StringList& stringList)
+{
+ std::vector<std::string> values;
+ for (const auto& value : stringList)
+ values.emplace_back(value.to8Bit(true));
+ return values;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.h b/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.h
new file mode 100644
index 0000000..fe97a5e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerAudioID3.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDMessageQueue.h"
+#include "IVideoPlayer.h"
+#include "Interface/TimingConstants.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <taglib/id3v1tag.h>
+#include <taglib/id3v2tag.h>
+#include <taglib/tstringlist.h>
+
+class CVideoPlayerAudioID3 : public IDVDStreamPlayer, private CThread
+{
+public:
+ explicit CVideoPlayerAudioID3(CProcessInfo& processInfo);
+ ~CVideoPlayerAudioID3() override;
+
+ bool CheckStream(const CDVDStreamInfo& hints);
+ void Flush();
+ void WaitForBuffers();
+
+ bool OpenStream(CDVDStreamInfo hints) override;
+ void CloseStream(bool bWaitForBuffers) override;
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override;
+ void FlushMessages() override;
+ bool IsInited() const override;
+ bool AcceptsData() const override;
+ bool IsStalled() const override;
+
+protected:
+ void OnExit() override;
+ void Process() override;
+
+private:
+ void ProcessID3(const unsigned char* data, unsigned int length) const;
+ void ProcessID3v1(const TagLib::ID3v1::Tag* tag) const;
+ void ProcessID3v2(const TagLib::ID3v2::Tag* tag) const;
+
+ static std::vector<std::string> GetID3v2StringList(const TagLib::ID3v2::FrameList& frameList);
+ static std::vector<std::string> StringListToVectorString(const TagLib::StringList& stringList);
+
+ int m_speed = DVD_PLAYSPEED_NORMAL;
+ CCriticalSection m_critSection;
+ CDVDMessageQueue m_messageQueue;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp b/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp
new file mode 100644
index 0000000..08963e7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp
@@ -0,0 +1,1678 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+//#define RDS_IMPROVE_CHECK 1
+
+/*
+ * The RDS decoder bases partly on the source of the VDR radio plugin.
+ * http://www.egal-vdr.de/plugins/
+ * and reworked a bit with references from SPB 490, IEC62106
+ * and several other documents.
+ *
+ * A lot more information is sendet which is currently unused and partly
+ * not required.
+ */
+
+#include "VideoPlayerRadioRDS.h"
+
+#include "DVDStreamInfo.h"
+#include "GUIInfoManager.h"
+#include "GUIUserMessages.h"
+#include "Interface/DemuxPacket.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "interfaces/AnnouncementManager.h"
+#include "music/tags/MusicInfoTag.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRRadioRDSInfoTag.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace XFILE;
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+/**
+ * Universal Encoder Communication Protocol (UECP)
+ * List of defined commands
+ * iaw.: SPB 490
+ */
+
+/// UECP Message element pointers (different on several commands)
+#define UECP_ME_MEC 0 // Message Element Code
+#define UECP_ME_DSN 1 // Data Set Number
+#define UECP_ME_PSN 2 // Program Service Number
+#define UECP_ME_MEL 3 // Message Element data Length
+#define UECP_ME_DATA 4 //
+
+/// RDS message commands
+#define UECP_RDS_PI 0x01 // Program Identification
+#define UECP_RDS_PS 0x02 // Program Service name
+#define UECP_RDS_PIN 0x06 // Program Item Number
+#define UECP_RDS_DI 0x04 // Decoder Identification and dynamic PTY indicator
+#define UECP_RDS_TA_TP 0x03 // Traffic Announcement identification / Traffic Program identification
+#define UECP_RDS_MS 0x05 // Music/Speech switch
+#define UECP_RDS_PTY 0x07 // Program TYpe
+#define UECP_RDS_PTYN 0x3A // Program TYpe Name
+#define UECP_RDS_RT 0x0A // RadioText
+#define UECP_RDS_AF 0x13 // Alternative Frequencies list
+#define UECP_RDS_EON_AF 0x14 // Enhanced Other Networks information
+#define UECP_SLOW_LABEL_CODES 0x1A // Slow Labeling codes
+#define UECP_LINKAGE_INFO 0x2E // Linkage information
+
+/// Open Data Application commands
+#define UECP_ODA_CONF_SHORT_MSG_CMD 0x40 // ODA configuration and short message command
+#define UECP_ODA_IDENT_GROUP_USAGE_SEQ 0x41 // ODA identification group usage sequence
+#define UECP_ODA_FREE_FORMAT_GROUP 0x42 // ODA free-format group
+#define UECP_ODA_REL_PRIOR_GROUP_SEQ 0x43 // ODA relative priority group sequence
+#define UECP_ODA_BURST_MODE_CONTROL 0x44 // ODA “Burst mode” control
+#define UECP_ODA_SPINN_WHEEL_TIMING_CTL 0x45 // ODA “Spinning Wheel” timing control
+#define UECP_ODA_DATA 0x46 // ODA Data
+#define UECP_ODA_DATA_CMD_ACCESS_RIGHT 0x47 // ODA data command access right
+
+/// DAB
+#define UECP_DAB_DYN_LABEL_CMD 0x48 // DAB: Dynamic Label command
+#define UECP_DAB_DYN_LABEL_MSG 0xAA // DAB: Dynamic Label message (DL)
+
+/// Transparent data commands
+#define UECP_TDC_TDC 0x26 // TDC
+#define UECP_TDC_EWS 0x2B // EWS
+#define UECP_TDC_IH 0x25 // IH
+#define UECP_TDC_TMC 0x30 // TMC
+#define UECP_TDC_FREE_FMT_GROUP 0x24 // Free-format group
+
+/// Paging commands
+#define UECP_PAGING_CALL_WITHOUT_MESSAGE 0x0C
+#define UECP_PAGING_CALL_NUMERIC_MESSAGE_10DIGITS 0x08
+#define UECP_PAGING_CALL_NUMERIC_MESSAGE_18DIGITS 0x20
+#define UECP_PAGING_CALL_ALPHANUMERIC_MESSAGE_80CHARACTERS 0x1B
+#define UECP_INTERNATIONAL_PAGING_NUMERIC_MESSAGE_15DIGITS 0x11
+#define UECP_INTERNATIONAL_PAGING_FUNCTIONS_MESSAGE 0x10
+#define UECP_TRANSMITTER_NETWORK_GROUP_DESIGNATION 0x12
+#define UECP_EPP_TM_INFO 0x31
+#define UECP_EPP_CALL_WITHOUT_ADDITIONAL_MESSAGE 0x32
+#define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_ALPHANUMERIC_MESSAGE 0x33
+#define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_VARIABLE_LENGTH_NUMERIC_MESSAGE 0x34
+#define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_VARIABLE_LENGTH_FUNCTIONS_MESSAGE 0x35
+
+/// Clock setting and control
+#define UECP_CLOCK_RTC 0x0D // Real time clock
+#define UECP_CLOCK_RTC_CORR 0x09 // Real time clock correction
+#define UECP_CLOCK_CT_ON_OFF 0x19 // CT On/Off
+
+/// RDS adjustment and control
+#define RDS_ON_OFF 0x1E
+#define RDS_PHASE 0x22
+#define RDS_LEVEL 0x0E
+
+/// ARI adjustment and control
+#define UECP_ARI_ARI_ON_OFF 0x21
+#define UECP_ARI_ARI_AREA (BK) 0x0F
+#define UECP_ARI_ARI_LEVEL 0x1F
+
+/// Control and set up commands
+#define UECP_CTR_SITE_ADDRESS 0x23
+#define UECP_CTR_ENCODER_ADDRESS 0x27
+#define UECP_CTR_MAKE_PSN_LIST 0x28
+#define UECP_CTR_PSN_ENABLE_DISABLE 0x0B
+#define UECP_CTR_COMMUNICATION_MODE 0x2C
+#define UECP_CTR_TA_CONTROL 0x2A
+#define UECP_CTR_EON_TA_CONTROL 0x15
+#define UECP_CTR_REFERENCE_INPUT_SEL 0x1D
+#define UECP_CTR_DATA_SET_SELECT 0x1C // Data set select
+#define UECP_CTR_GROUP_SEQUENCE 0x16
+#define UECP_CTR_GROUP_VAR_CODE_SEQ 0x29
+#define UECP_CTR_EXTENDED_GROUP_SEQ 0x38
+#define UECP_CTR_PS_CHAR_CODE_TBL_SEL 0x2F
+#define UECP_CTR_ENCODER_ACCESS_RIGHT 0x3A
+#define UECP_CTR_COM_PORT_CONF_MODE 0x3B
+#define UECP_CTR_COM_PORT_CONF_SPEED 0x3C
+#define UECP_CTR_COM_PORT_CONF_TMEOUT 0x3D
+
+/// Other commands
+#define UECP_OTHER_RASS 0xda
+
+/// Bi-directional commands (Remote and configuration commands)
+#define BIDIR_MESSAGE_ACKNOWLEDGMENT 0x18
+#define BIDIR_REQUEST_MESSAGE 0x17
+
+/// Specific message commands
+#define SPEC_MFG_SPECIFIC_CMD 0x2D
+
+/**
+ * RDS and RBDS relevant
+ */
+
+/// RDS Program type id's
+enum {
+ RDS_PTY_NONE = 0,
+ RDS_PTY_NEWS,
+ RDS_PTY_CURRENT_AFFAIRS,
+ RDS_PTY_INFORMATION,
+ RDS_PTY_SPORT,
+ RDS_PTY_EDUCATION,
+ RDS_PTY_DRAMA,
+ RDS_PTY_CULTURE,
+ RDS_PTY_SCIENCE,
+ RDS_PTY_VARIED,
+ RDS_PTY_POP_MUSIC,
+ RDS_PTY_ROCK_MUSIC,
+ RDS_PTY_MOR_MUSIC,
+ RDS_PTY_LIGHT_CLASSICAL,
+ RDS_PTY_SERIOUS_CLASSICAL,
+ RDS_PTY_OTHER_MUSIC,
+ RDS_PTY_WEATHER,
+ RDS_PTY_FINANCE,
+ RDS_PTY_CHILDRENS_PROGRAMMES,
+ RDS_PTY_SOCIAL_AFFAIRS,
+ RDS_PTY_RELIGION,
+ RDS_PTY_PHONE_IN,
+ RDS_PTY_TRAVEL,
+ RDS_PTY_LEISURE,
+ RDS_PTY_JAZZ_MUSIC,
+ RDS_PTY_COUNTRY_MUSIC,
+ RDS_PTY_NATIONAL_MUSIC,
+ RDS_PTY_OLDIES_MUSIC,
+ RDS_PTY_FOLK_MUSIC,
+ RDS_PTY_DOCUMENTARY,
+ RDS_PTY_ALARM_TEST,
+ RDS_PTY_ALARM
+};
+
+/// RBDS Program type id's
+enum {
+ RBDS_PTY_NONE = 0,
+ RBDS_PTY_NEWS,
+ RBDS_PTY_INFORMATION,
+ RBDS_PTY_SPORT,
+ RBDS_PTY_TALK,
+ RBDS_PTY_ROCK_MUSIC,
+ RBDS_PTY_CLASSIC_ROCK_MUSIC,
+ RBDS_PTY_ADULT_HITS,
+ RBDS_PTY_SOFT_ROCK,
+ RBDS_PTY_TOP_40,
+ RBDS_PTY_COUNTRY,
+ RBDS_PTY_OLDIES,
+ RBDS_PTY_SOFT,
+ RBDS_PTY_NOSTALGIA,
+ RBDS_PTY_JAZZ,
+ RBDS_PTY_CLASSICAL,
+ RBDS_PTY_R__B,
+ RBDS_PTY_SOFT_R__B,
+ RBDS_PTY_LANGUAGE,
+ RBDS_PTY_RELIGIOUS_MUSIC,
+ RBDS_PTY_RELIGIOUS_TALK,
+ RBDS_PTY_PERSONALITY,
+ RBDS_PTY_PUBLIC,
+ RBDS_PTY_COLLEGE,
+ RBDS_PTY_WEATHER = 29,
+ RBDS_PTY_EMERGENCY_TEST,
+ RBDS_PTY_EMERGENCY
+};
+
+/// RadioText+ message type id's
+enum {
+ RTPLUS_DUMMY_CLASS = 0,
+
+ RTPLUS_ITEM_TITLE = 1,
+ RTPLUS_ITEM_ALBUM = 2,
+ RTPLUS_ITEM_TRACKNUMBER = 3,
+ RTPLUS_ITEM_ARTIST = 4,
+ RTPLUS_ITEM_COMPOSITION = 5,
+ RTPLUS_ITEM_MOVEMENT = 6,
+ RTPLUS_ITEM_CONDUCTOR = 7,
+ RTPLUS_ITEM_COMPOSER = 8,
+ RTPLUS_ITEM_BAND = 9,
+ RTPLUS_ITEM_COMMENT = 10,
+ RTPLUS_ITEM_GENRE = 11,
+
+ RTPLUS_INFO_NEWS = 12,
+ RTPLUS_INFO_NEWS_LOCAL = 13,
+ RTPLUS_INFO_STOCKMARKET = 14,
+ RTPLUS_INFO_SPORT = 15,
+ RTPLUS_INFO_LOTTERY = 16,
+ RTPLUS_INFO_HOROSCOPE = 17,
+ RTPLUS_INFO_DAILY_DIVERSION = 18,
+ RTPLUS_INFO_HEALTH = 19,
+ RTPLUS_INFO_EVENT = 20,
+ RTPLUS_INFO_SZENE = 21,
+ RTPLUS_INFO_CINEMA = 22,
+ RTPLUS_INFO_STUPIDITY_MACHINE = 23,
+ RTPLUS_INFO_DATE_TIME = 24,
+ RTPLUS_INFO_WEATHER = 25,
+ RTPLUS_INFO_TRAFFIC = 26,
+ RTPLUS_INFO_ALARM = 27,
+ RTPLUS_INFO_ADVERTISEMENT = 28,
+ RTPLUS_INFO_URL = 29,
+ RTPLUS_INFO_OTHER = 30,
+
+ RTPLUS_STATIONNAME_SHORT = 31,
+ RTPLUS_STATIONNAME_LONG = 32,
+
+ RTPLUS_PROGRAMME_NOW = 33,
+ RTPLUS_PROGRAMME_NEXT = 34,
+ RTPLUS_PROGRAMME_PART = 35,
+ RTPLUS_PROGRAMME_HOST = 36,
+ RTPLUS_PROGRAMME_EDITORIAL_STAFF = 37,
+ RTPLUS_PROGRAMME_FREQUENCY= 38,
+ RTPLUS_PROGRAMME_HOMEPAGE = 39,
+ RTPLUS_PROGRAMME_SUBCHANNEL = 40,
+
+ RTPLUS_PHONE_HOTLINE = 41,
+ RTPLUS_PHONE_STUDIO = 42,
+ RTPLUS_PHONE_OTHER = 43,
+
+ RTPLUS_SMS_STUDIO = 44,
+ RTPLUS_SMS_OTHER = 45,
+
+ RTPLUS_EMAIL_HOTLINE = 46,
+ RTPLUS_EMAIL_STUDIO = 47,
+ RTPLUS_EMAIL_OTHER = 48,
+
+ RTPLUS_MMS_OTHER = 49,
+
+ RTPLUS_CHAT = 50,
+ RTPLUS_CHAT_CENTER = 51,
+
+ RTPLUS_VOTE_QUESTION = 52,
+ RTPLUS_VOTE_CENTER = 53,
+
+ RTPLUS_PLACE = 59,
+ RTPLUS_APPOINTMENT = 60,
+ RTPLUS_IDENTIFIER = 61,
+ RTPLUS_PURCHASE = 62,
+ RTPLUS_GET_DATA = 63
+};
+
+/* page 71, Annex D, table D.1 in the standard and Annex N */
+static const char *piCountryCodes_A[15][7]=
+{
+ // 0 1 2 3 4 5 6
+ {"US","__","AI","BO","GT","__","__"}, // 1
+ {"US","__","AG","CO","HN","__","__"}, // 2
+ {"US","__","EC","JM","AW","__","__"}, // 3
+ {"US","__","FK","MQ","__","__","__"}, // 4
+ {"US","__","BB","GF","MS","__","__"}, // 5
+ {"US","__","BZ","PY","TT","__","__"}, // 6
+ {"US","__","KY","NI","PE","__","__"}, // 7
+ {"US","__","CR","__","SR","__","__"}, // 8
+ {"US","__","CU","PA","UY","__","__"}, // 9
+ {"US","__","AR","DM","KN","__","__"}, // A
+ {"US","CA","BR","DO","LC","MX","__"}, // B
+ {"__","CA","BM","CL","SV","VC","__"}, // C
+ {"US","CA","AN","GD","HT","MX","__"}, // D
+ {"US","CA","GP","TC","VE","MX","__"}, // E
+ {"__","GL","BS","GY","__","VG","PM"} // F
+};
+
+static const char *piCountryCodes_D[15][7]=
+{
+ // 0 1 2 3 4 5 6
+ {"CM","NA","SL","__","__","__","__"}, // 1
+ {"CF","LR","ZW","__","__","__","__"}, // 2
+ {"DJ","GH","MZ","EH","__","__","__"}, // 3
+ {"MG","MR","UG","xx","__","__","__"}, // 4
+ {"ML","ST","SZ","RW","__","__","__"}, // 5
+ {"AO","CV","KE","LS","__","__","__"}, // 6
+ {"GQ","SN","SO","__","__","__","__"}, // 7
+ {"GA","GM","NE","SC","__","__","__"}, // 8
+ {"GN","BI","TD","__","__","__","__"}, // 9
+ {"ZA","AC","GW","MU","__","__","__"}, // A
+ {"BF","BW","ZR","__","__","__","__"}, // B
+ {"CG","KM","CI","SD","__","__","__"}, // C
+ {"TG","TZ","Zanzibar","__","__","__","__"}, // D
+ {"BJ","ET","ZM","__","__","__","__"}, // E
+ {"MW","NG","__","__","__","__","__"} // F
+};
+
+static const char *piCountryCodes_E[15][7]=
+{
+ // 0 1 2 3 4 5 6
+ {"DE","GR","MA","__","MD","__","__"},
+ {"DZ","CY","CZ","IE","EE","__","__"},
+ {"AD","SM","PL","TR","KG","__","__"},
+ {"IL","CH","VA","MK","__","__","__"},
+ {"IT","JO","SK","TJ","__","__","__"},
+ {"BE","FI","SY","__","UA","__","__"},
+ {"RU","LU","TN","__","__","__","__"},
+ {"PS","BG","__","NL","PT","__","__"},
+ {"AL","DK","LI","LV","SI","__","__"}, // 9
+ {"AT","GI","IS","LB","AM","__","__"}, // A
+ {"HU","IQ","MC","AZ","UZ","__","__"}, // B
+ {"MT","GB","LT","HR","GE","__","__"}, // C
+ {"DE","LY","YU","KZ","__","__","__"}, // D
+ {"__","RO","ES","SE","TM","__","__"}, // E
+ {"EG","FR","NO","BY","BA","__","__"} // F
+};
+
+static const char *piCountryCodes_F[15][7]=
+{
+ // 0 1 2 3 4 5 6
+ {"AU","KI","KW","LA","__","__","__"}, // 1
+ {"AU","BT","QA","TH","__","__","__"}, // 2
+ {"AU","BD","KH","TO","__","__","__"}, // 3
+ {"AU","PK","WS","__","__","__","__"}, // 4
+ {"AU","FJ","IN","__","__","__","__"}, // 5
+ {"AU","OM","MO","__","__","__","__"}, // 6
+ {"AU","NR","VN","__","__","__","__"}, // 7
+ {"AU","IR","PH","__","__","__","__"}, // 8
+ {"SA","NZ","JP","PG","__","__","__"}, // 9
+ {"AF","SB","SG","__","__","__","__"}, // A
+ {"MM","BN","MV","YE","__","__","__"}, // B
+ {"CN","LK","ID","__","__","__","__"}, // C
+ {"KP","TW","AE","__","__","__","__"}, // D
+ {"BH","KR","NP","FM","__","__","__"}, // E
+ {"MY","HK","VU","MN","__","__","__"} // F
+};
+
+/* see page 84, Annex J in the standard */
+static const std::string piRDSLanguageCodes[128]=
+{
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ "___", "alb", "bre", "cat", "hrv", "wel", "cze", "dan", "ger", "eng", "spa", "epo", "est", "baq", "fae", "fre", // 0
+ "fry", "gle", "gla", "glg", "ice", "ita", "smi", "lat", "lav", "ltz", "lit", "hun", "mlt", "dut", "nor", "oci", // 1
+ "pol", "por", "rum", "rom", "srp", "slo", "slv", "fin", "swe", "tur", "nld", "wln", "___", "___", "___", "___", // 2
+ "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", // 3
+ "___", "___", "___", "___", "___", "zul", "vie", "uzb", "urd", "ukr", "tha", "tel", "tat", "tam", "tgk", "swa", // 4
+ "srn", "som", "sin", "sna", "scc", "rue", "rus", "que", "pus", "pan", "per", "pap", "ori", "nep", "nde", "mar", // 5
+ "mol", "mys", "mlg", "mkd", "_?_", "kor", "khm", "kaz", "kan", "jpn", "ind", "hin", "heb", "hau", "grn", "guj", // 6
+ "gre", "geo", "ful", "prs", "chv", "chi", "bur", "bul", "ben", "bel", "bam", "aze", "asm", "arm", "ara", "amh" // 7
+};
+
+/* ----------------------------------------------------------------------------------------------------------- */
+
+#define EntityChars 56
+static const char *entitystr[EntityChars] = { "&apos;", "&amp;", "&quot;", "&gt", "&lt", "&copy;", "&times;", "&nbsp;",
+ "&Auml;", "&auml;", "&Ouml;", "&ouml;", "&Uuml;", "&uuml;", "&szlig;", "&deg;",
+ "&Agrave;", "&Aacute;", "&Acirc;", "&Atilde;", "&agrave;", "&aacute;", "&acirc;", "&atilde;",
+ "&Egrave;", "&Eacute;", "&Ecirc;", "&Euml;", "&egrave;", "&eacute;", "&ecirc;", "&euml;",
+ "&Igrave;", "&Iacute;", "&Icirc;", "&Iuml;", "&igrave;", "&iacute;", "&icirc;", "&iuml;",
+ "&Ograve;", "&Oacute;", "&Ocirc;", "&Otilde;", "&ograve;", "&oacute;", "&ocirc;", "&otilde;",
+ "&Ugrave;", "&Uacute;", "&Ucirc;", "&Ntilde;", "&ugrave;", "&uacute;", "&ucirc;", "&ntilde;" };
+static const char *entitychar[EntityChars] = { "'", "&", "\"", ">", "<", "c", "*", " ",
+ "Ä", "ä", "Ö", "ö", "Ü", "ü", "ß", "°",
+ "À", "Á", "Â", "Ã", "à", "á", "â", "ã",
+ "È", "É", "Ê", "Ë", "è", "é", "ê", "ë",
+ "Ì", "Í", "Î", "Ï", "ì", "í", "î", "ï",
+ "Ò", "Ó", "Ô", "Õ", "ò", "ó", "ô", "õ",
+ "Ù", "Ú", "Û", "Ñ", "ù", "ú", "û", "ñ" };
+
+// RDS-Chartranslation: 0x80..0xff
+static unsigned char sRDSAddChar[128] =
+{
+ 0xe1, 0xe0, 0xe9, 0xe8, 0xed, 0xec, 0xf3, 0xf2,
+ 0xfa, 0xf9, 0xd1, 0xc7, 0x8c, 0xdf, 0x8e, 0x8f,
+ 0xe2, 0xe4, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf6,
+ 0xfb, 0xfc, 0xf1, 0xe7, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xaa, 0xa1, 0xa9, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xa3, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xba, 0xb9, 0xb2, 0xb3, 0xb1, 0xa1, 0xb6, 0xb7,
+ 0xb5, 0xbf, 0xf7, 0xb0, 0xbc, 0xbd, 0xbe, 0xa7,
+ 0xc1, 0xc0, 0xc9, 0xc8, 0xcd, 0xcc, 0xd3, 0xd2,
+ 0xda, 0xd9, 0xca, 0xcb, 0xcc, 0xcd, 0xd0, 0xcf,
+ 0xc2, 0xc4, 0xca, 0xcb, 0xce, 0xcf, 0xd4, 0xd6,
+ 0xdb, 0xdc, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xc3, 0xc5, 0xc6, 0xe3, 0xe4, 0xdd, 0xd5, 0xd8,
+ 0xde, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xf0,
+ 0xe3, 0xe5, 0xe6, 0xf3, 0xf4, 0xfd, 0xf5, 0xf8,
+ 0xfe, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+static char *rds_entitychar(char *text)
+{
+ int i = 0, l, lof, lre, space;
+ char *temp;
+
+ while (i < EntityChars)
+ {
+ if ((temp = strstr(text, entitystr[i])) != NULL)
+ {
+ l = strlen(entitystr[i]);
+ lof = (temp-text);
+ if (strlen(text) < RT_MEL)
+ {
+ lre = strlen(text) - lof - l;
+ space = 1;
+ }
+ else
+ {
+ lre = RT_MEL - 1 - lof - l;
+ space = 0;
+ }
+ memmove(text+lof, entitychar[i], 1);
+ memmove(text+lof+1, temp+l, lre);
+ if (space != 0)
+ memmove(text+lof+1+lre, " ", l-1);
+ }
+ else
+ ++i;
+ }
+
+ return text;
+}
+
+static unsigned short crc16_ccitt(const unsigned char *data, int len, bool skipfirst)
+{
+ // CRC16-CCITT: x^16 + x^12 + x^5 + 1
+ // with start 0xffff and result inverse
+ unsigned short crc = 0xffff;
+
+ if (skipfirst)
+ ++data;
+
+ while (len--)
+ {
+ crc = (crc >> 8) | (crc << 8);
+ crc ^= *data++;
+ crc ^= (crc & 0xff) >> 4;
+ crc ^= (crc << 8) << 4;
+ crc ^= ((crc & 0xff) << 4) << 1;
+ }
+
+ return ~(crc);
+}
+
+
+/// --- CDVDRadioRDSData ------------------------------------------------------------
+
+CDVDRadioRDSData::CDVDRadioRDSData(CProcessInfo &processInfo)
+ : CThread("DVDRDSData")
+ , IDVDStreamPlayer(processInfo)
+ , m_speed(DVD_PLAYSPEED_NORMAL)
+ , m_messageQueue("rds")
+{
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - new {}", __FUNCTION__);
+
+ m_messageQueue.SetMaxDataSize(40 * 256 * 1024);
+}
+
+CDVDRadioRDSData::~CDVDRadioRDSData()
+{
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - delete {}", __FUNCTION__);
+ StopThread();
+}
+
+bool CDVDRadioRDSData::CheckStream(const CDVDStreamInfo& hints)
+{
+ if (hints.type == STREAM_RADIO_RDS)
+ return true;
+
+ return false;
+}
+
+bool CDVDRadioRDSData::OpenStream(CDVDStreamInfo hints)
+{
+ CloseStream(true);
+
+ m_messageQueue.Init();
+ if (hints.type == STREAM_RADIO_RDS)
+ {
+ Flush();
+ CLog::Log(LOGINFO, "Creating UECP (RDS) data thread");
+ Create();
+ return true;
+ }
+ return false;
+}
+
+void CDVDRadioRDSData::CloseStream(bool bWaitForBuffers)
+{
+ m_messageQueue.Abort();
+
+ // wait for decode_video thread to end
+ CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - waiting for data thread to exit");
+
+ StopThread(); // will set this->m_bStop to true
+
+ m_messageQueue.End();
+ m_currentInfoTag.reset();
+ if (m_currentChannel)
+ m_currentChannel->SetRadioRDSInfoTag(m_currentInfoTag);
+ m_currentChannel.reset();
+}
+
+void CDVDRadioRDSData::ResetRDSCache()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_currentFileUpdate = false;
+
+ m_UECPDataStart = false;
+ m_UECPDatabStuff = false;
+ m_UECPDataIndex = 0;
+
+ m_RDS_IsRBDS = false;
+ m_RDS_SlowLabelingCodesPresent = false;
+
+ m_PI_Current = 0;
+ m_PI_CountryCode = 0;
+ m_PI_ProgramType = 0;
+ m_PI_ProgramReferenceNumber = 0;
+
+ m_EPP_TM_INFO_ExtendedCountryCode = 0;
+
+ m_DI_IsStereo = true;
+ m_DI_ArtificialHead = false;
+ m_DI_Compressed = false;
+ m_DI_DynamicPTY = false;
+
+ m_TA_TP_TrafficAdvisory = false;
+ m_TA_TP_TrafficVolume = 0.0;
+
+ m_MS_SpeechActive = false;
+
+ m_PTY = 0;
+ memset(m_PTYN, 0x20, 8);
+ m_PTYN[8] = 0;
+ m_PTYN_Present = false;
+
+ m_RT_NewItem = false;
+
+ m_RTPlus_TToggle = false;
+ m_RTPlus_Show = false;
+ m_RTPlus_iToggle = 0;
+ m_RTPlus_ItemToggle = 1;
+ m_RTPlus_Title[0] = 0;
+ m_RTPlus_Artist[0] = 0;
+ m_RTPlus_Starttime = time(NULL);
+ m_RTPlus_GenrePresent = false;
+
+ m_currentInfoTag = std::make_shared<CPVRRadioRDSInfoTag>();
+ m_currentChannel = g_application.CurrentFileItem().GetPVRChannelInfoTag();
+ if (m_currentChannel)
+ m_currentChannel->SetRadioRDSInfoTag(m_currentInfoTag);
+
+ // send a message to all windows to tell them to update the radiotext
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_RADIOTEXT);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+}
+
+void CDVDRadioRDSData::Process()
+{
+ CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - running thread");
+
+ while (!m_bStop)
+ {
+ std::shared_ptr<CDVDMsg> pMsg;
+ int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE) ? 1 : 0;
+ MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, 2000, iPriority);
+
+ if (ret == MSGQ_TIMEOUT)
+ {
+ /* Timeout for RDS is not a bad thing, so we continue without error */
+ continue;
+ }
+
+ if (MSGQ_IS_ERROR(ret))
+ {
+ if (!m_messageQueue.ReceivedAbortRequest())
+ CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret);
+
+ break;
+ }
+
+ if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket();
+
+ ProcessUECP(pPacket->pData, pPacket->iSize);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ m_speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)
+ || pMsg->IsType(CDVDMsg::GENERAL_RESET))
+ {
+ ResetRDSCache();
+ }
+ }
+}
+
+void CDVDRadioRDSData::Flush()
+{
+ if(!m_messageQueue.IsInited())
+ return;
+ /* flush using message as this get's called from VideoPlayer thread */
+ /* and any demux packet that has been taken out of queue need to */
+ /* be disposed of before we flush */
+ m_messageQueue.Flush();
+ m_messageQueue.Put(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_FLUSH));
+}
+
+void CDVDRadioRDSData::OnExit()
+{
+ CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - thread end");
+}
+
+void CDVDRadioRDSData::SetRadioStyle(const std::string& genre)
+{
+ g_application.CurrentFileItem().GetMusicInfoTag()->SetGenre(genre);
+ m_currentInfoTag->SetProgStyle(genre);
+ m_currentFileUpdate = true;
+
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - {} - Stream genre set to {}", __FUNCTION__,
+ genre);
+}
+
+void CDVDRadioRDSData::ProcessUECP(const unsigned char *data, unsigned int len)
+{
+ for (unsigned int i = 0; i < len; ++i)
+ {
+ if (data[i] == UECP_DATA_START) //!< Start
+ {
+ m_UECPDataIndex = -1;
+ m_UECPDataStart = true;
+ m_UECPDatabStuff = false;
+ }
+
+ if (m_UECPDataStart)
+ {
+ //! byte-stuffing reverse: 0xfd00->0xfd, 0xfd01->0xfe, 0xfd02->0xff
+ if (m_UECPDatabStuff == true)
+ {
+ switch (data[i])
+ {
+ case 0x00: m_UECPData[m_UECPDataIndex] = 0xfd; break;
+ case 0x01: m_UECPData[m_UECPDataIndex] = 0xfe; break;
+ case 0x02: m_UECPData[m_UECPDataIndex] = 0xff; break;
+ default: m_UECPData[++m_UECPDataIndex] = data[i]; // should never be
+ }
+ m_UECPDatabStuff = false;
+ }
+ else
+ {
+ m_UECPData[++m_UECPDataIndex] = data[i];
+ }
+
+ if (data[i] == 0xfd && m_UECPDataIndex > 0) //!< stuffing found
+ m_UECPDatabStuff = true;
+
+ if (m_UECPDataIndex >= UECP_SIZE_MAX) //!< max. UECP data length, garbage ?
+ {
+ CLog::Log(LOGERROR, "Radio UECP (RDS) Processor - Error(TS): too long, garbage ?");
+ m_UECPDataStart = false;
+ }
+ }
+
+ if (m_UECPDataStart == true && data[i] == UECP_DATA_STOP && m_currentInfoTag) //!< End
+ {
+ m_UECPDataStart = false;
+
+ if (m_UECPDataIndex < 9)
+ {
+ CLog::Log(LOGERROR, "Radio UECP (RDS) Processor - Error(TS): too short -> garbage ?");
+ }
+ else
+ {
+ //! crc16-check
+ unsigned short crc16 = crc16_ccitt(m_UECPData, m_UECPDataIndex-3, true);
+ if (crc16 != (m_UECPData[m_UECPDataIndex-2]<<8) + m_UECPData[m_UECPDataIndex-1])
+ {
+ CLog::Log(LOGERROR,
+ "Radio UECP (RDS) Processor - Error(TS): wrong CRC # calc = {:04x} <> transmit "
+ "= {:02x}{:02x}",
+ crc16, m_UECPData[m_UECPDataIndex - 2], m_UECPData[m_UECPDataIndex - 1]);
+ }
+ else
+ {
+ m_UECPDataDeadBreak = false;
+
+ unsigned int ret = 0;
+ unsigned int ptr = 5;
+ unsigned int len = m_UECPDataIndex-7;
+ do
+ {
+ uint8_t *msg = m_UECPData+ptr; //!< Current selected UECP message element (increased if more as one element is in frame)
+ switch (msg[UECP_ME_MEC])
+ {
+ case UECP_RDS_PI: ret = DecodePI(msg); break; //!< Program Identification
+ case UECP_RDS_PS: ret = DecodePS(msg); break; //!< Program Service name (PS)
+ case UECP_RDS_DI: ret = DecodeDI(msg); break; //!< Decoder Identification and dynamic PTY indicator
+ case UECP_RDS_TA_TP: ret = DecodeTA_TP(msg); break; //!< Traffic Announcement and Traffic Programme bits.
+ case UECP_RDS_MS: ret = DecodeMS(msg); break; //!< Music/Speech switch
+ case UECP_RDS_PTY: ret = DecodePTY(msg); break; //!< Program Type
+ case UECP_RDS_PTYN: ret = DecodePTYN(msg); break; //!< Program Type Name
+ case UECP_RDS_RT: ret = DecodeRT(msg, len); break; //!< RadioText
+ case UECP_ODA_DATA: ret = DecodeODA(msg, len); break; //!< Open Data Application
+ case UECP_OTHER_RASS: m_UECPDataDeadBreak = true; break; //!< Radio screen show (RaSS) (not present, before on SWR radio)
+ case UECP_CLOCK_RTC: ret = DecodeRTC(msg); break; //!< Real time clock
+ case UECP_TDC_TMC: ret = DecodeTMC(msg, len); break; //!< Traffic message channel
+ case UECP_EPP_TM_INFO: ret = DecodeEPPTransmitterInfo(msg); break; //!< EPP transmitter information
+ case UECP_SLOW_LABEL_CODES: ret = DecodeSlowLabelingCodes(msg); break; //!< Slow Labeling codes
+ case UECP_DAB_DYN_LABEL_CMD: ret = DecodeDABDynLabelCmd(msg, len); break; //!< DAB: Dynamic Label command
+ case UECP_DAB_DYN_LABEL_MSG: ret = DecodeDABDynLabelMsg(msg, len); break; //!< DAB: Dynamic Label message (DL)
+ case UECP_RDS_AF: ret = DecodeAF(msg, len); break; //!< Alternative Frequencies list
+ case UECP_RDS_EON_AF: ret = DecodeEonAF(msg, len); break; //!< EON Alternative Frequencies list
+ case UECP_TDC_TDC: ret = DecodeTDC(msg, len); break; //!< Transparent Data Channel
+ case UECP_LINKAGE_INFO: ret = 5; break; //!< Linkage information
+ case UECP_TDC_EWS: ret = 6; break; //!< Emergency warning system
+ case UECP_RDS_PIN: ret = 5; break; //!< Program Item Number
+ case UECP_TDC_IH: ret = 7; break; //!< In-house applications (Should be ignored)
+ case UECP_TDC_FREE_FMT_GROUP: ret = 7; break; //!< Free-format group (unused)
+ case UECP_ODA_CONF_SHORT_MSG_CMD: ret = 8; break; //!< ODA Configuration and Short Message Command (unused)
+ case UECP_CLOCK_RTC_CORR: ret = 3; break; //!< Real time clock correction (unused)
+ case UECP_CLOCK_CT_ON_OFF: ret = 2; break; //!< Real time clock on/off (unused)
+ default:
+#ifdef RDS_IMPROVE_CHECK
+ printf("Unknown UECP data packet = 0x%02X\n", msg[UECP_ME_MEC]);
+#endif
+ m_UECPDataDeadBreak = true;
+ break;
+ }
+ ptr += ret;
+ len -= ret;
+ }
+ while (ptr < m_UECPDataIndex-5 && !m_UECPDataDeadBreak && !m_bStop);
+
+ if (m_currentFileUpdate && !m_bStop)
+ {
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(g_application.CurrentFileItem());
+ m_currentFileUpdate = false;
+ }
+ }
+ }
+ }
+ }
+}
+
+unsigned int CDVDRadioRDSData::DecodePI(const uint8_t* msgElement)
+{
+ uint16_t PICode = (msgElement[3] << 8) | msgElement[4];
+ if (m_PI_Current != PICode)
+ {
+ m_PI_Current = PICode;
+
+ m_PI_CountryCode = (m_PI_Current>>12) & 0x0F;
+ m_PI_ProgramType = (m_PI_Current>>8) & 0x0F;
+ m_PI_ProgramReferenceNumber = m_PI_Current & 0xFF;
+
+ CLog::Log(LOGINFO,
+ "Radio UECP (RDS) Processor - PI code changed to Country {:X}, Type {:X} and "
+ "reference no. {}",
+ m_PI_CountryCode, m_PI_ProgramType, m_PI_ProgramReferenceNumber);
+ }
+
+ return 5;
+}
+
+unsigned int CDVDRadioRDSData::DecodePS(uint8_t *msgElement)
+{
+ uint8_t *text = msgElement+3;
+
+ char decodedText[9] = {};
+ for (int i = 0; i < 8; ++i)
+ {
+ if (text[i] <= 0xfe)
+ decodedText[i] = (text[i] >= 0x80)
+ ? sRDSAddChar[text[i] - 0x80]
+ : text[i]; //!< additional rds-character, see RBDS-Standard, Annex E
+ }
+
+ m_currentInfoTag->SetProgramServiceText(decodedText);
+
+ return 11;
+}
+
+unsigned int CDVDRadioRDSData::DecodeDI(const uint8_t* msgElement)
+{
+ bool value;
+
+ value = (msgElement[3] & 1) != 0;
+ if (m_DI_IsStereo != value)
+ {
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - {} - Stream changed over to {}", __FUNCTION__,
+ value ? "Stereo" : "Mono");
+ m_DI_IsStereo = value;
+ }
+
+ value = (msgElement[3] & 2) != 0;
+ if (m_DI_ArtificialHead != value)
+ {
+ CLog::Log(LOGDEBUG,
+ "Radio UECP (RDS) Processor - {} - Stream changed over to {}Artificial Head",
+ __FUNCTION__, value ? "" : "Not ");
+ m_DI_ArtificialHead = value;
+ }
+
+ value = (msgElement[3] & 4) != 0;
+ if (m_DI_ArtificialHead != value)
+ {
+ CLog::Log(LOGDEBUG,
+ "Radio UECP (RDS) Processor - {} - Stream changed over to {}Compressed Head",
+ __FUNCTION__, value ? "" : "Not ");
+ m_DI_ArtificialHead = value;
+ }
+
+ value = (msgElement[3] & 8) != 0;
+ if (m_DI_DynamicPTY != value)
+ {
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - {} - Stream changed over to {} PTY",
+ __FUNCTION__, value ? "dynamic" : "static");
+ m_DI_DynamicPTY = value;
+ }
+
+ return 4;
+}
+
+unsigned int CDVDRadioRDSData::DecodeTA_TP(const uint8_t* msgElement)
+{
+ uint8_t dsn = msgElement[1];
+ bool traffic_announcement = (msgElement[3] & 1) != 0;
+ bool traffic_programme = (msgElement[3] & 2) != 0;
+
+ if (traffic_announcement && !m_TA_TP_TrafficAdvisory && traffic_programme && dsn == 0 && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("pvrplayback.trafficadvisory"))
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(19021), g_localizeStrings.Get(29930));
+ m_TA_TP_TrafficAdvisory = true;
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ m_TA_TP_TrafficVolume = appVolume->GetVolumePercent();
+ float trafAdvVol = (float)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("pvrplayback.trafficadvisoryvolume");
+ if (trafAdvVol)
+ appVolume->SetVolume(m_TA_TP_TrafficVolume + trafAdvVol);
+
+ CVariant data(CVariant::VariantTypeObject);
+ data["on"] = true;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTA", data);
+ }
+
+ if (!traffic_announcement && m_TA_TP_TrafficAdvisory && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("pvrplayback.trafficadvisory"))
+ {
+ m_TA_TP_TrafficAdvisory = false;
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ appVolume->SetVolume(m_TA_TP_TrafficVolume);
+
+ CVariant data(CVariant::VariantTypeObject);
+ data["on"] = false;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTA", data);
+ }
+
+ return 4;
+}
+
+unsigned int CDVDRadioRDSData::DecodeMS(const uint8_t* msgElement)
+{
+ bool speechActive = msgElement[3] == 0;
+ if (m_MS_SpeechActive != speechActive)
+ {
+ m_currentInfoTag->SetSpeechActive(m_MS_SpeechActive);
+ CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - {} - Stream changed over to {}", __FUNCTION__,
+ speechActive ? "Speech" : "Music");
+ m_MS_SpeechActive = speechActive;
+ }
+
+ return 4;
+}
+
+/*!
+ * EBU - SPB 490 - 3.3.7 and 62106IEC:1999 - 3.2.1.2, Message Name: Programme Type
+ * Message Element Code: 07
+ */
+//! @todo Improve and test alarm message
+typedef struct { const char *style_name; int name; } pty_skin_info;
+pty_skin_info pty_skin_info_table[32][2] =
+{
+ { { "none", 29940 }, { "none", 29940 } },
+ { { "news", 29941 }, { "news", 29941 } },
+ { { "currentaffairs", 29942 }, { "information", 29943 } },
+ { { "information", 29943 }, { "sport", 29944 } },
+ { { "sport", 29944 }, { "talk", 29939 } },
+ { { "education", 29945 }, { "rockmusic", 29951 } },
+ { { "drama", 29946 }, { "classicrockmusic",29977 } },
+ { { "cultures", 29947 }, { "adulthits", 29937 } },
+ { { "science", 29948 }, { "softrock", 29938 } },
+ { { "variedspeech", 29949 }, { "top40", 29972 } },
+ { { "popmusic", 29950 }, { "countrymusic", 29965 } },
+ { { "rockmusic", 29951 }, { "oldiesmusic", 29967 } },
+ { { "easylistening", 29952 }, { "softmusic", 29936 } },
+ { { "lightclassics", 29953 }, { "nostalgia", 29979 } },
+ { { "seriousclassics",29954 }, { "jazzmusic", 29964 } },
+ { { "othermusic", 29955 }, { "classical", 29978 } },
+ { { "weather", 29956 }, { "randb", 29975 } },
+ { { "finance", 29957 }, { "softrandb", 29976 } },
+ { { "childrensprogs", 29958 }, { "language", 29932 } },
+ { { "socialaffairs", 29959 }, { "religiousmusic", 29973 } },
+ { { "religion", 29960 }, { "religioustalk", 29974 } },
+ { { "phonein", 29961 }, { "personality", 29934 } },
+ { { "travelandtouring",29962 },{ "public", 29935 } },
+ { { "leisureandhobby",29963 }, { "college", 29933 } },
+ { { "jazzmusic", 29964 }, { "spanishtalk", 29927 } },
+ { { "countrymusic", 29965 }, { "spanishmusic", 29928 } },
+ { { "nationalmusic", 29966 }, { "hiphop", 29929 } },
+ { { "oldiesmusic", 29967 }, { "", -1 } },
+ { { "folkmusic", 29968 }, { "", -1 } },
+ { { "documentary", 29969 }, { "weather", 29956 } },
+ { { "alarmtest", 29970 }, { "alarmtest", 29970 } },
+ { { "alarm-alarm", 29971 }, { "alarm-alarm", 29971 } }
+};
+
+unsigned int CDVDRadioRDSData::DecodePTY(const uint8_t* msgElement)
+{
+ int pty = msgElement[3];
+ if (pty >= 0 && pty < 32 && m_PTY != pty)
+ {
+ m_PTY = pty;
+
+ // save info
+ m_currentInfoTag->SetRadioStyle(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].style_name);
+ if (!m_RTPlus_GenrePresent && !m_PTYN_Present)
+ SetRadioStyle(g_localizeStrings.Get(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].name));
+
+ if (m_PTY == RDS_PTY_ALARM_TEST)
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(29931), g_localizeStrings.Get(29970), TOAST_DISPLAY_TIME, false);
+
+ if (m_PTY == RDS_PTY_ALARM)
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(29931), g_localizeStrings.Get(29971), TOAST_DISPLAY_TIME*2, true);
+ }
+ }
+
+ return 4;
+}
+
+unsigned int CDVDRadioRDSData::DecodePTYN(uint8_t *msgElement)
+{
+ // decode Text
+ uint8_t *text = msgElement+3;
+
+ for (int i = 0; i < 8; ++i)
+ {
+ if (text[i] <= 0xfe)
+ m_PTYN[i] = (text[i] >= 0x80) ? sRDSAddChar[text[i]-0x80] : text[i];
+ }
+
+ m_PTYN_Present = true;
+
+ if (!m_RTPlus_GenrePresent)
+ {
+ std::string progTypeName = StringUtils::Format(
+ "{}: {}", g_localizeStrings.Get(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].name), m_PTYN);
+ SetRadioStyle(progTypeName);
+ }
+
+ return 11;
+}
+
+inline void rtrim_str(std::string &text)
+{
+ for (int i = text.length()-1; i >= 0; --i)
+ {
+ if (text[i] == ' ' || text[i] == '\t' || text[i] == '\n' || text[i] == '\r')
+ text[i] = 0;
+ else
+ break;
+ }
+}
+
+unsigned int CDVDRadioRDSData::DecodeRT(uint8_t *msgElement, unsigned int len)
+{
+ m_currentInfoTag->SetPlayingRadioText(true);
+
+ int bufConf = (msgElement[UECP_ME_DATA] >> 5) & 0x03;
+ unsigned int msgLength = msgElement[UECP_ME_MEL];
+ if (msgLength > len-2)
+ {
+ CLog::Log(LOGERROR,
+ "Radio UECP (RDS) - {} - RT-Error: Length=0 or not correct (MFL= {}, MEL= {})",
+ __FUNCTION__, len, msgLength);
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+ else if (msgLength == 0 || (msgLength == 1 && bufConf == 0))
+ {
+ return msgLength + 4;
+ }
+ else
+ {
+ // bool flagToggle = msgElement[UECP_ME_DATA] & 0x01 ? true : false;
+ // int txQty = (msgElement[UECP_ME_DATA] >> 1) & 0x0F;
+ // int bufConf = (msgElement[UECP_ME_DATA] >> 5) & 0x03;
+
+ //! byte 4 = RT-Status bitcodet (0=AB-flagcontrol, 1-4=Transmission-Number, 5+6=Buffer-Config, ignored, always 0x01 ?)
+ char temptext[RT_MEL];
+ memset(temptext, 0x0, RT_MEL);
+ for (unsigned int i = 1, ii = 0; i < msgLength; ++i)
+ {
+ if (msgElement[UECP_ME_DATA+i] <= 0xfe) // additional rds-character, see RBDS-Standard, Annex E
+ temptext[ii++] = (msgElement[UECP_ME_DATA+i] >= 0x80) ? sRDSAddChar[msgElement[UECP_ME_DATA+i]-0x80] : msgElement[UECP_ME_DATA+i];
+ }
+
+ memcpy(m_RTPlus_WorkText, temptext, RT_MEL);
+ rds_entitychar(temptext);
+
+ m_currentInfoTag->SetRadioText(temptext);
+
+ m_RTPlus_iToggle = 0x03; // Bit 0/1 = Title/Artist
+ }
+ return msgLength+4;
+}
+
+#define UECP_CLOCK_YEAR 1
+#define UECP_CLOCK_MONTH 2
+#define UECP_CLOCK_DAY 3
+#define UECP_CLOCK_HOURS 4
+#define UECP_CLOCK_MINUTES 5
+#define UECP_CLOCK_SECONDS 6
+#define UECP_CLOCK_CENTSEC 7
+#define UECP_CLOCK_LOCALOFFSET 8
+unsigned int CDVDRadioRDSData::DecodeRTC(uint8_t *msgElement)
+{
+ uint8_t hours = msgElement[UECP_CLOCK_HOURS];
+ uint8_t minutes = msgElement[UECP_CLOCK_MINUTES];
+ bool minus = (msgElement[UECP_CLOCK_LOCALOFFSET] & 0x20) != 0;
+ if (minus)
+ {
+ if (msgElement[UECP_CLOCK_LOCALOFFSET] >> 1)
+ hours -= msgElement[UECP_CLOCK_LOCALOFFSET] >> 1;
+ if (msgElement[UECP_CLOCK_LOCALOFFSET] & 1)
+ minutes -= 30;
+ }
+ else
+ {
+ if (msgElement[UECP_CLOCK_LOCALOFFSET] >> 1)
+ hours += msgElement[UECP_CLOCK_LOCALOFFSET] >> 1;
+ if (msgElement[UECP_CLOCK_LOCALOFFSET] & 1)
+ minutes += 30;
+ }
+ m_RTC_DateTime.SetDateTime(msgElement[UECP_CLOCK_YEAR], msgElement[UECP_CLOCK_MONTH], msgElement[UECP_CLOCK_DAY],
+ hours, minutes, msgElement[UECP_CLOCK_SECONDS]);
+
+ CLog::Log(LOGDEBUG,
+ "Radio UECP (RDS) - {} - Current RDS Data Time: {:02}.{:02}.{:02} - UTC: "
+ "{:02}:{:02}:{:02},0.{}s - Local: {}{} min",
+ __FUNCTION__, msgElement[UECP_CLOCK_DAY], msgElement[UECP_CLOCK_MONTH],
+ msgElement[UECP_CLOCK_YEAR], msgElement[UECP_CLOCK_HOURS],
+ msgElement[UECP_CLOCK_MINUTES], msgElement[UECP_CLOCK_SECONDS],
+ msgElement[UECP_CLOCK_CENTSEC], minus ? '-' : '+',
+ msgElement[UECP_CLOCK_LOCALOFFSET] * 30);
+
+ CVariant data(CVariant::VariantTypeObject);
+ data["dateTime"] = (m_RTC_DateTime.IsValid()) ? m_RTC_DateTime.GetAsRFC1123DateTime() : "";
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioRTC", data);
+
+ return 8;
+}
+
+unsigned int CDVDRadioRDSData::DecodeODA(uint8_t *msgElement, unsigned int len)
+{
+ unsigned int procData = msgElement[1];
+ if (procData == 0 || procData > len-2)
+ {
+ CLog::Log(LOGERROR, "Radio UECP (RDS) - Invalid ODA data size");
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ switch ((msgElement[2]<<8)+msgElement[3]) // ODA-ID
+ {
+ case 0x4bd7: //!< RT+
+ procData = DecodeRTPlus(msgElement, len);
+ break;
+ case 0x0d45: //!< TMC Alert-C
+ case 0xcd46:
+ SendTMCSignal(msgElement[4], msgElement+5);
+ break;
+ default:
+ m_UECPDataDeadBreak = true;
+#ifdef RDS_IMPROVE_CHECK
+ printf("[RDS-ODA AID '%02x%02x' not used -> End]\n", msgElement[2], msgElement[3]);
+#endif // RDS_IMPROVE_CHECK
+ break;
+ }
+ return procData;
+}
+
+unsigned int CDVDRadioRDSData::DecodeRTPlus(uint8_t *msgElement, unsigned int len)
+{
+ if (m_RTPlus_iToggle == 0) // RTplus tags V2.1, only if RT
+ return 10;
+
+ m_currentInfoTag->SetPlayingRadioTextPlus(true);
+
+ if (msgElement[1] > len-2 || msgElement[1] != 8) // byte 6 = MEL, only 8 byte for 2 tags
+ {
+ CLog::Log(LOGERROR, "Radio UECP (RDS) - {} - RTp-Error: Length not correct (MEL= {})",
+ __FUNCTION__, msgElement[1]);
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+ unsigned int rtp_typ[2], rtp_start[2], rtp_len[2];
+ // byte 2+3 = ApplicationID, always 0x4bd7
+ // byte 4 = Applicationgroup Typecode / PTY ?
+ // bit 10#4 = Item Togglebit
+ // bit 10#3 = Item Runningbit321
+ // Tag1: bit 10#2..11#5 = Contenttype, 11#4..12#7 = Startmarker, 12#6..12#1 = Length
+ rtp_typ[0] = (0x38 & msgElement[5]<<3) | msgElement[6]>>5;
+ rtp_start[0] = (0x3e & msgElement[6]<<1) | msgElement[7]>>7;
+ rtp_len[0] = 0x3f & msgElement[7]>>1;
+ // Tag2: bit 12#0..13#3 = Contenttype, 13#2..14#5 = Startmarker, 14#4..14#0 = Length(5bit)
+ rtp_typ[1] = (0x20 & msgElement[7]<<5) | msgElement[8]>>3;
+ rtp_start[1] = (0x38 & msgElement[8]<<3) | msgElement[9]>>5;
+ rtp_len[1] = 0x1f & msgElement[9];
+
+ /// Hack for error on BR Classic
+ if ((msgElement[5]&0x10) && (msgElement[5]&0x08) && rtp_typ[0] == RTPLUS_INFO_URL && rtp_typ[1] == RTPLUS_ITEM_ARTIST)
+ return 10;
+
+ // save info
+ MUSIC_INFO::CMusicInfoTag *currentMusic = g_application.CurrentFileItem().GetMusicInfoTag();
+
+ for (int i = 0; i < 2; ++i)
+ {
+ if (rtp_start[i]+rtp_len[i]+1 >= RT_MEL) // length-error
+ {
+ CLog::Log(
+ LOGERROR,
+ "Radio UECP (RDS) - {} - (tag#{} = Typ/Start/Len): {}/{}/{} (Start+Length > 'RT-MEL' !)",
+ __FUNCTION__, i + 1, rtp_typ[i], rtp_start[i], rtp_len[i]);
+ }
+ else
+ {
+ // +Memory
+ memset(m_RTPlus_Temptext, 0x20, RT_MEL);
+ memcpy(m_RTPlus_Temptext, m_RTPlus_WorkText+rtp_start[i], rtp_len[i]+1);
+ m_RTPlus_Temptext[rtp_len[i]+1] = 0;
+ rds_entitychar(m_RTPlus_Temptext);
+ switch (rtp_typ[i])
+ {
+ case RTPLUS_DUMMY_CLASS:
+ break;
+ case RTPLUS_ITEM_TITLE: // Item-Title...
+ if ((msgElement[5] & 0x08) > 0 && (m_RTPlus_iToggle & 0x01) == 0x01)
+ {
+ m_RTPlus_iToggle -= 0x01;
+ if (memcmp(m_RTPlus_Title, m_RTPlus_Temptext, RT_MEL) != 0 || (msgElement[5] & 0x10) != m_RTPlus_ItemToggle)
+ {
+ memcpy(m_RTPlus_Title, m_RTPlus_Temptext, RT_MEL);
+ if (m_RTPlus_Show && m_RTPlus_iTime.GetElapsedSeconds() > 1)
+ m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
+ if (!m_RT_NewItem)
+ {
+ m_RTPlus_Starttime = time(NULL);
+ m_RTPlus_iTime.StartZero();
+ m_RTPlus_Artist[0] = 0;
+ }
+ m_RT_NewItem = (!m_RT_NewItem) ? true : false;
+ m_RTPlus_Show = m_RTPlus_TToggle = true;
+ }
+ }
+ break;
+ case RTPLUS_ITEM_ALBUM:
+ m_currentInfoTag->SetAlbum(m_RTPlus_Temptext);
+ currentMusic->SetAlbum(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_ITEM_TRACKNUMBER:
+ m_currentInfoTag->SetAlbumTrackNumber(atoi(m_RTPlus_Temptext));
+ currentMusic->SetAlbumId(atoi(m_RTPlus_Temptext));
+ break;
+ case RTPLUS_ITEM_ARTIST: // Item-Artist..
+ if ((msgElement[5] & 0x08) > 0 && (m_RTPlus_iToggle & 0x02) == 0x02)
+ {
+ m_RTPlus_iToggle -= 0x02;
+ if (memcmp(m_RTPlus_Artist, m_RTPlus_Temptext, RT_MEL) != 0 || (msgElement[5] & 0x10) != m_RTPlus_ItemToggle)
+ {
+ memcpy(m_RTPlus_Artist, m_RTPlus_Temptext, RT_MEL);
+ if (m_RTPlus_Show && m_RTPlus_iTime.GetElapsedSeconds() > 1)
+ m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
+ if (!m_RT_NewItem)
+ {
+ m_RTPlus_Starttime = time(NULL);
+ m_RTPlus_iTime.StartZero();
+ m_RTPlus_Title[0] = 0;
+ }
+ m_RT_NewItem = (!m_RT_NewItem) ? true : false;
+ m_RTPlus_Show = m_RTPlus_TToggle = true;
+ }
+ }
+ break;
+ case RTPLUS_ITEM_CONDUCTOR:
+ m_currentInfoTag->SetConductor(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_ITEM_COMPOSER:
+ case RTPLUS_ITEM_COMPOSITION:
+ m_currentInfoTag->SetComposer(m_RTPlus_Temptext);
+ if (m_currentInfoTag->GetRadioStyle() == "unknown")
+ m_currentInfoTag->SetRadioStyle("classical");
+ break;
+ case RTPLUS_ITEM_BAND:
+ m_currentInfoTag->SetBand(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_ITEM_COMMENT:
+ m_currentInfoTag->SetComment(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_ITEM_GENRE:
+ {
+ std::string str = m_RTPlus_Temptext;
+ g_charsetConverter.unknownToUTF8(str);
+ m_RTPlus_GenrePresent = true;
+ m_currentInfoTag->SetProgStyle(str);
+ }
+ break;
+ case RTPLUS_INFO_NEWS: // Info_News
+ m_currentInfoTag->SetInfoNews(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_NEWS_LOCAL: // Info_NewsLocal
+ m_currentInfoTag->SetInfoNewsLocal(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_STOCKMARKET: // Info_Stockmarket
+ m_currentInfoTag->SetInfoStock(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_SPORT: // Info_Sport
+ m_currentInfoTag->SetInfoSport(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_LOTTERY: // Info_Lottery
+ m_currentInfoTag->SetInfoLottery(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_HOROSCOPE:
+ m_currentInfoTag->SetInfoHoroscope(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_CINEMA:
+ m_currentInfoTag->SetInfoCinema(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_WEATHER: // Info_Weather/
+ m_currentInfoTag->SetInfoWeather(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_URL: // Info_Url
+ if (m_currentInfoTag->GetProgWebsite().empty())
+ m_currentInfoTag->SetProgWebsite(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_INFO_OTHER: // Info_Other
+ m_currentInfoTag->SetInfoOther(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_STATIONNAME_LONG: // Programme_Stationname.Long
+ m_currentInfoTag->SetProgStation(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PROGRAMME_NOW: // Programme_Now
+ m_currentInfoTag->SetProgNow(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PROGRAMME_NEXT: // Programme_Next
+ m_currentInfoTag->SetProgNext(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PROGRAMME_HOST: // Programme_Host
+ m_currentInfoTag->SetProgHost(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PROGRAMME_EDITORIAL_STAFF: // Programme_EditorialStaff
+ m_currentInfoTag->SetEditorialStaff(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PROGRAMME_HOMEPAGE: // Programme_Homepage
+ m_currentInfoTag->SetProgWebsite(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PHONE_HOTLINE: // Phone_Hotline
+ m_currentInfoTag->SetPhoneHotline(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_PHONE_STUDIO: // Phone_Studio
+ m_currentInfoTag->SetPhoneStudio(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_SMS_STUDIO: // SMS_Studio
+ m_currentInfoTag->SetSMSStudio(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_EMAIL_HOTLINE: // Email_Hotline
+ m_currentInfoTag->SetEMailHotline(m_RTPlus_Temptext);
+ break;
+ case RTPLUS_EMAIL_STUDIO: // Email_Studio
+ m_currentInfoTag->SetEMailStudio(m_RTPlus_Temptext);
+ break;
+ /**
+ * Currently unused radiotext plus messages
+ * Must be check where present and if it is usable
+ */
+ case RTPLUS_ITEM_MOVEMENT:
+ case RTPLUS_INFO_DAILY_DIVERSION:
+ case RTPLUS_INFO_HEALTH:
+ case RTPLUS_INFO_EVENT:
+ case RTPLUS_INFO_SZENE:
+ case RTPLUS_INFO_STUPIDITY_MACHINE:
+ case RTPLUS_INFO_TRAFFIC:
+ case RTPLUS_INFO_ALARM:
+ case RTPLUS_INFO_ADVERTISEMENT:
+ case RTPLUS_PROGRAMME_PART:
+ case RTPLUS_PROGRAMME_FREQUENCY:
+ case RTPLUS_PROGRAMME_SUBCHANNEL:
+ case RTPLUS_PHONE_OTHER:
+ case RTPLUS_SMS_OTHER:
+ case RTPLUS_EMAIL_OTHER:
+ case RTPLUS_MMS_OTHER:
+ case RTPLUS_CHAT:
+ case RTPLUS_CHAT_CENTER:
+ case RTPLUS_VOTE_QUESTION:
+ case RTPLUS_VOTE_CENTER:
+ case RTPLUS_PLACE:
+ case RTPLUS_APPOINTMENT:
+ case RTPLUS_IDENTIFIER:
+ case RTPLUS_PURCHASE:
+ case RTPLUS_GET_DATA:
+#ifdef RDS_IMPROVE_CHECK
+ printf(" RTp-Unkn. : %02i - %s\n", rtp_typ[i], m_RTPlus_Temptext);
+ break;
+#endif // RDS_IMPROVE_CHECK
+ /// Unused and not needed data information
+ case RTPLUS_STATIONNAME_SHORT: //!< Must be rechecked under DAB
+ case RTPLUS_INFO_DATE_TIME:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Title-end @ no Item-Running'
+ if ((msgElement[5] & 0x08) == 0)
+ {
+ m_RTPlus_Title[0] = 0;
+ m_RTPlus_Artist[0] = 0;
+ m_currentInfoTag->ResetSongInformation();
+ currentMusic->SetAlbum("");
+ if (m_RTPlus_GenrePresent)
+ {
+ m_currentInfoTag->SetProgStyle("");
+ m_RTPlus_GenrePresent = false;
+ }
+
+ if (m_RTPlus_Show)
+ {
+ m_RTPlus_Show = false;
+ m_RTPlus_TToggle = true;
+ m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
+ m_RTPlus_Starttime = time(NULL);
+ }
+ m_RT_NewItem = false;
+ }
+
+ if (m_RTPlus_TToggle)
+ {
+#ifdef RDS_IMPROVE_CHECK
+ {
+ struct tm tm_store;
+ struct tm *ts = localtime_r(&m_RTPlus_Starttime, &tm_store);
+ if (m_RTPlus_iDiffs > 0)
+ printf(" StartTime : %02d:%02d:%02d (last Title elapsed = %d s)\n", ts->tm_hour, ts->tm_min, ts->tm_sec, m_RTPlus_iDiffs);
+ else
+ printf(" StartTime : %02d:%02d:%02d\n", ts->tm_hour, ts->tm_min, ts->tm_sec);
+ printf(" RTp-Title : %s\n RTp-Artist: %s\n", m_RTPlus_Title, m_RTPlus_Artist);
+ }
+#endif // RDS_IMPROVE_CHECK
+ m_RTPlus_ItemToggle = msgElement[5] & 0x10;
+ m_RTPlus_TToggle = false;
+ m_RTPlus_iDiffs = 0;
+
+ std::string str;
+
+ str = m_RTPlus_Artist;
+ m_currentInfoTag->SetArtist(str);
+ if (str.empty() && !m_currentInfoTag->GetComposer().empty())
+ str = m_currentInfoTag->GetComposer();
+ else if (str.empty() && !m_currentInfoTag->GetConductor().empty())
+ str = m_currentInfoTag->GetConductor();
+ else if (str.empty() && !m_currentInfoTag->GetBand().empty())
+ str = m_currentInfoTag->GetBand();
+
+ if (!str.empty())
+ g_charsetConverter.unknownToUTF8(str);
+ currentMusic->SetArtist(str);
+
+ str = m_RTPlus_Title;
+ g_charsetConverter.unknownToUTF8(str);
+ currentMusic->SetTitle(str);
+ m_currentInfoTag->SetTitle(str);
+ m_currentFileUpdate = true;
+ }
+ m_RTPlus_iToggle = 0;
+
+ return 10;
+}
+
+unsigned int CDVDRadioRDSData::DecodeTMC(uint8_t *msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[1];
+ if (msgElementLength == 0)
+ msgElementLength = 6;
+ if (msgElementLength + 2 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ for (unsigned int i = 0; i < msgElementLength; i += 5)
+ SendTMCSignal(msgElement[2], msgElement+3+i);
+
+ return msgElementLength + 2;
+}
+
+unsigned int CDVDRadioRDSData::DecodeEPPTransmitterInfo(const uint8_t* msgElement)
+{
+ if (!m_RDS_SlowLabelingCodesPresent && m_PI_CountryCode != 0)
+ {
+ int codeHigh = msgElement[2]&0xF0;
+ int codeLow = msgElement[2]&0x0F;
+ if (codeLow > 7)
+ {
+ CLog::Log(LOGERROR, "Radio RDS - {} - invalid country code {:#02X}{:02X}", __FUNCTION__,
+ codeHigh, codeLow);
+ return 7;
+ }
+
+ std::string countryName;
+ switch (codeHigh)
+ {
+ case 0xA0:
+ countryName = piCountryCodes_A[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xD0:
+ countryName = piCountryCodes_D[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xE0:
+ countryName = piCountryCodes_E[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xF0:
+ countryName = piCountryCodes_F[m_PI_CountryCode-1][codeLow];
+ break;
+ default:
+ CLog::Log(LOGERROR, "Radio RDS - {} - invalid extended country region code:{:02X}{:02X}",
+ __FUNCTION__, codeHigh, codeLow);
+ return 7;
+ }
+
+ // The United States, Canada, and Mexico use the RBDS standard
+ m_RDS_IsRBDS = (countryName == "US" || countryName == "CA" || countryName == "MX");
+
+ m_currentInfoTag->SetCountry(countryName);
+ }
+
+ return 7;
+}
+
+/* SLOW LABELLING: see page 23 in the standard
+ * for paging see page 90, Annex M in the standard (NOT IMPLEMENTED)
+ * for extended country codes see page 69, Annex D.2 in the standard
+ * for language codes see page 84, Annex J in the standard
+ * for emergency warning systems (EWS) see page 53 in the standard */
+#define VARCODE_PAGING_EXTCOUNTRYCODE 0
+#define VARCODE_TMC_IDENT 1
+#define VARCODE_PAGING_IDENT 2
+#define VARCODE_LANGUAGE_CODES 3
+#define VARCODE_OWN_BROADCASTER 6
+#define VARCODE_EWS_CHANNEL_IDENT 7
+unsigned int CDVDRadioRDSData::DecodeSlowLabelingCodes(const uint8_t* msgElement)
+{
+ uint16_t slowLabellingCode = (msgElement[2]<<8 | msgElement[3]) & 0xfff;
+ int VariantCode = (msgElement[2]>>4) & 0x7;
+
+ switch (VariantCode)
+ {
+ case VARCODE_PAGING_EXTCOUNTRYCODE: // paging + ecc
+ {
+ // int paging = (slowLabellingCode>>8)&0x0f; unused
+
+ if (m_PI_CountryCode != 0)
+ {
+ int codeHigh = slowLabellingCode&0xF0;
+ int codeLow = slowLabellingCode&0x0F;
+ if (codeLow > 5)
+ {
+ CLog::Log(LOGERROR, "Radio RDS - {} - invalid country code {:#02X}{:02X}", __FUNCTION__,
+ codeHigh, codeLow);
+ return 4;
+ }
+
+ std::string countryName;
+ switch (codeHigh)
+ {
+ case 0xA0:
+ countryName = piCountryCodes_A[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xD0:
+ countryName = piCountryCodes_D[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xE0:
+ countryName = piCountryCodes_E[m_PI_CountryCode-1][codeLow];
+ break;
+ case 0xF0:
+ countryName = piCountryCodes_F[m_PI_CountryCode-1][codeLow];
+ break;
+ default:
+ CLog::Log(LOGERROR,
+ "Radio RDS - {} - invalid extended country region code:{:02X}{:02X}",
+ __FUNCTION__, codeHigh, codeLow);
+ return 4;
+ }
+
+ m_currentInfoTag->SetCountry(countryName);
+ }
+ break;
+ }
+ case VARCODE_LANGUAGE_CODES: // language codes
+ if (slowLabellingCode > 1 && slowLabellingCode < 0x80)
+ m_currentInfoTag->SetLanguage(piRDSLanguageCodes[slowLabellingCode]);
+ else
+ CLog::Log(LOGERROR, "Radio RDS - {} - invalid language code {}", __FUNCTION__,
+ slowLabellingCode);
+ break;
+
+ case VARCODE_TMC_IDENT: // TMC identification
+ case VARCODE_PAGING_IDENT: // Paging identification
+ case VARCODE_OWN_BROADCASTER:
+ case VARCODE_EWS_CHANNEL_IDENT:
+ default:
+ break;
+ }
+
+ m_RDS_SlowLabelingCodesPresent = true;
+ return 4;
+}
+
+/*!
+ * currently unused need to be checked on DAB, processed here to have length of it
+ */
+unsigned int CDVDRadioRDSData::DecodeDABDynLabelCmd(const uint8_t* msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[1];
+ if (msgElementLength < 1 || msgElementLength + 2 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ return msgElementLength+2;
+}
+
+/*!
+ * currently unused need to be checked on DAB, processed here to have length of it
+ */
+unsigned int CDVDRadioRDSData::DecodeDABDynLabelMsg(const uint8_t* msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[1];
+ if (msgElementLength < 2 || msgElementLength + 2 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ return msgElementLength+2;
+}
+
+/*!
+ * unused processed here to have length of it
+ */
+unsigned int CDVDRadioRDSData::DecodeAF(uint8_t *msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[3];
+ if (msgElementLength < 3 || msgElementLength + 4 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ return msgElementLength+4;
+}
+
+/*!
+ * unused processed here to have length of it
+ */
+unsigned int CDVDRadioRDSData::DecodeEonAF(uint8_t *msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[3];
+ if (msgElementLength < 4 || msgElementLength + 4 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ return msgElementLength+4;
+}
+
+/*!
+ * unused processed here to have length of it
+ */
+unsigned int CDVDRadioRDSData::DecodeTDC(uint8_t *msgElement, unsigned int len)
+{
+ unsigned int msgElementLength = msgElement[1];
+ if (msgElementLength < 2 || msgElementLength+2 > len)
+ {
+ m_UECPDataDeadBreak = true;
+ return 0;
+ }
+
+ return msgElementLength+2;
+}
+
+void CDVDRadioRDSData::SendTMCSignal(unsigned int flags, uint8_t *data)
+{
+ if (!(flags & 0x80) && (memcmp(data, m_TMC_LastData, 5) == 0))
+ return;
+
+ memcpy(m_TMC_LastData, data, 5);
+
+ if (m_currentChannel)
+ {
+ CVariant msg(CVariant::VariantTypeObject);
+ msg["channel"] = m_currentChannel->ChannelName();
+ msg["ident"] = m_PI_Current;
+ msg["flags"] = flags;
+ msg["x"] = m_TMC_LastData[0];
+ msg["y"] = (unsigned int)(m_TMC_LastData[1]<<8 | m_TMC_LastData[2]);
+ msg["z"] = (unsigned int)(m_TMC_LastData[3]<<8 | m_TMC_LastData[4]);
+
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTMC", msg);
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.h b/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.h
new file mode 100644
index 0000000..0e593c1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDMessageQueue.h"
+#include "IVideoPlayer.h"
+#include "threads/Thread.h"
+#include "utils/Stopwatch.h"
+
+#include <memory>
+
+class CDVDStreamInfo;
+
+namespace PVR
+{
+class CPVRChannel;
+class CPVRRadioRDSInfoTag;
+}
+
+/// --- CDVDRadioRDSData ------------------------------------------------------------
+
+#define UECP_DATA_START 0xFE /*!< A data record starts with the start byte */
+#define UECP_DATA_STOP 0xFF /*!< A data record stops with the stop byte */
+#define UECP_SIZE_MAX 263 /*!< The Max possible size of a UECP packet
+ max. 255(MSG)+4(ADD/SQC/MFL)+2(CRC)+2(Start/Stop) of RDS-data */
+#define RT_MEL 65
+#define MAX_RTPC 50
+
+class CDVDRadioRDSData : public CThread, public IDVDStreamPlayer
+{
+public:
+ explicit CDVDRadioRDSData(CProcessInfo &processInfo);
+ ~CDVDRadioRDSData() override;
+
+ bool CheckStream(const CDVDStreamInfo& hints);
+ bool OpenStream(CDVDStreamInfo hints) override;
+ void CloseStream(bool bWaitForBuffers) override;
+ void Flush();
+
+ // waits until all available data has been rendered
+ void WaitForBuffers() { m_messageQueue.WaitUntilEmpty(); }
+ bool AcceptsData() const override { return !m_messageQueue.IsFull(); }
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override
+ {
+ if (m_messageQueue.IsInited())
+ m_messageQueue.Put(pMsg, priority);
+ }
+ void FlushMessages() override { m_messageQueue.Flush(); }
+ bool IsInited() const override { return true; }
+ bool IsStalled() const override { return true; }
+
+protected:
+ void OnExit() override;
+ void Process() override;
+
+private:
+ void ResetRDSCache();
+ void ProcessUECP(const unsigned char *Data, unsigned int Length);
+
+ inline unsigned int DecodePI(const uint8_t* msgElement);
+ inline unsigned int DecodePS(uint8_t *msgElement);
+ inline unsigned int DecodeDI(const uint8_t* msgElement);
+ inline unsigned int DecodeTA_TP(const uint8_t* msgElement);
+ inline unsigned int DecodeMS(const uint8_t* msgElement);
+ inline unsigned int DecodePTY(const uint8_t* msgElement);
+ inline unsigned int DecodePTYN(uint8_t *msgElement);
+ inline unsigned int DecodeRT(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeRTC(uint8_t *msgElement);
+ inline unsigned int DecodeODA(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeRTPlus(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeTMC(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeEPPTransmitterInfo(const uint8_t* msgElement);
+ inline unsigned int DecodeSlowLabelingCodes(const uint8_t* msgElement);
+ inline unsigned int DecodeDABDynLabelCmd(const uint8_t* msgElement, unsigned int len);
+ inline unsigned int DecodeDABDynLabelMsg(const uint8_t* msgElement, unsigned int len);
+ inline unsigned int DecodeAF(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeEonAF(uint8_t *msgElement, unsigned int len);
+ inline unsigned int DecodeTDC(uint8_t *msgElement, unsigned int len);
+
+ void SendTMCSignal(unsigned int flags, uint8_t *data);
+ void SetRadioStyle(const std::string& genre);
+
+ std::shared_ptr<PVR::CPVRRadioRDSInfoTag> m_currentInfoTag;
+ std::shared_ptr<PVR::CPVRChannel> m_currentChannel;
+ bool m_currentFileUpdate;
+ int m_speed;
+ CCriticalSection m_critSection;
+ CDVDMessageQueue m_messageQueue;
+
+ uint8_t m_UECPData[UECP_SIZE_MAX+1];
+ unsigned int m_UECPDataIndex;
+ bool m_UECPDataStart;
+ bool m_UECPDatabStuff;
+ bool m_UECPDataDeadBreak;
+
+ bool m_RDS_IsRBDS;
+ bool m_RDS_SlowLabelingCodesPresent;
+
+ uint16_t m_PI_Current;
+ unsigned int m_PI_CountryCode;
+ unsigned int m_PI_ProgramType;
+ unsigned int m_PI_ProgramReferenceNumber;
+
+ unsigned int m_EPP_TM_INFO_ExtendedCountryCode;
+
+ bool m_DI_IsStereo;
+ bool m_DI_ArtificialHead;
+ bool m_DI_Compressed;
+ bool m_DI_DynamicPTY;
+
+ bool m_TA_TP_TrafficAdvisory;
+ float m_TA_TP_TrafficVolume;
+
+ bool m_MS_SpeechActive;
+
+ int m_PTY;
+ char m_PTYN[9];
+ bool m_PTYN_Present;
+
+ bool m_RT_NewItem;
+
+ uint8_t m_RTPlus_WorkText[RT_MEL+1];
+ bool m_RTPlus_TToggle;
+ int m_RTPlus_iDiffs;
+ CStopWatch m_RTPlus_iTime;
+ bool m_RTPlus_GenrePresent;
+ char m_RTPlus_Temptext[RT_MEL];
+ bool m_RTPlus_Show;
+ char m_RTPlus_Title[RT_MEL];
+ char m_RTPlus_Artist[RT_MEL];
+ int m_RTPlus_iToggle;
+ unsigned int m_RTPlus_ItemToggle;
+ time_t m_RTPlus_Starttime;
+
+ CDateTime m_RTC_DateTime; ///< From RDS transmitted date / time data
+
+ uint8_t m_TMC_LastData[5];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.cpp b/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.cpp
new file mode 100644
index 0000000..2632090
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerSubtitle.h"
+
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "DVDCodecs/Overlay/DVDOverlay.h"
+#include "DVDCodecs/Overlay/DVDOverlayCodec.h"
+#include "DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "DVDSubtitles/DVDSubtitleParser.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+CVideoPlayerSubtitle::CVideoPlayerSubtitle(CDVDOverlayContainer* pOverlayContainer, CProcessInfo &processInfo)
+: IDVDStreamPlayer(processInfo)
+{
+ m_pOverlayContainer = pOverlayContainer;
+ m_lastPts = DVD_NOPTS_VALUE;
+}
+
+CVideoPlayerSubtitle::~CVideoPlayerSubtitle()
+{
+ CloseStream(true);
+}
+
+
+void CVideoPlayerSubtitle::Flush()
+{
+ SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_FLUSH), 0);
+}
+
+void CVideoPlayerSubtitle::SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ auto pMsgDemuxerPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg);
+ DemuxPacket* pPacket = pMsgDemuxerPacket->GetPacket();
+
+ if (m_pOverlayCodec)
+ {
+ OverlayMessage result = m_pOverlayCodec->Decode(pPacket);
+
+ if (result == OverlayMessage::OC_OVERLAY)
+ {
+ CDVDOverlay* overlay;
+
+ while ((overlay = m_pOverlayCodec->GetOverlay()))
+ {
+ m_pOverlayContainer->ProcessAndAddOverlayIfValid(overlay);
+ overlay->Release();
+ }
+ }
+ }
+ else if (m_streaminfo.codec == AV_CODEC_ID_DVD_SUBTITLE)
+ {
+ CDVDOverlaySpu* pSPUInfo = m_dvdspus.AddData(pPacket->pData, pPacket->iSize, pPacket->pts);
+ if (pSPUInfo)
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayer::ProcessSubData: Got complete SPU packet");
+ m_pOverlayContainer->ProcessAndAddOverlayIfValid(pSPUInfo);
+ pSPUInfo->Release();
+ }
+ }
+
+ }
+ else if( pMsg->IsType(CDVDMsg::SUBTITLE_CLUTCHANGE) )
+ {
+ auto pData = std::static_pointer_cast<CDVDMsgSubtitleClutChange>(pMsg);
+ for (int i = 0; i < 16; i++)
+ {
+ uint8_t* color = m_dvdspus.m_clut[i];
+ uint8_t* t = (uint8_t*)pData->m_data[i];
+
+// pData->m_data[i] points to an uint32_t
+// Byte swapping is needed between big and little endian systems
+#ifdef WORDS_BIGENDIAN
+ color[0] = t[1]; // Y
+ color[1] = t[2]; // Cr
+ color[2] = t[3]; // Cb
+#else
+ color[0] = t[2]; // Y
+ color[1] = t[0]; // Cr
+ color[2] = t[1]; // Cb
+#endif
+ }
+ m_dvdspus.m_bHasClut = true;
+ }
+ else if( pMsg->IsType(CDVDMsg::GENERAL_FLUSH)
+ || pMsg->IsType(CDVDMsg::GENERAL_RESET) )
+ {
+ m_dvdspus.Reset();
+ if (m_pSubtitleFileParser)
+ m_pSubtitleFileParser->Reset();
+
+ if (m_pOverlayCodec)
+ m_pOverlayCodec->Flush();
+
+ /* We must flush active overlays on flush or if we have a file
+ * parser since it will re-populate active items. */
+ if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH) || m_pSubtitleFileParser)
+ m_pOverlayContainer->Flush();
+
+ m_lastPts = DVD_NOPTS_VALUE;
+ }
+}
+
+bool CVideoPlayerSubtitle::OpenStream(CDVDStreamInfo &hints, std::string &filename)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ CloseStream(false);
+ m_streaminfo = hints;
+
+ // okey check if this is a filesubtitle
+ if(filename.size() && filename != "dvd" )
+ {
+ m_pSubtitleFileParser.reset(CDVDFactorySubtitle::CreateParser(filename));
+ if (!m_pSubtitleFileParser)
+ {
+ CLog::Log(LOGERROR, "{} - Unable to create subtitle parser", __FUNCTION__);
+ CloseStream(true);
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "Created subtitles parser: {}", m_pSubtitleFileParser->GetName());
+
+ if (!m_pSubtitleFileParser->Open(hints))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to init subtitle parser", __FUNCTION__);
+ CloseStream(true);
+ return false;
+ }
+ m_pSubtitleFileParser->Reset();
+ return true;
+ }
+
+ // dvd's use special subtitle decoder
+ if(hints.codec == AV_CODEC_ID_DVD_SUBTITLE && filename == "dvd")
+ return true;
+
+ m_pOverlayCodec = CDVDFactoryCodec::CreateOverlayCodec(hints);
+ if (m_pOverlayCodec)
+ {
+ CLog::Log(LOGDEBUG, "Created subtitles overlay codec: {}", m_pOverlayCodec->GetName());
+ return true;
+ }
+
+ CLog::Log(LOGERROR, "{} - Unable to init overlay codec", __FUNCTION__);
+ return false;
+}
+
+void CVideoPlayerSubtitle::CloseStream(bool bWaitForBuffers)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ m_pSubtitleFileParser.reset();
+ m_pOverlayCodec.reset();
+
+ m_dvdspus.FlushCurrentPacket();
+
+ if (!bWaitForBuffers)
+ m_pOverlayContainer->Clear();
+}
+
+void CVideoPlayerSubtitle::Process(double pts, double offset)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ if (m_pSubtitleFileParser)
+ {
+ if(pts == DVD_NOPTS_VALUE)
+ return;
+
+ if (pts + DVD_SEC_TO_TIME(1) < m_lastPts)
+ {
+ m_pOverlayContainer->Clear();
+ m_pSubtitleFileParser->Reset();
+ }
+
+ if(m_pOverlayContainer->GetSize() >= 5)
+ return;
+
+ CDVDOverlay* pOverlay = m_pSubtitleFileParser->Parse(pts);
+ // add all overlays which fit the pts
+ while(pOverlay)
+ {
+ pOverlay->iPTSStartTime -= offset;
+ if(pOverlay->iPTSStopTime != 0.0)
+ pOverlay->iPTSStopTime -= offset;
+
+ m_pOverlayContainer->ProcessAndAddOverlayIfValid(pOverlay);
+ pOverlay->Release();
+ pOverlay = m_pSubtitleFileParser->Parse(pts);
+ }
+
+ m_lastPts = pts;
+ }
+}
+
+bool CVideoPlayerSubtitle::AcceptsData() const
+{
+ // FIXME : This may still be causing problems + magic number :(
+ return m_pOverlayContainer->GetSize() < 5;
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.h b/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.h
new file mode 100644
index 0000000..23baecc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerSubtitle.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDDemuxSPU.h"
+#include "DVDMessageQueue.h"
+#include "DVDOverlayContainer.h"
+#include "DVDStreamInfo.h"
+#include "DVDSubtitles/DVDFactorySubtitle.h"
+#include "IVideoPlayer.h"
+
+class CDVDInputStream;
+class CDVDSubtitleStream;
+class CDVDSubtitleParser;
+class CDVDInputStreamNavigator;
+class CDVDOverlayCodec;
+
+class CVideoPlayerSubtitle : public IDVDStreamPlayer
+{
+public:
+ CVideoPlayerSubtitle(CDVDOverlayContainer* pOverlayContainer, CProcessInfo &processInfo);
+ ~CVideoPlayerSubtitle() override;
+
+ void Process(double pts, double offset);
+ void Flush();
+ void FindSubtitles(const char* strFilename);
+ int GetSubtitleCount();
+
+ void UpdateOverlayInfo(const std::shared_ptr<CDVDInputStreamNavigator>& pStream, int iAction)
+ {
+ m_pOverlayContainer->UpdateOverlayInfo(pStream, &m_dvdspus, iAction);
+ }
+
+ bool AcceptsData() const override;
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override;
+ void FlushMessages() override {}
+ bool OpenStream(CDVDStreamInfo hints) override { return OpenStream(hints, hints.filename); }
+ bool OpenStream(CDVDStreamInfo &hints, std::string& filename);
+ void CloseStream(bool bWaitForBuffers) override;
+
+ bool IsInited() const override { return true; }
+ bool IsStalled() const override { return m_pOverlayContainer->GetSize() == 0; }
+private:
+ CDVDOverlayContainer* m_pOverlayContainer;
+
+ std::unique_ptr<CDVDSubtitleParser> m_pSubtitleFileParser;
+ std::unique_ptr<CDVDOverlayCodec> m_pOverlayCodec;
+ CDVDDemuxSPU m_dvdspus;
+
+ CDVDStreamInfo m_streaminfo;
+ double m_lastPts;
+
+
+ CCriticalSection m_section;
+};
+
+
+//typedef struct SubtitleInfo
+//{
+
+//
+//} SubtitleInfo;
+
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp
new file mode 100644
index 0000000..289cc29
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerTeletext.h"
+
+#include "DVDStreamInfo.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+const uint8_t rev_lut[32] =
+{
+ 0x00,0x08,0x04,0x0c, /* upper nibble */
+ 0x02,0x0a,0x06,0x0e,
+ 0x01,0x09,0x05,0x0d,
+ 0x03,0x0b,0x07,0x0f,
+ 0x00,0x80,0x40,0xc0, /* lower nibble */
+ 0x20,0xa0,0x60,0xe0,
+ 0x10,0x90,0x50,0xd0,
+ 0x30,0xb0,0x70,0xf0
+};
+
+void CDVDTeletextTools::NextDec(int *i) /* skip to next decimal */
+{
+ (*i)++;
+
+ if ((*i & 0x0F) > 0x09)
+ *i += 0x06;
+
+ if ((*i & 0xF0) > 0x90)
+ *i += 0x60;
+
+ if (*i > 0x899)
+ *i = 0x100;
+}
+
+void CDVDTeletextTools::PrevDec(int *i) /* counting down */
+{
+ (*i)--;
+
+ if ((*i & 0x0F) > 0x09)
+ *i -= 0x06;
+
+ if ((*i & 0xF0) > 0x90)
+ *i -= 0x60;
+
+ if (*i < 0x100)
+ *i = 0x899;
+}
+
+/* print hex-number into string, s points to last digit, caller has to provide enough space, no termination */
+void CDVDTeletextTools::Hex2Str(char *s, unsigned int n)
+{
+ do {
+ char c = (n & 0xF);
+ *s-- = number2char(c);
+ n >>= 4;
+ } while (n);
+}
+
+signed int CDVDTeletextTools::deh24(unsigned char *p)
+{
+ int e = hamm24par[0][p[0]]
+ ^ hamm24par[1][p[1]]
+ ^ hamm24par[2][p[2]];
+
+ int x = hamm24val[p[0]]
+ + (p[1] & 127) * 16
+ + (p[2] & 127) * 2048;
+
+ return (x ^ hamm24cor[e]) | hamm24err[e];
+}
+
+
+CDVDTeletextData::CDVDTeletextData(CProcessInfo &processInfo)
+: CThread("DVDTeletextData")
+, IDVDStreamPlayer(processInfo)
+, m_messageQueue("teletext")
+{
+ m_speed = DVD_PLAYSPEED_NORMAL;
+
+ m_messageQueue.SetMaxDataSize(40 * 256 * 1024);
+
+ /* Initialize Data structures */
+ memset(&m_TXTCache->astCachetable, 0, sizeof(m_TXTCache->astCachetable));
+ memset(&m_TXTCache->astP29, 0, sizeof(m_TXTCache->astP29));
+ ResetTeletextCache();
+}
+
+CDVDTeletextData::~CDVDTeletextData()
+{
+ StopThread();
+ ResetTeletextCache();
+}
+
+bool CDVDTeletextData::CheckStream(CDVDStreamInfo &hints)
+{
+ if (hints.codec == AV_CODEC_ID_DVB_TELETEXT)
+ return true;
+
+ return false;
+}
+
+bool CDVDTeletextData::OpenStream(CDVDStreamInfo hints)
+{
+ CloseStream(true);
+
+ m_messageQueue.Init();
+
+ if (hints.codec == AV_CODEC_ID_DVB_TELETEXT)
+ {
+ CLog::Log(LOGINFO, "Creating teletext data thread");
+ Create();
+ return true;
+ }
+
+ return false;
+}
+
+void CDVDTeletextData::CloseStream(bool bWaitForBuffers)
+{
+ m_messageQueue.Abort();
+
+ // wait for decode_video thread to end
+ CLog::Log(LOGINFO, "waiting for teletext data thread to exit");
+
+ StopThread(); // will set this->m_bStop to true
+
+ m_messageQueue.End();
+ ResetTeletextCache();
+}
+
+
+void CDVDTeletextData::ResetTeletextCache()
+{
+ std::unique_lock<CCriticalSection> lock(m_TXTCache->m_critSection);
+
+ /* Reset Data structures */
+ for (auto& pages : m_TXTCache->astCachetable)
+ {
+ for (TextCachedPage_t*& page : pages)
+ {
+ if (page)
+ {
+ TextPageinfo_t *p = &(page->pageinfo);
+ if (p->p24)
+ free(p->p24);
+
+ if (p->ext)
+ {
+ if (p->ext->p27)
+ free(p->ext->p27);
+
+ for (unsigned char* const d26 : p->ext->p26)
+ {
+ if (d26)
+ free(d26);
+ }
+ free(p->ext);
+ }
+ delete page;
+ page = 0;
+ }
+ }
+ }
+
+ for (int i = 0; i < 9; i++)
+ {
+ if (m_TXTCache->astP29[i])
+ {
+ if (m_TXTCache->astP29[i]->p27)
+ free(m_TXTCache->astP29[i]->p27);
+
+ for (unsigned char* const d26 : m_TXTCache->astP29[i]->p26)
+ {
+ if (d26)
+ free(d26);
+ }
+ free(m_TXTCache->astP29[i]);
+ m_TXTCache->astP29[i] = 0;
+ }
+ m_TXTCache->CurrentPage[i] = -1;
+ m_TXTCache->CurrentSubPage[i] = -1;
+ }
+
+ memset(&m_TXTCache->SubPageTable, 0xFF, sizeof(m_TXTCache->SubPageTable));
+ memset(&m_TXTCache->astP29, 0, sizeof(m_TXTCache->astP29));
+ memset(&m_TXTCache->BasicTop, 0, sizeof(m_TXTCache->BasicTop));
+ memset(&m_TXTCache->ADIPTable, 0, sizeof(m_TXTCache->ADIPTable));
+ memset(&m_TXTCache->FlofPages, 0, sizeof(m_TXTCache->FlofPages));
+ memset(&m_TXTCache->SubtitlePages, 0, sizeof(m_TXTCache->SubtitlePages));
+ memset(&m_TXTCache->astCachetable, 0, sizeof(m_TXTCache->astCachetable));
+ memset(&m_TXTCache->TimeString, 0x20, 8);
+
+ m_TXTCache->NationalSubset = NAT_DEFAULT;/* default */
+ m_TXTCache->NationalSubsetSecondary = NAT_DEFAULT;
+ m_TXTCache->ZapSubpageManual = false;
+ m_TXTCache->PageUpdate = false;
+ m_TXTCache->ADIP_PgMax = -1;
+ m_TXTCache->BTTok = false;
+ m_TXTCache->CachedPages = 0;
+ m_TXTCache->PageReceiving = -1;
+ m_TXTCache->Page = 0x100;
+ m_TXTCache->SubPage = m_TXTCache->SubPageTable[m_TXTCache->Page];
+ m_TXTCache->line30 = "";
+ if (m_TXTCache->SubPage == 0xff)
+ m_TXTCache->SubPage = 0;
+}
+
+void CDVDTeletextData::Process()
+{
+ int b1, b2, b3, b4;
+ int packet_number;
+ TextPageinfo_t *pageinfo_thread;
+ unsigned char vtxt_row[42];
+ unsigned char pagedata[9][23*40];
+ unsigned char magazine = 0xff;
+// int doupdate = 0;
+
+ CLog::Log(LOGINFO, "running thread: CDVDTeletextData");
+
+ while (!m_bStop)
+ {
+ std::shared_ptr<CDVDMsg> pMsg;
+ int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE) ? 1 : 0;
+ MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, 2000, iPriority);
+
+ if (ret == MSGQ_TIMEOUT)
+ {
+ /* Timeout for Teletext is not a bad thing, so we continue without error */
+ continue;
+ }
+
+ if (MSGQ_IS_ERROR(ret))
+ {
+ if (!m_messageQueue.ReceivedAbortRequest())
+ CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret);
+
+ break;
+ }
+
+ if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ std::unique_lock<CCriticalSection> lock(m_TXTCache->m_critSection);
+
+ DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket();
+ uint8_t *Datai = pPacket->pData;
+ int rows = (pPacket->iSize - 1) / 46;
+
+ /* Is it a ITU-R System B Teletext stream in acc. to EN 300 472 */
+ if (Datai[0] >= 0x10 && Datai[0] <= 0x1F) /* Check we have a valid data identifier */
+ {
+ /* Go thru the pages stored inside this frame */
+ for (int row=0; row < rows; row++)
+ {
+ uint8_t *vtx_rowbyte = &Datai[(row*46)+1];
+
+ /* Check for valid data_unit_id */
+ if ((vtx_rowbyte[0] == 0x02 || vtx_rowbyte[0] == 0x03) && (vtx_rowbyte[1] == 0x2C))
+ {
+ /* clear rowbuffer */
+ /* convert row from lsb to msb (begin with magazine number) */
+ for (int i = 4; i < 46; i++)
+ {
+ uint8_t upper = (vtx_rowbyte[i] >> 4) & 0xf;
+ uint8_t lower = vtx_rowbyte[i] & 0xf;
+ vtxt_row[i-4] = (rev_lut[upper]) | (rev_lut[lower+16]);
+ }
+
+ /* get packet number */
+ b1 = dehamming[vtxt_row[0]];
+ b2 = dehamming[vtxt_row[1]];
+
+ if (b1 == 0xFF || b2 == 0xFF)
+ continue;
+
+ b1 &= 8;
+
+ /* get packet and magazine number */
+ packet_number = b1>>3 | b2<<1;
+ magazine = dehamming[vtxt_row[0]] & 7;
+ if (!magazine) magazine = 8;
+
+ if (packet_number == 0 && m_TXTCache->CurrentPage[magazine] != -1 && m_TXTCache->CurrentSubPage[magazine] != -1)
+ SavePage(m_TXTCache->CurrentPage[magazine], m_TXTCache->CurrentSubPage[magazine], pagedata[magazine]);
+
+ /* analyze row */
+ if (packet_number == 0)
+ {
+ /* get pagenumber */
+ b2 = dehamming[vtxt_row[3]];
+ b3 = dehamming[vtxt_row[2]];
+
+ if (b2 == 0xFF || b3 == 0xFF)
+ {
+ m_TXTCache->CurrentPage[magazine] = m_TXTCache->PageReceiving = -1;
+ continue;
+ }
+
+ m_TXTCache->CurrentPage[magazine] = m_TXTCache->PageReceiving = magazine<<8 | b2<<4 | b3;
+
+ if (b2 == 0x0f && b3 == 0x0f)
+ {
+ m_TXTCache->CurrentSubPage[magazine] = -1; /* ?ff: ignore data transmissions */
+ continue;
+ }
+
+ /* get subpagenumber */
+ b1 = dehamming[vtxt_row[7]];
+ b2 = dehamming[vtxt_row[6]];
+ b3 = dehamming[vtxt_row[5]];
+ b4 = dehamming[vtxt_row[4]];
+
+ if (b1 == 0xFF || b2 == 0xFF || b3 == 0xFF || b4 == 0xFF)
+ {
+ m_TXTCache->CurrentSubPage[magazine] = -1;
+ continue;
+ }
+
+ b1 &= 3;
+ b3 &= 7;
+
+ if (IsDec(m_TXTCache->PageReceiving)) /* ignore other subpage bits for hex pages */
+ m_TXTCache->CurrentSubPage[magazine] = b3<<4 | b4;
+ else
+ m_TXTCache->CurrentSubPage[magazine] = b4; /* max 16 subpages for hex pages */
+
+ /* store current subpage for this page */
+ m_TXTCache->SubPageTable[m_TXTCache->CurrentPage[magazine]] = m_TXTCache->CurrentSubPage[magazine];
+
+ AllocateCache(magazine);
+ LoadPage(m_TXTCache->CurrentPage[magazine], m_TXTCache->CurrentSubPage[magazine], pagedata[magazine]);
+ pageinfo_thread = &(m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]]->pageinfo);
+ if (!pageinfo_thread)
+ continue;
+
+ if ((m_TXTCache->PageReceiving & 0xff) == 0xfe) /* ?fe: magazine organization table (MOT) */
+ pageinfo_thread->function = FUNC_MOT;
+
+ /* check controlbits */
+ if (dehamming[vtxt_row[5]] & 8) /* C4 -> erase page */
+ {
+ memset(m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]]->data, ' ', 23*40);
+ memset(pagedata[magazine],' ', 23*40);
+ }
+// if (dehamming[vtxt_row[9]] & 8) /* C8 -> update page */
+// doupdate = m_TXTCache->PageReceiving;
+
+ pageinfo_thread->boxed = !!(dehamming[vtxt_row[7]] & 0x0c);
+
+ /* get country control bits */
+ b1 = dehamming[vtxt_row[9]];
+ if (b1 != 0xFF)
+ {
+ pageinfo_thread->nationalvalid = 1;
+ pageinfo_thread->national = rev_lut[b1] & 0x07;
+ }
+
+ if (dehamming[vtxt_row[7]] & 0x08)// subtitle page
+ {
+ int i = 0, found = -1, use = -1;
+ for (; i < 8; i++)
+ {
+ if (use == -1 && !m_TXTCache->SubtitlePages[i].page)
+ use = i;
+ else if (m_TXTCache->SubtitlePages[i].page == m_TXTCache->PageReceiving)
+ {
+ found = i;
+ use = i;
+ break;
+ }
+ }
+ if (found == -1 && use != -1)
+ m_TXTCache->SubtitlePages[use].page = m_TXTCache->PageReceiving;
+ if (use != -1)
+ m_TXTCache->SubtitlePages[use].language = CountryConversionTable[pageinfo_thread->national];
+ }
+
+ /* check parity, copy line 0 to cache (start and end 8 bytes are not needed and used otherwise) */
+ unsigned char *p = m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]]->p0;
+ for (int i = 10; i < 42-8; i++)
+ *p++ = deparity[vtxt_row[i]];
+
+ if (!IsDec(m_TXTCache->PageReceiving))
+ continue; /* valid hex page number: just copy headline, ignore TimeString */
+
+ /* copy TimeString */
+ p = m_TXTCache->TimeString;
+ for (int i = 42-8; i < 42; i++)
+ *p++ = deparity[vtxt_row[i]];
+ }
+ else if (packet_number == 29 && dehamming[vtxt_row[2]]== 0) /* packet 29/0 replaces 28/0 for a whole magazine */
+ {
+ Decode_p2829(vtxt_row, &(m_TXTCache->astP29[magazine]));
+ }
+ else if (m_TXTCache->CurrentPage[magazine] != -1 && m_TXTCache->CurrentSubPage[magazine] != -1)
+ /* packet>0, 0 has been correctly received, buffer allocated */
+ {
+ pageinfo_thread = &(m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]]->pageinfo);
+ if (!pageinfo_thread)
+ continue;
+
+ /* pointer to current info struct */
+ if (packet_number <= 25)
+ {
+ unsigned char *p = NULL;
+ if (packet_number < 24)
+ {
+ p = pagedata[magazine] + 40*(packet_number-1);
+ }
+ else
+ {
+ if (!(pageinfo_thread->p24))
+ pageinfo_thread->p24 = (unsigned char*) calloc(2, 40);
+ if (pageinfo_thread->p24)
+ p = pageinfo_thread->p24 + (packet_number - 24) * 40;
+ }
+ if (p)
+ {
+ if (IsDec(m_TXTCache->CurrentPage[magazine]))
+ {
+ for (int i = 2; i < 42; i++)
+ {
+ *p++ = vtxt_row[i] & 0x7f; /* allow values with parity errors as some channels don't care :( */
+ }
+ }
+ else if ((m_TXTCache->CurrentPage[magazine] & 0xff) == 0xfe)
+ {
+ for (int i = 2; i < 42; i++)
+ {
+ *p++ = dehamming[vtxt_row[i]]; /* decode hamming 8/4 */
+ }
+ }
+ else /* other hex page: no parity check, just copy */
+ memcpy(p, &vtxt_row[2], 40);
+ }
+ }
+ else if (packet_number == 27)
+ {
+ int descode = dehamming[vtxt_row[2]]; /* designation code (0..15) */
+ if (descode == 0xff)
+ continue;
+
+ if (descode == 0) // reading FLOF-Pagelinks
+ {
+ b1 = dehamming[vtxt_row[0]];
+ if (b1 != 0xff)
+ {
+ b1 &= 7;
+
+ for (int i = 0; i < FLOFSIZE; i++)
+ {
+ b2 = dehamming[vtxt_row[4+i*6]];
+ b3 = dehamming[vtxt_row[3+i*6]];
+
+ if (b2 != 0xff && b3 != 0xff)
+ {
+ b4 = ((b1 ^ (dehamming[vtxt_row[8+i*6]]>>1)) & 6) | ((b1 ^ (dehamming[vtxt_row[6+i*6]]>>3)) & 1);
+ if (b4 == 0)
+ b4 = 8;
+ if (b2 <= 9 && b3 <= 9)
+ m_TXTCache->FlofPages[m_TXTCache->CurrentPage[magazine] ][i] = b4<<8 | b2<<4 | b3;
+ }
+ }
+
+ /* copy last 2 links to ADIPTable for TOP-Index */
+ if (pageinfo_thread->p24) /* packet 24 received */
+ {
+ int a, a1, e=39, l=3;
+ unsigned char *p = pageinfo_thread->p24;
+ do
+ {
+ for (;
+ l >= 2 && 0 == m_TXTCache->FlofPages[m_TXTCache->CurrentPage[magazine]][l];
+ l--)
+ ; /* find used linkindex */
+ for (;
+ e >= 1 && !isalnum(p[e]);
+ e--)
+ ; /* find end */
+ for (a = a1 = e - 1;
+ a >= 0 && p[a] >= ' ';
+ a--) /* find start */
+ if (p[a] > ' ')
+ a1 = a; /* first non-space */
+ if (a >= 0 && l >= 2)
+ {
+ strncpy(m_TXTCache->ADIPTable[m_TXTCache->FlofPages[m_TXTCache->CurrentPage[magazine]][l]], (const char*) &p[a1], 12);
+ if (e-a1 < 11)
+ m_TXTCache->ADIPTable[m_TXTCache->FlofPages[m_TXTCache->CurrentPage[magazine]][l]][e-a1+1] = '\0';
+ }
+ e = a - 1;
+ l--;
+ } while (l >= 2);
+ }
+ }
+ }
+ else if (descode == 4) /* level 2.5 links (ignore level 3.5 links of /4 and /5) */
+ {
+ int i;
+ Textp27_t *p;
+
+ if (!pageinfo_thread->ext)
+ pageinfo_thread->ext = (TextExtData_t*) calloc(1, sizeof(TextExtData_t));
+ if (!pageinfo_thread->ext)
+ continue;
+ if (!(pageinfo_thread->ext->p27))
+ pageinfo_thread->ext->p27 = (Textp27_t*) calloc(4, sizeof(Textp27_t));
+ if (!(pageinfo_thread->ext->p27))
+ continue;
+ p = pageinfo_thread->ext->p27;
+ for (i = 0; i < 4; i++)
+ {
+ int d1 = CDVDTeletextTools::deh24(&vtxt_row[6*i + 3]);
+ int d2 = CDVDTeletextTools::deh24(&vtxt_row[6*i + 6]);
+ if (d1 < 0 || d2 < 0)
+ continue;
+
+ p->local = i & 0x01;
+ p->drcs = !!(i & 0x02);
+ p->l25 = !!(d1 & 0x04);
+ p->l35 = !!(d1 & 0x08);
+ p->page =
+ (((d1 & 0x000003c0) >> 6) |
+ ((d1 & 0x0003c000) >> (14-4)) |
+ ((d1 & 0x00003800) >> (11-8))) ^
+ (dehamming[vtxt_row[0]] << 8);
+ if (p->page < 0x100)
+ p->page += 0x800;
+ p->subpage = d2 >> 2;
+ if ((p->page & 0xff) == 0xff)
+ p->page = 0;
+ else if (p->page > 0x899)
+ {
+ // workaround for crash on RTL Shop ...
+ // sorry.. i dont understand whats going wrong here :)
+ continue;
+ }
+ else if (m_TXTCache->astCachetable[p->page][0]) /* link valid && linked page cached */
+ {
+ TextPageinfo_t *pageinfo_link = &(m_TXTCache->astCachetable[p->page][0]->pageinfo);
+ if (p->local)
+ pageinfo_link->function = p->drcs ? FUNC_DRCS : FUNC_POP;
+ else
+ pageinfo_link->function = p->drcs ? FUNC_GDRCS : FUNC_GPOP;
+ }
+ p++; /* */
+ }
+ }
+ }
+ else if (packet_number == 26)
+ {
+ int descode = dehamming[vtxt_row[2]]; /* designation code (0..15) */
+ if (descode == 0xff)
+ continue;
+
+ if (!pageinfo_thread->ext)
+ pageinfo_thread->ext = (TextExtData_t*) calloc(1, sizeof(TextExtData_t));
+ if (!pageinfo_thread->ext)
+ continue;
+ if (!(pageinfo_thread->ext->p26[descode]))
+ pageinfo_thread->ext->p26[descode] = (unsigned char*) malloc(13 * 3);
+ if (pageinfo_thread->ext->p26[descode])
+ memcpy(pageinfo_thread->ext->p26[descode], &vtxt_row[3], 13 * 3);
+ }
+ else if (packet_number == 28)
+ {
+ int descode = dehamming[vtxt_row[2]]; /* designation code (0..15) */
+
+ if (descode == 0xff)
+ continue;
+
+ if (descode != 2)
+ {
+ int t1 = CDVDTeletextTools::deh24(&vtxt_row[7-4]);
+ pageinfo_thread->function = t1 & 0x0f;
+ if (!pageinfo_thread->nationalvalid)
+ {
+ pageinfo_thread->nationalvalid = 1;
+ pageinfo_thread->national = (t1>>4) & 0x07;
+ }
+ }
+
+ switch (descode) /* designation code */
+ {
+ case 0: /* basic level 1 page */
+ Decode_p2829(vtxt_row, &(pageinfo_thread->ext));
+ break;
+ case 1: /* G0/G1 designation for older decoders, level 3.5: DCLUT4/16, colors for multicolored bitmaps */
+ break; /* ignore */
+ case 2: /* page key */
+ break; /* ignore */
+ case 3: /* types of PTUs in DRCS */
+ break; //! @todo implement
+ case 4: /* CLUTs 0/1, only level 3.5 */
+ break; /* ignore */
+ default:
+ break; /* invalid, ignore */
+ } /* switch designation code */
+ }
+ else if (packet_number == 30)
+ {
+ m_TXTCache->line30 = "";
+ for (int i=26-4; i <= 45-4; i++) /* station ID */
+ m_TXTCache->line30.append(1, deparity[vtxt_row[i]]);
+ }
+ }
+
+ /* set update flag */
+ if (m_TXTCache->CurrentPage[magazine] == m_TXTCache->Page && m_TXTCache->CurrentSubPage[magazine] != -1)
+ {
+ SavePage(m_TXTCache->CurrentPage[magazine], m_TXTCache->CurrentSubPage[magazine], pagedata[magazine]);
+ m_TXTCache->PageUpdate = true;
+// doupdate = 0;
+ if (!m_TXTCache->ZapSubpageManual)
+ m_TXTCache->SubPage = m_TXTCache->CurrentSubPage[magazine];
+ }
+ }
+ }
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ m_speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)
+ || pMsg->IsType(CDVDMsg::GENERAL_RESET))
+ {
+ ResetTeletextCache();
+ }
+ }
+}
+
+void CDVDTeletextData::OnExit()
+{
+ CLog::Log(LOGINFO, "thread end: data_thread");
+}
+
+void CDVDTeletextData::Flush()
+{
+ if(!m_messageQueue.IsInited())
+ return;
+ /* flush using message as this get's called from VideoPlayer thread */
+ /* and any demux packet that has been taken out of queue need to */
+ /* be disposed of before we flush */
+ m_messageQueue.Flush();
+ m_messageQueue.Put(std::make_shared<CDVDMsg>(CDVDMsg::GENERAL_FLUSH));
+}
+
+void CDVDTeletextData::Decode_p2829(unsigned char *vtxt_row, TextExtData_t **ptExtData)
+{
+ unsigned int bitsleft, colorindex;
+ unsigned char *p;
+ int t1 = CDVDTeletextTools::deh24(&vtxt_row[7-4]);
+ int t2 = CDVDTeletextTools::deh24(&vtxt_row[10-4]);
+
+ if (t1 < 0 || t2 < 0)
+ return;
+
+ if (!(*ptExtData))
+ (*ptExtData) = (TextExtData_t*) calloc(1, sizeof(TextExtData_t));
+ if (!(*ptExtData))
+ return;
+
+ (*ptExtData)->p28Received = 1;
+ (*ptExtData)->DefaultCharset = (t1>>7) & 0x7f;
+ (*ptExtData)->SecondCharset = ((t1>>14) & 0x0f) | ((t2<<4) & 0x70);
+ (*ptExtData)->LSP = !!(t2 & 0x08);
+ (*ptExtData)->RSP = !!(t2 & 0x10);
+ (*ptExtData)->SPL25 = !!(t2 & 0x20);
+ (*ptExtData)->LSPColumns = (t2>>6) & 0x0f;
+
+ bitsleft = 8; /* # of bits not evaluated in val */
+ t2 >>= 10; /* current data */
+ p = &vtxt_row[13-4]; /* pointer to next data triplet */
+ for (colorindex = 0; colorindex < 16; colorindex++)
+ {
+ if (bitsleft < 12)
+ {
+ t2 |= CDVDTeletextTools::deh24(p) << bitsleft;
+ if (t2 < 0) /* hamming error */
+ break;
+ p += 3;
+ bitsleft += 18;
+ }
+ (*ptExtData)->bgr[colorindex] = t2 & 0x0fff;
+ bitsleft -= 12;
+ t2 >>= 12;
+ }
+ if (t2 < 0 || bitsleft != 14)
+ {
+ (*ptExtData)->p28Received = 0;
+ return;
+ }
+ (*ptExtData)->DefScreenColor = t2 & 0x1f;
+ t2 >>= 5;
+ (*ptExtData)->DefRowColor = t2 & 0x1f;
+ (*ptExtData)->BlackBgSubst = !!(t2 & 0x20);
+ t2 >>= 6;
+ (*ptExtData)->ColorTableRemapping = t2 & 0x07;
+}
+
+void CDVDTeletextData::SavePage(int p, int sp, unsigned char* buffer)
+{
+ std::unique_lock<CCriticalSection> lock(m_TXTCache->m_critSection);
+ TextCachedPage_t* pg = m_TXTCache->astCachetable[p][sp];
+ if (!pg)
+ {
+ CLog::Log(LOGERROR, "CDVDTeletextData: trying to save a not allocated page!!");
+ return;
+ }
+
+ memcpy(pg->data, buffer, 23*40);
+}
+
+void CDVDTeletextData::LoadPage(int p, int sp, unsigned char* buffer)
+{
+ std::unique_lock<CCriticalSection> lock(m_TXTCache->m_critSection);
+ TextCachedPage_t* pg = m_TXTCache->astCachetable[p][sp];
+ if (!pg)
+ {
+ CLog::Log(LOGERROR, "CDVDTeletextData: trying to load a not allocated page!!");
+ return;
+ }
+
+ memcpy(buffer, pg->data, 23*40);
+}
+
+void CDVDTeletextData::ErasePage(int magazine)
+{
+ std::unique_lock<CCriticalSection> lock(m_TXTCache->m_critSection);
+ TextCachedPage_t* pg = m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]];
+ if (pg)
+ {
+ memset(&(pg->pageinfo), 0, sizeof(TextPageinfo_t)); /* struct pageinfo */
+ memset(pg->p0, ' ', 24);
+ memset(pg->data, ' ', 23*40);
+ }
+}
+
+void CDVDTeletextData::AllocateCache(int magazine)
+{
+ /* check cachetable and allocate memory if needed */
+ if (m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]] == 0)
+ {
+ m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]] = new TextCachedPage_t;
+ if (m_TXTCache->astCachetable[m_TXTCache->CurrentPage[magazine]][m_TXTCache->CurrentSubPage[magazine]] )
+ {
+ ErasePage(magazine);
+ m_TXTCache->CachedPages++;
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerTeletext.h b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.h
new file mode 100644
index 0000000..b613b82
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDMessageQueue.h"
+#include "IVideoPlayer.h"
+#include "threads/Thread.h"
+#include "video/TeletextDefines.h"
+
+class CDVDStreamInfo;
+
+class CDVDTeletextData : public CThread, public IDVDStreamPlayer
+{
+public:
+ explicit CDVDTeletextData(CProcessInfo &processInfo);
+ ~CDVDTeletextData() override;
+
+ bool CheckStream(CDVDStreamInfo &hints);
+ bool OpenStream(CDVDStreamInfo hints) override;
+ void CloseStream(bool bWaitForBuffers) override;
+ void Flush();
+
+ // waits until all available data has been rendered
+ void WaitForBuffers() { m_messageQueue.WaitUntilEmpty(); }
+ bool AcceptsData() const override { return !m_messageQueue.IsFull(); }
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override
+ {
+ if (m_messageQueue.IsInited())
+ m_messageQueue.Put(pMsg, priority);
+ }
+ void FlushMessages() override { m_messageQueue.Flush(); }
+ bool IsInited() const override { return true; }
+ bool IsStalled() const override { return true; }
+
+ std::shared_ptr<TextCacheStruct_t> GetTeletextCache() { return m_TXTCache; }
+ void LoadPage(int p, int sp, unsigned char* buffer);
+
+protected:
+ void OnExit() override;
+ void Process() override;
+
+private:
+ void ResetTeletextCache();
+ void Decode_p2829(unsigned char *vtxt_row, TextExtData_t **ptExtData);
+ void SavePage(int p, int sp, unsigned char* buffer);
+ void ErasePage(int magazine);
+ void AllocateCache(int magazine);
+
+ int m_speed;
+ std::shared_ptr<TextCacheStruct_t> m_TXTCache = std::make_shared<TextCacheStruct_t>();
+ CDVDMessageQueue m_messageQueue;
+};
+
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp
new file mode 100644
index 0000000..20f6b3b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp
@@ -0,0 +1,1166 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerVideo.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "DVDCodecs/DVDFactoryCodec.h"
+#include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Interface/DemuxPacket.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <iomanip>
+#include <iterator>
+#include <mutex>
+#include <numeric>
+#include <sstream>
+
+using namespace std::chrono_literals;
+
+class CDVDMsgVideoCodecChange : public CDVDMsg
+{
+public:
+ CDVDMsgVideoCodecChange(const CDVDStreamInfo& hints, std::unique_ptr<CDVDVideoCodec> codec)
+ : CDVDMsg(GENERAL_STREAMCHANGE), m_codec(std::move(codec)), m_hints(hints)
+ {}
+ ~CDVDMsgVideoCodecChange() override = default;
+
+ std::unique_ptr<CDVDVideoCodec> m_codec;
+ CDVDStreamInfo m_hints;
+};
+
+
+CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock
+ ,CDVDOverlayContainer* pOverlayContainer
+ ,CDVDMessageQueue& parent
+ ,CRenderManager& renderManager
+ ,CProcessInfo &processInfo)
+: CThread("VideoPlayerVideo")
+, IDVDStreamPlayerVideo(processInfo)
+, m_messageQueue("video")
+, m_messageParent(parent)
+, m_renderManager(renderManager)
+{
+ m_pClock = pClock;
+ m_pOverlayContainer = pOverlayContainer;
+ m_speed = DVD_PLAYSPEED_NORMAL;
+
+ m_bRenderSubs = false;
+ m_paused = false;
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_iSubtitleDelay = 0;
+ m_iLateFrames = 0;
+ m_iDroppedRequest = 0;
+ m_fForcedAspectRatio = 0;
+ m_messageQueue.SetMaxDataSize(40 * 1024 * 1024);
+ m_messageQueue.SetMaxTimeSize(8.0);
+
+ m_iDroppedFrames = 0;
+ m_fFrameRate = 25;
+ m_fStableFrameRate = 0.0;
+ m_iFrameRateCount = 0;
+ m_bAllowDrop = false;
+ m_iFrameRateErr = 0;
+ m_iFrameRateLength = 0;
+ m_bFpsInvalid = false;
+}
+
+CVideoPlayerVideo::~CVideoPlayerVideo()
+{
+ m_bAbortOutput = true;
+ StopThread();
+}
+
+double CVideoPlayerVideo::GetOutputDelay()
+{
+ double time = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET);
+ if( m_fFrameRate )
+ time = (time * DVD_TIME_BASE) / m_fFrameRate;
+ else
+ time = 0.0;
+
+ if( m_speed != 0 )
+ time = time * DVD_PLAYSPEED_NORMAL / abs(m_speed);
+
+ return time;
+}
+
+bool CVideoPlayerVideo::OpenStream(CDVDStreamInfo hint)
+{
+ if (hint.flags & AV_DISPOSITION_ATTACHED_PIC)
+ return false;
+ if (hint.extrasize == 0)
+ {
+ // codecs which require extradata
+ // clang-format off
+ if (hint.codec == AV_CODEC_ID_NONE ||
+ hint.codec == AV_CODEC_ID_MPEG1VIDEO ||
+ hint.codec == AV_CODEC_ID_MPEG2VIDEO ||
+ hint.codec == AV_CODEC_ID_H264 ||
+ hint.codec == AV_CODEC_ID_HEVC ||
+ hint.codec == AV_CODEC_ID_MPEG4 ||
+ hint.codec == AV_CODEC_ID_WMV3 ||
+ hint.codec == AV_CODEC_ID_VC1 ||
+ hint.codec == AV_CODEC_ID_AV1)
+ // clang-format on
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "Creating video codec with codec id: {}", hint.codec);
+
+ if (m_messageQueue.IsInited())
+ {
+ if (m_pVideoCodec && !m_processInfo.IsVideoHwDecoder())
+ {
+ hint.codecOptions |= CODEC_ALLOW_FALLBACK;
+ }
+
+ std::unique_ptr<CDVDVideoCodec> codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo);
+ if (!codec)
+ {
+ CLog::Log(LOGINFO, "CVideoPlayerVideo::OpenStream - could not open video codec");
+ }
+
+ SendMessage(std::make_shared<CDVDMsgVideoCodecChange>(hint, std::move(codec)), 0);
+ }
+ else
+ {
+ m_processInfo.ResetVideoCodecInfo();
+ hint.codecOptions |= CODEC_ALLOW_FALLBACK;
+
+ std::unique_ptr<CDVDVideoCodec> codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo);
+ if (!codec)
+ {
+ CLog::Log(LOGERROR, "CVideoPlayerVideo::OpenStream - could not open video codec");
+ return false;
+ }
+
+ OpenStream(hint, std::move(codec));
+ CLog::Log(LOGINFO, "Creating video thread");
+ m_messageQueue.Init();
+ m_processInfo.SetLevelVQ(0);
+ Create();
+ }
+ return true;
+}
+
+void CVideoPlayerVideo::OpenStream(CDVDStreamInfo& hint, std::unique_ptr<CDVDVideoCodec> codec)
+{
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo::OpenStream - open stream with codec id: {}", hint.codec);
+
+ m_processInfo.GetVideoBufferManager().ReleasePools();
+
+ //reported fps is usually not completely correct
+ if (hint.fpsrate && hint.fpsscale)
+ {
+ m_fFrameRate = DVD_TIME_BASE / CDVDCodecUtils::NormalizeFrameduration((double)DVD_TIME_BASE * hint.fpsscale / hint.fpsrate);
+ m_bFpsInvalid = false;
+ m_processInfo.SetVideoFps(static_cast<float>(m_fFrameRate));
+ }
+ else
+ {
+ m_fFrameRate = 25;
+ m_bFpsInvalid = true;
+ m_processInfo.SetVideoFps(0);
+ }
+
+ m_ptsTracker.ResetVFRDetection();
+ ResetFrameRateCalc();
+
+ m_iDroppedRequest = 0;
+ m_iLateFrames = 0;
+
+ if( m_fFrameRate > 120 || m_fFrameRate < 5 )
+ {
+ CLog::Log(LOGERROR,
+ "CVideoPlayerVideo::OpenStream - Invalid framerate {}, using forced 25fps and just "
+ "trust timestamps",
+ (int)m_fFrameRate);
+ m_fFrameRate = 25;
+ }
+
+ // use aspect in stream if available
+ if (hint.forced_aspect)
+ m_fForcedAspectRatio = static_cast<float>(hint.aspect);
+ else
+ m_fForcedAspectRatio = 0.0f;
+
+ if (m_pVideoCodec && m_pVideoCodec->Reconfigure(hint))
+ {
+ // reuse old decoder
+ codec = std::move(m_pVideoCodec);
+ }
+
+ m_pVideoCodec.reset();
+
+ if (!codec)
+ {
+ CLog::Log(LOGINFO, "Creating video codec with codec id: {}", hint.codec);
+ hint.codecOptions |= CODEC_ALLOW_FALLBACK;
+ codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo);
+ if (!codec)
+ {
+ CLog::Log(LOGERROR, "CVideoPlayerVideo::OpenStream - could not open video codec");
+ m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_ABORT));
+ StopThread();
+ }
+ }
+
+ m_pVideoCodec = std::move(codec);
+ m_hints = hint;
+ m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0;
+ m_rewindStalled = false;
+ m_packets.clear();
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_renderManager.ShowVideo(false);
+}
+
+void CVideoPlayerVideo::CloseStream(bool bWaitForBuffers)
+{
+ // wait until buffers are empty
+ if (bWaitForBuffers && m_speed > 0)
+ {
+ SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::VIDEO_DRAIN), 0);
+ m_messageQueue.WaitUntilEmpty();
+ }
+
+ m_messageQueue.Abort();
+
+ // wait for decode_video thread to end
+ CLog::Log(LOGINFO, "waiting for video thread to exit");
+
+ m_bAbortOutput = true;
+ StopThread();
+
+ m_messageQueue.End();
+
+ CLog::Log(LOGINFO, "deleting video codec");
+ m_pVideoCodec.reset();
+
+ if (m_picture.videoBuffer)
+ {
+ m_picture.videoBuffer->Release();
+ m_picture.videoBuffer = nullptr;
+ }
+}
+
+bool CVideoPlayerVideo::AcceptsData() const
+{
+ bool full = m_messageQueue.IsFull();
+ return !full;
+}
+
+bool CVideoPlayerVideo::HasData() const
+{
+ return m_messageQueue.GetDataSize() > 0;
+}
+
+bool CVideoPlayerVideo::IsInited() const
+{
+ return m_messageQueue.IsInited();
+}
+
+inline void CVideoPlayerVideo::SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority)
+{
+ m_messageQueue.Put(pMsg, priority);
+ m_processInfo.SetLevelVQ(m_messageQueue.GetLevel());
+}
+
+inline void CVideoPlayerVideo::SendMessageBack(const std::shared_ptr<CDVDMsg>& pMsg, int priority)
+{
+ m_messageQueue.PutBack(pMsg, priority);
+ m_processInfo.SetLevelVQ(m_messageQueue.GetLevel());
+}
+
+inline void CVideoPlayerVideo::FlushMessages()
+{
+ m_messageQueue.Flush();
+ m_processInfo.SetLevelVQ(m_messageQueue.GetLevel());
+}
+
+inline MsgQueueReturnCode CVideoPlayerVideo::GetMessage(std::shared_ptr<CDVDMsg>& pMsg,
+ unsigned int iTimeoutInMilliSeconds,
+ int& priority)
+{
+ MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, iTimeoutInMilliSeconds, priority);
+ m_processInfo.SetLevelVQ(m_messageQueue.GetLevel());
+ return ret;
+}
+
+void CVideoPlayerVideo::Process()
+{
+ CLog::Log(LOGINFO, "running thread: video_thread");
+
+ double pts = 0;
+ double frametime = (double)DVD_TIME_BASE / m_fFrameRate;
+
+ bool bRequestDrop = false;
+ int iDropDirective;
+ bool onlyPrioMsgs = false;
+
+ m_videoStats.Start();
+ m_droppingStats.Reset();
+ m_iDroppedFrames = 0;
+ m_rewindStalled = false;
+ m_outputSate = OUTPUT_NORMAL;
+
+ while (!m_bStop)
+ {
+ int iQueueTimeOut = (int)(m_stalled ? frametime : frametime * 10) / 1000;
+ int iPriority = 0;
+
+ if (m_syncState == IDVDStreamPlayer::SYNC_WAITSYNC)
+ iPriority = 1;
+
+ if (m_paused)
+ iPriority = 1;
+
+ if (onlyPrioMsgs)
+ {
+ iPriority = 1;
+ iQueueTimeOut = 1;
+ }
+
+ std::shared_ptr<CDVDMsg> pMsg;
+ MsgQueueReturnCode ret = GetMessage(pMsg, iQueueTimeOut, iPriority);
+
+ onlyPrioMsgs = false;
+
+ if (MSGQ_IS_ERROR(ret))
+ {
+ if (!m_messageQueue.ReceivedAbortRequest())
+ CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret);
+
+ break;
+ }
+ else if (ret == MSGQ_TIMEOUT)
+ {
+ if (m_outputSate == OUTPUT_AGAIN &&
+ m_picture.videoBuffer)
+ {
+ m_outputSate = OutputPicture(&m_picture);
+ if (m_outputSate == OUTPUT_AGAIN)
+ {
+ onlyPrioMsgs = true;
+ continue;
+ }
+ }
+ // don't ask for a new frame if we can't deliver it to renderer
+ else if ((m_speed != DVD_PLAYSPEED_PAUSE ||
+ m_processInfo.IsFrameAdvance() ||
+ m_syncState != IDVDStreamPlayer::SYNC_INSYNC) && !m_paused)
+ {
+ if (ProcessDecoderOutput(frametime, pts))
+ {
+ onlyPrioMsgs = true;
+ continue;
+ }
+ }
+
+ // if we only wanted priority messages, this isn't a stall
+ if (iPriority)
+ continue;
+
+ //Okey, start rendering at stream fps now instead, we are likely in a stillframe
+ if (!m_stalled)
+ {
+ // squeeze pictures out
+ while (!m_bStop && m_pVideoCodec)
+ {
+ m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN);
+ if (!ProcessDecoderOutput(frametime, pts))
+ break;
+ }
+
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - Stillframe detected, switching to forced {:f} fps",
+ m_fFrameRate);
+ m_stalled = true;
+ pts += frametime * 4;
+ }
+
+ // Waiting timed out, output last picture
+ if (m_picture.videoBuffer)
+ {
+ m_picture.pts = pts;
+ m_outputSate = OutputPicture(&m_picture);
+ pts += frametime;
+ }
+
+ continue;
+ }
+
+ if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE))
+ {
+ if (std::static_pointer_cast<CDVDMsgGeneralSynchronize>(pMsg)->Wait(100ms, SYNCSOURCE_VIDEO))
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE");
+ }
+ else
+ SendMessage(pMsg, 1); /* push back as prio message, to process other prio messages */
+ m_droppingStats.Reset();
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC))
+ {
+ pts = std::static_pointer_cast<CDVDMsgDouble>(pMsg)->m_value;
+
+ m_syncState = IDVDStreamPlayer::SYNC_INSYNC;
+ m_droppingStats.Reset();
+ m_rewindStalled = false;
+ m_renderManager.ShowVideo(true);
+
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_RESYNC({:f})", pts);
+ }
+ else if (pMsg->IsType(CDVDMsg::VIDEO_SET_ASPECT))
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::VIDEO_SET_ASPECT");
+ m_fForcedAspectRatio = static_cast<float>(*std::static_pointer_cast<CDVDMsgDouble>(pMsg));
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_RESET))
+ {
+ if(m_pVideoCodec)
+ m_pVideoCodec->Reset();
+
+ if (m_picture.videoBuffer)
+ {
+ m_picture.videoBuffer->Release();
+ m_picture.videoBuffer = nullptr;
+ }
+ m_packets.clear();
+ m_droppingStats.Reset();
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_renderManager.ShowVideo(false);
+ m_rewindStalled = false;
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) // private message sent by (CVideoPlayerVideo::Flush())
+ {
+ bool sync = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value;
+ if(m_pVideoCodec)
+ m_pVideoCodec->Reset();
+
+ if (m_picture.videoBuffer)
+ {
+ m_picture.videoBuffer->Release();
+ m_picture.videoBuffer = nullptr;
+ }
+ m_packets.clear();
+ pts = 0;
+ m_rewindStalled = false;
+
+ m_ptsTracker.Flush();
+ //we need to recalculate the framerate
+ //! @todo this needs to be set on a streamchange instead
+ ResetFrameRateCalc();
+ m_droppingStats.Reset();
+
+ m_stalled = true;
+ if (sync)
+ {
+ m_syncState = IDVDStreamPlayer::SYNC_STARTING;
+ m_renderManager.ShowVideo(false);
+ }
+
+ m_renderManager.DiscardBuffer();
+ FlushMessages();
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
+ {
+ m_speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value;
+ if (m_pVideoCodec)
+ m_pVideoCodec->SetSpeed(m_speed);
+
+ m_droppingStats.Reset();
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE))
+ {
+ auto msg = std::static_pointer_cast<CDVDMsgVideoCodecChange>(pMsg);
+
+ while (!m_bStop && m_pVideoCodec)
+ {
+ m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN);
+ bool cont = ProcessDecoderOutput(frametime, pts);
+
+ if (!cont)
+ break;
+ }
+
+ OpenStream(msg->m_hints, std::move(msg->m_codec));
+ msg->m_codec = NULL;
+ if (m_picture.videoBuffer)
+ {
+ m_picture.videoBuffer->Release();
+ m_picture.videoBuffer = nullptr;
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::VIDEO_DRAIN))
+ {
+ while (!m_bStop && m_pVideoCodec)
+ {
+ m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN);
+ if (!ProcessDecoderOutput(frametime, pts))
+ break;
+ }
+ }
+ else if (pMsg->IsType(CDVDMsg::GENERAL_PAUSE))
+ {
+ m_paused = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value;
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_PAUSE: {}", m_paused);
+ }
+ else if (pMsg->IsType(CDVDMsg::PLAYER_REQUEST_STATE))
+ {
+ SStateMsg msg;
+ msg.player = VideoPlayer_VIDEO;
+ msg.syncState = m_syncState;
+ m_messageParent.Put(
+ std::make_shared<CDVDMsgType<SStateMsg>>(CDVDMsg::PLAYER_REPORT_STATE, msg));
+ }
+ else if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
+ {
+ DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket();
+ bool bPacketDrop = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacketDrop();
+
+ if (m_stalled)
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - Stillframe left, switching to normal playback");
+ m_stalled = false;
+ }
+
+ bRequestDrop = false;
+ iDropDirective = CalcDropRequirement(pts);
+ if ((iDropDirective & DROP_VERYLATE) &&
+ m_bAllowDrop &&
+ !bPacketDrop)
+ {
+ bRequestDrop = true;
+ }
+ if (iDropDirective & DROP_DROPPED)
+ {
+ m_iDroppedFrames++;
+ m_ptsTracker.Flush();
+ }
+ if (m_messageQueue.GetDataSize() == 0 || m_speed < 0)
+ {
+ bRequestDrop = false;
+ m_iDroppedRequest = 0;
+ m_iLateFrames = 0;
+ }
+
+ int codecControl = 0;
+ if (iDropDirective & DROP_BUFFER_LEVEL)
+ codecControl |= DVD_CODEC_CTRL_HURRY;
+ if (m_speed > DVD_PLAYSPEED_NORMAL)
+ codecControl |= DVD_CODEC_CTRL_NO_POSTPROC;
+ if (bPacketDrop)
+ codecControl |= DVD_CODEC_CTRL_DROP;
+ if (bRequestDrop)
+ codecControl |= DVD_CODEC_CTRL_DROP_ANY;
+ if (!m_renderManager.Supports(RENDERFEATURE_ROTATION))
+ codecControl |= DVD_CODEC_CTRL_ROTATE;
+ m_pVideoCodec->SetCodecControl(codecControl);
+
+ if (m_pVideoCodec->AddData(*pPacket))
+ {
+ // buffer packets so we can recover should decoder flush for some reason
+ if (m_pVideoCodec->GetConvergeCount() > 0)
+ {
+ m_packets.emplace_back(pMsg, 0);
+ if (m_packets.size() > m_pVideoCodec->GetConvergeCount() ||
+ m_packets.size() * frametime > DVD_SEC_TO_TIME(10))
+ m_packets.pop_front();
+ }
+
+ m_videoStats.AddSampleBytes(pPacket->iSize);
+
+ if (ProcessDecoderOutput(frametime, pts))
+ {
+ onlyPrioMsgs = true;
+ }
+ }
+ else
+ {
+ SendMessageBack(pMsg);
+ onlyPrioMsgs = true;
+ }
+ }
+ }
+}
+
+bool CVideoPlayerVideo::ProcessDecoderOutput(double &frametime, double &pts)
+{
+ CDVDVideoCodec::VCReturn decoderState = m_pVideoCodec->GetPicture(&m_picture);
+
+ if (decoderState == CDVDVideoCodec::VC_BUFFER)
+ {
+ return false;
+ }
+
+ // if decoder was flushed, we need to seek back again to resume rendering
+ if (decoderState == CDVDVideoCodec::VC_FLUSHED)
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder was flushed");
+ while (!m_packets.empty())
+ {
+ auto msg = std::static_pointer_cast<CDVDMsgDemuxerPacket>(m_packets.front().message);
+ m_packets.pop_front();
+
+ SendMessage(msg, 10);
+ }
+
+ m_pVideoCodec->Reset();
+ m_packets.clear();
+ //picture.iFlags &= ~DVP_FLAG_ALLOCATED;
+ m_renderManager.DiscardBuffer();
+ return false;
+ }
+
+ if (decoderState == CDVDVideoCodec::VC_REOPEN)
+ {
+ while (!m_packets.empty())
+ {
+ auto msg = std::static_pointer_cast<CDVDMsgDemuxerPacket>(m_packets.front().message);
+ m_packets.pop_front();
+ SendMessage(msg, 10);
+ }
+
+ m_pVideoCodec->Reopen();
+ m_packets.clear();
+ m_renderManager.DiscardBuffer();
+ return false;
+ }
+
+ // if decoder had an error, tell it to reset to avoid more problems
+ if (decoderState == CDVDVideoCodec::VC_ERROR)
+ {
+ CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder returned error");
+ return false;
+ }
+
+ if (decoderState == CDVDVideoCodec::VC_EOF)
+ {
+ if (m_syncState == IDVDStreamPlayer::SYNC_STARTING)
+ {
+ SStartMsg msg;
+ msg.player = VideoPlayer_VIDEO;
+ msg.cachetime = DVD_MSEC_TO_TIME(50);
+ msg.cachetotal = DVD_MSEC_TO_TIME(100);
+ msg.timestamp = DVD_NOPTS_VALUE;
+ m_messageParent.Put(std::make_shared<CDVDMsgType<SStartMsg>>(CDVDMsg::PLAYER_STARTED, msg));
+ }
+ return false;
+ }
+
+ // check for a new picture
+ if (decoderState == CDVDVideoCodec::VC_PICTURE)
+ {
+ bool hasTimestamp = true;
+
+ m_picture.iDuration = frametime;
+
+ // validate picture timing,
+ // if both dts/pts invalid, use pts calculated from picture.iDuration
+ // if pts invalid use dts, else use picture.pts as passed
+ if (m_picture.dts == DVD_NOPTS_VALUE && m_picture.pts == DVD_NOPTS_VALUE)
+ {
+ m_picture.pts = pts;
+ hasTimestamp = false;
+ }
+ else if (m_picture.pts == DVD_NOPTS_VALUE)
+ m_picture.pts = m_picture.dts;
+
+ // use forced aspect if any
+ if (m_fForcedAspectRatio != 0.0f)
+ {
+ m_picture.iDisplayWidth = (int) (m_picture.iDisplayHeight * m_fForcedAspectRatio);
+ if (m_picture.iDisplayWidth > m_picture.iWidth)
+ {
+ m_picture.iDisplayWidth = m_picture.iWidth;
+ m_picture.iDisplayHeight = (int) (m_picture.iDisplayWidth / m_fForcedAspectRatio);
+ }
+ }
+
+ // set stereo mode if not set by decoder
+ if (m_picture.stereoMode.empty())
+ {
+ std::string stereoMode;
+ switch(m_processInfo.GetVideoSettings().m_StereoMode)
+ {
+ case RENDER_STEREO_MODE_SPLIT_VERTICAL:
+ stereoMode = "left_right";
+ if (m_processInfo.GetVideoSettings().m_StereoInvert)
+ stereoMode = "right_left";
+ break;
+ case RENDER_STEREO_MODE_SPLIT_HORIZONTAL:
+ stereoMode = "top_bottom";
+ if (m_processInfo.GetVideoSettings().m_StereoInvert)
+ stereoMode = "bottom_top";
+ break;
+ default:
+ stereoMode = m_hints.stereo_mode;
+ break;
+ }
+ if (!stereoMode.empty() && stereoMode != "mono")
+ {
+ m_picture.stereoMode = stereoMode;
+ }
+ }
+
+ // if frame has a pts (usually originating from demux packet), use that
+ if (m_picture.pts != DVD_NOPTS_VALUE)
+ {
+ pts = m_picture.pts;
+ }
+
+ double extraDelay = 0.0;
+ if (m_picture.iRepeatPicture)
+ {
+ extraDelay = m_picture.iRepeatPicture * m_picture.iDuration;
+ m_picture.iDuration += extraDelay;
+ }
+
+ m_picture.pts = pts + extraDelay;
+ // guess next frame pts. iDuration is always valid
+ if (m_speed != 0)
+ pts += m_picture.iDuration * m_speed / abs(m_speed);
+
+ m_outputSate = OutputPicture(&m_picture);
+
+ if (m_outputSate == OUTPUT_AGAIN)
+ {
+ return true;
+ }
+ else if (m_outputSate == OUTPUT_ABORT)
+ {
+ return false;
+ }
+ else if ((m_outputSate == OUTPUT_DROPPED) && !(m_picture.iFlags & DVP_FLAG_DROPPED))
+ {
+ m_iDroppedFrames++;
+ m_ptsTracker.Flush();
+ }
+
+ if (m_syncState == IDVDStreamPlayer::SYNC_STARTING &&
+ m_outputSate != OUTPUT_DROPPED &&
+ !(m_picture.iFlags & DVP_FLAG_DROPPED))
+ {
+ m_syncState = IDVDStreamPlayer::SYNC_WAITSYNC;
+ SStartMsg msg;
+ msg.player = VideoPlayer_VIDEO;
+ msg.cachetime = DVD_MSEC_TO_TIME(50); //! @todo implement
+ msg.cachetotal = DVD_MSEC_TO_TIME(100); //! @todo implement
+ msg.timestamp = hasTimestamp ? (pts + m_renderManager.GetDelay() * 1000) : DVD_NOPTS_VALUE;
+ m_messageParent.Put(std::make_shared<CDVDMsgType<SStartMsg>>(CDVDMsg::PLAYER_STARTED, msg));
+ }
+
+ frametime = (double)DVD_TIME_BASE / m_fFrameRate;
+ }
+
+ return true;
+}
+
+void CVideoPlayerVideo::OnExit()
+{
+ CLog::Log(LOGINFO, "thread end: video_thread");
+}
+
+void CVideoPlayerVideo::SetSpeed(int speed)
+{
+ if(m_messageQueue.IsInited())
+ SendMessage(std::make_shared<CDVDMsgInt>(CDVDMsg::PLAYER_SETSPEED, speed), 1);
+ else
+ m_speed = speed;
+}
+
+void CVideoPlayerVideo::Flush(bool sync)
+{
+ /* flush using message as this get's called from VideoPlayer thread */
+ /* and any demux packet that has been taken out of queue need to */
+ /* be disposed of before we flush */
+ SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_FLUSH, sync), 1);
+ m_bAbortOutput = true;
+}
+
+void CVideoPlayerVideo::ProcessOverlays(const VideoPicture* pSource, double pts)
+{
+ // remove any overlays that are out of time
+ if (m_syncState == IDVDStreamPlayer::SYNC_INSYNC)
+ m_pOverlayContainer->CleanUp(pts - m_iSubtitleDelay);
+
+ VecOverlays overlays;
+
+ {
+ std::unique_lock<CCriticalSection> lock(*m_pOverlayContainer);
+
+ VecOverlays* pVecOverlays = m_pOverlayContainer->GetOverlays();
+ VecOverlaysIter it = pVecOverlays->begin();
+
+ //Check all overlays and render those that should be rendered, based on time and forced
+ //Both forced and subs should check timing
+ while (it != pVecOverlays->end())
+ {
+ CDVDOverlay* pOverlay = *it++;
+ if(!pOverlay->bForced && !m_bRenderSubs)
+ continue;
+
+ double pts2 = pOverlay->bForced ? pts : pts - m_iSubtitleDelay;
+
+ if((pOverlay->iPTSStartTime <= pts2 && (pOverlay->iPTSStopTime > pts2 || pOverlay->iPTSStopTime == 0LL)))
+ {
+ if(pOverlay->IsOverlayType(DVDOVERLAY_TYPE_GROUP))
+ overlays.insert(overlays.end(), static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.begin()
+ , static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.end());
+ else
+ overlays.push_back(pOverlay);
+ }
+ }
+
+ for(it = overlays.begin(); it != overlays.end(); ++it)
+ {
+ double pts2 = (*it)->bForced ? pts : pts - m_iSubtitleDelay;
+ m_renderManager.AddOverlay(*it, pts2);
+ }
+ }
+}
+
+CVideoPlayerVideo::EOutputState CVideoPlayerVideo::OutputPicture(const VideoPicture* pPicture)
+{
+ m_bAbortOutput = false;
+
+ if (m_processInfo.GetVideoStereoMode() != pPicture->stereoMode)
+ {
+ m_processInfo.SetVideoStereoMode(pPicture->stereoMode);
+ // signal about changes in video parameters
+ m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_AVCHANGE));
+ }
+
+ double config_framerate = m_bFpsInvalid ? 0.0 : m_fFrameRate;
+ if (m_processInfo.GetVideoInterlaced())
+ {
+ if (MathUtils::FloatEquals(config_framerate, 25.0, 0.02))
+ config_framerate = 50.0;
+ else if (MathUtils::FloatEquals(config_framerate, 29.97, 0.02))
+ config_framerate = 59.94;
+ }
+
+ int sorient = m_processInfo.GetVideoSettings().m_Orientation;
+ int orientation = sorient != 0 ? (sorient + m_hints.orientation) % 360
+ : m_hints.orientation;
+
+ if (!m_renderManager.Configure(*pPicture,
+ static_cast<float>(config_framerate),
+ orientation,
+ m_pVideoCodec->GetAllowedReferences()))
+ {
+ CLog::Log(LOGERROR, "{} - failed to configure renderer", __FUNCTION__);
+ return OUTPUT_ABORT;
+ }
+
+ //try to calculate the framerate
+ m_ptsTracker.Add(pPicture->pts);
+ if (!m_stalled)
+ CalcFrameRate();
+
+ // signal to clock what our framerate is, it may want to adjust it's
+ // speed to better match with our video renderer's output speed
+ m_pClock->UpdateFramerate(m_fFrameRate);
+
+ // calculate the time we need to delay this picture before displaying
+ double iPlayingClock, iCurrentClock;
+
+ iPlayingClock = m_pClock->GetClock(iCurrentClock, false); // snapshot current clock
+
+ if (m_speed < 0)
+ {
+ double renderPts;
+ int queued, discard;
+ int lateframes;
+ double inputPts = m_droppingStats.m_lastPts;
+ m_renderManager.GetStats(lateframes, renderPts, queued, discard);
+ if (pPicture->pts > renderPts || queued > 0)
+ {
+ if (inputPts >= renderPts)
+ {
+ m_rewindStalled = true;
+ CThread::Sleep(50ms);
+ }
+ return OUTPUT_DROPPED;
+ }
+ else if (pPicture->pts < iPlayingClock)
+ {
+ return OUTPUT_DROPPED;
+ }
+ }
+
+ if ((pPicture->iFlags & DVP_FLAG_DROPPED))
+ {
+ m_droppingStats.AddOutputDropGain(pPicture->pts, 1);
+ CLog::Log(LOGDEBUG, "{} - dropped in output", __FUNCTION__);
+ return OUTPUT_DROPPED;
+ }
+
+ auto timeToDisplay = std::chrono::milliseconds(DVD_TIME_TO_MSEC(pPicture->pts - iPlayingClock));
+
+ // make sure waiting time is not negative
+ std::chrono::milliseconds maxWaitTime = std::min(std::max(timeToDisplay + 500ms, 50ms), 500ms);
+ // don't wait when going ff
+ if (m_speed > DVD_PLAYSPEED_NORMAL)
+ maxWaitTime = std::max(timeToDisplay, 0ms);
+ int buffer = m_renderManager.WaitForBuffer(m_bAbortOutput, maxWaitTime);
+ if (buffer < 0)
+ {
+ if (m_speed != DVD_PLAYSPEED_PAUSE)
+ CLog::Log(LOGWARNING, "{} - timeout waiting for buffer", __FUNCTION__);
+ return OUTPUT_AGAIN;
+ }
+
+ ProcessOverlays(pPicture, pPicture->pts);
+
+ EINTERLACEMETHOD deintMethod = EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE;
+ deintMethod = m_processInfo.GetVideoSettings().m_InterlaceMethod;
+ if (!m_processInfo.Supports(deintMethod))
+ deintMethod = m_processInfo.GetDeinterlacingMethodDefault();
+
+ if (!m_renderManager.AddVideoPicture(*pPicture, m_bAbortOutput, deintMethod, (m_syncState == ESyncState::SYNC_STARTING)))
+ {
+ m_droppingStats.AddOutputDropGain(pPicture->pts, 1);
+ return OUTPUT_DROPPED;
+ }
+
+ return OUTPUT_NORMAL;
+}
+
+std::string CVideoPlayerVideo::GetPlayerInfo()
+{
+ std::ostringstream s;
+ s << "vq:" << std::setw(2) << std::min(99, m_processInfo.GetLevelVQ()) << "%";
+ s << ", Mb/s:" << std::fixed << std::setprecision(2) << (double)GetVideoBitrate() / (1024.0*1024.0);
+ s << ", fr:" << std::fixed << std::setprecision(3) << m_fFrameRate;
+ s << ", drop:" << m_iDroppedFrames;
+ s << ", skip:" << m_renderManager.GetSkippedFrames();
+
+ int pc = m_ptsTracker.GetPatternLength();
+ if (pc > 0)
+ s << ", pc:" << pc;
+ else
+ s << ", pc:none";
+
+ return s.str();
+}
+
+int CVideoPlayerVideo::GetVideoBitrate()
+{
+ return (int)m_videoStats.GetBitrate();
+}
+
+void CVideoPlayerVideo::ResetFrameRateCalc()
+{
+ m_fStableFrameRate = 0.0;
+ m_iFrameRateCount = 0;
+ m_iFrameRateLength = 1;
+ m_iFrameRateErr = 0;
+ m_bAllowDrop = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 0;
+}
+
+double CVideoPlayerVideo::GetCurrentPts()
+{
+ double renderPts;
+ int sleepTime;
+ int queued, discard;
+
+ // get render stats
+ m_renderManager.GetStats(sleepTime, renderPts, queued, discard);
+
+ if (renderPts == DVD_NOPTS_VALUE)
+ return DVD_NOPTS_VALUE;
+ else if (m_stalled)
+ return DVD_NOPTS_VALUE;
+ else if (m_speed == DVD_PLAYSPEED_NORMAL)
+ {
+ if (renderPts < 0)
+ renderPts = 0;
+ }
+ return renderPts;
+}
+
+#define MAXFRAMERATEDIFF 0.01
+#define MAXFRAMESERR 1000
+
+void CVideoPlayerVideo::CalcFrameRate()
+{
+ if (m_iFrameRateLength >= 128 || CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 0)
+ return; //don't calculate the fps
+
+ if (!m_ptsTracker.HasFullBuffer())
+ return; //we can only calculate the frameduration if m_pullupCorrection has a full buffer
+
+ //see if m_pullupCorrection was able to detect a pattern in the timestamps
+ //and is able to calculate the correct frame duration from it
+ double frameduration = m_ptsTracker.GetFrameDuration();
+ if (m_ptsTracker.VFRDetection())
+ frameduration = m_ptsTracker.GetMinFrameDuration();
+
+ if ((frameduration==DVD_NOPTS_VALUE) ||
+ ((CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 1) && ((m_ptsTracker.GetPatternLength() > 1) && !m_ptsTracker.VFRDetection())))
+ {
+ //reset the stored framerates if no good framerate was detected
+ m_fStableFrameRate = 0.0;
+ m_iFrameRateCount = 0;
+ m_iFrameRateErr++;
+
+ if (m_iFrameRateErr == MAXFRAMESERR && m_iFrameRateLength == 1)
+ {
+ CLog::Log(LOGDEBUG,
+ "{} counted {} frames without being able to calculate the framerate, giving up",
+ __FUNCTION__, m_iFrameRateErr);
+ m_bAllowDrop = true;
+ m_iFrameRateLength = 128;
+ }
+ return;
+ }
+
+ double framerate = DVD_TIME_BASE / frameduration;
+
+ //store the current calculated framerate if we don't have any yet
+ if (m_iFrameRateCount == 0)
+ {
+ m_fStableFrameRate = framerate;
+ m_iFrameRateCount++;
+ }
+ //check if the current detected framerate matches with the stored ones
+ else if (fabs(m_fStableFrameRate / m_iFrameRateCount - framerate) <= MAXFRAMERATEDIFF)
+ {
+ m_fStableFrameRate += framerate; //store the calculated framerate
+ m_iFrameRateCount++;
+
+ //if we've measured m_iFrameRateLength seconds of framerates,
+ if (m_iFrameRateCount >= MathUtils::round_int(framerate) * m_iFrameRateLength)
+ {
+ //store the calculated framerate if it differs too much from m_fFrameRate
+ if (fabs(m_fFrameRate - (m_fStableFrameRate / m_iFrameRateCount)) > MAXFRAMERATEDIFF || m_bFpsInvalid)
+ {
+ CLog::Log(LOGDEBUG, "{} framerate was:{:f} calculated:{:f}", __FUNCTION__, m_fFrameRate,
+ m_fStableFrameRate / m_iFrameRateCount);
+ m_fFrameRate = m_fStableFrameRate / m_iFrameRateCount;
+ m_bFpsInvalid = false;
+ m_processInfo.SetVideoFps(static_cast<float>(m_fFrameRate));
+ }
+
+ //reset the stored framerates
+ m_fStableFrameRate = 0.0;
+ m_iFrameRateCount = 0;
+ m_iFrameRateLength *= 2; //double the length we should measure framerates
+
+ //we're allowed to drop frames because we calculated a good framerate
+ m_bAllowDrop = true;
+ }
+ }
+ else //the calculated framerate didn't match, reset the stored ones
+ {
+ m_fStableFrameRate = 0.0;
+ m_iFrameRateCount = 0;
+ }
+}
+
+int CVideoPlayerVideo::CalcDropRequirement(double pts)
+{
+ int result = 0;
+ int lateframes;
+ double iDecoderPts, iRenderPts;
+ int iSkippedPicture = -1;
+ int iDroppedFrames = -1;
+ int iBufferLevel;
+ int queued, discard;
+
+ m_droppingStats.m_lastPts = pts;
+
+ // get decoder stats
+ if (!m_pVideoCodec->GetCodecStats(iDecoderPts, iDroppedFrames, iSkippedPicture))
+ iDecoderPts = pts;
+ if (iDecoderPts == DVD_NOPTS_VALUE)
+ iDecoderPts = pts;
+
+ // get render stats
+ m_renderManager.GetStats(lateframes, iRenderPts, queued, discard);
+ iBufferLevel = queued + discard;
+
+ if (iBufferLevel < 0)
+ result |= DROP_BUFFER_LEVEL;
+ else if (iBufferLevel < 2)
+ {
+ result |= DROP_BUFFER_LEVEL;
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CVideoPlayerVideo::CalcDropRequirement - hurry: {}",
+ iBufferLevel);
+ }
+
+ if (m_bAllowDrop)
+ {
+ if (iSkippedPicture > 0)
+ {
+ CDroppingStats::CGain gain;
+ gain.frames = iSkippedPicture;
+ gain.pts = iDecoderPts;
+ m_droppingStats.m_gain.push_back(gain);
+ m_droppingStats.m_totalGain += gain.frames;
+ result |= DROP_DROPPED;
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CVideoPlayerVideo::CalcDropRequirement - dropped pictures, lateframes: {}, "
+ "Bufferlevel: {}, dropped: {}",
+ lateframes, iBufferLevel, iSkippedPicture);
+ }
+ if (iDroppedFrames > 0)
+ {
+ CDroppingStats::CGain gain;
+ gain.frames = iDroppedFrames;
+ gain.pts = iDecoderPts;
+ m_droppingStats.m_gain.push_back(gain);
+ m_droppingStats.m_totalGain += iDroppedFrames;
+ result |= DROP_DROPPED;
+ CLog::Log(LOGDEBUG, LOGVIDEO,
+ "CVideoPlayerVideo::CalcDropRequirement - dropped in decoder, lateframes: {}, "
+ "Bufferlevel: {}, dropped: {}",
+ lateframes, iBufferLevel, iDroppedFrames);
+ }
+ }
+
+ // subtract gains
+ while (!m_droppingStats.m_gain.empty() &&
+ iRenderPts >= m_droppingStats.m_gain.front().pts)
+ {
+ m_droppingStats.m_totalGain -= m_droppingStats.m_gain.front().frames;
+ m_droppingStats.m_gain.pop_front();
+ }
+
+ // calculate lateness
+ int lateness = lateframes - m_droppingStats.m_totalGain;
+
+ if (lateness > 0 && m_speed)
+ {
+ result |= DROP_VERYLATE;
+ }
+ return result;
+}
+
+void CDroppingStats::Reset()
+{
+ m_gain.clear();
+ m_totalGain = 0;
+}
+
+void CDroppingStats::AddOutputDropGain(double pts, int frames)
+{
+ CDroppingStats::CGain gain;
+ gain.frames = frames;
+ gain.pts = pts;
+ m_gain.push_back(gain);
+ m_totalGain += frames;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h
new file mode 100644
index 0000000..e5d7254
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDClock.h"
+#include "DVDCodecs/Video/DVDVideoCodec.h"
+#include "DVDMessageQueue.h"
+#include "DVDOverlayContainer.h"
+#include "DVDStreamInfo.h"
+#include "IVideoPlayer.h"
+#include "PTSTracker.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
+#include "threads/Thread.h"
+#include "utils/BitstreamStats.h"
+
+#include <atomic>
+
+#define DROP_DROPPED 1
+#define DROP_VERYLATE 2
+#define DROP_BUFFER_LEVEL 4
+
+class CDemuxStreamVideo;
+
+class CDroppingStats
+{
+public:
+ void Reset();
+ void AddOutputDropGain(double pts, int frames);
+ struct CGain
+ {
+ int frames;
+ double pts;
+ };
+ std::deque<CGain> m_gain;
+ int m_totalGain;
+ double m_lastPts;
+};
+
+class CVideoPlayerVideo : public CThread, public IDVDStreamPlayerVideo
+{
+public:
+ CVideoPlayerVideo(CDVDClock* pClock
+ ,CDVDOverlayContainer* pOverlayContainer
+ ,CDVDMessageQueue& parent
+ ,CRenderManager& renderManager,
+ CProcessInfo &processInfo);
+ ~CVideoPlayerVideo() override;
+
+ bool OpenStream(CDVDStreamInfo hint) override;
+ void CloseStream(bool bWaitForBuffers) override;
+ void Flush(bool sync) override;
+ bool AcceptsData() const override;
+ bool HasData() const override;
+ bool IsInited() const override;
+ void SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority = 0) override;
+ void FlushMessages() override;
+
+ void EnableSubtitle(bool bEnable) override { m_bRenderSubs = bEnable; }
+ bool IsSubtitleEnabled() override { return m_bRenderSubs; }
+ double GetSubtitleDelay() override { return m_iSubtitleDelay; }
+ void SetSubtitleDelay(double delay) override { m_iSubtitleDelay = delay; }
+ bool IsStalled() const override { return m_stalled; }
+ bool IsRewindStalled() const override { return m_rewindStalled; }
+ double GetCurrentPts() override;
+ double GetOutputDelay() override; /* returns the expected delay, from that a packet is put in queue */
+ std::string GetPlayerInfo() override;
+ int GetVideoBitrate() override;
+ void SetSpeed(int iSpeed) override;
+
+ // classes
+ CDVDOverlayContainer* m_pOverlayContainer;
+ CDVDClock* m_pClock;
+
+protected:
+
+ enum EOutputState
+ {
+ OUTPUT_NORMAL,
+ OUTPUT_ABORT,
+ OUTPUT_DROPPED,
+ OUTPUT_AGAIN
+ };
+
+ void OnExit() override;
+ void Process() override;
+
+ bool ProcessDecoderOutput(double &frametime, double &pts);
+ void SendMessageBack(const std::shared_ptr<CDVDMsg>& pMsg, int priority = 0);
+ MsgQueueReturnCode GetMessage(std::shared_ptr<CDVDMsg>& pMsg,
+ unsigned int iTimeoutInMilliSeconds,
+ int& priority);
+
+ EOutputState OutputPicture(const VideoPicture* src);
+ void ProcessOverlays(const VideoPicture* pSource, double pts);
+ void OpenStream(CDVDStreamInfo& hint, std::unique_ptr<CDVDVideoCodec> codec);
+
+ void ResetFrameRateCalc();
+ void CalcFrameRate();
+ int CalcDropRequirement(double pts);
+
+ double m_iSubtitleDelay;
+
+ int m_iLateFrames;
+ int m_iDroppedFrames;
+ int m_iDroppedRequest;
+
+ double m_fFrameRate; //framerate of the video currently playing
+ double m_fStableFrameRate; //place to store calculated framerates
+ int m_iFrameRateCount; //how many calculated framerates we stored in m_fStableFrameRate
+ bool m_bAllowDrop; //we can't drop frames until we've calculated the framerate
+ int m_iFrameRateErr; //how many frames we couldn't calculate the framerate, we give up after a while
+ int m_iFrameRateLength; //how many seconds we should measure the framerate
+ //this is increased exponentially from CVideoPlayerVideo::CalcFrameRate()
+
+ bool m_bFpsInvalid; // needed to ignore fps (e.g. dvd stills)
+ bool m_bRenderSubs;
+ float m_fForcedAspectRatio;
+ int m_speed;
+ std::atomic_bool m_stalled = false;
+ std::atomic_bool m_rewindStalled;
+ bool m_paused;
+ IDVDStreamPlayer::ESyncState m_syncState;
+ std::atomic_bool m_bAbortOutput;
+
+ BitstreamStats m_videoStats;
+
+ CDVDMessageQueue m_messageQueue;
+ CDVDMessageQueue& m_messageParent;
+ CDVDStreamInfo m_hints;
+ std::unique_ptr<CDVDVideoCodec> m_pVideoCodec;
+ CPtsTracker m_ptsTracker;
+ std::list<DVDMessageListItem> m_packets;
+ CDroppingStats m_droppingStats;
+ CRenderManager& m_renderManager;
+ VideoPicture m_picture;
+
+ EOutputState m_outputSate;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoReferenceClock.cpp b/xbmc/cores/VideoPlayer/VideoReferenceClock.cpp
new file mode 100644
index 0000000..7508978
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoReferenceClock.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "VideoReferenceClock.h"
+
+#include "ServiceBroker.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/VideoSync.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+CVideoReferenceClock::CVideoReferenceClock() : CThread("RefClock")
+{
+ m_SystemFrequency = CurrentHostFrequency();
+ m_ClockSpeed = 1.0;
+ m_TotalMissedVblanks = 0;
+ m_UseVblank = false;
+
+ m_CurrTime = 0;
+ m_LastIntTime = 0;
+ m_CurrTimeFract = 0.0;
+ m_RefreshRate = 0.0;
+ m_MissedVblanks = 0;
+ m_VblankTime = 0;
+ m_vsyncStopEvent.Reset();
+
+ Start();
+}
+
+CVideoReferenceClock::~CVideoReferenceClock()
+{
+ m_bStop = true;
+ m_vsyncStopEvent.Set();
+ StopThread();
+}
+
+void CVideoReferenceClock::Start()
+{
+ if(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK) && !IsRunning())
+ Create();
+}
+
+void CVideoReferenceClock::CBUpdateClock(int NrVBlanks, uint64_t time, void *clock)
+{
+ {
+ CVideoReferenceClock *refClock = static_cast<CVideoReferenceClock*>(clock);
+ std::unique_lock<CCriticalSection> lock(refClock->m_CritSection);
+ refClock->m_VblankTime = time;
+ refClock->UpdateClock(NrVBlanks, true);
+ }
+}
+
+void CVideoReferenceClock::Process()
+{
+ bool SetupSuccess = false;
+ int64_t Now;
+
+ while(!m_bStop)
+ {
+ m_pVideoSync = CServiceBroker::GetWinSystem()->GetVideoSync(this);
+
+ if (m_pVideoSync)
+ {
+ SetupSuccess = m_pVideoSync->Setup(CBUpdateClock);
+ UpdateRefreshrate();
+ }
+
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+ Now = CurrentHostCounter();
+ m_CurrTime = Now;
+ m_LastIntTime = m_CurrTime;
+ m_CurrTimeFract = 0.0;
+ m_ClockSpeed = 1.0;
+ m_TotalMissedVblanks = 0;
+ m_MissedVblanks = 0;
+
+ if (SetupSuccess)
+ {
+ m_UseVblank = true; //tell other threads we're using vblank as clock
+ m_VblankTime = Now; //initialize the timestamp of the last vblank
+ SingleLock.unlock();
+
+ // we might got signalled while we did not wait
+ if (!m_vsyncStopEvent.Signaled())
+ {
+ //run the clock
+ m_pVideoSync->Run(m_vsyncStopEvent);
+ m_vsyncStopEvent.Reset();
+ }
+ }
+ else
+ {
+ SingleLock.unlock();
+ CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setup failed, falling back to CurrentHostCounter()");
+ }
+
+ SingleLock.lock();
+ m_UseVblank = false; //we're back to using the systemclock
+ SingleLock.unlock();
+
+ //clean up the vblank clock
+ if (m_pVideoSync)
+ {
+ m_pVideoSync->Cleanup();
+ m_pVideoSync.reset();
+ }
+
+ if (!SetupSuccess)
+ break;
+ }
+}
+
+//this is called from the vblank run function and from CVideoReferenceClock::Wait in case of a late update
+void CVideoReferenceClock::UpdateClock(int NrVBlanks, bool CheckMissed)
+{
+ if (CheckMissed) //set to true from the vblank run function, set to false from Wait and GetTime
+ {
+ if (NrVBlanks < m_MissedVblanks) //if this is true the vblank detection in the run function is wrong
+ CLog::Log(
+ LOGDEBUG,
+ "CVideoReferenceClock: detected {} vblanks, missed {}, refreshrate might have changed",
+ NrVBlanks, m_MissedVblanks);
+
+ NrVBlanks -= m_MissedVblanks; //subtract the vblanks we missed
+ m_MissedVblanks = 0;
+ }
+ else
+ {
+ m_MissedVblanks += NrVBlanks; //tell the vblank clock how many vblanks it missed
+ m_TotalMissedVblanks += NrVBlanks; //for the codec information screen
+ m_VblankTime += m_SystemFrequency * static_cast<int64_t>(NrVBlanks) / MathUtils::round_int(m_RefreshRate); //set the vblank time forward
+ }
+
+ if (NrVBlanks > 0) //update the clock with the adjusted frequency if we have any vblanks
+ {
+ double increment = UpdateInterval() * NrVBlanks;
+ double integer = floor(increment);
+ m_CurrTime += static_cast<int64_t>(integer + 0.5); //make sure it gets correctly converted to int
+
+ //accumulate what we lost due to rounding in m_CurrTimeFract, then add the integer part of that to m_CurrTime
+ m_CurrTimeFract += increment - integer;
+ integer = floor(m_CurrTimeFract);
+ m_CurrTime += static_cast<int64_t>(integer + 0.5);
+ m_CurrTimeFract -= integer;
+ }
+}
+
+double CVideoReferenceClock::UpdateInterval() const
+{
+ return m_ClockSpeed / m_RefreshRate * static_cast<double>(m_SystemFrequency);
+}
+
+//called from dvdclock to get the time
+int64_t CVideoReferenceClock::GetTime(bool interpolated /* = true*/)
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+
+ //when using vblank, get the time from that, otherwise use the systemclock
+ if (m_UseVblank)
+ {
+ int64_t NextVblank;
+ int64_t Now;
+
+ Now = CurrentHostCounter(); //get current system time
+ NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen
+
+ while(Now >= NextVblank) //keep looping until the next vblank is in the future
+ {
+ UpdateClock(1, false); //update clock when next vblank should have happened already
+ NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen
+ }
+
+ if (interpolated)
+ {
+ //interpolate from the last time the clock was updated
+ double elapsed = static_cast<double>(Now - m_VblankTime) * m_ClockSpeed;
+ //don't interpolate more than 2 vblank periods
+ elapsed = std::min(elapsed, UpdateInterval() * 2.0);
+
+ //make sure the clock doesn't go backwards
+ int64_t intTime = m_CurrTime + static_cast<int64_t>(elapsed);
+ if (intTime > m_LastIntTime)
+ m_LastIntTime = intTime;
+
+ return m_LastIntTime;
+ }
+ else
+ {
+ return m_CurrTime;
+ }
+ }
+ else
+ {
+ return CurrentHostCounter();
+ }
+}
+
+void CVideoReferenceClock::SetSpeed(double Speed)
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+ //VideoPlayer can change the speed to fit the rereshrate
+ if (m_UseVblank)
+ {
+ if (Speed != m_ClockSpeed)
+ {
+ m_ClockSpeed = Speed;
+ CLog::Log(LOGDEBUG, "CVideoReferenceClock: Clock speed {:0.2f} %", m_ClockSpeed * 100.0);
+ }
+ }
+}
+
+double CVideoReferenceClock::GetSpeed()
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+
+ //VideoPlayer needs to know the speed for the resampler
+ if (m_UseVblank)
+ return m_ClockSpeed;
+ else
+ return 1.0;
+}
+
+void CVideoReferenceClock::UpdateRefreshrate()
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+ m_RefreshRate = static_cast<double>(m_pVideoSync->GetFps());
+ m_ClockSpeed = 1.0;
+
+ CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate: {:.3f} hertz", m_RefreshRate);
+}
+
+//VideoPlayer needs to know the refreshrate for matching the fps of the video playing to it
+double CVideoReferenceClock::GetRefreshRate(double* interval /*= NULL*/)
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+
+ if (m_UseVblank)
+ {
+ if (interval)
+ *interval = m_ClockSpeed / m_RefreshRate;
+
+ return m_RefreshRate;
+ }
+ else
+ return -1;
+}
+
+#define MAXVBLANKDELAY 13LL
+//guess when the next vblank should happen,
+//based on the refreshrate and when the previous one happened
+//increase that by 30% to allow for errors
+int64_t CVideoReferenceClock::TimeOfNextVblank() const
+{
+ return m_VblankTime + (m_SystemFrequency / MathUtils::round_int(m_RefreshRate) * MAXVBLANKDELAY / 10LL);
+}
+
+//for the codec information screen
+bool CVideoReferenceClock::GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const
+{
+ std::unique_lock<CCriticalSection> SingleLock(m_CritSection);
+
+ if (m_UseVblank)
+ {
+ MissedVblanks = m_TotalMissedVblanks;
+ ClockSpeed = m_ClockSpeed;
+ RefreshRate = m_RefreshRate;
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoReferenceClock.h b/xbmc/cores/VideoPlayer/VideoReferenceClock.h
new file mode 100644
index 0000000..4419b6d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoReferenceClock.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+
+#include <memory>
+
+class CVideoSync;
+
+class CVideoReferenceClock : CThread
+{
+ public:
+ CVideoReferenceClock();
+ ~CVideoReferenceClock() override;
+
+ int64_t GetTime(bool interpolated = true);
+ void SetSpeed(double Speed);
+ double GetSpeed();
+ double GetRefreshRate(double* interval = nullptr);
+ bool GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const;
+
+ private:
+ void Process() override;
+ void Start();
+ void UpdateRefreshrate();
+ void UpdateClock(int NrVBlanks, bool CheckMissed);
+ double UpdateInterval() const;
+ int64_t TimeOfNextVblank() const;
+ static void CBUpdateClock(int NrVBlanks, uint64_t time, void *clock);
+
+ int64_t m_CurrTime; //the current time of the clock when using vblank as clock source
+ int64_t m_LastIntTime; //last interpolated clock value, to make sure the clock doesn't go backwards
+ double m_CurrTimeFract; //fractional part that is lost due to rounding when updating the clock
+ double m_ClockSpeed; //the frequency of the clock set by VideoPlayer
+ int64_t m_SystemFrequency; //frequency of the systemclock
+
+ bool m_UseVblank; //set to true when vblank is used as clock source
+ double m_RefreshRate; //current refreshrate
+ int m_MissedVblanks; //number of clock updates missed by the vblank clock
+ int m_TotalMissedVblanks;//total number of clock updates missed, used by codec information screen
+ int64_t m_VblankTime; //last time the clock was updated when using vblank as clock
+
+ CEvent m_vsyncStopEvent;
+
+ mutable CCriticalSection m_CritSection;
+
+ std::unique_ptr<CVideoSync> m_pVideoSync;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp
new file mode 100644
index 0000000..b8649da
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BaseRenderer.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <algorithm>
+#include <cstdlib> // std::abs(int) prototype
+
+
+CBaseRenderer::CBaseRenderer()
+{
+ for (int i=0; i < 4; i++)
+ {
+ m_rotatedDestCoords[i].x = 0;
+ m_rotatedDestCoords[i].y = 0;
+ m_savedRotatedDestCoords[i].x = 0;
+ m_savedRotatedDestCoords[i].y = 0;
+ }
+}
+
+CBaseRenderer::~CBaseRenderer() = default;
+
+float CBaseRenderer::GetAspectRatio() const
+{
+ float width = (float)m_sourceWidth;
+ float height = (float)m_sourceHeight;
+ return m_sourceFrameRatio * width / height * m_sourceHeight / m_sourceWidth;
+}
+
+void CBaseRenderer::GetVideoRect(CRect& source, CRect& dest, CRect& view) const
+{
+ source = m_sourceRect;
+ dest = m_destRect;
+ view = m_viewRect;
+}
+
+inline void CBaseRenderer::ReorderDrawPoints()
+{
+ // 0 - top left, 1 - top right, 2 - bottom right, 3 - bottom left
+ float origMat[4][2] = {{m_destRect.x1, m_destRect.y1},
+ {m_destRect.x2, m_destRect.y1},
+ {m_destRect.x2, m_destRect.y2},
+ {m_destRect.x1, m_destRect.y2}};
+
+ int pointOffset = m_renderOrientation / 90;
+
+ // if renderer doesn't support rotation
+ // treat orientation as 0 degree so that
+ // ffmpeg might handle it.
+ if (!Supports(RENDERFEATURE_ROTATION))
+ {
+ pointOffset = 0;
+ }
+
+ for (int destIdx=0, srcIdx=pointOffset; destIdx < 4; destIdx++)
+ {
+ m_rotatedDestCoords[destIdx].x = origMat[srcIdx][0];
+ m_rotatedDestCoords[destIdx].y = origMat[srcIdx][1];
+
+ srcIdx++;
+ srcIdx = srcIdx % 4;
+ }
+}
+
+void CBaseRenderer::saveRotatedCoords()
+{
+ for (int i = 0; i < 4; i++)
+ m_savedRotatedDestCoords[i] = m_rotatedDestCoords[i];
+}
+
+void CBaseRenderer::syncDestRectToRotatedPoints()
+{
+ m_rotatedDestCoords[0].x = m_destRect.x1;
+ m_rotatedDestCoords[0].y = m_destRect.y1;
+ m_rotatedDestCoords[1].x = m_destRect.x2;
+ m_rotatedDestCoords[1].y = m_destRect.y1;
+ m_rotatedDestCoords[2].x = m_destRect.x2;
+ m_rotatedDestCoords[2].y = m_destRect.y2;
+ m_rotatedDestCoords[3].x = m_destRect.x1;
+ m_rotatedDestCoords[3].y = m_destRect.y2;
+}
+
+void CBaseRenderer::restoreRotatedCoords()
+{
+ for (int i = 0; i < 4; i++)
+ m_rotatedDestCoords[i] = m_savedRotatedDestCoords[i];
+}
+
+void CBaseRenderer::CalcDestRect(float offsetX,
+ float offsetY,
+ float width,
+ float height,
+ float inputFrameRatio,
+ float zoomAmount,
+ float verticalShift,
+ CRect& destRect)
+{
+ // if view window is empty, set empty destination
+ if (height == 0 || width == 0)
+ {
+ destRect.SetRect(0.0f, 0.0f, 0.0f, 0.0f);
+ return;
+ }
+
+ // scale up image as much as possible
+ // and keep the aspect ratio (introduces with black bars)
+ // calculate the correct output frame ratio (using the users pixel ratio setting
+ // and the output pixel ratio setting)
+
+ float outputFrameRatio = inputFrameRatio / CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo().fPixelRatio;
+
+ // allow a certain error to maximize size of render area
+ float fCorrection = width / height / outputFrameRatio - 1.0f;
+ float fAllowed = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ERRORINASPECT) * 0.01f;
+ if (fCorrection > fAllowed)
+ fCorrection = fAllowed;
+ if (fCorrection < -fAllowed)
+ fCorrection = - fAllowed;
+
+ outputFrameRatio *= 1.0f + fCorrection;
+
+ bool isRotated = false;
+ if (m_renderOrientation == 90 ||
+ m_renderOrientation == 270)
+ isRotated = true;
+
+ float newWidth;
+ float newHeight;
+
+ if (!isRotated)
+ {
+ // maximize the movie width
+ newWidth = width;
+ newHeight = newWidth / outputFrameRatio;
+ if (newHeight > height)
+ {
+ newHeight = height;
+ newWidth = newHeight * outputFrameRatio;
+ }
+ }
+ else
+ {
+ // maximize the movie height
+ newHeight = std::min(width, height);
+ newWidth = newHeight / outputFrameRatio;
+ if (newWidth > width)
+ {
+ newWidth = std::min(width, height);
+ newHeight = newWidth * outputFrameRatio;
+ }
+ }
+
+ // Scale the movie up by set zoom amount
+ newWidth *= zoomAmount;
+ newHeight *= zoomAmount;
+
+ // if we are less than one pixel off use the complete screen instead
+ if (std::abs(newWidth - width) < 1.0f)
+ newWidth = width;
+ if (std::abs(newHeight - height) < 1.0f)
+ newHeight = height;
+
+ // Centre the movie
+ float posY = (height - newHeight) / 2;
+ float posX = (width - newWidth) / 2;
+
+ // vertical shift range -1 to 1 shifts within the top and bottom black bars
+ // if there are no top and bottom black bars, this range does nothing
+ float blackBarSize = std::max((height - newHeight) / 2.0f, 0.0f);
+ posY += blackBarSize * std::max(std::min(verticalShift, 1.0f), -1.0f);
+
+ // vertical shift ranges -2 to -1 and 1 to 2 will shift the image out of the screen
+ // if vertical shift is -2 it will be completely shifted out the top,
+ // if it's 2 it will be completely shifted out the bottom
+ float shiftRange = std::min(newHeight, newHeight - (newHeight - height) / 2.0f);
+ if (verticalShift > 1.0f)
+ posY += shiftRange * (verticalShift - 1.0f);
+ else if (verticalShift < -1.0f)
+ posY += shiftRange * (verticalShift + 1.0f);
+
+ destRect.x1 = static_cast<float>(MathUtils::round_int(static_cast<double>(posX + offsetX)));
+ destRect.x2 = destRect.x1 + MathUtils::round_int(static_cast<double>(newWidth));
+ destRect.y1 = static_cast<float>(MathUtils::round_int(static_cast<double>(posY + offsetY)));
+ destRect.y2 = destRect.y1 + MathUtils::round_int(static_cast<double>(newHeight));
+}
+
+void CBaseRenderer::CalcNormalRenderRect(float offsetX,
+ float offsetY,
+ float width,
+ float height,
+ float inputFrameRatio,
+ float zoomAmount,
+ float verticalShift)
+{
+ CalcDestRect(offsetX, offsetY, width, height, inputFrameRatio, zoomAmount, verticalShift,
+ m_destRect);
+
+ // bail out if view window is empty
+ if (height == 0 || width == 0)
+ return;
+
+ // clip as needed
+ if (!(CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() || CServiceBroker::GetWinSystem()->GetGfxContext().IsCalibrating()))
+ {
+ CRect original(m_destRect);
+ m_destRect.Intersect(CRect(offsetX, offsetY, offsetX + width, offsetY + height));
+ if (m_destRect != original)
+ {
+ float scaleX = m_sourceRect.Width() / original.Width();
+ float scaleY = m_sourceRect.Height() / original.Height();
+ m_sourceRect.x1 += (m_destRect.x1 - original.x1) * scaleX;
+ m_sourceRect.y1 += (m_destRect.y1 - original.y1) * scaleY;
+ m_sourceRect.x2 += (m_destRect.x2 - original.x2) * scaleX;
+ m_sourceRect.y2 += (m_destRect.y2 - original.y2) * scaleY;
+ }
+ }
+
+ ReorderDrawPoints();
+}
+
+//***************************************************************************************
+// CalculateFrameAspectRatio()
+//
+// Considers the source frame size and output frame size (as suggested by mplayer)
+// to determine if the pixels in the source are not square. It calculates the aspect
+// ratio of the output frame. We consider the cases of VCD, SVCD and DVD separately,
+// as these are intended to be viewed on a non-square pixel TV set, so the pixels are
+// defined to be the same ratio as the intended display pixels.
+// These formats are determined by frame size.
+//***************************************************************************************
+void CBaseRenderer::CalculateFrameAspectRatio(unsigned int desired_width, unsigned int desired_height)
+{
+ m_sourceFrameRatio = (float)desired_width / desired_height;
+
+ // Check whether mplayer has decided that the size of the video file should be changed
+ // This indicates either a scaling has taken place (which we didn't ask for) or it has
+ // found an aspect ratio parameter from the file, and is changing the frame size based
+ // on that.
+ if (m_sourceWidth == desired_width && m_sourceHeight == desired_height)
+ return ;
+
+ // mplayer is scaling in one or both directions. We must alter our Source Pixel Ratio
+ float imageFrameRatio = (float)m_sourceWidth / m_sourceHeight;
+
+ // OK, most sources will be correct now, except those that are intended
+ // to be displayed on non-square pixel based output devices (ie PAL or NTSC TVs)
+ // This includes VCD, SVCD, and DVD (and possibly others that we are not doing yet)
+ // For this, we can base the pixel ratio on the pixel ratios of PAL and NTSC,
+ // though we will need to adjust for anamorphic sources (ie those whose
+ // output frame ratio is not 4:3) and for SVCDs which have 2/3rds the
+ // horizontal resolution of the default NTSC or PAL frame sizes
+
+ // The following are the defined standard ratios for PAL and NTSC pixels
+ // NOTE: These aren't technically (in terms of BT601) correct - the commented values are,
+ // but it seems that many DVDs nowadays are mastered incorrectly, so two wrongs
+ // may indeed make a right. The "wrong" values here ensure the output frame is
+ // 4x3 (or 16x9)
+ const float PALPixelRatio = 16.0f / 15.0f; // 128.0f / 117.0f;
+ const float NTSCPixelRatio = 8.0f / 9.0f; // 4320.0f / 4739.0f;
+
+ // Calculate the correction needed for anamorphic sources
+ float Non4by3Correction = m_sourceFrameRatio / (4.0f / 3.0f);
+
+ // Finally, check for a VCD, SVCD or DVD frame size as these need special aspect ratios
+ if (m_sourceWidth == 352)
+ { // VCD?
+ if (m_sourceHeight == 240) // NTSC
+ m_sourceFrameRatio = imageFrameRatio * NTSCPixelRatio;
+ if (m_sourceHeight == 288) // PAL
+ m_sourceFrameRatio = imageFrameRatio * PALPixelRatio;
+ }
+ if (m_sourceWidth == 480)
+ { // SVCD?
+ if (m_sourceHeight == 480) // NTSC
+ m_sourceFrameRatio = imageFrameRatio * 3.0f / 2.0f * NTSCPixelRatio * Non4by3Correction;
+ if (m_sourceHeight == 576) // PAL
+ m_sourceFrameRatio = imageFrameRatio * 3.0f / 2.0f * PALPixelRatio * Non4by3Correction;
+ }
+ if (m_sourceWidth == 720)
+ { // DVD?
+ if (m_sourceHeight == 480) // NTSC
+ m_sourceFrameRatio = imageFrameRatio * NTSCPixelRatio * Non4by3Correction;
+ if (m_sourceHeight == 576) // PAL
+ m_sourceFrameRatio = imageFrameRatio * PALPixelRatio * Non4by3Correction;
+ }
+}
+
+void CBaseRenderer::ManageRenderArea()
+{
+ m_viewRect = CServiceBroker::GetWinSystem()->GetGfxContext().GetViewWindow();
+
+ m_sourceRect.x1 = 0.0f;
+ m_sourceRect.y1 = 0.0f;
+ m_sourceRect.x2 = (float)m_sourceWidth;
+ m_sourceRect.y2 = (float)m_sourceHeight;
+
+ unsigned int stereo_mode = CONF_FLAGS_STEREO_MODE_MASK(m_iFlags);
+ int stereo_view = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoView();
+
+ if(CONF_FLAGS_STEREO_CADENCE(m_iFlags) == CONF_FLAGS_STEREO_CADANCE_RIGHT_LEFT)
+ {
+ if (stereo_view == RENDER_STEREO_VIEW_LEFT) stereo_view = RENDER_STEREO_VIEW_RIGHT;
+ else if(stereo_view == RENDER_STEREO_VIEW_RIGHT) stereo_view = RENDER_STEREO_VIEW_LEFT;
+ }
+
+ switch(stereo_mode)
+ {
+ case CONF_FLAGS_STEREO_MODE_TAB:
+ if (stereo_view == RENDER_STEREO_VIEW_LEFT)
+ m_sourceRect.y2 *= 0.5f;
+ else if(stereo_view == RENDER_STEREO_VIEW_RIGHT)
+ m_sourceRect.y1 += m_sourceRect.y2*0.5f;
+ break;
+
+ case CONF_FLAGS_STEREO_MODE_SBS:
+ if (stereo_view == RENDER_STEREO_VIEW_LEFT)
+ m_sourceRect.x2 *= 0.5f;
+ else if(stereo_view == RENDER_STEREO_VIEW_RIGHT)
+ m_sourceRect.x1 += m_sourceRect.x2*0.5f;
+ break;
+
+ default:
+ break;
+ }
+
+ CalcNormalRenderRect(m_viewRect.x1, m_viewRect.y1, m_viewRect.Width(), m_viewRect.Height(),
+ GetAspectRatio() * CDisplaySettings::GetInstance().GetPixelRatio(),
+ CDisplaySettings::GetInstance().GetZoomAmount(),
+ CDisplaySettings::GetInstance().GetVerticalShift());
+}
+
+EShaderFormat CBaseRenderer::GetShaderFormat()
+{
+ EShaderFormat ret = SHADER_NONE;
+
+ if (m_format == AV_PIX_FMT_YUV420P)
+ ret = SHADER_YV12;
+ else if (m_format == AV_PIX_FMT_YUV420P9)
+ ret = SHADER_YV12_9;
+ else if (m_format == AV_PIX_FMT_YUV420P10)
+ ret = SHADER_YV12_10;
+ else if (m_format == AV_PIX_FMT_YUV420P12)
+ ret = SHADER_YV12_12;
+ else if (m_format == AV_PIX_FMT_YUV420P14)
+ ret = SHADER_YV12_14;
+ else if (m_format == AV_PIX_FMT_YUV420P16)
+ ret = SHADER_YV12_16;
+ else if (m_format == AV_PIX_FMT_NV12)
+ ret = SHADER_NV12;
+ else if (m_format == AV_PIX_FMT_YUYV422)
+ ret = SHADER_YUY2;
+ else if (m_format == AV_PIX_FMT_UYVY422)
+ ret = SHADER_UYVY;
+ else
+ CLog::Log(LOGERROR, "CBaseRenderer::GetShaderFormat - unsupported format {}", m_format);
+
+ return ret;
+}
+
+void CBaseRenderer::SetViewMode(int viewMode)
+{
+ if (viewMode < ViewModeNormal || viewMode > ViewModeZoom110Width)
+ viewMode = ViewModeNormal;
+
+ m_videoSettings.m_ViewMode = viewMode;
+
+ // get our calibrated full screen resolution
+ RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ float screenWidth = (float)(info.Overscan.right - info.Overscan.left);
+ float screenHeight = (float)(info.Overscan.bottom - info.Overscan.top);
+
+ // and the source frame ratio
+ float sourceFrameRatio = GetAspectRatio();
+
+ bool is43 = (sourceFrameRatio < 8.f/(3.f*sqrt(3.f)) &&
+ m_videoSettings.m_ViewMode == ViewModeNormal);
+
+ // Splitres scaling factor
+ float xscale = (float)info.iScreenWidth / (float)info.iWidth;
+ float yscale = (float)info.iScreenHeight / (float)info.iHeight;
+
+ screenWidth *= xscale;
+ screenHeight *= yscale;
+
+ CDisplaySettings::GetInstance().SetVerticalShift(0.0f);
+ CDisplaySettings::GetInstance().SetNonLinearStretched(false);
+
+ if (m_videoSettings.m_ViewMode == ViewModeZoom ||
+ (is43 && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_STRETCH43) == ViewModeZoom))
+ { // zoom image so no black bars
+ CDisplaySettings::GetInstance().SetPixelRatio(1.0);
+ // calculate the desired output ratio
+ float outputFrameRatio = sourceFrameRatio * CDisplaySettings::GetInstance().GetPixelRatio() / info.fPixelRatio;
+ // now calculate the correct zoom amount. First zoom to full height.
+ float newHeight = screenHeight;
+ float newWidth = newHeight * outputFrameRatio;
+ CDisplaySettings::GetInstance().SetZoomAmount(newWidth / screenWidth);
+ if (newWidth < screenWidth)
+ { // zoom to full width
+ newWidth = screenWidth;
+ newHeight = newWidth / outputFrameRatio;
+ CDisplaySettings::GetInstance().SetZoomAmount(newHeight / screenHeight);
+ }
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeStretch4x3)
+ { // stretch image to 4:3 ratio
+ CDisplaySettings::GetInstance().SetZoomAmount(1.0);
+ // now we need to set CDisplaySettings::GetInstance().GetPixelRatio() so that
+ // fOutputFrameRatio = 4:3.
+ CDisplaySettings::GetInstance().SetPixelRatio((4.0f / 3.0f) / sourceFrameRatio);
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeWideZoom ||
+ (is43 && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_STRETCH43) == ViewModeWideZoom))
+ { // super zoom
+ float stretchAmount = (screenWidth / screenHeight) * info.fPixelRatio / sourceFrameRatio;
+ CDisplaySettings::GetInstance().SetPixelRatio(pow(stretchAmount, float(2.0/3.0)));
+ CDisplaySettings::GetInstance().SetZoomAmount(
+ pow(stretchAmount, float((stretchAmount < 1.0f) ? -1.0 / 3.0 : 1.0 / 3.0)));
+ CDisplaySettings::GetInstance().SetNonLinearStretched(true);
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeStretch16x9 ||
+ m_videoSettings.m_ViewMode == ViewModeStretch16x9Nonlin ||
+ (is43 && (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_STRETCH43) == ViewModeStretch16x9 ||
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_STRETCH43) == ViewModeStretch16x9Nonlin)))
+ { // stretch image to 16:9 ratio
+ CDisplaySettings::GetInstance().SetZoomAmount(1.0);
+ // stretch to the limits of the 16:9 screen.
+ // incorrect behaviour, but it's what the users want, so...
+ CDisplaySettings::GetInstance().SetPixelRatio((screenWidth / screenHeight) * info.fPixelRatio / sourceFrameRatio);
+ bool nonlin = (is43 && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_STRETCH43) == ViewModeStretch16x9Nonlin) ||
+ m_videoSettings.m_ViewMode == ViewModeStretch16x9Nonlin;
+ CDisplaySettings::GetInstance().SetNonLinearStretched(nonlin);
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeOriginal)
+ { // zoom image so that the height is the original size
+ CDisplaySettings::GetInstance().SetPixelRatio(1.0);
+ // get the size of the media file
+ // calculate the desired output ratio
+ float outputFrameRatio = sourceFrameRatio * CDisplaySettings::GetInstance().GetPixelRatio() / info.fPixelRatio;
+ // now calculate the correct zoom amount. First zoom to full width.
+ float newHeight = screenWidth / outputFrameRatio;
+ if (newHeight > screenHeight)
+ { // zoom to full height
+ newHeight = screenHeight;
+ }
+ // now work out the zoom amount so that no zoom is done
+ CDisplaySettings::GetInstance().SetZoomAmount(m_sourceHeight / newHeight);
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeCustom)
+ {
+ CDisplaySettings::GetInstance().SetZoomAmount(m_videoSettings.m_CustomZoomAmount);
+ CDisplaySettings::GetInstance().SetPixelRatio(m_videoSettings.m_CustomPixelRatio);
+ CDisplaySettings::GetInstance().SetNonLinearStretched(m_videoSettings.m_CustomNonLinStretch);
+ CDisplaySettings::GetInstance().SetVerticalShift(m_videoSettings.m_CustomVerticalShift);
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeZoom120Width)
+ {
+ float fitHeightZoom = sourceFrameRatio * screenHeight / (info.fPixelRatio * screenWidth);
+ CDisplaySettings::GetInstance().SetPixelRatio(1.0f);
+ CDisplaySettings::GetInstance().SetZoomAmount(fitHeightZoom < 1.0f ? 1.0f : (fitHeightZoom > 1.2f ? 1.2f : fitHeightZoom));
+ }
+ else if (m_videoSettings.m_ViewMode == ViewModeZoom110Width)
+ {
+ float fitHeightZoom = sourceFrameRatio * screenHeight / (info.fPixelRatio * screenWidth);
+ CDisplaySettings::GetInstance().SetPixelRatio(1.0f);
+ CDisplaySettings::GetInstance().SetZoomAmount(fitHeightZoom < 1.0f ? 1.0f : (fitHeightZoom > 1.1f ? 1.1f : fitHeightZoom));
+ }
+ else // if (CMediaSettings::GetInstance().GetCurrentVideoSettings().m_ViewMode == ViewModeNormal)
+ {
+ CDisplaySettings::GetInstance().SetPixelRatio(1.0);
+ CDisplaySettings::GetInstance().SetZoomAmount(1.0);
+ }
+
+ //@TODO
+ m_videoSettings.m_CustomZoomAmount = CDisplaySettings::GetInstance().GetZoomAmount();
+ m_videoSettings.m_CustomPixelRatio = CDisplaySettings::GetInstance().GetPixelRatio();
+ m_videoSettings.m_CustomNonLinStretch = CDisplaySettings::GetInstance().IsNonLinearStretched();
+ m_videoSettings.m_CustomVerticalShift = CDisplaySettings::GetInstance().GetVerticalShift();
+}
+
+void CBaseRenderer::MarkDirty()
+{
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(m_destRect);
+}
+
+void CBaseRenderer::SetVideoSettings(const CVideoSettings &settings)
+{
+ m_videoSettings = settings;
+}
+
+void CBaseRenderer::SettingOptionsRenderMethodsFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data)
+{
+ list.emplace_back(g_localizeStrings.Get(13416), RENDER_METHOD_AUTO);
+
+#ifdef HAS_DX
+ list.push_back(IntegerSettingOption(g_localizeStrings.Get(16319), RENDER_METHOD_DXVA));
+ list.push_back(IntegerSettingOption(g_localizeStrings.Get(13431), RENDER_METHOD_D3D_PS));
+ list.push_back(IntegerSettingOption(g_localizeStrings.Get(13419), RENDER_METHOD_SOFTWARE));
+#endif
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h
new file mode 100644
index 0000000..67871f9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DebugInfo.h"
+#include "RenderCapture.h"
+#include "RenderInfo.h"
+#include "VideoShaders/ShaderFormats.h"
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "utils/Geometry.h"
+
+#include <utility>
+#include <vector>
+
+#define MAX_FIELDS 3
+#define NUM_BUFFERS 6
+
+class CSetting;
+struct IntegerSettingOption;
+
+enum EFIELDSYNC
+{
+ FS_NONE,
+ FS_TOP,
+ FS_BOT
+};
+
+// Render Methods
+enum RenderMethods
+{
+ RENDER_METHOD_AUTO = 0,
+ RENDER_METHOD_GLSL,
+ RENDER_METHOD_SOFTWARE,
+ RENDER_METHOD_D3D_PS,
+ RENDER_METHOD_DXVA,
+ RENDER_OVERLAYS = 99 // to retain compatibility
+};
+
+struct VideoPicture;
+
+class CBaseRenderer
+{
+public:
+ CBaseRenderer();
+ virtual ~CBaseRenderer();
+
+ // Player functions
+ virtual bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) = 0;
+ virtual bool IsConfigured() = 0;
+ virtual void AddVideoPicture(const VideoPicture &picture, int index) = 0;
+ virtual bool IsPictureHW(const VideoPicture& picture) { return false; }
+ virtual void UnInit() = 0;
+ virtual bool Flush(bool saveBuffers) { return false; }
+ virtual void SetBufferSize(int numBuffers) { }
+ virtual void ReleaseBuffer(int idx) { }
+ virtual bool NeedBuffer(int idx) { return false; }
+ virtual bool IsGuiLayer() { return true; }
+ // Render info, can be called before configure
+ virtual CRenderInfo GetRenderInfo() { return CRenderInfo(); }
+ virtual void Update() = 0;
+ virtual void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) = 0;
+ virtual bool RenderCapture(CRenderCapture* capture) = 0;
+ virtual bool ConfigChanged(const VideoPicture &picture) = 0;
+
+ // Feature support
+ virtual bool SupportsMultiPassRendering() = 0;
+ virtual bool Supports(ERENDERFEATURE feature) const { return false; }
+ virtual bool Supports(ESCALINGMETHOD method) const = 0;
+
+ virtual bool WantsDoublePass() { return false; }
+
+ void SetViewMode(int viewMode);
+
+ /*! \brief Get video rectangle and view window
+ \param source is original size of the video
+ \param dest is the target rendering area honoring aspect ratio of source
+ \param view is the entire target rendering area for the video (including black bars)
+ */
+ void GetVideoRect(CRect& source, CRect& dest, CRect& view) const;
+ float GetAspectRatio() const;
+
+ static void SettingOptionsRenderMethodsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data);
+
+ void SetVideoSettings(const CVideoSettings &settings);
+
+ // Gets debug info from render buffer
+ virtual DEBUG_INFO_VIDEO GetDebugInfo(int idx) { return {}; }
+
+ virtual CRenderCapture* GetRenderCapture() { return nullptr; }
+
+protected:
+ void CalcDestRect(float offsetX,
+ float offsetY,
+ float width,
+ float height,
+ float inputFrameRatio,
+ float zoomAmount,
+ float verticalShift,
+ CRect& destRect);
+ void CalcNormalRenderRect(float offsetX, float offsetY, float width, float height,
+ float inputFrameRatio, float zoomAmount, float verticalShift);
+ void CalculateFrameAspectRatio(unsigned int desired_width, unsigned int desired_height);
+ virtual void ManageRenderArea();
+ virtual void ReorderDrawPoints();
+ virtual EShaderFormat GetShaderFormat();
+ void MarkDirty();
+
+ //@todo drop those
+ void saveRotatedCoords();//saves the current state of m_rotatedDestCoords
+ void syncDestRectToRotatedPoints();//sync any changes of m_destRect to m_rotatedDestCoords
+ void restoreRotatedCoords();//restore the current state of m_rotatedDestCoords from saveRotatedCoords
+
+ unsigned int m_sourceWidth = 720;
+ unsigned int m_sourceHeight = 480;
+ float m_sourceFrameRatio = 1.0f;
+ float m_fps = 0.0f;
+
+ unsigned int m_renderOrientation = 0; // orientation of the video in degrees counter clockwise
+ // for drawing the texture with glVertex4f (holds all 4 corner points of the destination rect
+ // with correct orientation based on m_renderOrientation
+ // 0 - top left, 1 - top right, 2 - bottom right, 3 - bottom left
+ CPoint m_rotatedDestCoords[4];
+ CPoint m_savedRotatedDestCoords[4];//saved points from saveRotatedCoords call
+
+ CRect m_destRect;
+ CRect m_sourceRect;
+ CRect m_viewRect;
+
+ // rendering flags
+ unsigned m_iFlags = 0;
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+
+ CVideoSettings m_videoSettings;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt
new file mode 100644
index 0000000..edafa9e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt
@@ -0,0 +1,58 @@
+set(SOURCES BaseRenderer.cpp
+ ColorManager.cpp
+ OverlayRenderer.cpp
+ OverlayRendererUtil.cpp
+ RenderCapture.cpp
+ RenderFactory.cpp
+ RenderFlags.cpp
+ RenderManager.cpp
+ DebugRenderer.cpp)
+
+set(HEADERS BaseRenderer.h
+ ColorManager.h
+ DebugInfo.h
+ OverlayRenderer.h
+ OverlayRendererUtil.h
+ RenderCapture.h
+ RenderFactory.h
+ RenderFlags.h
+ RenderInfo.h
+ RenderManager.h
+ DebugRenderer.h)
+
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES WinRenderer.cpp
+ OverlayRendererDX.cpp
+ RenderCaptureDX.cpp)
+ list(APPEND HEADERS WinRenderer.h
+ OverlayRendererDX.h
+ RenderCapture.h)
+endif()
+
+if (OPENGL_FOUND OR OPENGLES_FOUND)
+ list(APPEND SOURCES OverlayRendererGL.cpp
+ FrameBufferObject.cpp)
+ list(APPEND HEADERS OverlayRendererGL.h
+ FrameBufferObject.h)
+endif()
+
+if(OPENGL_FOUND)
+ list(APPEND SOURCES LinuxRendererGL.cpp
+ RenderCaptureGL.cpp)
+ list(APPEND HEADERS LinuxRendererGL.h
+ RenderCaptureGL.h)
+endif()
+
+if(OPENGLES_FOUND AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "ios" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "x11" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "wayland" IN_LIST CORE_PLATFORM_NAME_LC))
+ list(APPEND SOURCES LinuxRendererGLES.cpp
+ RenderCaptureGLES.cpp)
+ list(APPEND HEADERS LinuxRendererGLES.h
+ RenderCaptureGLES.h)
+endif()
+
+core_add_library(videorenderers)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.cpp
new file mode 100644
index 0000000..e188881
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.cpp
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2016 Lauri Mylläri
+ * http://kodi.org
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ColorManager.h"
+
+#include "ServiceBroker.h"
+#include "filesystem/File.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+
+#include <cstdint>
+#include <math.h>
+#include <vector>
+
+using namespace XFILE;
+
+CColorManager::CColorManager()
+{
+ m_curVideoPrimaries = CMS_PRIMARIES_AUTO;
+ m_curClutSize = 0;
+ m_curCmsToken = 0;
+ m_curCmsMode = 0;
+}
+
+#if defined(HAVE_LCMS2)
+CColorManager::~CColorManager()
+{
+ if (m_hProfile)
+ {
+ cmsCloseProfile(m_hProfile);
+ m_hProfile = nullptr;
+ }
+}
+#else
+CColorManager::~CColorManager() = default;
+#endif //defined(HAVE_LCMS2)
+
+bool CColorManager::IsEnabled() const
+{
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("videoscreen.cmsenabled") && IsValid();
+}
+
+bool CColorManager::IsValid() const
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ if (!settings->GetBool("videoscreen.cmsenabled"))
+ return true;
+
+ int cmsmode = settings->GetInt("videoscreen.cmsmode");
+ switch (cmsmode)
+ {
+ case CMS_MODE_3DLUT:
+ {
+ std::string fileName = settings->GetString("videoscreen.cms3dlut");
+ if (fileName.empty())
+ return false;
+ if (!CFile::Exists(fileName))
+ return false;
+ return true;
+ }
+#if defined(HAVE_LCMS2)
+ case CMS_MODE_PROFILE:
+ {
+ int cmslutsize = settings->GetInt("videoscreen.cmslutsize");
+ if (cmslutsize <= 0)
+ return false;
+ return true;
+ }
+#endif
+ default:
+ return false;
+ }
+}
+
+CMS_PRIMARIES avColorToCmsPrimaries(AVColorPrimaries primaries)
+{
+ switch (primaries)
+ {
+ case AVCOL_PRI_BT709:
+ return CMS_PRIMARIES_BT709;
+ case AVCOL_PRI_BT470M:
+ return CMS_PRIMARIES_BT470M;
+ case AVCOL_PRI_BT470BG:
+ return CMS_PRIMARIES_BT470BG;
+ case AVCOL_PRI_SMPTE170M:
+ return CMS_PRIMARIES_170M;
+ case AVCOL_PRI_SMPTE240M:
+ return CMS_PRIMARIES_240M;
+ case AVCOL_PRI_BT2020:
+ return CMS_PRIMARIES_BT2020;
+ default:
+ return CMS_PRIMARIES_BT709;
+ }
+}
+
+bool CColorManager::Get3dLutSize(CMS_DATA_FORMAT format, int *clutSize, int *dataSize)
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ int cmsmode = settings->GetInt("videoscreen.cmsmode");
+ switch (cmsmode)
+ {
+ case CMS_MODE_3DLUT:
+ {
+ std::string fileName = settings->GetString("videoscreen.cms3dlut");
+ if (fileName.empty())
+ return false;
+
+ int clutDimention;
+ if (!Probe3dLut(fileName, &clutDimention))
+ return false;
+
+ if (clutSize)
+ *clutSize = clutDimention;
+
+ if (dataSize)
+ {
+ int bytesInSample = format == CMS_DATA_FMT_RGBA ? 4 : 3;
+ *dataSize = sizeof(uint16_t) * clutDimention * clutDimention * clutDimention * bytesInSample;
+ }
+ return true;
+ }
+ case CMS_MODE_PROFILE:
+ {
+ int cmslutsize = settings->GetInt("videoscreen.cmslutsize");
+ if (cmslutsize <= 0)
+ return false;
+
+ int clutDimention = 1 << cmslutsize;
+ if (clutSize)
+ *clutSize = clutDimention;
+
+ if (dataSize)
+ {
+ int bytesInSample = format == CMS_DATA_FMT_RGBA ? 4 : 3;
+ *dataSize = sizeof(uint16_t) * clutDimention * clutDimention * clutDimention * bytesInSample;
+ }
+ return true;
+ }
+ default:
+ CLog::Log(LOGDEBUG, "ColorManager: unknown CMS mode {}", cmsmode);
+ return false;
+ }
+}
+
+bool CColorManager::GetVideo3dLut(AVColorPrimaries srcPrimaries, int* cmsToken,
+ CMS_DATA_FORMAT format, int clutSize, uint16_t* clutData)
+{
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ CMS_PRIMARIES videoPrimaries = avColorToCmsPrimaries(srcPrimaries);
+ CLog::Log(LOGDEBUG, "ColorManager: video primaries: {}", (int)videoPrimaries);
+ switch (settings->GetInt("videoscreen.cmsmode"))
+ {
+ case CMS_MODE_3DLUT:
+ CLog::Log(LOGDEBUG, "ColorManager: CMS_MODE_3DLUT");
+ m_cur3dlutFile = settings->GetString("videoscreen.cms3dlut");
+ if (!Load3dLut(m_cur3dlutFile, format, clutSize, clutData))
+ return false;
+ m_curCmsMode = CMS_MODE_3DLUT;
+ break;
+
+ case CMS_MODE_PROFILE:
+ CLog::Log(LOGDEBUG, "ColorManager: CMS_MODE_PROFILE");
+#if defined(HAVE_LCMS2)
+ {
+ // check if display profile is not loaded, or has changed
+ if (m_curIccProfile != settings->GetString("videoscreen.displayprofile"))
+ {
+ // free old profile if there is one
+ if (m_hProfile)
+ cmsCloseProfile(m_hProfile);
+ // load profile
+ m_hProfile = LoadIccDisplayProfile(settings->GetString("videoscreen.displayprofile"));
+ if (!m_hProfile)
+ return false;
+ // detect blackpoint
+ if (cmsDetectBlackPoint(&m_blackPoint, m_hProfile, INTENT_PERCEPTUAL, 0))
+ {
+ CLog::Log(LOGDEBUG, "ColorManager: black point: {:f}", m_blackPoint.Y);
+ }
+ m_curIccProfile = settings->GetString("videoscreen.displayprofile");
+ }
+ // create gamma curve
+ cmsToneCurve* gammaCurve;
+ m_m_curIccGammaMode = static_cast<CMS_TRC_TYPE>(settings->GetInt("videoscreen.cmsgammamode"));
+ m_curIccGamma = settings->GetInt("videoscreen.cmsgamma");
+ gammaCurve = CreateToneCurve(m_m_curIccGammaMode, m_curIccGamma / 100.0, m_blackPoint);
+
+ // create source profile
+ m_curIccWhitePoint = static_cast<CMS_WHITEPOINT>(settings->GetInt("videoscreen.cmswhitepoint"));
+ m_curIccPrimaries = static_cast<CMS_PRIMARIES>(settings->GetInt("videoscreen.cmsprimaries"));
+ CLog::Log(LOGDEBUG, "ColorManager: primaries setting: {}", (int)m_curIccPrimaries);
+ if (m_curIccPrimaries == CMS_PRIMARIES_AUTO)
+ m_curIccPrimaries = videoPrimaries;
+ CLog::Log(LOGDEBUG, "ColorManager: source profile primaries: {}", (int)m_curIccPrimaries);
+ cmsHPROFILE sourceProfile = CreateSourceProfile(m_curIccPrimaries, gammaCurve, m_curIccWhitePoint);
+
+ // link profiles
+ // TODO: intent selection, switch output to 16 bits?
+ cmsSetAdaptationState(0.0);
+ uint32_t fmt = format == CMS_DATA_FMT_RGBA ? TYPE_RGBA_FLT : TYPE_RGB_FLT;
+ cmsHTRANSFORM deviceLink = cmsCreateTransform(sourceProfile, fmt, m_hProfile, fmt, INTENT_ABSOLUTE_COLORIMETRIC, 0);
+
+ // sample the transformation
+ if (deviceLink)
+ Create3dLut(deviceLink, format, clutSize, clutData);
+
+ // free gamma curve, source profile and transformation
+ if (deviceLink)
+ cmsDeleteTransform(deviceLink);
+ cmsCloseProfile(sourceProfile);
+ cmsFreeToneCurve(gammaCurve);
+ }
+
+ m_curCmsMode = CMS_MODE_PROFILE;
+ break;
+#else //defined(HAVE_LCMS2)
+ return false;
+#endif //defined(HAVE_LCMS2)
+
+ default:
+ CLog::Log(LOGDEBUG, "ColorManager: unknown CMS mode {}",
+ settings->GetInt("videoscreen.cmsmode"));
+ return false;
+ }
+
+ // set current state
+ m_curVideoPrimaries = videoPrimaries;
+ m_curClutSize = clutSize;
+ *cmsToken = ++m_curCmsToken;
+ return true;
+}
+
+bool CColorManager::CheckConfiguration(int cmsToken, AVColorPrimaries srcPrimaries)
+{
+ if (cmsToken != m_curCmsToken)
+ return false;
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (m_curCmsMode != settings->GetInt("videoscreen.cmsmode"))
+ return false; // CMS mode has changed
+ switch (m_curCmsMode)
+ {
+ case CMS_MODE_3DLUT:
+ if (m_cur3dlutFile != settings->GetString("videoscreen.cms3dlut"))
+ return false; // different 3dlut file selected
+ break;
+ case CMS_MODE_PROFILE:
+#if defined(HAVE_LCMS2)
+ if (m_curIccProfile != settings->GetString("videoscreen.displayprofile"))
+ return false; // different ICC profile selected
+ if (m_curIccWhitePoint != settings->GetInt("videoscreen.cmswhitepoint"))
+ return false; // whitepoint changed
+ {
+ CMS_PRIMARIES primaries = static_cast<CMS_PRIMARIES>(settings->GetInt("videoscreen.cmsprimaries"));
+ if (primaries == CMS_PRIMARIES_AUTO)
+ primaries = avColorToCmsPrimaries(srcPrimaries);
+ if (m_curIccPrimaries != primaries)
+ return false; // primaries changed
+ }
+ if (m_m_curIccGammaMode != static_cast<CMS_TRC_TYPE>(settings->GetInt("videoscreen.cmsgammamode")))
+ return false; // gamma mode changed
+ if (m_curIccGamma != settings->GetInt("videoscreen.cmsgamma"))
+ return false; // effective gamma changed
+ if (m_curClutSize != 1 << settings->GetInt("videoscreen.cmslutsize"))
+ return false; // CLUT size changed
+ // TODO: check other parameters
+#else //defined(HAVE_LCMS2)
+ return true;
+#endif //defined(HAVE_LCMS2)
+ break;
+ default:
+ CLog::Log(LOGERROR, "ColorManager: unexpected CMS mode: {}", m_curCmsMode);
+ return false;
+ }
+ return true;
+}
+
+
+
+// madvr 3dlut file format support
+struct H3DLUT
+{
+ char signature[4]; // file signature; must be: '3DLT'
+ uint32_t fileVersion; // file format version number (currently "1")
+ char programName[32]; // name of the program that created the file
+ uint64_t programVersion; // version number of the program that created the file
+ uint32_t inputBitDepth[3]; // input bit depth per component (Y,Cb,Cr or R,G,B)
+ uint32_t inputColorEncoding; // input color encoding standard
+ uint32_t outputBitDepth; // output bit depth for all components (valid values are 8, 16 and 32)
+ uint32_t outputColorEncoding; // output color encoding standard
+ uint32_t parametersFileOffset;// number of bytes between the beginning of the file and array parametersData
+ uint32_t parametersSize; // size in bytes of the array parametersData
+ uint32_t lutFileOffset; // number of bytes between the beginning of the file and array lutData
+ uint32_t lutCompressionMethod;// type of compression used if any (0 = none, ...)
+ uint32_t lutCompressedSize; // size in bytes of the array lutData inside the file, whether compressed or not
+ uint32_t lutUncompressedSize; // true size in bytes of the array lutData when in memory for usage (outside the file)
+ // This header is followed by the char array 'parametersData', of length 'parametersSize',
+ // and by the array 'lutDataxx', of length 'lutCompressedSize'.
+};
+
+bool CColorManager::Probe3dLut(const std::string& filename, int* clutSize)
+{
+ struct H3DLUT header;
+ CFile lutFile;
+
+ if (!lutFile.Open(filename))
+ {
+ CLog::Log(LOGERROR, "{}: Could not open 3DLUT file: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ if (lutFile.Read(&header, sizeof(header)) < static_cast<ssize_t>(sizeof(header)))
+ {
+ CLog::Log(LOGERROR, "{}: Could not read 3DLUT header: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ if ( !(header.signature[0]=='3'
+ && header.signature[1]=='D'
+ && header.signature[2]=='L'
+ && header.signature[3]=='T') )
+ {
+ CLog::Log(LOGERROR, "{}: Not a 3DLUT file: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ if ( header.fileVersion != 1
+ || header.lutCompressionMethod != 0
+ || header.inputColorEncoding != 0
+ || header.outputColorEncoding != 0 )
+ {
+ CLog::Log(LOGERROR, "{}: Unsupported 3DLUT file: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ int rSize = 1 << header.inputBitDepth[0];
+ int gSize = 1 << header.inputBitDepth[1];
+ int bSize = 1 << header.inputBitDepth[2];
+ if (rSize != gSize || rSize != bSize)
+ {
+ CLog::Log(LOGERROR, "{}: Different channel resolutions unsupported: {}", __FUNCTION__,
+ filename);
+ return false;
+ }
+
+ if (clutSize)
+ *clutSize = rSize;
+
+ lutFile.Close();
+ return true;
+}
+
+bool CColorManager::Load3dLut(const std::string& filename,
+ CMS_DATA_FORMAT format,
+ int CLUTsize,
+ uint16_t* clutData)
+{
+ struct H3DLUT header;
+ CFile lutFile;
+
+ if (!lutFile.Open(filename))
+ {
+ CLog::Log(LOGERROR, "{}: Could not open 3DLUT file: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ if (lutFile.Read(&header, sizeof(header)) < static_cast<ssize_t>(sizeof(header)))
+ {
+ CLog::Log(LOGERROR, "{}: Could not read 3DLUT header: {}", __FUNCTION__, filename);
+ return false;
+ }
+
+ int rSize = 1 << header.inputBitDepth[0];
+ int gSize = 1 << header.inputBitDepth[1];
+ int bSize = 1 << header.inputBitDepth[2];
+
+ if ( rSize != CLUTsize || rSize != gSize || rSize != bSize)
+ {
+ CLog::Log(LOGERROR, "{}: Different channel resolutions unsupported: {}", __FUNCTION__,
+ filename);
+ return false;
+ }
+
+ lutFile.Seek(header.lutFileOffset, SEEK_SET);
+
+ int components = format == CMS_DATA_FMT_RGBA ? 4 : 3;
+ for (int rIndex = 0; rIndex < rSize; rIndex++)
+ {
+ for (int gIndex = 0; gIndex < gSize; gIndex++)
+ {
+ std::vector<uint16_t> input(bSize * 3); // always 3 components
+ lutFile.Read(input.data(), input.size() * sizeof(input[0]));
+ int index = (rIndex + gIndex * rSize) * components;
+ for (int bIndex = 0; bIndex < bSize; bIndex++)
+ {
+ int offset = index + bIndex * rSize * gSize * components;
+ clutData[offset + 0] = input[bIndex * 3 + 2];
+ clutData[offset + 1] = input[bIndex * 3 + 1];
+ clutData[offset + 2] = input[bIndex * 3 + 0];
+ if (format == CMS_DATA_FMT_RGBA)
+ clutData[offset + 3] = 0xFFFF;
+ }
+ }
+ }
+
+ lutFile.Close();
+ return true;
+}
+
+#if defined(HAVE_LCMS2)
+// ICC profile support
+
+cmsHPROFILE CColorManager::LoadIccDisplayProfile(const std::string& filename)
+{
+ cmsHPROFILE hProfile;
+
+ hProfile = cmsOpenProfileFromFile(filename.c_str(), "r");
+ if (!hProfile)
+ {
+ CLog::Log(LOGERROR, "ICC profile not found");
+ }
+ return hProfile;
+}
+
+
+cmsToneCurve* CColorManager::CreateToneCurve(CMS_TRC_TYPE gammaType,
+ double gammaValue,
+ cmsCIEXYZ blackPoint)
+{
+ const int tableSize = 1024;
+ cmsFloat32Number gammaTable[tableSize];
+
+ switch (gammaType)
+ {
+ case CMS_TRC_INPUT_OFFSET:
+ // calculate gamma to match effective gamma provided, then fall through to bt.1886
+ {
+ double effectiveGamma = gammaValue;
+ double gammaLow = effectiveGamma; // low limit for infinite contrast ratio
+ double gammaHigh = 3.2; // high limit for 2.4 gamma on 200:1 contrast ratio
+ double gammaGuess = 0.0;
+#define TARGET(gamma) (pow(0.5, (gamma)))
+#define GAIN(bkpt, gamma) (pow(1-pow((bkpt), 1/(gamma)), (gamma)))
+#define LIFT(bkpt, gamma) (pow((bkpt), 1/(gamma)) / (1-pow((bkpt), 1/(gamma))))
+#define HALFPT(bkpt, gamma) (GAIN(bkpt, gamma)*pow(0.5+LIFT(bkpt, gamma), gamma))
+ for (int i=0; i<3; i++)
+ {
+ // calculate 50% output for gammaLow and gammaHigh, compare to target 50% output
+ gammaGuess = gammaLow + (gammaHigh-gammaLow)
+ * ((HALFPT(blackPoint.Y, gammaLow)-TARGET(effectiveGamma))
+ / (HALFPT(blackPoint.Y, gammaLow)-HALFPT(blackPoint.Y, gammaHigh)));
+ if (HALFPT(blackPoint.Y, gammaGuess) < TARGET(effectiveGamma))
+ {
+ // guess is too high
+ // move low limit half way to guess
+ gammaLow = gammaLow + (gammaGuess-gammaLow)/2;
+ // set high limit to guess
+ gammaHigh = gammaGuess;
+ }
+ else
+ {
+ // guess is too low
+ // set low limit to guess
+ gammaLow = gammaGuess;
+ // move high limit half way to guess
+ gammaHigh = gammaHigh + (gammaGuess-gammaLow)/2;
+ }
+ }
+ gammaValue = gammaGuess;
+ CLog::Log(LOGINFO, "calculated technical gamma {:0.3f} (50% target {:0.4f}, output {:0.4f})",
+ gammaValue, TARGET(effectiveGamma), HALFPT(blackPoint.Y, gammaValue));
+#undef TARGET
+#undef GAIN
+#undef LIFT
+#undef HALFPT
+ }
+ // fall through to bt.1886 with calculated technical gamma
+ [[fallthrough]];
+
+ case CMS_TRC_BT1886:
+ {
+ double bkipow = pow(blackPoint.Y, 1.0/gammaValue);
+ double wtipow = 1.0;
+ double lift = bkipow / (wtipow - bkipow);
+ double gain = pow(wtipow - bkipow, gammaValue);
+ for (int i=0; i<tableSize; i++)
+ {
+ gammaTable[i] = gain * pow(((double) i)/(tableSize-1) + lift, gammaValue);
+ }
+ }
+ break;
+
+ case CMS_TRC_OUTPUT_OFFSET:
+ {
+ double gain = 1-blackPoint.Y;
+ // TODO: here gamma is adjusted to match absolute gamma output at 50%
+ // - is it a good idea or should the provided gamma be kept?
+ double adjustedGamma = log(gain/(gain+pow(2,-gammaValue)-1))/log(2);
+ for (int i=0; i<tableSize; i++)
+ {
+ gammaTable[i] = gain * pow(((double) i)/(tableSize-1), adjustedGamma) + blackPoint.Y;
+ }
+ }
+ break;
+
+ case CMS_TRC_ABSOLUTE:
+ {
+ for (int i=0; i<tableSize; i++)
+ {
+ gammaTable[i] = fmax(blackPoint.Y, pow(((double) i)/(tableSize-1), gammaValue));
+ }
+ }
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "gamma type {} not implemented", gammaType);
+ }
+
+ cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(0,
+ tableSize,
+ gammaTable);
+ return result;
+}
+
+
+cmsHPROFILE CColorManager::CreateSourceProfile(CMS_PRIMARIES primaries, cmsToneCurve *gamma, CMS_WHITEPOINT whitepoint)
+{
+ cmsToneCurve* Gamma3[3];
+ cmsHPROFILE hProfile;
+ cmsCIExyY whiteCoords[] = {
+ { 0.3127, 0.3290, 1.0 }, // D65 as specified in BT.709
+ { 0.2830, 0.2980, 1.0 } // Japanese D93 - is there a definitive source? NHK? ARIB TR-B9?
+ };
+ cmsCIExyYTRIPLE primaryCoords[] = {
+ {{0.640, 0.330, 1.0}, // auto setting, these should not be used (BT.709 just in case)
+ {0.300, 0.600, 1.0},
+ {0.150, 0.060, 1.0}},
+ {{0.640, 0.330, 1.0}, // BT.709 (HDTV, sRGB)
+ {0.300, 0.600, 1.0},
+ {0.150, 0.060, 1.0}},
+ {{0.630, 0.340, 1.0}, // SMPTE 170M (SDTV)
+ {0.310, 0.595, 1.0},
+ {0.155, 0.070, 1.0}},
+ {{0.670, 0.330, 1.0}, // BT.470 M (obsolete NTSC 1953)
+ {0.210, 0.710, 1.0},
+ {0.140, 0.080, 1.0}},
+ {{0.640, 0.330, 1.0}, // BT.470 B/G (obsolete PAL/SECAM 1975)
+ {0.290, 0.600, 1.0},
+ {0.150, 0.060, 1.0}},
+ {{0.630, 0.340, 1.0}, // SMPTE 240M (obsolete HDTV 1988)
+ {0.310, 0.595, 1.0},
+ {0.155, 0.070, 1.0}},
+ {{0.708, 0.292, 1.0}, // BT.2020 UHDTV
+ {0.170, 0.797, 1.0},
+ {0.131, 0.046, 1.0}}};
+
+ Gamma3[0] = Gamma3[1] = Gamma3[2] = gamma;
+ hProfile = cmsCreateRGBProfile(&whiteCoords[whitepoint],
+ &primaryCoords[primaries],
+ Gamma3);
+ return hProfile;
+}
+
+
+void CColorManager::Create3dLut(cmsHTRANSFORM transform, CMS_DATA_FORMAT format, int clutSize, uint16_t *clutData)
+{
+ const int lutResolution = clutSize;
+ int components = format == CMS_DATA_FMT_RGBA ? 4 : 3;
+
+ cmsFloat32Number *input = new cmsFloat32Number[components*lutResolution];
+ cmsFloat32Number *output = new cmsFloat32Number[components*lutResolution];
+
+#define clamp(x, l, h) ( ((x) < (l)) ? (l) : ( ((x) > (h)) ? (h) : (x) ) )
+#define videoToPC(x) ( clamp((((x)*255)-16)/219,0,1) )
+#define PCToVideo(x) ( (((x)*219)+16)/255 )
+
+ for (int bIndex=0; bIndex<lutResolution; bIndex++)
+ {
+ for (int gIndex=0; gIndex<lutResolution; gIndex++)
+ {
+ for (int rIndex=0; rIndex<lutResolution; rIndex++)
+ {
+ int offset = rIndex * components;
+ input[offset + 0] = videoToPC(rIndex / (lutResolution-1.0));
+ input[offset + 1] = videoToPC(gIndex / (lutResolution-1.0));
+ input[offset + 2] = videoToPC(bIndex / (lutResolution-1.0));
+ if (format == CMS_DATA_FMT_RGBA)
+ input[offset + 3] = 0.0f;
+ }
+ int index = (bIndex*lutResolution*lutResolution + gIndex*lutResolution)*components;
+ cmsDoTransform(transform, input, output, lutResolution);
+ for (int i=0; i < lutResolution * components; i++)
+ {
+ clutData[index + i] = PCToVideo(output[i]) * 65535;
+ }
+ }
+ }
+
+ for (int y=0; y<lutResolution; y+=1)
+ {
+ int index = components*(y*lutResolution*lutResolution + y*lutResolution + y);
+ CLog::Log(LOGDEBUG, " {} ({}): {} {} {}", (int)round(y * 255 / (lutResolution - 1.0)), y,
+ (int)round(clutData[index + 0]), (int)round(clutData[index + 1]),
+ (int)round(clutData[index + 2]));
+ }
+ delete[] input;
+ delete[] output;
+}
+
+#endif //defined(HAVE_LCMS2)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.h b/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.h
new file mode 100644
index 0000000..1366c3a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/ColorManager.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 Lauri Mylläri
+ * http://kodi.org
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#if defined(HAVE_LCMS2)
+#include <lcms2.h>
+#endif
+
+#include <cstdint>
+#include <string>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+enum CMS_DATA_FORMAT
+{
+ CMS_DATA_FMT_RGB,
+ CMS_DATA_FMT_RGBA,
+ CMS_DATA_FMT_COUNT
+};
+
+enum CMS_MODE
+{
+ CMS_MODE_3DLUT,
+ CMS_MODE_PROFILE,
+ CMS_MODE_COUNT
+};
+
+enum CMS_WHITEPOINT
+{
+ CMS_WHITEPOINT_D65,
+ CMS_WHITEPOINT_D93,
+ CMS_WHITEPOINT_COUNT
+};
+
+enum CMS_PRIMARIES
+{
+ CMS_PRIMARIES_AUTO,
+ CMS_PRIMARIES_BT709, // HDTV
+ CMS_PRIMARIES_170M, // SDTV
+ CMS_PRIMARIES_BT470M, // old NTSC (1953)
+ CMS_PRIMARIES_BT470BG, // old PAL/SECAM (1975)
+ CMS_PRIMARIES_240M, // old HDTV (1988)
+ CMS_PRIMARIES_BT2020, // UHDTV
+ CMS_PRIMARIES_COUNT
+};
+
+enum CMS_TRC_TYPE
+{
+ CMS_TRC_BT1886,
+ CMS_TRC_INPUT_OFFSET,
+ CMS_TRC_OUTPUT_OFFSET,
+ CMS_TRC_ABSOLUTE,
+ CMS_TRC_COUNT
+};
+
+class CColorManager
+{
+public:
+ CColorManager();
+ virtual ~CColorManager();
+
+ /*!
+ \brief Check if user has requested color management
+ \return true on enabled, false otherwise
+ */
+ bool IsEnabled() const;
+
+ /*!
+ \brief Check if configuration of color management is valid
+ \return true on valid, false otherwise
+ */
+ bool IsValid() const;
+
+ /*!
+ \brief Get a 3D LUT for video color correction
+ \param srcPrimaries video primaries (see AVColorPrimaries)
+ \param cmsToken pointer to a color manager configuration token
+ \param format of CLUT data
+ \param clutSize CLUT resolution
+ \param clutData pointer to CLUT data
+ \return true on success, false otherwise
+ */
+ bool GetVideo3dLut(AVColorPrimaries srcPrimaries, int* cmsToken, CMS_DATA_FORMAT format,
+ int clutSize, uint16_t* clutData);
+
+ /*!
+ \brief Check if a 3D LUT is still valid
+ \param cmsToken pointer to a color manager configuration token
+ \param srcPrimaries video primaries (see AVColorPrimaries)
+ \return true on valid, false if 3D LUT should be reloaded
+ */
+ bool CheckConfiguration(int cmsToken, AVColorPrimaries srcPrimaries);
+
+ /*!
+ \brief Get a 3D LUT dimension and data size for video color correction
+ \param format required format of CLUT data
+ \param clutSize pointer to CLUT resolution
+ \param dataSize pointer to CLUT data size
+ \return true on success, false otherwise
+ */
+ static bool Get3dLutSize(CMS_DATA_FORMAT format, int *clutSize, int *dataSize);
+
+private:
+ /*! \brief Check .3dlut file validity
+ \param filename full path and filename
+ \param clutSize pointer to CLUT resolution
+ \return true if the file can be loaded, false otherwise
+ */
+ static bool Probe3dLut(const std::string& filename, int* clutSize);
+
+ /*! \brief Load a .3dlut file
+ \param filename full path and filename
+ \param format of CLUT data
+ \param clutSize CLUT resolution
+ \param clutData pointer to CLUT data
+ \return true on success, false otherwise
+ */
+ static bool Load3dLut(const std::string& filename,
+ CMS_DATA_FORMAT format,
+ int clutSize,
+ uint16_t* clutData);
+
+
+#if defined(HAVE_LCMS2)
+ // ProbeIccDisplayProfile
+
+ // ProbeIccDeviceLink (?)
+
+
+ /* \brief Load an ICC display profile
+ \param filename full path and filename
+ \return display profile (cmsHPROFILE)
+ */
+ cmsHPROFILE LoadIccDisplayProfile(const std::string& filename);
+
+ /* \brief Load an ICC device link
+ \param filename full path and filename
+ \return device link (cmsHTRANSFORM)
+ */
+ // LoadIccDeviceLink (?)
+
+
+ // create a gamma curve
+ cmsToneCurve* CreateToneCurve(CMS_TRC_TYPE gammaType, double gammaValue, cmsCIEXYZ blackPoint);
+
+ // create a source profile
+ cmsHPROFILE CreateSourceProfile(CMS_PRIMARIES primaries, cmsToneCurve *gamma, CMS_WHITEPOINT whitepoint);
+
+
+ /* \brief Create 3D LUT
+ Samples a cmsHTRANSFORM object to create a 3D LUT of specified resolution
+ \param transform cmsHTRANSFORM object to sample
+ \param format of CLUT data
+ \param resolution size of the 3D LUT to create
+ \param clut pointer to LUT data
+ */
+ void Create3dLut(cmsHTRANSFORM transform, CMS_DATA_FORMAT format, int clutSize, uint16_t *clutData);
+
+ // keep current display profile loaded here
+ cmsHPROFILE m_hProfile = nullptr;
+ cmsCIEXYZ m_blackPoint = { 0, 0, 0 };
+
+ // display parameters (gamma, input/output offset, primaries, whitepoint, intent?)
+ CMS_WHITEPOINT m_curIccWhitePoint;
+ CMS_PRIMARIES m_curIccPrimaries;
+ CMS_TRC_TYPE m_m_curIccGammaMode;
+ int m_curIccGamma; // gamma multiplied by 100
+#endif // defined(HAVE_LCMS2)
+
+ // current configuration:
+ CMS_PRIMARIES m_curVideoPrimaries;
+ int m_curClutSize;
+ int m_curCmsToken;
+ // (compare the following to system settings to see if configuration is still valid)
+ int m_curCmsMode;
+ std::string m_cur3dlutFile;
+ std::string m_curIccProfile;
+
+};
+
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/DebugInfo.h b/xbmc/cores/VideoPlayer/VideoRenderers/DebugInfo.h
new file mode 100644
index 0000000..d800c1a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/DebugInfo.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+struct DEBUG_INFO_PLAYER
+{
+ std::string audio;
+ std::string video;
+ std::string player;
+ std::string vsync;
+};
+
+struct DEBUG_INFO_VIDEO
+{
+ std::string videoSource;
+ std::string metaPrim;
+ std::string metaLight;
+ std::string shader;
+};
+
+struct DEBUG_INFO_RENDER
+{
+ std::string renderFlags;
+ std::string videoOutput;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.cpp
new file mode 100644
index 0000000..87acd24
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DebugRenderer.h"
+
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "settings/SubtitlesSettings.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+using namespace OVERLAY;
+
+CDebugRenderer::CDebugRenderer()
+{
+}
+
+CDebugRenderer::~CDebugRenderer()
+{
+ Dispose();
+}
+
+void CDebugRenderer::Initialize()
+{
+ if (m_isInitialized)
+ return;
+
+ m_adapter = new CSubtitlesAdapter();
+
+ m_isInitialized = m_adapter->Initialize();
+ if (!m_isInitialized)
+ {
+ CLog::Log(LOGERROR, "{} - Failed to configure OSD info debug renderer", __FUNCTION__);
+ delete m_adapter;
+ m_adapter = nullptr;
+ return;
+ }
+
+ // We create only a single overlay with a fixed PTS for each rendered frame
+ m_overlay = m_adapter->CreateOverlay();
+ m_overlayRenderer.AddOverlay(m_overlay, 1000000., 0);
+}
+
+void CDebugRenderer::Dispose()
+{
+ m_isInitialized = false;
+ m_overlayRenderer.Flush();
+ if (m_overlay)
+ {
+ m_overlay->Release();
+ m_overlay = nullptr;
+ }
+ if (m_adapter)
+ {
+ delete m_adapter;
+ m_adapter = nullptr;
+ }
+}
+
+void CDebugRenderer::SetInfo(DEBUG_INFO_PLAYER& info)
+{
+ if (!m_isInitialized)
+ return;
+
+ // FIXME: We currently force ASS_Event's and the current PTS
+ // of rendering with fixed values to allow perpetual
+ // display of on-screen text. It would be appropriate for Libass
+ // provide a way to allow fixed on-screen text display
+ // without use all these fixed values.
+ m_adapter->FlushSubtitles();
+ m_adapter->AddSubtitle(info.audio, 0., 5000000.);
+ m_adapter->AddSubtitle(info.video, 0., 5000000.);
+ m_adapter->AddSubtitle(info.player, 0., 5000000.);
+ m_adapter->AddSubtitle(info.vsync, 0., 5000000.);
+}
+
+void CDebugRenderer::SetInfo(DEBUG_INFO_VIDEO& video, DEBUG_INFO_RENDER& render)
+{
+ if (!m_isInitialized)
+ return;
+
+ // FIXME: We currently force ASS_Event's and the current PTS
+ // of rendering with fixed values to allow perpetual
+ // display of on-screen text. It would be appropriate for Libass
+ // provide a way to allow fixed on-screen text display
+ // without use all these fixed values.
+ m_adapter->FlushSubtitles();
+ m_adapter->AddSubtitle(video.videoSource, 0., 5000000.);
+ m_adapter->AddSubtitle(video.metaPrim, 0., 5000000.);
+ m_adapter->AddSubtitle(video.metaLight, 0., 5000000.);
+ m_adapter->AddSubtitle(video.shader, 0., 5000000.);
+ m_adapter->AddSubtitle(render.renderFlags, 0., 5000000.);
+ m_adapter->AddSubtitle(render.videoOutput, 0., 5000000.);
+}
+
+void CDebugRenderer::Render(CRect& src, CRect& dst, CRect& view)
+{
+ if (!m_isInitialized)
+ return;
+
+ m_overlayRenderer.SetVideoRect(src, dst, view);
+ m_overlayRenderer.Render(0);
+}
+
+void CDebugRenderer::Flush()
+{
+ if (!m_isInitialized)
+ return;
+
+ m_adapter->FlushSubtitles();
+}
+
+CDebugRenderer::CRenderer::CRenderer() : OVERLAY::CRenderer()
+{
+}
+
+void CDebugRenderer::CRenderer::Render(int idx)
+{
+ std::vector<SElement>& list = m_buffers[idx];
+ for (std::vector<SElement>::iterator it = list.begin(); it != list.end(); ++it)
+ {
+ if (it->overlay_dvd)
+ {
+ CDVDOverlayLibass* ovAss = static_cast<CDVDOverlayLibass*>(it->overlay_dvd);
+ if (!ovAss || !ovAss->GetLibassHandler())
+ continue;
+
+ bool updateStyle = !m_debugOverlayStyle;
+ if (updateStyle)
+ CreateSubtitlesStyle();
+
+ COverlay* o = ConvertLibass(ovAss, it->pts, updateStyle, m_debugOverlayStyle);
+
+ if (o)
+ OVERLAY::CRenderer::Render(o);
+ }
+ }
+ ReleaseUnused();
+}
+
+void CDebugRenderer::CRenderer::CreateSubtitlesStyle()
+{
+ m_debugOverlayStyle = std::make_shared<KODI::SUBTITLES::STYLE::style>();
+ m_debugOverlayStyle->fontName = KODI::SUBTITLES::FONT_DEFAULT_FAMILYNAME;
+ m_debugOverlayStyle->fontSize = 20.0;
+ m_debugOverlayStyle->marginVertical = 12;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.h
new file mode 100644
index 0000000..718d1b4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/DebugRenderer.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DebugInfo.h"
+#include "OverlayRenderer.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h"
+#include "cores/VideoPlayer/DVDSubtitles/SubtitlesAdapter.h"
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+class CDebugRenderer
+{
+public:
+ CDebugRenderer();
+ virtual ~CDebugRenderer();
+ void Initialize();
+ void Dispose();
+ void SetInfo(DEBUG_INFO_PLAYER& info);
+ void SetInfo(DEBUG_INFO_VIDEO& video, DEBUG_INFO_RENDER& render);
+ void Render(CRect& src, CRect& dst, CRect& view);
+ void Flush();
+
+protected:
+ class CRenderer : public OVERLAY::CRenderer
+ {
+ public:
+ CRenderer();
+ void Render(int idx) override;
+ void CreateSubtitlesStyle();
+
+ private:
+ // Implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override{};
+
+ std::shared_ptr<struct KODI::SUBTITLES::STYLE::style> m_debugOverlayStyle;
+ };
+
+ CRenderer m_overlayRenderer;
+
+private:
+ CSubtitlesAdapter* m_adapter{nullptr};
+ std::atomic_bool m_isInitialized{false};
+ CDVDOverlay* m_overlay{nullptr};
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.cpp
new file mode 100644
index 0000000..58c3410
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FrameBufferObject.h"
+
+#include "ServiceBroker.h"
+#include "rendering/RenderSystem.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+//////////////////////////////////////////////////////////////////////
+// CFrameBufferObject
+//////////////////////////////////////////////////////////////////////
+
+CFrameBufferObject::CFrameBufferObject()
+{
+ m_valid = false;
+ m_supported = false;
+ m_bound = false;
+}
+
+bool CFrameBufferObject::IsSupported()
+{
+ if(CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_framebuffer_object"))
+ m_supported = true;
+ else
+ m_supported = false;
+ return m_supported;
+}
+
+bool CFrameBufferObject::Initialize()
+{
+ if (!IsSupported())
+ return false;
+
+ Cleanup();
+
+ glGenFramebuffers(1, &m_fbo);
+ VerifyGLState();
+
+ if (!m_fbo)
+ return false;
+
+ m_valid = true;
+ return true;
+}
+
+void CFrameBufferObject::Cleanup()
+{
+ if (!IsValid())
+ return;
+
+ if (m_fbo)
+ glDeleteFramebuffers(1, &m_fbo);
+
+ if (m_texid)
+ glDeleteTextures(1, &m_texid);
+
+ m_texid = 0;
+ m_fbo = 0;
+ m_valid = false;
+ m_bound = false;
+}
+
+bool CFrameBufferObject::CreateAndBindToTexture(GLenum target, int width, int height, GLenum format, GLenum type,
+ GLenum filter, GLenum clampmode)
+{
+ if (!IsValid())
+ return false;
+
+ if (m_texid)
+ glDeleteTextures(1, &m_texid);
+
+ glGenTextures(1, &m_texid);
+ glBindTexture(target, m_texid);
+ glTexImage2D(target, 0, format, width, height, 0, GL_RGBA, type, NULL);
+ glTexParameteri(target, GL_TEXTURE_WRAP_S, clampmode);
+ glTexParameteri(target, GL_TEXTURE_WRAP_T, clampmode);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
+ VerifyGLState();
+
+ m_bound = false;
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+ glBindTexture(target, m_texid);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, m_texid, 0);
+ VerifyGLState();
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ VerifyGLState();
+ return false;
+ }
+ m_bound = true;
+ return true;
+}
+
+void CFrameBufferObject::SetFiltering(GLenum target, GLenum mode)
+{
+ glBindTexture(target, m_texid);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mode);
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, mode);
+}
+
+// Begin rendering to FBO
+bool CFrameBufferObject::BeginRender()
+{
+ if (IsValid() && IsBound())
+ {
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+ return true;
+ }
+ return false;
+}
+
+// Finish rendering to FBO
+void CFrameBufferObject::EndRender() const
+{
+ if (IsValid())
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.h b/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.h
new file mode 100644
index 0000000..a284c82
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/FrameBufferObject.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "system_gl.h"
+
+//
+// CFrameBufferObject
+// A class that abstracts FBOs to facilitate Render To Texture
+//
+// Requires OpenGL 1.5+ or the GL_EXT_framebuffer_object extension.
+//
+// Usage:
+//
+// CFrameBufferObject *fbo = new CFrameBufferObject();
+// fbo->Initialize();
+// fbo->CreateAndBindToTexture(GL_TEXTURE_2D, 256, 256, GL_RGBA);
+// OR fbo->BindToTexture(GL_TEXTURE_2D, <existing texture ID>);
+// fbo->BeginRender();
+// <normal GL rendering calls>
+// fbo->EndRender();
+// bind and use texture anywhere
+// glBindTexture(GL_TEXTURE_2D, fbo->Texture());
+//
+
+class CFrameBufferObject
+{
+public:
+ // Constructor
+ CFrameBufferObject();
+
+ // returns true if FBO support is detected
+ bool IsSupported();
+
+ // returns true if FBO has been initialized
+ bool IsValid() const { return m_valid; }
+
+ // returns true if FBO has a texture bound to it
+ bool IsBound() const { return m_bound; }
+
+ // initialize the FBO
+ bool Initialize();
+
+ // Cleanup
+ void Cleanup();
+
+ // Set texture filtering
+ void SetFiltering(GLenum target, GLenum mode);
+
+ // Create a new texture and bind to it
+ bool CreateAndBindToTexture(GLenum target, int width, int height, GLenum format, GLenum type=GL_UNSIGNED_BYTE,
+ GLenum filter=GL_LINEAR, GLenum clampmode=GL_CLAMP_TO_EDGE);
+
+ // Return the internally created texture ID
+ GLuint Texture() const { return m_texid; }
+
+ // Begin rendering to FBO
+ bool BeginRender();
+ // Finish rendering to FBO
+ void EndRender() const;
+
+private:
+ GLuint m_fbo = 0;
+ bool m_valid;
+ bool m_bound;
+ bool m_supported;
+ GLuint m_texid = 0;
+};
+
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt
new file mode 100644
index 0000000..b128623
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt
@@ -0,0 +1,69 @@
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES DXVAHD.cpp)
+ list(APPEND HEADERS DXVAHD.h)
+endif()
+
+if(VAAPI_FOUND)
+ if(OPENGL_FOUND)
+ list(APPEND SOURCES RendererVAAPIGL.cpp)
+ list(APPEND HEADERS RendererVAAPIGL.h)
+ endif()
+ if(OPENGLES_FOUND)
+ list(APPEND SOURCES RendererVAAPIGLES.cpp)
+ list(APPEND HEADERS RendererVAAPIGLES.h)
+ endif()
+ if(EGL_FOUND)
+ list(APPEND SOURCES VaapiEGL.cpp)
+ list(APPEND HEADERS VaapiEGL.h)
+ endif()
+endif()
+
+if(VDPAU_FOUND)
+ list(APPEND SOURCES RendererVDPAU.cpp
+ VdpauGL.cpp)
+ list(APPEND HEADERS RendererVDPAU.h
+ VdpauGL.h)
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL osx)
+ if(OPENGL_FOUND)
+ list(APPEND SOURCES RendererVTBGL.cpp)
+ list(APPEND HEADERS RendererVTBGL.h)
+ endif()
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL darwin_embedded)
+ if(OPENGLES_FOUND)
+ list(APPEND SOURCES RendererVTBGLES.cpp)
+ list(APPEND HEADERS RendererVTBGLES.h)
+ endif()
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL android)
+ list(APPEND SOURCES RendererMediaCodec.cpp
+ RendererMediaCodecSurface.cpp)
+ list(APPEND HEADERS RendererMediaCodec.h
+ RendererMediaCodecSurface.h)
+endif()
+
+if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC)
+ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC)
+ list(APPEND SOURCES RendererDRMPRIME.cpp
+ VideoLayerBridgeDRMPRIME.cpp)
+ list(APPEND HEADERS RendererDRMPRIME.h
+ VideoLayerBridgeDRMPRIME.h)
+ endif()
+
+ if(OPENGLES_FOUND)
+ list(APPEND SOURCES RendererDRMPRIMEGLES.cpp
+ DRMPRIMEEGL.cpp)
+ list(APPEND HEADERS RendererDRMPRIMEGLES.h
+ DRMPRIMEEGL.h)
+ endif()
+endif()
+
+# we might want to build on linux systems
+# with ENABLE_VDPAU=OFF and ENABLE_VAAPI=OFF
+if(SOURCES)
+ core_add_library(videorenderers_hwdec)
+endif()
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp
new file mode 100644
index 0000000..ee540c9
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DRMPRIMEEGL.h"
+
+#include "utils/log.h"
+
+using namespace DRMPRIME;
+
+namespace
+{
+
+int GetEGLColorSpace(const VideoPicture& picture)
+{
+ switch (picture.color_space)
+ {
+ case AVCOL_SPC_BT2020_CL:
+ case AVCOL_SPC_BT2020_NCL:
+ return EGL_ITU_REC2020_EXT;
+ case AVCOL_SPC_SMPTE170M:
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_FCC:
+ return EGL_ITU_REC601_EXT;
+ case AVCOL_SPC_BT709:
+ return EGL_ITU_REC709_EXT;
+ case AVCOL_SPC_RESERVED:
+ case AVCOL_SPC_UNSPECIFIED:
+ default:
+ if (picture.iWidth > 1024 || picture.iHeight >= 600)
+ return EGL_ITU_REC709_EXT;
+ else
+ return EGL_ITU_REC601_EXT;
+ }
+}
+
+int GetEGLColorRange(const VideoPicture& picture)
+{
+ if (picture.color_range)
+ return EGL_YUV_FULL_RANGE_EXT;
+
+ return EGL_YUV_NARROW_RANGE_EXT;
+}
+
+} // namespace
+
+CDRMPRIMETexture::~CDRMPRIMETexture()
+{
+ glDeleteTextures(1, &m_texture);
+}
+
+void CDRMPRIMETexture::Init(EGLDisplay eglDisplay)
+{
+ m_eglImage.reset(new CEGLImage(eglDisplay));
+}
+
+bool CDRMPRIMETexture::Map(CVideoBufferDRMPRIME* buffer)
+{
+ if (m_primebuffer)
+ return true;
+
+ if (!buffer->AcquireDescriptor())
+ {
+ CLog::Log(LOGERROR, "CDRMPRIMETexture::{} - failed to acquire descriptor", __FUNCTION__);
+ return false;
+ }
+
+ m_texWidth = buffer->GetWidth();
+ m_texHeight = buffer->GetHeight();
+
+ AVDRMFrameDescriptor* descriptor = buffer->GetDescriptor();
+ if (descriptor && descriptor->nb_layers)
+ {
+ AVDRMLayerDescriptor* layer = &descriptor->layers[0];
+
+ std::array<CEGLImage::EglPlane, CEGLImage::MAX_NUM_PLANES> planes;
+
+ for (int i = 0; i < layer->nb_planes; i++)
+ {
+ planes[i].fd = descriptor->objects[layer->planes[i].object_index].fd;
+ planes[i].modifier = descriptor->objects[layer->planes[i].object_index].format_modifier;
+ planes[i].offset = layer->planes[i].offset;
+ planes[i].pitch = layer->planes[i].pitch;
+ }
+
+ CEGLImage::EglAttrs attribs;
+
+ attribs.width = m_texWidth;
+ attribs.height = m_texHeight;
+ attribs.format = layer->format;
+ attribs.colorSpace = GetEGLColorSpace(buffer->GetPicture());
+ attribs.colorRange = GetEGLColorRange(buffer->GetPicture());
+ attribs.planes = planes;
+
+ if (!m_eglImage->CreateImage(attribs))
+ {
+ buffer->ReleaseDescriptor();
+ return false;
+ }
+
+ if (!glIsTexture(m_texture))
+ glGenTextures(1, &m_texture);
+ glBindTexture(m_textureTarget, m_texture);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_eglImage->UploadImage(m_textureTarget);
+ glBindTexture(m_textureTarget, 0);
+ }
+
+ m_primebuffer = buffer;
+ m_primebuffer->Acquire();
+
+ return true;
+}
+
+void CDRMPRIMETexture::Unmap()
+{
+ if (!m_primebuffer)
+ return;
+
+ m_eglImage->DestroyImage();
+
+ m_primebuffer->ReleaseDescriptor();
+
+ m_primebuffer->Release();
+ m_primebuffer = nullptr;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.h
new file mode 100644
index 0000000..e1e1310
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+#include "utils/EGLImage.h"
+#include "utils/Geometry.h"
+
+#include "system_gl.h"
+
+class CDRMPRIMETexture
+{
+public:
+ CDRMPRIMETexture() = default;
+ ~CDRMPRIMETexture();
+
+ bool Map(CVideoBufferDRMPRIME* buffer);
+ void Unmap();
+ void Init(EGLDisplay eglDisplay);
+
+ GLuint GetTexture() { return m_texture; }
+ CSizeInt GetTextureSize() { return {m_texWidth, m_texHeight}; }
+
+protected:
+ CVideoBufferDRMPRIME* m_primebuffer{nullptr};
+ std::unique_ptr<CEGLImage> m_eglImage;
+
+ const GLenum m_textureTarget{GL_TEXTURE_EXTERNAL_OES};
+ GLuint m_texture{0};
+ int m_texWidth{0};
+ int m_texHeight{0};
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp
new file mode 100644
index 0000000..486c1f4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// setting that here because otherwise SampleFormat is defined to AVSampleFormat
+// which we don't use here
+#define FF_API_OLD_SAMPLE_FMT 0
+#define DEFAULT_STREAM_INDEX (0)
+
+#include "DXVAHD.h"
+
+#include "VideoRenderers/RenderFlags.h"
+#include "VideoRenderers/RenderManager.h"
+#include "VideoRenderers/windows/RendererBase.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#include <Windows.h>
+#include <d3d11_4.h>
+#include <dxgi1_5.h>
+
+using namespace DXVA;
+using namespace Microsoft::WRL;
+
+#define LOGIFERROR(a) \
+ do \
+ { \
+ HRESULT res = a; \
+ if (FAILED(res)) \
+ { \
+ CLog::LogF(LOGERROR, "failed executing " #a " at line {} with error {:x}", __LINE__, res); \
+ } \
+ } while (0);
+
+CProcessorHD::CProcessorHD()
+{
+ DX::Windowing()->Register(this);
+}
+
+CProcessorHD::~CProcessorHD()
+{
+ DX::Windowing()->Unregister(this);
+ UnInit();
+}
+
+void CProcessorHD::UnInit()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ Close();
+}
+
+void CProcessorHD::Close()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_pEnumerator = nullptr;
+ m_pVideoProcessor = nullptr;
+ m_pVideoContext = nullptr;
+ m_pVideoDevice = nullptr;
+}
+
+bool CProcessorHD::PreInit() const
+{
+ ComPtr<ID3D11VideoDevice> pVideoDevice;
+ ComPtr<ID3D11VideoProcessorEnumerator> pEnumerator;
+ ComPtr<ID3D11Device> pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (FAILED(pD3DDevice.As(&pVideoDevice)))
+ {
+ CLog::LogF(LOGWARNING, "failed to get video device.");
+ return false;
+ }
+
+ D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc1 = {};
+ desc1.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
+ desc1.InputWidth = 640;
+ desc1.InputHeight = 480;
+ desc1.OutputWidth = 640;
+ desc1.OutputHeight = 480;
+ desc1.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
+
+ // try to create video enum
+ if (FAILED(pVideoDevice->CreateVideoProcessorEnumerator(&desc1, &pEnumerator)))
+ {
+ CLog::LogF(LOGWARNING, "failed to create Video Enumerator.");
+ return false;
+ }
+ return true;
+}
+
+bool CProcessorHD::InitProcessor()
+{
+ m_pVideoDevice = nullptr;
+ m_pVideoContext = nullptr;
+ m_pEnumerator = nullptr;
+
+ ComPtr<ID3D11DeviceContext1> pD3DDeviceContext = DX::DeviceResources::Get()->GetImmediateContext();
+ ComPtr<ID3D11Device> pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (FAILED(pD3DDeviceContext.As(&m_pVideoContext)))
+ {
+ CLog::LogF(LOGWARNING, "video context initialization is failed.");
+ return false;
+ }
+ if (FAILED(pD3DDevice.As(&m_pVideoDevice)))
+ {
+ CLog::LogF(LOGWARNING, "video device initialization is failed.");
+ return false;
+ }
+
+ CLog::LogF(LOGDEBUG, "initing video enumerator with params: {}x{}.", m_width, m_height);
+
+ D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc = {};
+ contentDesc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
+ contentDesc.InputWidth = m_width;
+ contentDesc.InputHeight = m_height;
+ contentDesc.OutputWidth = m_width;
+ contentDesc.OutputHeight = m_height;
+ contentDesc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
+
+ if (FAILED(m_pVideoDevice->CreateVideoProcessorEnumerator(&contentDesc, m_pEnumerator.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGWARNING, "failed to init video enumerator with params: {}x{}.", m_width,
+ m_height);
+ return false;
+ }
+
+ if (FAILED(m_pEnumerator->GetVideoProcessorCaps(&m_vcaps)))
+ {
+ CLog::LogF(LOGWARNING, "failed to get processor caps.");
+ return false;
+ }
+
+ CLog::LogF(LOGDEBUG, "video processor has {} rate conversion.", m_vcaps.RateConversionCapsCount);
+ CLog::LogF(LOGDEBUG, "video processor has {:#x} feature caps.", m_vcaps.FeatureCaps);
+ CLog::LogF(LOGDEBUG, "video processor has {:#x} device caps.", m_vcaps.DeviceCaps);
+ CLog::LogF(LOGDEBUG, "video processor has {:#x} input format caps.", m_vcaps.InputFormatCaps);
+ CLog::LogF(LOGDEBUG, "video processor has {} max input streams.", m_vcaps.MaxInputStreams);
+ CLog::LogF(LOGDEBUG, "video processor has {} max stream states.", m_vcaps.MaxStreamStates);
+ if (m_vcaps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_METADATA_HDR10)
+ CLog::LogF(LOGDEBUG, "video processor supports HDR10.");
+
+ if (0 != (m_vcaps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_LEGACY))
+ CLog::LogF(LOGWARNING, "the video driver does not support full video processing capabilities.");
+
+ m_max_back_refs = 0;
+ m_max_fwd_refs = 0;
+ m_procIndex = 0;
+
+ unsigned maxProcCaps = 0;
+ // try to find best processor
+ for (unsigned int i = 0; i < m_vcaps.RateConversionCapsCount; i++)
+ {
+ D3D11_VIDEO_PROCESSOR_RATE_CONVERSION_CAPS convCaps;
+ LOGIFERROR(m_pEnumerator->GetVideoProcessorRateConversionCaps(i, &convCaps))
+
+ // check only deinterlace caps
+ if ((convCaps.ProcessorCaps & 15) > maxProcCaps)
+ {
+ m_procIndex = i;
+ maxProcCaps = convCaps.ProcessorCaps & 15;
+ }
+ }
+
+ CLog::LogF(LOGDEBUG, "selected video processor index: {}.", m_procIndex);
+
+ LOGIFERROR(m_pEnumerator->GetVideoProcessorRateConversionCaps(m_procIndex, &m_rateCaps))
+ m_max_fwd_refs = std::min(m_rateCaps.FutureFrames, 2u);
+ m_max_back_refs = std::min(m_rateCaps.PastFrames, 4u);
+
+ CLog::LogF(LOGINFO, "supported deinterlace methods: blend:{}, bob:{}, adaptive:{}, mocomp:{}.",
+ (m_rateCaps.ProcessorCaps & 0x1) != 0 ? "yes" : "no", // BLEND
+ (m_rateCaps.ProcessorCaps & 0x2) != 0 ? "yes" : "no", // BOB
+ (m_rateCaps.ProcessorCaps & 0x4) != 0 ? "yes" : "no", // ADAPTIVE
+ (m_rateCaps.ProcessorCaps & 0x8) != 0 ? "yes" : "no" // MOTION_COMPENSATION
+ );
+
+ CLog::LogF(LOGDEBUG, "selected video processor allows {} future frames and {} past frames.",
+ m_rateCaps.FutureFrames, m_rateCaps.PastFrames);
+
+ //m_size = m_max_back_refs + 1 + m_max_fwd_refs; // refs + 1 display
+
+ // Get the image filtering capabilities.
+ for (size_t i = 0; i < NUM_FILTERS; i++)
+ {
+ if (m_vcaps.FilterCaps & (1 << i))
+ {
+ m_Filters[i].Range = {};
+ m_Filters[i].bSupported = SUCCEEDED(m_pEnumerator->GetVideoProcessorFilterRange(PROCAMP_FILTERS[i], &m_Filters[i].Range));
+
+ if (m_Filters[i].bSupported)
+ {
+ CLog::LogF(LOGDEBUG, "filter {} has following params - max: {}, min: {}, default: {}",
+ PROCAMP_FILTERS[i], m_Filters[i].Range.Maximum, m_Filters[i].Range.Minimum,
+ m_Filters[i].Range.Default);
+ }
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "filter {} not supported by processor.", PROCAMP_FILTERS[i]);
+ m_Filters[i].bSupported = false;
+ }
+ }
+
+ ComPtr<ID3D11VideoProcessorEnumerator1> pEnumerator1;
+
+ if (SUCCEEDED(m_pEnumerator.As(&pEnumerator1)))
+ {
+ DXGI_FORMAT format = DX::Windowing()->GetBackBuffer().GetFormat();
+ BOOL supported = 0;
+ HRESULT hr;
+
+ // Check if HLG color space conversion is supported by driver
+ hr = pEnumerator1->CheckVideoProcessorFormatConversion(
+ DXGI_FORMAT_P010, DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020, format,
+ DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, &supported);
+ m_bSupportHLG = SUCCEEDED(hr) && !!supported;
+
+ // Check if HDR10 RGB limited range output is supported by driver
+ hr = pEnumerator1->CheckVideoProcessorFormatConversion(
+ DXGI_FORMAT_P010, DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020, format,
+ DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020, &supported);
+ m_bSupportHDR10Limited = SUCCEEDED(hr) && !!supported;
+ }
+
+ CLog::LogF(LOGDEBUG, "HLG color space conversion is{}supported.", m_bSupportHLG ? " " : " NOT ");
+ CLog::LogF(LOGDEBUG, "HDR10 RGB limited range output is{}supported.",
+ m_bSupportHDR10Limited ? " " : " NOT ");
+
+ return true;
+}
+
+bool CProcessorHD::IsFormatSupported(DXGI_FORMAT format, D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT support) const
+{
+ UINT uiFlags;
+ if (S_OK == m_pEnumerator->CheckVideoProcessorFormat(format, &uiFlags))
+ {
+ if (uiFlags & support)
+ return true;
+ }
+
+ CLog::LogF(LOGERROR, "unsupported format {} for {}.", format, support);
+ return false;
+}
+
+bool CProcessorHD::CheckFormats() const
+{
+ // check default output format (as render target)
+ return IsFormatSupported(DX::Windowing()->GetBackBuffer().GetFormat(), D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT);
+}
+
+bool CProcessorHD::Open(UINT width, UINT height)
+{
+ Close();
+
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ m_width = width;
+ m_height = height;
+
+ if (!InitProcessor())
+ return false;
+
+ if (!CheckFormats())
+ return false;
+
+ return OpenProcessor();
+}
+
+bool CProcessorHD::ReInit()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ Close();
+
+ if (!InitProcessor())
+ return false;
+
+ if (!CheckFormats())
+ return false;
+
+ return true;
+}
+
+bool CProcessorHD::OpenProcessor()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ // restore the device if it was lost
+ if (!m_pEnumerator && !ReInit())
+ return false;
+
+ CLog::LogF(LOGDEBUG, "creating processor.");
+
+ // create processor
+ HRESULT hr = m_pVideoDevice->CreateVideoProcessor(m_pEnumerator.Get(), m_procIndex, m_pVideoProcessor.ReleaseAndGetAddressOf());
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGDEBUG, "failed creating video processor with error {:x}.", hr);
+ return false;
+ }
+
+ // Output background color (black)
+ D3D11_VIDEO_COLOR color;
+ color.YCbCr = { 0.0625f, 0.5f, 0.5f, 1.0f }; // black color
+ m_pVideoContext->VideoProcessorSetOutputBackgroundColor(m_pVideoProcessor.Get(), TRUE, &color);
+
+ return true;
+}
+
+void CProcessorHD::ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER filter, int value, int min, int max, int def) const
+{
+ if (filter >= static_cast<D3D11_VIDEO_PROCESSOR_FILTER>(NUM_FILTERS))
+ return;
+
+ // Unsupported filter. Ignore.
+ if (!m_Filters[filter].bSupported)
+ return;
+
+ D3D11_VIDEO_PROCESSOR_FILTER_RANGE range = m_Filters[filter].Range;
+ int val;
+
+ if(value > def)
+ val = range.Default + (range.Maximum - range.Default) * (value - def) / (max - def);
+ else if(value < def)
+ val = range.Default + (range.Minimum - range.Default) * (value - def) / (min - def);
+ else
+ val = range.Default;
+
+ m_pVideoContext->VideoProcessorSetStreamFilter(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, filter, val != range.Default, val);
+}
+
+ID3D11VideoProcessorInputView* CProcessorHD::GetInputView(CRenderBuffer* view) const
+{
+ ComPtr<ID3D11VideoProcessorInputView> inputView;
+ D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC vpivd = {0, D3D11_VPIV_DIMENSION_TEXTURE2D, {0, 0}};
+
+ ComPtr<ID3D11Resource> resource;
+ unsigned arrayIdx = 0;
+ HRESULT hr = view->GetResource(resource.GetAddressOf(), &arrayIdx);
+ if (SUCCEEDED(hr))
+ {
+ vpivd.Texture2D.ArraySlice = arrayIdx;
+ hr = m_pVideoDevice->CreateVideoProcessorInputView(resource.Get(), m_pEnumerator.Get(), &vpivd, inputView.GetAddressOf());
+ }
+
+ if (FAILED(hr) || hr == S_FALSE)
+ CLog::LogF(LOGERROR, "cannot create processor input view.");
+
+ return inputView.Detach();
+}
+
+DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpaceSource(CRenderBuffer* view, bool supportHDR, bool supportHLG)
+{
+ // RGB
+ if (view->color_space == AVCOL_SPC_RGB)
+ {
+ if (!view->full_range)
+ {
+ if (view->primaries == AVCOL_PRI_BT2020)
+ {
+ if (view->color_transfer == AVCOL_TRC_SMPTEST2084 && supportHDR)
+ return DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020;
+
+ return DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020;
+ }
+ return DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709;
+ }
+
+ if (view->primaries == AVCOL_PRI_BT2020)
+ {
+ if (view->color_transfer == AVCOL_TRC_SMPTEST2084)
+ return DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+
+ return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020;
+ }
+ if (view->color_transfer == AVCOL_TRC_LINEAR ||
+ view->color_transfer == AVCOL_TRC_LOG)
+ return DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+
+ return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ }
+ // UHDTV
+ if (view->primaries == AVCOL_PRI_BT2020)
+ {
+ // Windows 10 doesn't support HLG passthrough, always is used PQ for HDR passthrough
+ if ((view->color_transfer == AVCOL_TRC_SMPTEST2084 ||
+ view->color_transfer == AVCOL_TRC_ARIB_STD_B67) && supportHDR) // is HDR display ON
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020;
+
+ // HLG transfer can be used for HLG source in SDR display if is supported
+ if (view->color_transfer == AVCOL_TRC_ARIB_STD_B67 && supportHLG) // driver supports HLG
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020;
+
+ if (view->full_range)
+ return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020;
+
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020;
+ }
+ // SDTV
+ if (view->primaries == AVCOL_PRI_BT470BG ||
+ view->primaries == AVCOL_PRI_SMPTE170M)
+ {
+ if (view->full_range)
+ return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601;
+
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601;
+ }
+ // HDTV
+ if (view->full_range)
+ {
+ if (view->color_transfer == AVCOL_TRC_SMPTE170M)
+ return DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601;
+
+ return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709;
+ }
+
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709;
+}
+
+DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpaceTarget(CRenderBuffer* view, bool supportHDR)
+{
+ DXGI_COLOR_SPACE_TYPE color;
+
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709
+ : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+
+ if (!DX::Windowing()->IsHDROutput())
+ return color;
+
+ // HDR10 or HLG
+ if (view->primaries == AVCOL_PRI_BT2020 && (view->color_transfer == AVCOL_TRC_SMPTE2084 ||
+ view->color_transfer == AVCOL_TRC_ARIB_STD_B67))
+ {
+ if (supportHDR)
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020
+ : DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+ }
+ else
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020
+ : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020;
+ }
+ }
+
+ return color;
+}
+
+bool CProcessorHD::Render(CRect src, CRect dst, ID3D11Resource* target, CRenderBuffer** views, DWORD flags, UINT frameIdx, UINT rotation, float contrast, float brightness)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ // restore processor if it was lost
+ if (!m_pVideoProcessor && !OpenProcessor())
+ return false;
+
+ if (!views[2])
+ return false;
+
+ RECT sourceRECT = {static_cast<LONG>(src.x1), static_cast<LONG>(src.y1),
+ static_cast<LONG>(src.x2), static_cast<LONG>(src.y2)};
+ RECT dstRECT = {static_cast<LONG>(dst.x1), static_cast<LONG>(dst.y1), static_cast<LONG>(dst.x2),
+ static_cast<LONG>(dst.y2)};
+
+ D3D11_VIDEO_FRAME_FORMAT dxvaFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
+
+ unsigned int providedPast = 0;
+ for (int i = 3; i < 8; i++)
+ {
+ if (views[i])
+ providedPast++;
+ }
+ unsigned int providedFuture = 0;
+ for (int i = 1; i >= 0; i--)
+ {
+ if (views[i])
+ providedFuture++;
+ }
+ const int futureFrames = std::min(providedFuture, m_rateCaps.FutureFrames);
+ const int pastFrames = std::min(providedPast, m_rateCaps.PastFrames);
+ std::vector<ID3D11VideoProcessorInputView*> pastViews(pastFrames, nullptr);
+ std::vector<ID3D11VideoProcessorInputView*> futureViews(futureFrames, nullptr);
+
+ D3D11_VIDEO_PROCESSOR_STREAM stream_data = {};
+ stream_data.Enable = TRUE;
+ stream_data.PastFrames = pastFrames;
+ stream_data.FutureFrames = futureFrames;
+ stream_data.ppPastSurfaces = pastViews.data();
+ stream_data.ppFutureSurfaces = futureViews.data();
+
+ std::vector<ComPtr<ID3D11VideoProcessorInputView>> all_views;
+ const int start = 2 - futureFrames;
+ const int end = 2 + pastFrames;
+ int count = 0;
+
+ for (int i = start; i <= end; i++)
+ {
+ if (!views[i])
+ continue;
+
+ ComPtr<ID3D11VideoProcessorInputView> view;
+ view.Attach(GetInputView(views[i]));
+
+ if (i > 2)
+ {
+ // frames order should be { ?, T-3, T-2, T-1 }
+ pastViews[2 + pastFrames - i] = view.Get();
+ }
+ else if (i == 2)
+ {
+ stream_data.pInputSurface = view.Get();
+ }
+ else if (i < 2)
+ {
+ // frames order should be { T+1, T+2, T+3, .. }
+ futureViews[1 - i] = view.Get();
+ }
+ if (view)
+ {
+ count++;
+ all_views.push_back(view);
+ }
+ }
+
+ if (count != pastFrames + futureFrames + 1)
+ {
+ CLog::LogF(LOGERROR, "incomplete views set.");
+ return false;
+ }
+
+ if (flags & RENDER_FLAG_FIELD0 && flags & RENDER_FLAG_TOP)
+ dxvaFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
+ else if (flags & RENDER_FLAG_FIELD1 && flags & RENDER_FLAG_BOT)
+ dxvaFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
+ if (flags & RENDER_FLAG_FIELD0 && flags & RENDER_FLAG_BOT)
+ dxvaFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_BOTTOM_FIELD_FIRST;
+ if (flags & RENDER_FLAG_FIELD1 && flags & RENDER_FLAG_TOP)
+ dxvaFrameFormat = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_BOTTOM_FIELD_FIRST;
+
+ bool frameProgressive = dxvaFrameFormat == D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
+
+ // Progressive or Interlaced video at normal rate.
+ stream_data.InputFrameOrField = frameIdx;
+ stream_data.OutputIndex = flags & RENDER_FLAG_FIELD1 && !frameProgressive ? 1 : 0;
+
+ // input format
+ m_pVideoContext->VideoProcessorSetStreamFrameFormat(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, dxvaFrameFormat);
+ // Source rect
+ m_pVideoContext->VideoProcessorSetStreamSourceRect(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, TRUE, &sourceRECT);
+ // Stream dest rect
+ m_pVideoContext->VideoProcessorSetStreamDestRect(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, TRUE, &dstRECT);
+ // Output rect
+ m_pVideoContext->VideoProcessorSetOutputTargetRect(m_pVideoProcessor.Get(), TRUE, &dstRECT);
+
+ ComPtr<ID3D11VideoContext1> videoCtx1;
+ if (SUCCEEDED(m_pVideoContext.As(&videoCtx1)))
+ {
+ bool supportHDR = DX::Windowing()->IsHDROutput() &&
+ (m_bSupportHDR10Limited || !DX::Windowing()->UseLimitedColor());
+
+ const DXGI_COLOR_SPACE_TYPE sourceColor =
+ GetDXGIColorSpaceSource(views[2], supportHDR, m_bSupportHLG);
+ const DXGI_COLOR_SPACE_TYPE targetColor = GetDXGIColorSpaceTarget(views[2], supportHDR);
+
+ videoCtx1->VideoProcessorSetStreamColorSpace1(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX,
+ sourceColor);
+ videoCtx1->VideoProcessorSetOutputColorSpace1(m_pVideoProcessor.Get(), targetColor);
+ // makes target available for processing in shaders
+ videoCtx1->VideoProcessorSetOutputShaderUsage(m_pVideoProcessor.Get(), 1);
+ }
+ else
+ {
+ // input colorspace
+ bool isBT601 = views[2]->color_space == AVCOL_SPC_BT470BG || views[2]->color_space == AVCOL_SPC_SMPTE170M;
+ // clang-format off
+ D3D11_VIDEO_PROCESSOR_COLOR_SPACE colorSpace
+ {
+ 0u, // 0 - Playback, 1 - Processing
+ views[2]->full_range ? 0u : 1u, // 0 - Full (0-255), 1 - Limited (16-235) (RGB)
+ isBT601 ? 1u : 0u, // 0 - BT.601, 1 - BT.709
+ 0u, // 0 - Conventional YCbCr, 1 - xvYCC
+ views[2]->full_range ? 2u : 1u // 0 - driver defaults, 2 - Full range [0-255], 1 - Studio range [16-235] (YUV)
+ };
+ // clang-format on
+ m_pVideoContext->VideoProcessorSetStreamColorSpace(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, &colorSpace);
+ // Output color space
+ // don't apply any color range conversion, this will be fixed at later stage.
+ colorSpace.Usage = 0; // 0 - playback, 1 - video processing
+ colorSpace.RGB_Range = DX::Windowing()->UseLimitedColor() ? 1 : 0; // 0 - 0-255, 1 - 16-235
+ colorSpace.YCbCr_Matrix = 1; // 0 - BT.601, 1 = BT.709
+ colorSpace.YCbCr_xvYCC = 1; // 0 - Conventional YCbCr, 1 - xvYCC
+ colorSpace.Nominal_Range = 0; // 2 - 0-255, 1 = 16-235, 0 - undefined
+ m_pVideoContext->VideoProcessorSetOutputColorSpace(m_pVideoProcessor.Get(), &colorSpace);
+ }
+
+ // brightness
+ ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER_BRIGHTNESS, static_cast<int>(brightness), 0, 100, 50);
+ // contrast
+ ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER_CONTRAST, static_cast<int>(contrast), 0, 100, 50);
+ // unused filters
+ ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER_HUE, 50, 0, 100, 50);
+ ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER_SATURATION, 50, 0, 100, 50);
+ // Rotation
+ m_pVideoContext->VideoProcessorSetStreamRotation(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, rotation != 0,
+ static_cast<D3D11_VIDEO_PROCESSOR_ROTATION>(rotation / 90));
+
+ // create output view for surface.
+ D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC OutputViewDesc = { D3D11_VPOV_DIMENSION_TEXTURE2D, { 0 }};
+ ComPtr<ID3D11VideoProcessorOutputView> pOutputView;
+ HRESULT hr = m_pVideoDevice->CreateVideoProcessorOutputView(target, m_pEnumerator.Get(), &OutputViewDesc, &pOutputView);
+ if (S_OK != hr)
+ CLog::LogF(FAILED(hr) ? LOGERROR : LOGWARNING,
+ "video device returns result '{:x}' while creating processor output view.", hr);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = m_pVideoContext->VideoProcessorBlt(m_pVideoProcessor.Get(), pOutputView.Get(), frameIdx, 1, &stream_data);
+ if (S_OK != hr)
+ {
+ CLog::LogF(FAILED(hr) ? LOGERROR : LOGWARNING,
+ "video device returns result '{:x}' while VideoProcessorBlt execution.", hr);
+ }
+ }
+
+ return !FAILED(hr);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h
new file mode 100644
index 0000000..8a30fb6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
+#include "DVDCodecs/Video/DXVA.h"
+#include "guilib/D3DResource.h"
+#include "utils/Geometry.h"
+
+#include <mutex>
+
+#include <wrl/client.h>
+
+class CRenderBuffer;
+
+namespace DXVA {
+
+// ProcAmp filters d3d11 filters
+const D3D11_VIDEO_PROCESSOR_FILTER PROCAMP_FILTERS[] =
+{
+ D3D11_VIDEO_PROCESSOR_FILTER_BRIGHTNESS,
+ D3D11_VIDEO_PROCESSOR_FILTER_CONTRAST,
+ D3D11_VIDEO_PROCESSOR_FILTER_HUE,
+ D3D11_VIDEO_PROCESSOR_FILTER_SATURATION
+};
+
+const size_t NUM_FILTERS = ARRAYSIZE(PROCAMP_FILTERS);
+
+class CProcessorHD : public ID3DResource
+{
+public:
+ explicit CProcessorHD();
+ ~CProcessorHD();
+
+ bool PreInit() const;
+ void UnInit();
+ bool Open(UINT width, UINT height);
+ void Close();
+ bool Render(CRect src, CRect dst, ID3D11Resource* target, CRenderBuffer **views, DWORD flags, UINT frameIdx, UINT rotation, float contrast, float brightness);
+ uint8_t PastRefs() const { return m_max_back_refs; }
+ bool IsFormatSupported(DXGI_FORMAT format, D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT support) const;
+
+ // ID3DResource overrides
+ void OnCreateDevice() override {}
+ void OnDestroyDevice(bool) override
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ UnInit();
+ }
+
+ static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpaceSource(CRenderBuffer* view, bool supportHDR, bool supportHLG);
+ static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpaceTarget(CRenderBuffer* view, bool supportHDR);
+
+protected:
+ bool ReInit();
+ bool InitProcessor();
+ bool CheckFormats() const;
+ bool OpenProcessor();
+ void ApplyFilter(D3D11_VIDEO_PROCESSOR_FILTER filter, int value, int min, int max, int def) const;
+ ID3D11VideoProcessorInputView* GetInputView(CRenderBuffer* view) const;
+
+ CCriticalSection m_section;
+
+ uint32_t m_width = 0;
+ uint32_t m_height = 0;
+ uint8_t m_max_back_refs = 0;
+ uint8_t m_max_fwd_refs = 0;
+ uint32_t m_procIndex = 0;
+ D3D11_VIDEO_PROCESSOR_CAPS m_vcaps = {};
+ D3D11_VIDEO_PROCESSOR_RATE_CONVERSION_CAPS m_rateCaps = {};
+ bool m_bSupportHLG = false;
+ bool m_bSupportHDR10Limited = false;
+
+ struct ProcAmpInfo
+ {
+ bool bSupported;
+ D3D11_VIDEO_PROCESSOR_FILTER_RANGE Range;
+ };
+ ProcAmpInfo m_Filters[NUM_FILTERS]{};
+ Microsoft::WRL::ComPtr<ID3D11VideoDevice> m_pVideoDevice;
+ Microsoft::WRL::ComPtr<ID3D11VideoContext> m_pVideoContext;
+ Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator> m_pEnumerator;
+ Microsoft::WRL::ComPtr<ID3D11VideoProcessor> m_pVideoProcessor;
+};
+
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp
new file mode 100644
index 0000000..a07b4f7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererDRMPRIME.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderCapture.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/gbm/WinSystemGbm.h"
+#include "windowing/gbm/drm/DRMAtomic.h"
+
+using namespace KODI::WINDOWING::GBM;
+
+const std::string SETTING_VIDEOPLAYER_USEPRIMERENDERER = "videoplayer.useprimerenderer";
+
+CRendererDRMPRIME::~CRendererDRMPRIME()
+{
+ Flush(false);
+}
+
+CBaseRenderer* CRendererDRMPRIME::Create(CVideoBuffer* buffer)
+{
+ if (buffer && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ SETTING_VIDEOPLAYER_USEPRIMERENDERER) == 0)
+ {
+ auto buf = dynamic_cast<CVideoBufferDRMPRIME*>(buffer);
+ if (!buf)
+ return nullptr;
+
+ auto winSystem = static_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return nullptr;
+
+ auto drm = std::static_pointer_cast<CDRMAtomic>(winSystem->GetDrm());
+ if (!drm)
+ return nullptr;
+
+ if (!buf->AcquireDescriptor())
+ return nullptr;
+
+ AVDRMFrameDescriptor* desc = buf->GetDescriptor();
+ if (!desc)
+ {
+ buf->ReleaseDescriptor();
+ return nullptr;
+ }
+
+ AVDRMLayerDescriptor* layer = &desc->layers[0];
+ uint32_t format = layer->format;
+ uint64_t modifier = desc->objects[0].format_modifier;
+
+ buf->ReleaseDescriptor();
+
+ auto gui = drm->GetGuiPlane();
+ if (!gui)
+ return nullptr;
+
+ if (!gui->SupportsFormat(CDRMUtils::FourCCWithAlpha(gui->GetFormat())))
+ return nullptr;
+
+ auto plane = drm->GetVideoPlane();
+ if (!plane)
+ return nullptr;
+
+ if (!plane->SupportsFormatAndModifier(format, modifier))
+ return nullptr;
+
+ return new CRendererDRMPRIME();
+ }
+
+ return nullptr;
+}
+
+void CRendererDRMPRIME::Register()
+{
+ CWinSystemGbm* winSystem = dynamic_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+ if (winSystem && winSystem->GetDrm()->GetVideoPlane() &&
+ std::dynamic_pointer_cast<CDRMAtomic>(winSystem->GetDrm()))
+ {
+ CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetSetting(SETTING_VIDEOPLAYER_USEPRIMERENDERER)
+ ->SetVisible(true);
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("drm_prime", CRendererDRMPRIME::Create);
+ return;
+ }
+}
+
+bool CRendererDRMPRIME::Configure(const VideoPicture& picture, float fps, unsigned int orientation)
+{
+ m_format = picture.videoBuffer->GetFormat();
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+
+ m_iFlags = GetFlagsChromaPosition(picture.chroma_position) |
+ GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight) |
+ GetFlagsColorPrimaries(picture.color_primaries) |
+ GetFlagsStereoMode(picture.stereoMode);
+
+ // Calculate the input frame aspect ratio.
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+ ManageRenderArea();
+
+ Flush(false);
+
+ m_bConfigured = true;
+ return true;
+}
+
+void CRendererDRMPRIME::ManageRenderArea()
+{
+ CBaseRenderer::ManageRenderArea();
+
+ RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ if (info.iScreenWidth != info.iWidth)
+ {
+ CalcDestRect(0, 0, info.iScreenWidth, info.iScreenHeight,
+ GetAspectRatio() * CDisplaySettings::GetInstance().GetPixelRatio(),
+ CDisplaySettings::GetInstance().GetZoomAmount(),
+ CDisplaySettings::GetInstance().GetVerticalShift(), m_planeDestRect);
+ }
+ else
+ {
+ m_planeDestRect = m_destRect;
+ }
+}
+
+void CRendererDRMPRIME::AddVideoPicture(const VideoPicture& picture, int index)
+{
+ BUFFER& buf = m_buffers[index];
+ if (buf.videoBuffer)
+ {
+ CLog::LogF(LOGERROR, "unreleased video buffer");
+ buf.videoBuffer->Release();
+ }
+ buf.videoBuffer = picture.videoBuffer;
+ buf.videoBuffer->Acquire();
+}
+
+bool CRendererDRMPRIME::Flush(bool saveBuffers)
+{
+ if (!saveBuffers)
+ for (int i = 0; i < NUM_BUFFERS; i++)
+ ReleaseBuffer(i);
+
+ m_iLastRenderBuffer = -1;
+ return saveBuffers;
+}
+
+void CRendererDRMPRIME::ReleaseBuffer(int index)
+{
+ BUFFER& buf = m_buffers[index];
+ if (buf.videoBuffer)
+ {
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+bool CRendererDRMPRIME::NeedBuffer(int index)
+{
+ if (m_iLastRenderBuffer == index)
+ return true;
+
+ return false;
+}
+
+CRenderInfo CRendererDRMPRIME::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = NUM_BUFFERS;
+ return info;
+}
+
+void CRendererDRMPRIME::Update()
+{
+ if (!m_bConfigured)
+ return;
+
+ ManageRenderArea();
+}
+
+void CRendererDRMPRIME::RenderUpdate(
+ int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ if (m_iLastRenderBuffer == index && m_videoLayerBridge)
+ {
+ m_videoLayerBridge->UpdateVideoPlane();
+ return;
+ }
+
+ CVideoBufferDRMPRIME* buffer = dynamic_cast<CVideoBufferDRMPRIME*>(m_buffers[index].videoBuffer);
+ if (!buffer || !buffer->IsValid())
+ return;
+
+ if (!m_videoLayerBridge)
+ {
+ CWinSystemGbm* winSystem = static_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+ m_videoLayerBridge =
+ std::dynamic_pointer_cast<CVideoLayerBridgeDRMPRIME>(winSystem->GetVideoLayerBridge());
+ if (!m_videoLayerBridge)
+ m_videoLayerBridge = std::make_shared<CVideoLayerBridgeDRMPRIME>(
+ std::dynamic_pointer_cast<CDRMAtomic>(winSystem->GetDrm()));
+ winSystem->RegisterVideoLayerBridge(m_videoLayerBridge);
+ }
+
+ if (m_iLastRenderBuffer == -1)
+ m_videoLayerBridge->Configure(buffer);
+
+ m_videoLayerBridge->SetVideoPlane(buffer, m_planeDestRect);
+
+ m_iLastRenderBuffer = index;
+}
+
+bool CRendererDRMPRIME::RenderCapture(CRenderCapture* capture)
+{
+ capture->BeginRender();
+ capture->EndRender();
+ return true;
+}
+
+bool CRendererDRMPRIME::ConfigChanged(const VideoPicture& picture)
+{
+ if (picture.videoBuffer->GetFormat() != m_format)
+ return true;
+
+ return false;
+}
+
+bool CRendererDRMPRIME::Supports(ERENDERFEATURE feature) const
+{
+ switch (feature)
+ {
+ case RENDERFEATURE_STRETCH:
+ case RENDERFEATURE_ZOOM:
+ case RENDERFEATURE_VERTICAL_SHIFT:
+ case RENDERFEATURE_PIXEL_RATIO:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool CRendererDRMPRIME::Supports(ESCALINGMETHOD method) const
+{
+ return false;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h
new file mode 100644
index 0000000..c3953cc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h"
+
+class CVideoBuffer;
+class CVideoLayerBridgeDRMPRIME;
+
+class CRendererDRMPRIME : public CBaseRenderer
+{
+public:
+ CRendererDRMPRIME() = default;
+ ~CRendererDRMPRIME() override;
+
+ // Registration
+ static CBaseRenderer* Create(CVideoBuffer* buffer);
+ static void Register();
+
+ // Player functions
+ bool Configure(const VideoPicture& picture, float fps, unsigned int orientation) override;
+ bool IsConfigured() override { return m_bConfigured; }
+ void AddVideoPicture(const VideoPicture& picture, int index) override;
+ void UnInit() override {}
+ bool Flush(bool saveBuffers) override;
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+ bool IsGuiLayer() override { return false; }
+ CRenderInfo GetRenderInfo() override;
+ void Update() override;
+ void RenderUpdate(
+ int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ bool RenderCapture(CRenderCapture* capture) override;
+ bool ConfigChanged(const VideoPicture& picture) override;
+
+ // Feature support
+ bool SupportsMultiPassRendering() override { return false; }
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+protected:
+ void ManageRenderArea() override;
+
+private:
+ bool m_bConfigured = false;
+ int m_iLastRenderBuffer = -1;
+ CRect m_planeDestRect;
+
+ std::shared_ptr<CVideoLayerBridgeDRMPRIME> m_videoLayerBridge;
+
+ struct BUFFER
+ {
+ CVideoBuffer* videoBuffer = nullptr;
+ } m_buffers[NUM_BUFFERS];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp
new file mode 100644
index 0000000..2fcf54f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererDRMPRIMEGLES.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderCapture.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/EGLFence.h"
+#include "utils/EGLImage.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+#include "windowing/linux/WinSystemEGL.h"
+
+using namespace KODI::UTILS::EGL;
+
+CRendererDRMPRIMEGLES::~CRendererDRMPRIMEGLES()
+{
+ Flush(false);
+}
+
+CBaseRenderer* CRendererDRMPRIMEGLES::Create(CVideoBuffer* buffer)
+{
+ if (!buffer)
+ return nullptr;
+
+ auto buf = dynamic_cast<CVideoBufferDRMPRIME*>(buffer);
+ if (!buf)
+ return nullptr;
+
+#if defined(EGL_EXT_image_dma_buf_import_modifiers)
+ if (!buf->AcquireDescriptor())
+ return nullptr;
+
+ auto desc = buf->GetDescriptor();
+ if (!desc)
+ {
+ buf->ReleaseDescriptor();
+ return nullptr;
+ }
+
+ uint64_t modifier = desc->objects[0].format_modifier;
+ uint32_t format = desc->layers[0].format;
+
+ buf->ReleaseDescriptor();
+
+ auto winSystemEGL =
+ dynamic_cast<KODI::WINDOWING::LINUX::CWinSystemEGL*>(CServiceBroker::GetWinSystem());
+ if (!winSystemEGL)
+ return nullptr;
+
+ CEGLImage image{winSystemEGL->GetEGLDisplay()};
+ if (!image.SupportsFormatAndModifier(format, modifier))
+ return nullptr;
+#endif
+
+ return new CRendererDRMPRIMEGLES();
+}
+
+void CRendererDRMPRIMEGLES::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("drm_prime_gles", CRendererDRMPRIMEGLES::Create);
+}
+
+bool CRendererDRMPRIMEGLES::Configure(const VideoPicture& picture,
+ float fps,
+ unsigned int orientation)
+{
+ m_format = picture.videoBuffer->GetFormat();
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+
+ m_iFlags = GetFlagsChromaPosition(picture.chroma_position) |
+ GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight) |
+ GetFlagsColorPrimaries(picture.color_primaries) |
+ GetFlagsStereoMode(picture.stereoMode);
+
+ // Calculate the input frame aspect ratio.
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+ ManageRenderArea();
+
+ Flush(false);
+
+ auto winSystem = CServiceBroker::GetWinSystem();
+
+ if (!winSystem)
+ return false;
+
+ auto winSystemEGL = dynamic_cast<KODI::WINDOWING::LINUX::CWinSystemEGL*>(winSystem);
+
+ if (!winSystemEGL)
+ return false;
+
+ EGLDisplay eglDisplay = winSystemEGL->GetEGLDisplay();
+
+ for (auto&& buf : m_buffers)
+ {
+ if (!buf.fence)
+ {
+ buf.texture.Init(eglDisplay);
+ buf.fence.reset(new CEGLFence(eglDisplay));
+ }
+ }
+
+ m_clearColour = winSystem->UseLimitedColor() ? (16.0f / 0xff) : 0.0f;
+
+ m_configured = true;
+ return true;
+}
+
+void CRendererDRMPRIMEGLES::AddVideoPicture(const VideoPicture& picture, int index)
+{
+ BUFFER& buf = m_buffers[index];
+ if (buf.videoBuffer)
+ {
+ CLog::LogF(LOGERROR, "unreleased video buffer");
+ if (buf.fence)
+ buf.fence->DestroyFence();
+ buf.texture.Unmap();
+ buf.videoBuffer->Release();
+ }
+ buf.videoBuffer = picture.videoBuffer;
+ buf.videoBuffer->Acquire();
+}
+
+bool CRendererDRMPRIMEGLES::Flush(bool saveBuffers)
+{
+ if (!saveBuffers)
+ for (int i = 0; i < NUM_BUFFERS; i++)
+ ReleaseBuffer(i);
+
+ return saveBuffers;
+}
+
+void CRendererDRMPRIMEGLES::ReleaseBuffer(int index)
+{
+ BUFFER& buf = m_buffers[index];
+
+ if (buf.fence)
+ buf.fence->DestroyFence();
+
+ buf.texture.Unmap();
+
+ if (buf.videoBuffer)
+ {
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+bool CRendererDRMPRIMEGLES::NeedBuffer(int index)
+{
+ return !m_buffers[index].fence->IsSignaled();
+}
+
+CRenderInfo CRendererDRMPRIMEGLES::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = NUM_BUFFERS;
+ return info;
+}
+
+void CRendererDRMPRIMEGLES::Update()
+{
+ if (!m_configured)
+ return;
+
+ ManageRenderArea();
+}
+
+void CRendererDRMPRIMEGLES::DrawBlackBars()
+{
+ CRect windowRect(0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(),
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight());
+
+ auto quads = windowRect.SubtractRect(m_destRect);
+
+ struct Svertex
+ {
+ float x, y;
+ };
+
+ std::vector<Svertex> vertices(6 * quads.size());
+
+ GLubyte count = 0;
+ for (const auto& quad : quads)
+ {
+ vertices[count + 1].x = quad.x1;
+ vertices[count + 1].y = quad.y1;
+
+ vertices[count + 0].x = vertices[count + 5].x = quad.x1;
+ vertices[count + 0].y = vertices[count + 5].y = quad.y2;
+
+ vertices[count + 2].x = vertices[count + 3].x = quad.x2;
+ vertices[count + 2].y = vertices[count + 3].y = quad.y1;
+
+ vertices[count + 4].x = quad.x2;
+ vertices[count + 4].y = quad.y2;
+
+ count += 6;
+ }
+
+ glDisable(GL_BLEND);
+
+ CRenderSystemGLES* renderSystem =
+ dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ if (!renderSystem)
+ return;
+
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_DEFAULT);
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint uniCol = renderSystem->GUIShaderGetUniCol();
+
+ glUniform4f(uniCol, m_clearColour / 255.0f, m_clearColour / 255.0f, m_clearColour / 255.0f, 1.0f);
+
+ GLuint vertexVBO;
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Svertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Svertex), 0);
+ glEnableVertexAttribArray(posLoc);
+
+ glDrawArrays(GL_TRIANGLES, 0, vertices.size());
+
+ glDisableVertexAttribArray(posLoc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+
+ renderSystem->DisableGUIShader();
+}
+
+void CRendererDRMPRIMEGLES::RenderUpdate(
+ int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ if (!m_configured)
+ return;
+
+ ManageRenderArea();
+
+ if (clear)
+ {
+ if (alpha == 255)
+ DrawBlackBars();
+ else
+ {
+ glClearColor(m_clearColour, m_clearColour, m_clearColour, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0, 0, 0, 0);
+ }
+ }
+
+ if (alpha < 255)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+
+ Render(flags, index);
+
+ VerifyGLState();
+ glEnable(GL_BLEND);
+}
+
+bool CRendererDRMPRIMEGLES::RenderCapture(CRenderCapture* capture)
+{
+ capture->BeginRender();
+ capture->EndRender();
+ return true;
+}
+
+bool CRendererDRMPRIMEGLES::ConfigChanged(const VideoPicture& picture)
+{
+ if (picture.videoBuffer->GetFormat() != m_format)
+ return true;
+
+ return false;
+}
+
+void CRendererDRMPRIMEGLES::Render(unsigned int flags, int index)
+{
+ BUFFER& buf = m_buffers[index];
+
+ CVideoBufferDRMPRIME* buffer = dynamic_cast<CVideoBufferDRMPRIME*>(buf.videoBuffer);
+ if (!buffer || !buffer->IsValid())
+ return;
+
+ CRenderSystemGLES* renderSystem =
+ dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ if (!renderSystem)
+ return;
+
+ if (!buf.texture.Map(buffer))
+ return;
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, buf.texture.GetTexture());
+
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE_RGBA_OES);
+
+ GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip
+ GLuint vertexVBO;
+ GLuint indexVBO;
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ };
+
+ std::array<PackedVertex, 4> vertex;
+
+ GLint vertLoc = renderSystem->GUIShaderGetPos();
+ GLint loc = renderSystem->GUIShaderGetCoord0();
+
+ // top left
+ vertex[0].x = m_rotatedDestCoords[0].x;
+ vertex[0].y = m_rotatedDestCoords[0].y;
+ vertex[0].z = 0.0f;
+ vertex[0].u1 = 0.0f;
+ vertex[0].v1 = 0.0f;
+
+ // top right
+ vertex[1].x = m_rotatedDestCoords[1].x;
+ vertex[1].y = m_rotatedDestCoords[1].y;
+ vertex[1].z = 0.0f;
+ vertex[1].u1 = 1.0f;
+ vertex[1].v1 = 0.0f;
+
+ // bottom right
+ vertex[2].x = m_rotatedDestCoords[2].x;
+ vertex[2].y = m_rotatedDestCoords[2].y;
+ vertex[2].z = 0.0f;
+ vertex[2].u1 = 1.0f;
+ vertex[2].v1 = 1.0f;
+
+ // bottom left
+ vertex[3].x = m_rotatedDestCoords[3].x;
+ vertex[3].y = m_rotatedDestCoords[3].y;
+ vertex[3].z = 0.0f;
+ vertex[3].u1 = 0.0f;
+ vertex[3].v1 = 1.0f;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * vertex.size(), vertex.data(),
+ GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ renderSystem->DisableGUIShader();
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+
+ buf.fence->DestroyFence();
+ buf.fence->CreateFence();
+}
+
+bool CRendererDRMPRIMEGLES::Supports(ERENDERFEATURE feature) const
+{
+ switch (feature)
+ {
+ case RENDERFEATURE_STRETCH:
+ case RENDERFEATURE_ZOOM:
+ case RENDERFEATURE_VERTICAL_SHIFT:
+ case RENDERFEATURE_PIXEL_RATIO:
+ case RENDERFEATURE_ROTATION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool CRendererDRMPRIMEGLES::Supports(ESCALINGMETHOD method) const
+{
+ switch (method)
+ {
+ case VS_SCALINGMETHOD_LINEAR:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h
new file mode 100644
index 0000000..7b2cb07
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DRMPRIMEEGL.h"
+#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace UTILS
+{
+namespace EGL
+{
+class CEGLFence;
+}
+} // namespace UTILS
+} // namespace KODI
+
+class CRendererDRMPRIMEGLES : public CBaseRenderer
+{
+public:
+ CRendererDRMPRIMEGLES() = default;
+ ~CRendererDRMPRIMEGLES() override;
+
+ // Registration
+ static CBaseRenderer* Create(CVideoBuffer* buffer);
+ static void Register();
+
+ // Player functions
+ bool Configure(const VideoPicture& picture, float fps, unsigned int orientation) override;
+ bool IsConfigured() override { return m_configured; }
+ void AddVideoPicture(const VideoPicture& picture, int index) override;
+ void UnInit() override {}
+ bool Flush(bool saveBuffers) override;
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+ CRenderInfo GetRenderInfo() override;
+ void Update() override;
+ void RenderUpdate(
+ int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ bool RenderCapture(CRenderCapture* capture) override;
+ bool ConfigChanged(const VideoPicture& picture) override;
+
+ // Feature support
+ bool SupportsMultiPassRendering() override { return false; }
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+private:
+ void DrawBlackBars();
+ void Render(unsigned int flags, int index);
+
+ bool m_configured = false;
+ float m_clearColour{0.0f};
+
+ struct BUFFER
+ {
+ CVideoBuffer* videoBuffer = nullptr;
+ std::unique_ptr<KODI::UTILS::EGL::CEGLFence> fence;
+ CDRMPRIMETexture texture;
+ } m_buffers[NUM_BUFFERS];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.cpp
new file mode 100644
index 0000000..a655b89
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererMediaCodec.h"
+
+#include "../RenderFactory.h"
+#include "DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h"
+#include "ServiceBroker.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "settings/MediaSettings.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#if defined(EGL_KHR_reusable_sync) && !defined(EGL_EGLEXT_PROTOTYPES)
+static PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
+static PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
+static PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
+#endif
+
+CRendererMediaCodec::CRendererMediaCodec()
+{
+ CLog::Log(LOGINFO, "Instancing CRendererMediaCodec");
+#if defined(EGL_KHR_reusable_sync) && !defined(EGL_EGLEXT_PROTOTYPES)
+ if (!eglCreateSyncKHR) {
+ eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
+ }
+ if (!eglDestroySyncKHR) {
+ eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
+ }
+ if (!eglClientWaitSyncKHR) {
+ eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress("eglClientWaitSyncKHR");
+ }
+#endif
+}
+
+CRendererMediaCodec::~CRendererMediaCodec()
+{
+ for (int i(0); i < NUM_BUFFERS; ++i)
+ ReleaseBuffer(i);
+}
+
+CBaseRenderer* CRendererMediaCodec::Create(CVideoBuffer *buffer)
+{
+ if (buffer && dynamic_cast<CMediaCodecVideoBuffer*>(buffer) && dynamic_cast<CMediaCodecVideoBuffer*>(buffer)->HasSurfaceTexture())
+ return new CRendererMediaCodec();
+ return nullptr;
+}
+
+bool CRendererMediaCodec::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("mediacodec_egl", CRendererMediaCodec::Create);
+ return true;
+}
+
+void CRendererMediaCodec::AddVideoPicture(const VideoPicture &picture, int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CMediaCodecVideoBuffer *videoBuffer;
+ if (picture.videoBuffer && (videoBuffer = dynamic_cast<CMediaCodecVideoBuffer*>(picture.videoBuffer)))
+ {
+ CPictureBuffer &buf = m_buffers[index];
+ buf.videoBuffer = picture.videoBuffer;
+ buf.fields[0][0].id = videoBuffer->GetTextureId();
+ videoBuffer->Acquire();
+
+ // releaseOutputBuffer must be in same thread as
+ // dequeueOutputBuffer. We are in VideoPlayerVideo
+ // thread here, so we are safe.
+ videoBuffer->ReleaseOutputBuffer(true, 0);
+ }
+ else
+ buf.fields[0][0].id = 0;
+}
+
+void CRendererMediaCodec::ReleaseBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ CMediaCodecVideoBuffer* videoBuffer;
+ if (buf.videoBuffer && (videoBuffer = dynamic_cast<CMediaCodecVideoBuffer*>(buf.videoBuffer)))
+ {
+ // The media buffer has been queued to the SurfaceView but we didn't render it
+ // We have to do to the updateTexImage or it will get stuck
+ videoBuffer->UpdateTexImage();
+ videoBuffer->GetTransformMatrix(m_textureMatrix);
+ videoBuffer->Release();
+ buf.videoBuffer = NULL;
+ }
+}
+
+CRenderInfo CRendererMediaCodec::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = 4;
+ info.optimal_buffer_size = 3;
+ return info;
+}
+
+bool CRendererMediaCodec::LoadShadersHook()
+{
+ CLog::Log(LOGINFO, "GL: Using MediaCodec render method");
+ m_textureTarget = GL_TEXTURE_2D;
+ m_renderMethod = RENDER_CUSTOM;
+ return true;
+}
+
+bool CRendererMediaCodec::RenderHook(int index)
+{
+ CYuvPlane &plane = m_buffers[index].fields[0][0];
+ CYuvPlane &planef = m_buffers[index].fields[m_currentField][0];
+
+ glDisable(GL_DEPTH_TEST);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, plane.id);
+
+ CRenderSystemGLES* renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+
+ if (m_currentField != FIELD_FULL)
+ {
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES);
+ GLint fieldLoc = renderSystem->GUIShaderGetField();
+ GLint stepLoc = renderSystem->GUIShaderGetStep();
+
+ // Y is inverted, so invert fields
+ if (m_currentField == FIELD_TOP)
+ glUniform1i(fieldLoc, 0);
+ else if(m_currentField == FIELD_BOT)
+ glUniform1i(fieldLoc, 1);
+ glUniform1f(stepLoc, 1.0f / (float)plane.texheight);
+ }
+ else
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE_RGBA_OES);
+
+ GLint contrastLoc = renderSystem->GUIShaderGetContrast();
+ glUniform1f(contrastLoc, m_videoSettings.m_Contrast * 0.02f);
+ GLint brightnessLoc = renderSystem->GUIShaderGetBrightness();
+ glUniform1f(brightnessLoc, m_videoSettings.m_Brightness * 0.01f - 0.5f);
+
+ glUniformMatrix4fv(renderSystem->GUIShaderGetCoord0Matrix(), 1, GL_FALSE, m_textureMatrix);
+
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of triangle strip
+ GLfloat ver[4][4];
+ GLfloat tex[4][4];
+
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint texLoc = renderSystem->GUIShaderGetCoord0();
+
+
+ glVertexAttribPointer(posLoc, 4, GL_FLOAT, 0, 0, ver);
+ glVertexAttribPointer(texLoc, 4, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(texLoc);
+
+ // Set vertex coordinates
+ for(int i = 0; i < 4; i++)
+ {
+ ver[i][0] = m_rotatedDestCoords[i].x;
+ ver[i][1] = m_rotatedDestCoords[i].y;
+ ver[i][2] = 0.0f; // set z to 0
+ ver[i][3] = 1.0f;
+ }
+
+ // Set texture coordinates (MediaCodec is flipped in y)
+ if (m_currentField == FIELD_FULL)
+ {
+ tex[0][0] = tex[3][0] = plane.rect.x1;
+ tex[0][1] = tex[1][1] = plane.rect.y2;
+ tex[1][0] = tex[2][0] = plane.rect.x2;
+ tex[2][1] = tex[3][1] = plane.rect.y1;
+ }
+ else
+ {
+ tex[0][0] = tex[3][0] = planef.rect.x1;
+ tex[0][1] = tex[1][1] = planef.rect.y2 * 2.0f;
+ tex[1][0] = tex[2][0] = planef.rect.x2;
+ tex[2][1] = tex[3][1] = planef.rect.y1 * 2.0f;
+ }
+
+ for(int i = 0; i < 4; i++)
+ {
+ tex[i][2] = 0.0f;
+ tex[i][3] = 1.0f;
+ }
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(texLoc);
+
+ const float identity[16] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ glUniformMatrix4fv(renderSystem->GUIShaderGetCoord0Matrix(), 1, GL_FALSE, identity);
+
+ renderSystem->DisableGUIShader();
+ VerifyGLState();
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+ VerifyGLState();
+
+ return true;
+}
+
+bool CRendererMediaCodec::CreateTexture(int index)
+{
+ CPictureBuffer &buf(m_buffers[index]);
+
+ buf.image.height = m_sourceHeight;
+ buf.image.width = m_sourceWidth;
+
+ for (int f=0; f<3; ++f)
+ {
+ CYuvPlane &plane = buf.fields[f][0];
+
+ plane.texwidth = m_sourceWidth;
+ plane.texheight = m_sourceHeight;
+ plane.pixpertex_x = 1;
+ plane.pixpertex_y = 1;
+ }
+
+ return true;
+}
+
+void CRendererMediaCodec::DeleteTexture(int index)
+{
+ ReleaseBuffer(index);
+}
+
+bool CRendererMediaCodec::UploadTexture(int index)
+{
+ ReleaseBuffer(index);
+ CalculateTextureSourceRects(index, 1);
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h
new file mode 100644
index 0000000..7b4c3bf
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+
+class CRendererMediaCodec : public CLinuxRendererGLES
+{
+public:
+ CRendererMediaCodec();
+ ~CRendererMediaCodec() override;
+
+ // Registration
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ // Player functions
+ void AddVideoPicture(const VideoPicture& picture, int index) override;
+ void ReleaseBuffer(int idx) override;
+
+ // Feature support
+ CRenderInfo GetRenderInfo() override;
+
+protected:
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+
+ // hooks for hw dec renderer
+ bool LoadShadersHook() override;
+ bool RenderHook(int index) override;
+
+private:
+ float m_textureMatrix[16];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.cpp
new file mode 100644
index 0000000..1a9c278
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererMediaCodecSurface.h"
+
+#include "../RenderCapture.h"
+#include "../RenderFactory.h"
+#include "../RenderFlags.h"
+#include "DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h"
+#include "rendering/RenderSystem.h"
+#include "settings/MediaSettings.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+#include <chrono>
+#include <thread>
+
+CRendererMediaCodecSurface::CRendererMediaCodecSurface()
+{
+ CLog::Log(LOGINFO, "Instancing CRendererMediaCodecSurface");
+}
+
+CRendererMediaCodecSurface::~CRendererMediaCodecSurface()
+{
+ Reset();
+}
+
+CBaseRenderer* CRendererMediaCodecSurface::Create(CVideoBuffer *buffer)
+{
+ if (buffer && dynamic_cast<CMediaCodecVideoBuffer*>(buffer) && !dynamic_cast<CMediaCodecVideoBuffer*>(buffer)->HasSurfaceTexture())
+ return new CRendererMediaCodecSurface();
+ return nullptr;
+}
+
+bool CRendererMediaCodecSurface::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("mediacodec_surface", CRendererMediaCodecSurface::Create);
+ return true;
+}
+
+bool CRendererMediaCodecSurface::Configure(const VideoPicture &picture, float fps, unsigned int orientation)
+{
+ CLog::Log(LOGINFO, "CRendererMediaCodecSurface::Configure");
+
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+
+ m_iFlags = GetFlagsChromaPosition(picture.chroma_position) |
+ GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight) |
+ GetFlagsColorPrimaries(picture.color_primaries) |
+ GetFlagsStereoMode(picture.stereoMode);
+
+ // Calculate the input frame aspect ratio.
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+
+ return true;
+}
+
+CRenderInfo CRendererMediaCodecSurface::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = info.optimal_buffer_size = 4;
+ return info;
+}
+
+bool CRendererMediaCodecSurface::RenderCapture(CRenderCapture* capture)
+{
+ capture->BeginRender();
+ capture->EndRender();
+ return true;
+}
+
+void CRendererMediaCodecSurface::AddVideoPicture(const VideoPicture &picture, int index)
+{
+ ReleaseBuffer(index);
+
+ BUFFER &buf(m_buffers[index]);
+ if (picture.videoBuffer)
+ {
+ buf.videoBuffer = picture.videoBuffer;
+ buf.videoBuffer->Acquire();
+ }
+}
+
+void CRendererMediaCodecSurface::ReleaseVideoBuffer(int idx, bool render)
+{
+ BUFFER &buf(m_buffers[idx]);
+ if (buf.videoBuffer)
+ {
+ CMediaCodecVideoBuffer *mcvb(dynamic_cast<CMediaCodecVideoBuffer*>(buf.videoBuffer));
+ if (mcvb)
+ {
+ if (render && m_bConfigured)
+ mcvb->RenderUpdate(m_surfDestRect, CXBMCApp::Get().GetNextFrameTime());
+ else
+ mcvb->ReleaseOutputBuffer(render, 0);
+ }
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+void CRendererMediaCodecSurface::ReleaseBuffer(int idx)
+{
+ ReleaseVideoBuffer(idx, false);
+}
+
+bool CRendererMediaCodecSurface::Supports(ERENDERFEATURE feature) const
+{
+ if (feature == RENDERFEATURE_ZOOM || feature == RENDERFEATURE_STRETCH ||
+ feature == RENDERFEATURE_PIXEL_RATIO || feature == RENDERFEATURE_VERTICAL_SHIFT ||
+ feature == RENDERFEATURE_ROTATION)
+ return true;
+
+ return false;
+}
+
+void CRendererMediaCodecSurface::Reset()
+{
+ for (int i = 0 ; i < 4 ; ++i)
+ ReleaseVideoBuffer(i, false);
+ m_lastIndex = -1;
+}
+
+void CRendererMediaCodecSurface::RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ m_bConfigured = true;
+
+ // this hack is needed to get the 2D mode of a 3D movie going
+ RENDER_STEREO_MODE stereo_mode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
+ if (stereo_mode)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_LEFT);
+
+ ManageRenderArea();
+
+ if (stereo_mode)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_OFF);
+
+ m_surfDestRect = m_destRect;
+ switch (stereo_mode)
+ {
+ case RENDER_STEREO_MODE_SPLIT_HORIZONTAL:
+ m_surfDestRect.y2 *= 2.0;
+ break;
+ case RENDER_STEREO_MODE_SPLIT_VERTICAL:
+ m_surfDestRect.x2 *= 2.0;
+ break;
+ case RENDER_STEREO_MODE_MONO:
+ if (CONF_FLAGS_STEREO_MODE_MASK(m_iFlags) == CONF_FLAGS_STEREO_MODE_TAB)
+ m_surfDestRect.y2 = m_surfDestRect.y2 * 2.0f;
+ else
+ m_surfDestRect.x2 = m_surfDestRect.x2 * 2.0f;
+ break;
+ default:
+ break;
+ }
+
+ if (index != m_lastIndex)
+ {
+ ReleaseVideoBuffer(index, true);
+ m_lastIndex = index;
+ }
+}
+
+void CRendererMediaCodecSurface::ReorderDrawPoints()
+{
+ CBaseRenderer::ReorderDrawPoints();
+
+ // Handle orientation
+ switch (m_renderOrientation)
+ {
+ case 90:
+ case 270:
+ {
+ double scale = static_cast<double>(m_surfDestRect.Height() / m_surfDestRect.Width());
+ int diff = static_cast<int>(static_cast<double>(m_surfDestRect.Height()) * scale -
+ static_cast<double>(m_surfDestRect.Width())) /
+ 2;
+ m_surfDestRect = CRect(m_surfDestRect.x1 - diff, m_surfDestRect.y1, m_surfDestRect.x2 + diff, m_surfDestRect.y2);
+ }
+ default:
+ break;
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h
new file mode 100644
index 0000000..d27601e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h"
+
+class CMediaCodecVideoBuffer;
+
+class CRendererMediaCodecSurface : public CBaseRenderer
+{
+public:
+ CRendererMediaCodecSurface();
+ ~CRendererMediaCodecSurface() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ bool RenderCapture(CRenderCapture* capture) override;
+ void AddVideoPicture(const VideoPicture& picture, int index) override;
+ void ReleaseBuffer(int idx) override;
+ bool Configure(const VideoPicture& picture, float fps, unsigned int orientation) override;
+ bool IsConfigured() override { return m_bConfigured; }
+ bool ConfigChanged(const VideoPicture& picture) override { return false; }
+ CRenderInfo GetRenderInfo() override;
+ void UnInit() override{};
+ void Update() override{};
+ void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ bool SupportsMultiPassRendering() override { return false; }
+
+ // Player functions
+ bool IsGuiLayer() override { return false; }
+
+ // Feature support
+ bool Supports(ESCALINGMETHOD method) const override { return false; }
+ bool Supports(ERENDERFEATURE feature) const override;
+
+protected:
+ void ReorderDrawPoints() override;
+
+private:
+ void Reset();
+ void ReleaseVideoBuffer(int idx, bool render);
+
+ bool m_bConfigured = false;
+ CRect m_surfDestRect;
+ int m_lastIndex = -1;
+
+ struct BUFFER
+ {
+ CVideoBuffer *videoBuffer = nullptr;
+ } m_buffers[4];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp
new file mode 100644
index 0000000..4b2a19b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererVAAPIGL.h"
+
+#include "../RenderFactory.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+using namespace VAAPI;
+
+IVaapiWinSystem* CRendererVAAPIGL::m_pWinSystem = nullptr;
+
+CBaseRenderer* CRendererVAAPIGL::Create(CVideoBuffer* buffer)
+{
+ CVaapiRenderPicture *vb = dynamic_cast<CVaapiRenderPicture*>(buffer);
+ if (vb)
+ return new CRendererVAAPIGL();
+
+ return nullptr;
+}
+
+void CRendererVAAPIGL::Register(IVaapiWinSystem* winSystem,
+ VADisplay vaDpy,
+ EGLDisplay eglDisplay,
+ bool& general,
+ bool& deepColor)
+{
+ general = deepColor = false;
+
+ int major_version, minor_version;
+ if (vaInitialize(vaDpy, &major_version, &minor_version) != VA_STATUS_SUCCESS)
+ {
+ vaTerminate(vaDpy);
+ return;
+ }
+
+ CVaapi2Texture::TestInterop(vaDpy, eglDisplay, general, deepColor);
+ CLog::Log(LOGDEBUG, "Vaapi2 EGL interop test results: general {}, deepColor {}",
+ general ? "yes" : "no", deepColor ? "yes" : "no");
+ if (!general)
+ {
+ CVaapi1Texture::TestInterop(vaDpy, eglDisplay, general, deepColor);
+ CLog::Log(LOGDEBUG, "Vaapi1 EGL interop test results: general {}, deepColor {}",
+ general ? "yes" : "no", deepColor ? "yes" : "no");
+ }
+
+ vaTerminate(vaDpy);
+
+ if (general)
+ {
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("vaapi", CRendererVAAPIGL::Create);
+ m_pWinSystem = winSystem;
+ }
+}
+
+CRendererVAAPIGL::CRendererVAAPIGL() = default;
+
+CRendererVAAPIGL::~CRendererVAAPIGL()
+{
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ DeleteTexture(i);
+ }
+}
+
+bool CRendererVAAPIGL::Configure(const VideoPicture& picture, float fps, unsigned int orientation)
+{
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
+ if (pic->procPic.videoSurface == VA_INVALID_ID)
+ {
+ m_isVAAPIBuffer = false;
+ }
+ else
+ {
+ m_isVAAPIBuffer = true;
+
+ InteropInfo interop;
+ interop.textureTarget = GL_TEXTURE_2D;
+ interop.eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ interop.eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ interop.glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ interop.eglDisplay = CRendererVAAPIGL::m_pWinSystem->GetEGLDisplay();
+
+ bool useVaapi2 = VAAPI::CVaapi2Texture::TestInteropGeneral(
+ pic->vadsp, CRendererVAAPIGL::m_pWinSystem->GetEGLDisplay());
+
+ for (auto &tex : m_vaapiTextures)
+ {
+ if (useVaapi2)
+ {
+ tex.reset(new VAAPI::CVaapi2Texture);
+ }
+ else
+ {
+ tex.reset(new VAAPI::CVaapi1Texture);
+ }
+ tex->Init(interop);
+ }
+ }
+
+ for (auto &fence : m_fences)
+ {
+ fence = {};
+ }
+
+ return CLinuxRendererGL::Configure(picture, fps, orientation);
+}
+
+bool CRendererVAAPIGL::Flush(bool saveBuffers)
+{
+ for (auto &vaapiTexture : m_vaapiTextures)
+ {
+ if (m_isVAAPIBuffer)
+ {
+ vaapiTexture->Unmap();
+ }
+ }
+ return CLinuxRendererGL::Flush(saveBuffers);
+}
+
+bool CRendererVAAPIGL::ConfigChanged(const VideoPicture& picture)
+{
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
+ if ((pic->procPic.videoSurface != VA_INVALID_ID && !m_isVAAPIBuffer) ||
+ (pic->procPic.videoSurface == VA_INVALID_ID && m_isVAAPIBuffer))
+ return true;
+
+ return false;
+}
+
+bool CRendererVAAPIGL::Supports(ERENDERFEATURE feature) const
+{
+ return CLinuxRendererGL::Supports(feature);
+}
+
+bool CRendererVAAPIGL::Supports(ESCALINGMETHOD method) const
+{
+ return CLinuxRendererGL::Supports(method);
+}
+
+EShaderFormat CRendererVAAPIGL::GetShaderFormat()
+{
+ return SHADER_NV12;
+}
+
+bool CRendererVAAPIGL::LoadShadersHook()
+{
+ return false;
+}
+
+bool CRendererVAAPIGL::RenderHook(int idx)
+{
+ return false;
+}
+
+bool CRendererVAAPIGL::CreateTexture(int index)
+{
+ if (!m_isVAAPIBuffer)
+ {
+ return CreateNV12Texture(index);
+ }
+
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+
+ DeleteTexture(index);
+
+ memset(&im, 0, sizeof(im));
+ memset(&planes, 0, sizeof(CYuvPlane[YuvImage::MAX_PLANES]));
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+
+ planes[0].id = 1;
+
+ return true;
+}
+
+void CRendererVAAPIGL::DeleteTexture(int index)
+{
+ ReleaseBuffer(index);
+
+ if (!m_isVAAPIBuffer)
+ {
+ DeleteNV12Texture(index);
+ return;
+ }
+
+ CPictureBuffer &buf = m_buffers[index];
+ buf.fields[FIELD_FULL][0].id = 0;
+ buf.fields[FIELD_FULL][1].id = 0;
+ buf.fields[FIELD_FULL][2].id = 0;
+}
+
+bool CRendererVAAPIGL::UploadTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(buf.videoBuffer);
+
+ if (!pic || !pic->valid)
+ {
+ return false;
+ }
+
+ if (!m_isVAAPIBuffer)
+ {
+ if (!m_buffers[index].loaded)
+ {
+ YuvImage &dst = m_buffers[index].image;
+ YuvImage src;
+ pic->GetPlanes(src.plane);
+ pic->GetStrides(src.stride);
+ UnBindPbo(m_buffers[index]);
+ CVideoBuffer::CopyNV12Picture(&dst, &src);
+ BindPbo(m_buffers[index]);
+ }
+ CalculateTextureSourceRects(index, 3);
+ return UploadNV12Texture(index);
+ }
+
+ m_vaapiTextures[index]->Map(pic);
+
+ const YuvImage& im = buf.image;
+ CYuvPlane (&planes)[3] = buf.fields[0];
+
+ auto size = m_vaapiTextures[index]->GetTextureSize();
+ planes[0].texwidth = size.Width();
+ planes[0].texheight = size.Height();
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ // set textures
+ planes[0].id = m_vaapiTextures[index]->GetTextureY();
+ planes[1].id = m_vaapiTextures[index]->GetTextureVU();
+ planes[2].id = m_vaapiTextures[index]->GetTextureVU();
+
+ for (int p=0; p<2; p++)
+ {
+ glBindTexture(m_textureTarget, planes[p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+ VerifyGLState();
+ }
+
+ CalculateTextureSourceRects(index, 3);
+ return true;
+}
+
+void CRendererVAAPIGL::AfterRenderHook(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = {};
+ }
+ m_fences[idx] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+}
+
+bool CRendererVAAPIGL::NeedBuffer(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ GLint state;
+ GLsizei length;
+ glGetSynciv(m_fences[idx], GL_SYNC_STATUS, 1, &length, &state);
+ if (state == GL_SIGNALED)
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = {};
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CRendererVAAPIGL::ReleaseBuffer(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = {};
+ }
+ if (m_isVAAPIBuffer)
+ {
+ m_vaapiTextures[idx]->Unmap();
+ }
+ CLinuxRendererGL::ReleaseBuffer(idx);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h
new file mode 100644
index 0000000..eacba07
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "VaapiEGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+
+#include <memory>
+
+namespace VAAPI
+{
+class IVaapiWinSystem;
+}
+
+class CRendererVAAPIGL : public CLinuxRendererGL
+{
+public:
+ CRendererVAAPIGL();
+ ~CRendererVAAPIGL() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static void Register(VAAPI::IVaapiWinSystem *winSystem, VADisplay vaDpy, EGLDisplay eglDisplay, bool &general, bool &deepColor);
+
+ bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override;
+
+ // Player functions
+ bool ConfigChanged(const VideoPicture &picture) override;
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+ bool Flush(bool saveBuffers) override;
+
+ // Feature support
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+protected:
+ bool LoadShadersHook() override;
+ bool RenderHook(int idx) override;
+ void AfterRenderHook(int idx) override;
+
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+
+ EShaderFormat GetShaderFormat() override;
+
+ bool m_isVAAPIBuffer = true;
+ std::unique_ptr<VAAPI::CVaapiTexture> m_vaapiTextures[NUM_BUFFERS];
+ GLsync m_fences[NUM_BUFFERS];
+ static VAAPI::IVaapiWinSystem *m_pWinSystem;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp
new file mode 100644
index 0000000..65e1e84
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererVAAPIGLES.h"
+
+#include "../RenderFactory.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "utils/EGLFence.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+using namespace VAAPI;
+using namespace KODI::UTILS::EGL;
+
+IVaapiWinSystem* CRendererVAAPIGLES::m_pWinSystem = nullptr;
+
+CBaseRenderer* CRendererVAAPIGLES::Create(CVideoBuffer* buffer)
+{
+ CVaapiRenderPicture *vb = dynamic_cast<CVaapiRenderPicture*>(buffer);
+ if (vb)
+ return new CRendererVAAPIGLES();
+
+ return nullptr;
+}
+
+void CRendererVAAPIGLES::Register(IVaapiWinSystem* winSystem,
+ VADisplay vaDpy,
+ EGLDisplay eglDisplay,
+ bool& general,
+ bool& deepColor)
+{
+ general = deepColor = false;
+
+ int major_version, minor_version;
+ if (vaInitialize(vaDpy, &major_version, &minor_version) != VA_STATUS_SUCCESS)
+ {
+ vaTerminate(vaDpy);
+ return;
+ }
+
+ CVaapi2Texture::TestInterop(vaDpy, eglDisplay, general, deepColor);
+ CLog::Log(LOGDEBUG, "Vaapi2 EGL interop test results: general {}, deepColor {}",
+ general ? "yes" : "no", deepColor ? "yes" : "no");
+ if (!general)
+ {
+ CVaapi1Texture::TestInterop(vaDpy, eglDisplay, general, deepColor);
+ CLog::Log(LOGDEBUG, "Vaapi1 EGL interop test results: general {}, deepColor {}",
+ general ? "yes" : "no", deepColor ? "yes" : "no");
+ }
+
+ vaTerminate(vaDpy);
+
+ if (general)
+ {
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("vaapi", CRendererVAAPIGLES::Create);
+ m_pWinSystem = winSystem;
+ }
+}
+
+CRendererVAAPIGLES::CRendererVAAPIGLES() = default;
+
+CRendererVAAPIGLES::~CRendererVAAPIGLES()
+{
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ DeleteTexture(i);
+ }
+}
+
+bool CRendererVAAPIGLES::Configure(const VideoPicture& picture, float fps, unsigned int orientation)
+{
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
+ if (pic->procPic.videoSurface != VA_INVALID_ID)
+ m_isVAAPIBuffer = true;
+ else
+ m_isVAAPIBuffer = false;
+
+ InteropInfo interop;
+ interop.textureTarget = GL_TEXTURE_2D;
+ interop.eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ interop.eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ interop.glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ interop.eglDisplay = m_pWinSystem->GetEGLDisplay();
+
+ bool useVaapi2 = VAAPI::CVaapi2Texture::TestInteropGeneral(
+ pic->vadsp, CRendererVAAPIGLES::m_pWinSystem->GetEGLDisplay());
+
+ for (auto &tex : m_vaapiTextures)
+ {
+ if (useVaapi2)
+ {
+ tex.reset(new VAAPI::CVaapi2Texture);
+ }
+ else
+ {
+ tex.reset(new VAAPI::CVaapi1Texture);
+ }
+ tex->Init(interop);
+ }
+
+ for (auto& fence : m_fences)
+ {
+ fence.reset(new CEGLFence(CRendererVAAPIGLES::m_pWinSystem->GetEGLDisplay()));
+ }
+
+ return CLinuxRendererGLES::Configure(picture, fps, orientation);
+}
+
+bool CRendererVAAPIGLES::ConfigChanged(const VideoPicture& picture)
+{
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
+ if (pic->procPic.videoSurface != VA_INVALID_ID && !m_isVAAPIBuffer)
+ return true;
+
+ return false;
+}
+
+EShaderFormat CRendererVAAPIGLES::GetShaderFormat()
+{
+ EShaderFormat ret = SHADER_NONE;
+
+ if (m_isVAAPIBuffer)
+ ret = SHADER_NV12_RRG;
+ else
+ ret = SHADER_NV12;
+
+ return ret;
+}
+
+bool CRendererVAAPIGLES::LoadShadersHook()
+{
+ return false;
+}
+
+bool CRendererVAAPIGLES::RenderHook(int idx)
+{
+ return false;
+}
+
+bool CRendererVAAPIGLES::CreateTexture(int index)
+{
+ if (!m_isVAAPIBuffer)
+ {
+ return CreateNV12Texture(index);
+ }
+
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+
+ DeleteTexture(index);
+
+ im = {};
+ std::fill(std::begin(planes), std::end(planes), CYuvPlane{});
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+
+ planes[0].id = 1;
+
+ return true;
+}
+
+void CRendererVAAPIGLES::DeleteTexture(int index)
+{
+ ReleaseBuffer(index);
+
+ if (!m_isVAAPIBuffer)
+ {
+ DeleteNV12Texture(index);
+ return;
+ }
+
+ CPictureBuffer &buf = m_buffers[index];
+ buf.fields[FIELD_FULL][0].id = 0;
+ buf.fields[FIELD_FULL][1].id = 0;
+ buf.fields[FIELD_FULL][2].id = 0;
+}
+
+bool CRendererVAAPIGLES::UploadTexture(int index)
+{
+ if (!m_isVAAPIBuffer)
+ {
+ return UploadNV12Texture(index);
+ }
+
+ CPictureBuffer &buf = m_buffers[index];
+
+ CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(buf.videoBuffer);
+
+ if (!pic || !pic->valid)
+ {
+ return false;
+ }
+
+ m_vaapiTextures[index]->Map(pic);
+
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[3] = buf.fields[0];
+
+ auto size = m_vaapiTextures[index]->GetTextureSize();
+ planes[0].texwidth = size.Width();
+ planes[0].texheight = size.Height();
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ // set textures
+ planes[0].id = m_vaapiTextures[index]->GetTextureY();
+ planes[1].id = m_vaapiTextures[index]->GetTextureVU();
+ planes[2].id = m_vaapiTextures[index]->GetTextureVU();
+
+ for (int p=0; p<2; p++)
+ {
+ glBindTexture(m_textureTarget, planes[p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+ VerifyGLState();
+ }
+
+ CalculateTextureSourceRects(index, 3);
+ return true;
+}
+
+void CRendererVAAPIGLES::AfterRenderHook(int index)
+{
+ m_fences[index]->CreateFence();
+}
+
+bool CRendererVAAPIGLES::NeedBuffer(int index)
+{
+ return !m_fences[index]->IsSignaled();
+}
+
+void CRendererVAAPIGLES::ReleaseBuffer(int index)
+{
+ m_fences[index]->DestroyFence();
+
+ if (m_isVAAPIBuffer)
+ {
+ m_vaapiTextures[index]->Unmap();
+ }
+
+ CLinuxRendererGLES::ReleaseBuffer(index);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h
new file mode 100644
index 0000000..26b7865
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "VaapiEGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+
+#include <array>
+#include <memory>
+
+namespace KODI
+{
+namespace UTILS
+{
+namespace EGL
+{
+class CEGLFence;
+}
+}
+}
+
+namespace VAAPI
+{
+class IVaapiWinSystem;
+}
+
+class CRendererVAAPIGLES : public CLinuxRendererGLES
+{
+public:
+ CRendererVAAPIGLES();
+ ~CRendererVAAPIGLES() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static void Register(VAAPI::IVaapiWinSystem *winSystem, VADisplay vaDpy, EGLDisplay eglDisplay, bool &general, bool &deepColor);
+
+ bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override;
+
+ // Player functions
+ bool ConfigChanged(const VideoPicture &picture) override;
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+
+protected:
+ bool LoadShadersHook() override;
+ bool RenderHook(int idx) override;
+ void AfterRenderHook(int idx) override;
+
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+
+ EShaderFormat GetShaderFormat() override;
+
+ bool m_isVAAPIBuffer = true;
+ std::unique_ptr<VAAPI::CVaapiTexture> m_vaapiTextures[NUM_BUFFERS];
+ std::array<std::unique_ptr<KODI::UTILS::EGL::CEGLFence>, NUM_BUFFERS> m_fences;
+ static VAAPI::IVaapiWinSystem *m_pWinSystem;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.cpp
new file mode 100644
index 0000000..048323c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.cpp
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererVDPAU.h"
+
+#include "../RenderFactory.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VDPAU.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+using namespace VDPAU;
+
+CBaseRenderer* CRendererVDPAU::Create(CVideoBuffer *buffer)
+{
+ CVdpauRenderPicture *vb = dynamic_cast<CVdpauRenderPicture*>(buffer);
+ if (vb)
+ return new CRendererVDPAU();
+
+ return nullptr;
+}
+
+bool CRendererVDPAU::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("vdpau", CRendererVDPAU::Create);
+ return true;
+}
+
+CRendererVDPAU::CRendererVDPAU() = default;
+
+CRendererVDPAU::~CRendererVDPAU()
+{
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ DeleteTexture(i);
+ }
+ m_interopState.Finish();
+}
+
+bool CRendererVDPAU::Configure(const VideoPicture &picture, float fps, unsigned int orientation)
+{
+ CVdpauRenderPicture *pic = dynamic_cast<CVdpauRenderPicture*>(picture.videoBuffer);
+ if (pic->procPic.isYuv)
+ m_isYuv = true;
+ else
+ m_isYuv = false;
+
+ if (!m_interopState.Init(pic->device, pic->procFunc, pic->ident))
+ return false;
+
+ for (auto &tex : m_vdpauTextures)
+ {
+ tex.Init(m_interopState.GetInterop());
+ }
+ for (auto &fence : m_fences)
+ {
+ fence = {};
+ }
+
+ return CLinuxRendererGL::Configure(picture, fps, orientation);
+}
+
+bool CRendererVDPAU::ConfigChanged(const VideoPicture &picture)
+{
+ CVdpauRenderPicture *pic = dynamic_cast<CVdpauRenderPicture*>(picture.videoBuffer);
+ if (pic->procPic.isYuv && !m_isYuv)
+ return true;
+
+ if (m_interopState.NeedInit(pic->device, pic->procFunc, pic->ident))
+ return true;
+
+ return false;
+}
+
+bool CRendererVDPAU::NeedBuffer(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ GLint state;
+ GLsizei length;
+ glGetSynciv(m_fences[idx], GL_SYNC_STATUS, 1, &length, &state);
+ if (state == GL_SIGNALED)
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = {};
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CRendererVDPAU::Flush(bool saveBuffers)
+{
+ for (int i = 0; i < NUM_BUFFERS; i++)
+ m_vdpauTextures[i].Unmap();
+
+ return CLinuxRendererGL::Flush(saveBuffers);
+}
+
+
+void CRendererVDPAU::ReleaseBuffer(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = {};
+ }
+ m_vdpauTextures[idx].Unmap();
+ CLinuxRendererGL::ReleaseBuffer(idx);
+}
+
+bool CRendererVDPAU::Supports(ERENDERFEATURE feature) const
+{
+ if(feature == RENDERFEATURE_BRIGHTNESS ||
+ feature == RENDERFEATURE_CONTRAST)
+ {
+ if (!m_isYuv && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE))
+ return true;
+
+ return (m_renderMethod & RENDER_GLSL);
+ }
+ else if (feature == RENDERFEATURE_NOISE ||
+ feature == RENDERFEATURE_SHARPNESS)
+ {
+ if (!m_isYuv)
+ return true;
+ }
+ else if (feature == RENDERFEATURE_STRETCH ||
+ feature == RENDERFEATURE_ZOOM ||
+ feature == RENDERFEATURE_VERTICAL_SHIFT ||
+ feature == RENDERFEATURE_PIXEL_RATIO ||
+ feature == RENDERFEATURE_POSTPROCESS ||
+ feature == RENDERFEATURE_ROTATION ||
+ feature == RENDERFEATURE_NONLINSTRETCH)
+ return true;
+
+ return false;
+}
+
+bool CRendererVDPAU::Supports(ESCALINGMETHOD method) const
+{
+ if (m_isYuv)
+ return CLinuxRendererGL::Supports(method);
+
+ if (method == VS_SCALINGMETHOD_NEAREST)
+ return true;
+
+ if (method == VS_SCALINGMETHOD_LINEAR ||
+ method == VS_SCALINGMETHOD_AUTO)
+ return true;
+
+ if(method == VS_SCALINGMETHOD_CUBIC_B_SPLINE
+ || method == VS_SCALINGMETHOD_CUBIC_MITCHELL
+ || method == VS_SCALINGMETHOD_CUBIC_CATMULL
+ || method == VS_SCALINGMETHOD_CUBIC_0_075
+ || method == VS_SCALINGMETHOD_CUBIC_0_1
+ || method == VS_SCALINGMETHOD_LANCZOS2
+ || method == VS_SCALINGMETHOD_SPLINE36_FAST
+ || method == VS_SCALINGMETHOD_LANCZOS3_FAST
+ || method == VS_SCALINGMETHOD_SPLINE36
+ || method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ // if scaling is below level, avoid hq scaling
+ float scaleX = fabs(((float)m_sourceWidth - m_destRect.Width())/m_sourceWidth)*100;
+ float scaleY = fabs(((float)m_sourceHeight - m_destRect.Height())/m_sourceHeight)*100;
+ int minScale = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("videoplayer.hqscalers");
+ if (scaleX < minScale && scaleY < minScale)
+ return false;
+ }
+
+ return false;
+}
+
+EShaderFormat CRendererVDPAU::GetShaderFormat()
+{
+ EShaderFormat ret = SHADER_NONE;
+
+ if (m_isYuv)
+ ret = SHADER_NV12;
+
+ return ret;
+}
+
+bool CRendererVDPAU::LoadShadersHook()
+{
+ if (!m_isYuv)
+ {
+ CLog::Log(LOGINFO, "GL: Using VDPAU render method");
+ m_renderMethod = RENDER_CUSTOM;
+ m_fullRange = false;
+ return true;
+ }
+ return false;
+}
+
+bool CRendererVDPAU::RenderHook(int idx)
+{
+ UpdateVideoFilter();
+
+ if (m_isYuv)
+ {
+ switch(m_renderQuality)
+ {
+ case RQ_LOW:
+ case RQ_SINGLEPASS:
+ if (m_currentField == FIELD_FULL)
+ RenderProgressiveWeave(idx, m_currentField);
+ else
+ RenderSinglePass(idx, m_currentField);
+ VerifyGLState();
+ break;
+
+ case RQ_MULTIPASS:
+ if (m_currentField == FIELD_FULL)
+ RenderProgressiveWeave(idx, m_currentField);
+ else
+ {
+ RenderToFBO(idx, m_currentField);
+ RenderFromFBO();
+ }
+ VerifyGLState();
+ break;
+ }
+ }
+ else
+ {
+ RenderRGB(idx, m_currentField);
+ }
+
+ return true;
+}
+
+void CRendererVDPAU::AfterRenderHook(int idx)
+{
+ if (glIsSync(m_fences[idx]))
+ {
+ glDeleteSync(m_fences[idx]);
+ m_fences[idx] = None;
+ }
+ m_fences[idx] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+}
+
+bool CRendererVDPAU::CreateTexture(int index)
+{
+ if (!m_isYuv)
+ return CreateVDPAUTexture(index);
+ else
+ return CreateVDPAUTexture420(index);
+}
+
+void CRendererVDPAU::DeleteTexture(int index)
+{
+ ReleaseBuffer(index);
+
+ if (!m_isYuv)
+ DeleteVDPAUTexture(index);
+ else
+ DeleteVDPAUTexture420(index);
+}
+
+bool CRendererVDPAU::UploadTexture(int index)
+{
+ if (!m_isYuv)
+ return UploadVDPAUTexture(index);
+ else
+ return UploadVDPAUTexture420(index);
+}
+
+bool CRendererVDPAU::CreateVDPAUTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane &plane = buf.fields[FIELD_FULL][0];
+
+ DeleteVDPAUTexture(index);
+
+ memset(&im, 0, sizeof(im));
+ plane = {};
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+
+ plane.texwidth = im.width;
+ plane.texheight = im.height;
+
+ plane.pixpertex_x = 1;
+ plane.pixpertex_y = 1;
+
+ plane.id = 1;
+ return true;
+}
+
+void CRendererVDPAU::DeleteVDPAUTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane &plane = buf.fields[FIELD_FULL][0];
+
+ plane.id = 0;
+}
+
+bool CRendererVDPAU::UploadVDPAUTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ VDPAU::CVdpauRenderPicture *pic = dynamic_cast<VDPAU::CVdpauRenderPicture*>(buf.videoBuffer);
+
+ CYuvPlane &plane = buf.fields[FIELD_FULL][0];
+
+ if (!pic)
+ {
+ return false;
+ }
+
+ if (!m_vdpauTextures[index].Map(pic))
+ return false;
+
+ // in stereoscopic mode sourceRect may only
+ // be a part of the source video surface
+ plane.rect = m_sourceRect;
+
+ // clip rect
+ if (pic->crop.x1 > plane.rect.x1)
+ plane.rect.x1 = pic->crop.x1;
+ if (pic->crop.x2 < plane.rect.x2)
+ plane.rect.x2 = pic->crop.x2;
+ if (pic->crop.y1 > plane.rect.y1)
+ plane.rect.y1 = pic->crop.y1;
+ if (pic->crop.y2 < plane.rect.y2)
+ plane.rect.y2 = pic->crop.y2;
+
+ plane.texheight = m_vdpauTextures[index].m_texHeight;
+ plane.texwidth = m_vdpauTextures[index].m_texWidth;
+
+ if (m_textureTarget == GL_TEXTURE_2D)
+ {
+ plane.rect.y1 /= plane.texheight;
+ plane.rect.y2 /= plane.texheight;
+ plane.rect.x1 /= plane.texwidth;
+ plane.rect.x2 /= plane.texwidth;
+ }
+
+ // set texture
+ plane.id = m_vdpauTextures[index].m_texture;
+
+ return true;
+}
+
+bool CRendererVDPAU::CreateVDPAUTexture420(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+ GLuint *pbo = buf.pbo;
+
+ DeleteVDPAUTexture420(index);
+
+ memset(&im, 0, sizeof(im));
+ memset(&planes, 0, sizeof(CYuvPlane[YuvImage::MAX_PLANES]));
+
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+
+ im.plane[0] = nullptr;
+ im.plane[1] = nullptr;
+ im.plane[2] = nullptr;
+
+ for(int p=0; p<3; p++)
+ {
+ pbo[p] = None;
+ }
+
+ planes[0].id = 1;
+
+ return true;
+}
+
+void CRendererVDPAU::DeleteVDPAUTexture420(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+
+ buf.fields[0][0].id = 0;
+ buf.fields[1][0].id = 0;
+ buf.fields[1][1].id = 0;
+ buf.fields[2][0].id = 0;
+ buf.fields[2][1].id = 0;
+}
+
+bool CRendererVDPAU::UploadVDPAUTexture420(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+
+ VDPAU::CVdpauRenderPicture *pic = dynamic_cast<VDPAU::CVdpauRenderPicture*>(buf.videoBuffer);
+
+ if (!pic)
+ {
+ return false;
+ }
+
+ if (!m_vdpauTextures[index].Map(pic))
+ return false;
+
+ im.height = m_vdpauTextures[index].m_texHeight;
+ im.width = m_vdpauTextures[index].m_texWidth;
+
+ // YUV
+ for (int f = FIELD_TOP; f<=FIELD_BOT ; f++)
+ {
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[f];
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height >> 1;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+ }
+ // crop
+// m_sourceRect.x1 += vdpau->crop.x1;
+// m_sourceRect.x2 -= vdpau->crop.x2;
+// m_sourceRect.y1 += vdpau->crop.y1;
+// m_sourceRect.y2 -= vdpau->crop.y2;
+
+ // set textures
+ buf.fields[1][0].id = m_vdpauTextures[index].m_textureTopY;
+ buf.fields[1][1].id = m_vdpauTextures[index].m_textureTopUV;
+ buf.fields[1][2].id = m_vdpauTextures[index].m_textureTopUV;
+ buf.fields[2][0].id = m_vdpauTextures[index].m_textureBotY;
+ buf.fields[2][1].id = m_vdpauTextures[index].m_textureBotUV;
+ buf.fields[2][2].id = m_vdpauTextures[index].m_textureBotUV;
+
+ for (int f = FIELD_TOP; f <= FIELD_BOT; f++)
+ {
+ for (int p=0; p<2; p++)
+ {
+ glBindTexture(m_textureTarget, buf.fields[f][p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget,0);
+ VerifyGLState();
+ }
+ }
+ CalculateTextureSourceRects(index, 3);
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.h
new file mode 100644
index 0000000..71cac3d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "VdpauGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+
+class CRendererVDPAU : public CLinuxRendererGL
+{
+public:
+ CRendererVDPAU();
+ ~CRendererVDPAU() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override;
+
+ // Player functions
+ void ReleaseBuffer(int idx) override;
+ bool ConfigChanged(const VideoPicture &picture) override;
+ bool NeedBuffer(int idx) override;
+ bool Flush(bool saveBuffers) override;
+
+ // Feature support
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+protected:
+ bool LoadShadersHook() override;
+ bool RenderHook(int idx) override;
+ void AfterRenderHook(int idx) override;
+
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+
+ bool CreateVDPAUTexture(int index);
+ void DeleteVDPAUTexture(int index);
+ bool UploadVDPAUTexture(int index);
+
+ bool CreateVDPAUTexture420(int index);
+ void DeleteVDPAUTexture420(int index);
+ bool UploadVDPAUTexture420(int index);
+
+ EShaderFormat GetShaderFormat() override;
+
+ bool CanSaveBuffers() override { return false; }
+
+ bool m_isYuv = false;
+
+ VDPAU::CInteropState m_interopState;
+ VDPAU::CVdpauTexture m_vdpauTextures[NUM_BUFFERS];
+ GLsync m_fences[NUM_BUFFERS];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp
new file mode 100644
index 0000000..0069425
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererVTBGL.h"
+
+#include "../RenderFactory.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+#if defined(HAS_SDL)
+#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "windowing/osx/WinSystemOSX.h"
+#endif
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+#include <CoreVideo/CoreVideo.h>
+#include <OpenGL/CGLIOSurface.h>
+
+CBaseRenderer* CRendererVTB::Create(CVideoBuffer *buffer)
+{
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buffer);
+ if (vb)
+ return new CRendererVTB();
+
+ return nullptr;
+}
+
+bool CRendererVTB::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("vtbgl", CRendererVTB::Create);
+ return true;
+}
+
+CRendererVTB::~CRendererVTB()
+{
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ ReleaseBuffer(i);
+ DeleteTexture(i);
+ }
+}
+
+void CRendererVTB::ReleaseBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ if (buf.videoBuffer)
+ {
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buf.videoBuffer);
+ if (vb)
+ {
+ if (vb->m_fence && glIsFenceAPPLE(vb->m_fence))
+ {
+ glDeleteFencesAPPLE(1, &vb->m_fence);
+ vb->m_fence = 0;
+ }
+ }
+ vb->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+EShaderFormat CRendererVTB::GetShaderFormat()
+{
+ return SHADER_NV12;
+}
+
+bool CRendererVTB::LoadShadersHook()
+{
+ CLog::Log(LOGINFO, "GL: Using CVBREF render method");
+ m_textureTarget = GL_TEXTURE_RECTANGLE;
+ return false;
+}
+
+bool CRendererVTB::CreateTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+
+ ReleaseBuffer(index);
+ DeleteTexture(index);
+
+ memset(&im , 0, sizeof(im));
+ memset(&planes, 0, sizeof(CYuvPlane[YuvImage::MAX_PLANES]));
+
+ im.bpp = 1;
+ im.width = m_sourceWidth;
+ im.height = m_sourceHeight;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ glGenTextures(1, &planes[0].id);
+ glGenTextures(1, &planes[1].id);
+ planes[2].id = planes[1].id;
+
+ return true;
+}
+
+void CRendererVTB::DeleteTexture(int index)
+{
+ CPictureBuffer& buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+ buf.loaded = false;
+
+ if (planes[0].id && glIsTexture(planes[0].id))
+ {
+ glDeleteTextures(1, &planes[0].id);
+ }
+ if (planes[1].id && glIsTexture(planes[1].id))
+ {
+ glDeleteTextures(1, &planes[1].id);
+ }
+ planes[0].id = 0;
+ planes[1].id = 0;
+ planes[2].id = 0;
+}
+
+bool CRendererVTB::UploadTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[0];
+
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buf.videoBuffer);
+ if (!vb)
+ {
+ return false;
+ }
+
+ CVImageBufferRef cvBufferRef = vb->GetPB();
+
+ // It is the fastest way to render a CVPixelBuffer backed
+ // with an IOSurface as there is no CPU -> GPU upload.
+ CWinSystemOSX* winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ CGLContextObj cgl_ctx = (CGLContextObj)winSystem->GetCGLContextObj();
+ IOSurfaceRef surface = CVPixelBufferGetIOSurface(cvBufferRef);
+ OSType format_type = IOSurfaceGetPixelFormat(surface);
+
+ if (format_type != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
+ {
+ return false;
+ }
+
+ GLsizei surfplanes = IOSurfaceGetPlaneCount(surface);
+
+ if (surfplanes != 2)
+ {
+ return false;
+ }
+
+ GLsizei widthY = IOSurfaceGetWidthOfPlane(surface, 0);
+ GLsizei widthUV = IOSurfaceGetWidthOfPlane(surface, 1);
+ GLsizei heightY = IOSurfaceGetHeightOfPlane(surface, 0);
+ GLsizei heightUV = IOSurfaceGetHeightOfPlane(surface, 1);
+
+ glBindTexture(m_textureTarget, planes[0].id);
+
+ CGLTexImageIOSurface2D(cgl_ctx, m_textureTarget, GL_RED,
+ widthY, heightY, GL_RED, GL_UNSIGNED_BYTE, surface, 0);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, planes[1].id);
+
+ CGLTexImageIOSurface2D(cgl_ctx, m_textureTarget, GL_RG,
+ widthUV, heightUV, GL_RG, GL_UNSIGNED_BYTE, surface, 1);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+
+ CalculateTextureSourceRects(index, 3);
+
+ return true;
+}
+
+void CRendererVTB::AfterRenderHook(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buf.videoBuffer);
+ if (!vb)
+ {
+ return;
+ }
+
+ if (vb->m_fence && glIsFenceAPPLE(vb->m_fence))
+ {
+ glDeleteFencesAPPLE(1, &vb->m_fence);
+ }
+ glGenFencesAPPLE(1, &vb->m_fence);
+ glSetFenceAPPLE(vb->m_fence);
+}
+
+bool CRendererVTB::NeedBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buf.videoBuffer);
+ if (!vb)
+ {
+ return false;
+ }
+
+ if (vb->m_fence && glIsFenceAPPLE(vb->m_fence))
+ {
+ if (!glTestFenceAPPLE(vb->m_fence))
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h
new file mode 100644
index 0000000..ba2cdb0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+
+
+class CRendererVTB : public CLinuxRendererGL
+{
+public:
+ CRendererVTB() = default;
+ ~CRendererVTB() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ // Player functions
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+
+protected:
+ bool LoadShadersHook() override;
+ void AfterRenderHook(int idx) override;
+ EShaderFormat GetShaderFormat() override;
+
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+};
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.cpp
new file mode 100644
index 0000000..4515a9a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererVTBGLES.h"
+
+#include "../RenderFactory.h"
+#include "ServiceBroker.h"
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "settings/MediaSettings.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+#if defined(TARGET_DARWIN_IOS)
+#include "windowing/ios/WinSystemIOS.h"
+#define WIN_SYSTEM_CLASS CWinSystemIOS
+#elif defined(TARGET_DARWIN_TVOS)
+#include "windowing/tvos/WinSystemTVOS.h"
+#define WIN_SYSTEM_CLASS CWinSystemTVOS
+#endif
+
+#include <CoreVideo/CVBuffer.h>
+#include <CoreVideo/CVPixelBuffer.h>
+#include <OpenGLES/ES2/glext.h>
+
+CBaseRenderer* CRendererVTB::Create(CVideoBuffer *buffer)
+{
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buffer);
+ if (vb)
+ return new CRendererVTB();
+
+ return nullptr;
+}
+
+bool CRendererVTB::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("vtbgles", CRendererVTB::Create);
+ return true;
+}
+
+CRendererVTB::CRendererVTB()
+{
+ auto winSystem = dynamic_cast<WIN_SYSTEM_CLASS*>(CServiceBroker::GetWinSystem());
+ m_glContext = winSystem->GetEAGLContextObj();
+ CVReturn ret = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
+ NULL,
+ m_glContext,
+ NULL,
+ &m_textureCache);
+ if (ret != kCVReturnSuccess)
+ {
+ CLog::Log(LOGERROR, "CRendererVTB::CRendererVTB - Error creating texture cache (err: {})", ret);
+ }
+
+ for (auto &buf : m_vtbBuffers)
+ {
+ buf.m_textureY = nullptr;
+ buf.m_textureUV = nullptr;
+ buf.m_videoBuffer = nullptr;
+ buf.m_fence = nullptr;
+ }
+}
+
+CRendererVTB::~CRendererVTB()
+{
+ if (m_textureCache)
+ CFRelease(m_textureCache);
+
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ DeleteTexture(i);
+ }
+}
+
+void CRendererVTB::ReleaseBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ CRenderBuffer &renderBuf = m_vtbBuffers[idx];
+ if (buf.videoBuffer)
+ {
+ if (renderBuf.m_fence && glIsSyncAPPLE(renderBuf.m_fence))
+ {
+ glDeleteSyncAPPLE(renderBuf.m_fence);
+ renderBuf.m_fence = 0;
+ }
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+EShaderFormat CRendererVTB::GetShaderFormat()
+{
+ return SHADER_YV12;
+}
+
+bool CRendererVTB::LoadShadersHook()
+{
+ CLog::Log(LOGINFO, "GL: Using CVBREF render method");
+ m_textureTarget = GL_TEXTURE_2D;
+ m_renderMethod = RENDER_CUSTOM;
+
+ if (!m_textureCache)
+ {
+ CLog::Log(LOGINFO, "CRendererVTB::LoadShadersHook: no texture cache");
+ return false;
+ }
+
+ CVReturn ret = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
+ NULL,
+ m_glContext,
+ NULL,
+ &m_textureCache);
+ if (ret != kCVReturnSuccess)
+ return false;
+
+ return false;
+}
+
+bool CRendererVTB::CreateTexture(int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];
+
+ DeleteTexture(index);
+
+ memset(&im , 0, sizeof(im));
+ memset(&planes, 0, sizeof(CYuvPlane[YuvImage::MAX_PLANES]));
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height;
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ planes[0].id = 1;
+ return true;
+}
+
+void CRendererVTB::DeleteTexture(int index)
+{
+ CRenderBuffer &renderBuf = m_vtbBuffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[0];
+
+ if (renderBuf.m_textureY)
+ CFRelease(renderBuf.m_textureY);
+ renderBuf.m_textureY = nullptr;
+
+ if (renderBuf.m_textureUV)
+ CFRelease(renderBuf.m_textureUV);
+ renderBuf.m_textureUV = nullptr;
+
+ ReleaseBuffer(index);
+
+ planes[0].id = 0;
+ planes[1].id = 0;
+ planes[2].id = 0;
+}
+
+bool CRendererVTB::UploadTexture(int index)
+{
+ CRenderBuffer &renderBuf = m_vtbBuffers[index];
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[0];
+ YuvImage &im = m_buffers[index].image;
+
+ VTB::CVideoBufferVTB *vb = dynamic_cast<VTB::CVideoBufferVTB*>(buf.videoBuffer);
+ if (!vb)
+ {
+ return false;
+ }
+
+ CVOpenGLESTextureCacheFlush(m_textureCache, 0);
+
+ if (renderBuf.m_textureY)
+ CFRelease(renderBuf.m_textureY);
+ renderBuf.m_textureY = nullptr;
+
+ if (renderBuf.m_textureUV)
+ CFRelease(renderBuf.m_textureUV);
+ renderBuf.m_textureUV = nullptr;
+
+ CVReturn ret;
+ ret = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
+ m_textureCache,
+ vb->GetPB(), nullptr, GL_TEXTURE_2D, GL_LUMINANCE,
+ im.width, im.height, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+ 0,
+ &renderBuf.m_textureY);
+
+ if (ret != kCVReturnSuccess)
+ {
+ CLog::Log(LOGERROR, "CRendererVTB::UploadTexture - Error uploading texture Y (err: {})", ret);
+ return false;
+ }
+
+ ret = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
+ m_textureCache,
+ vb->GetPB(), nullptr, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA,
+ im.width/2, im.height/2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
+ 1,
+ &renderBuf.m_textureUV);
+
+ if (ret != kCVReturnSuccess)
+ {
+ CLog::Log(LOGERROR, "CRendererVTB::UploadTexture - Error uploading texture UV (err: {})", ret);
+ return false;
+ }
+
+ // set textures
+ planes[0].id = CVOpenGLESTextureGetName(renderBuf.m_textureY);
+ planes[1].id = CVOpenGLESTextureGetName(renderBuf.m_textureUV);
+ planes[2].id = CVOpenGLESTextureGetName(renderBuf.m_textureUV);
+
+ for (int p=0; p<2; p++)
+ {
+ glBindTexture(m_textureTarget, planes[p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(m_textureTarget, 0);
+ VerifyGLState();
+ }
+
+ CalculateTextureSourceRects(index, 3);
+ return true;
+}
+
+void CRendererVTB::AfterRenderHook(int idx)
+{
+ CRenderBuffer &renderBuf = m_vtbBuffers[idx];
+ if (renderBuf.m_fence && glIsSyncAPPLE(renderBuf.m_fence))
+ {
+ glDeleteSyncAPPLE(renderBuf.m_fence);
+ }
+ renderBuf.m_fence = glFenceSyncAPPLE(GL_SYNC_GPU_COMMANDS_COMPLETE_APPLE, 0);
+}
+
+bool CRendererVTB::NeedBuffer(int idx)
+{
+ CRenderBuffer &renderBuf = m_vtbBuffers[idx];
+ if (renderBuf.m_fence && glIsSyncAPPLE(renderBuf.m_fence))
+ {
+ int syncState = GL_UNSIGNALED_APPLE;
+ glGetSyncivAPPLE(renderBuf.m_fence, GL_SYNC_STATUS_APPLE, 1, nullptr, &syncState);
+ if (syncState != GL_SIGNALED_APPLE)
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h
new file mode 100644
index 0000000..8be4926
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+
+#include <CoreVideo/CVOpenGLESTextureCache.h>
+
+class CRendererVTB : public CLinuxRendererGLES
+{
+public:
+ CRendererVTB();
+ ~CRendererVTB() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ // Player functions
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+
+protected:
+ // hooks for hw dec renderer
+ bool LoadShadersHook() override;
+ void AfterRenderHook(int idx) override;
+ EShaderFormat GetShaderFormat() override;
+
+ // textures
+ bool UploadTexture(int index) override;
+ void DeleteTexture(int index) override;
+ bool CreateTexture(int index) override;
+
+ CVOpenGLESTextureCacheRef m_textureCache = nullptr;
+ struct CRenderBuffer
+ {
+ CVOpenGLESTextureRef m_textureY;
+ CVOpenGLESTextureRef m_textureUV;
+ CVBufferRef m_videoBuffer;
+ GLsync m_fence;
+ };
+ CRenderBuffer m_vtbBuffers[NUM_BUFFERS];
+ CVEAGLContext m_glContext;
+};
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp
new file mode 100644
index 0000000..5ee5891
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VaapiEGL.h"
+
+#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h"
+#include "utils/EGLUtils.h"
+#include "utils/log.h"
+
+#include <drm_fourcc.h>
+#include <va/va_drmcommon.h>
+
+#define HAVE_VAEXPORTSURFACHEHANDLE VA_CHECK_VERSION(1, 1, 0)
+
+using namespace VAAPI;
+
+void CVaapi1Texture::Init(InteropInfo &interop)
+{
+ m_interop = interop;
+}
+
+bool CVaapi1Texture::Map(CVaapiRenderPicture *pic)
+{
+ VAStatus status;
+
+ if (m_vaapiPic)
+ return true;
+
+ vaSyncSurface(pic->vadsp, pic->procPic.videoSurface);
+
+ status = vaDeriveImage(pic->vadsp, pic->procPic.videoSurface, &m_glSurface.vaImage);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "CVaapiTexture::{} - Error: {}({})", __FUNCTION__, vaErrorStr(status),
+ status);
+ return false;
+ }
+ memset(&m_glSurface.vBufInfo, 0, sizeof(m_glSurface.vBufInfo));
+ m_glSurface.vBufInfo.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
+ status = vaAcquireBufferHandle(pic->vadsp, m_glSurface.vaImage.buf, &m_glSurface.vBufInfo);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "CVaapiTexture::{} - Error: {}({})", __FUNCTION__, vaErrorStr(status),
+ status);
+ return false;
+ }
+
+ m_texWidth = m_glSurface.vaImage.width;
+ m_texHeight = m_glSurface.vaImage.height;
+
+ GLint attribs[23], *attrib;
+
+ switch (m_glSurface.vaImage.format.fourcc)
+ {
+ case VA_FOURCC('N','V','1','2'):
+ {
+ attrib = attribs;
+ *attrib++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *attrib++ = fourcc_code('R', '8', ' ', ' ');
+ *attrib++ = EGL_WIDTH;
+ *attrib++ = m_glSurface.vaImage.width;
+ *attrib++ = EGL_HEIGHT;
+ *attrib++ = m_glSurface.vaImage.height;
+ *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT;
+ *attrib++ = (intptr_t)m_glSurface.vBufInfo.handle;
+ *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ *attrib++ = m_glSurface.vaImage.offsets[0];
+ *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ *attrib++ = m_glSurface.vaImage.pitches[0];
+ *attrib++ = EGL_NONE;
+ m_glSurface.eglImageY = m_interop.eglCreateImageKHR(m_interop.eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (!m_glSurface.eglImageY)
+ {
+ EGLint err = eglGetError();
+ CLog::Log(LOGERROR, "failed to import VA buffer NV12 into EGL image: {}", err);
+ return false;
+ }
+
+ attrib = attribs;
+ *attrib++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *attrib++ = fourcc_code('G', 'R', '8', '8');
+ *attrib++ = EGL_WIDTH;
+ *attrib++ = (m_glSurface.vaImage.width + 1) >> 1;
+ *attrib++ = EGL_HEIGHT;
+ *attrib++ = (m_glSurface.vaImage.height + 1) >> 1;
+ *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT;
+ *attrib++ = (intptr_t)m_glSurface.vBufInfo.handle;
+ *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ *attrib++ = m_glSurface.vaImage.offsets[1];
+ *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ *attrib++ = m_glSurface.vaImage.pitches[1];
+ *attrib++ = EGL_NONE;
+ m_glSurface.eglImageVU = m_interop.eglCreateImageKHR(m_interop.eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (!m_glSurface.eglImageVU)
+ {
+ EGLint err = eglGetError();
+ CLog::Log(LOGERROR, "failed to import VA buffer NV12 into EGL image: {}", err);
+ return false;
+ }
+
+ glGenTextures(1, &m_textureY);
+ glBindTexture(m_interop.textureTarget, m_textureY);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, m_glSurface.eglImageY);
+
+ glGenTextures(1, &m_textureVU);
+ glBindTexture(m_interop.textureTarget, m_textureVU);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, m_glSurface.eglImageVU);
+
+ glBindTexture(m_interop.textureTarget, 0);
+
+ break;
+ }
+ case VA_FOURCC('P','0','1','0'):
+ {
+ attrib = attribs;
+ *attrib++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *attrib++ = fourcc_code('R', '1', '6', ' ');
+ *attrib++ = EGL_WIDTH;
+ *attrib++ = m_glSurface.vaImage.width;
+ *attrib++ = EGL_HEIGHT;
+ *attrib++ = m_glSurface.vaImage.height;
+ *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT;
+ *attrib++ = (intptr_t)m_glSurface.vBufInfo.handle;
+ *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ *attrib++ = m_glSurface.vaImage.offsets[0];
+ *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ *attrib++ = m_glSurface.vaImage.pitches[0];
+ *attrib++ = EGL_NONE;
+ m_glSurface.eglImageY = m_interop.eglCreateImageKHR(m_interop.eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (!m_glSurface.eglImageY)
+ {
+ EGLint err = eglGetError();
+ CLog::Log(LOGERROR, "failed to import VA buffer P010 into EGL image: {}", err);
+ return false;
+ }
+
+ attrib = attribs;
+ *attrib++ = EGL_LINUX_DRM_FOURCC_EXT;
+ *attrib++ = fourcc_code('G', 'R', '3', '2');
+ *attrib++ = EGL_WIDTH;
+ *attrib++ = (m_glSurface.vaImage.width + 1) >> 1;
+ *attrib++ = EGL_HEIGHT;
+ *attrib++ = (m_glSurface.vaImage.height + 1) >> 1;
+ *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT;
+ *attrib++ = (intptr_t)m_glSurface.vBufInfo.handle;
+ *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ *attrib++ = m_glSurface.vaImage.offsets[1];
+ *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ *attrib++ = m_glSurface.vaImage.pitches[1];
+ *attrib++ = EGL_NONE;
+ m_glSurface.eglImageVU = m_interop.eglCreateImageKHR(m_interop.eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (!m_glSurface.eglImageVU)
+ {
+ EGLint err = eglGetError();
+ CLog::Log(LOGERROR, "failed to import VA buffer P010 into EGL image: {}", err);
+ return false;
+ }
+
+ glGenTextures(1, &m_textureY);
+ glBindTexture(m_interop.textureTarget, m_textureY);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, m_glSurface.eglImageY);
+
+ glGenTextures(1, &m_textureVU);
+ glBindTexture(m_interop.textureTarget, m_textureVU);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, m_glSurface.eglImageVU);
+
+ glBindTexture(m_interop.textureTarget, 0);
+
+ break;
+ }
+ case VA_FOURCC('B','G','R','A'):
+ {
+ attrib = attribs;
+ *attrib++ = EGL_DRM_BUFFER_FORMAT_MESA;
+ *attrib++ = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA;
+ *attrib++ = EGL_WIDTH;
+ *attrib++ = m_glSurface.vaImage.width;
+ *attrib++ = EGL_HEIGHT;
+ *attrib++ = m_glSurface.vaImage.height;
+ *attrib++ = EGL_DRM_BUFFER_STRIDE_MESA;
+ *attrib++ = m_glSurface.vaImage.pitches[0] / 4;
+ *attrib++ = EGL_NONE;
+ m_glSurface.eglImage = m_interop.eglCreateImageKHR(m_interop.eglDisplay, EGL_NO_CONTEXT,
+ EGL_DRM_BUFFER_MESA,
+ (EGLClientBuffer)m_glSurface.vBufInfo.handle,
+ attribs);
+ if (!m_glSurface.eglImage)
+ {
+ EGLint err = eglGetError();
+ CLog::Log(LOGERROR, "failed to import VA buffer BGRA into EGL image: {}", err);
+ return false;
+ }
+
+ glGenTextures(1, &m_texture);
+ glBindTexture(m_interop.textureTarget, m_texture);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_interop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, m_glSurface.eglImage);
+
+ glBindTexture(m_interop.textureTarget, 0);
+
+ break;
+ }
+ default:
+ return false;
+ }
+
+ m_vaapiPic = pic;
+ m_vaapiPic->Acquire();
+ return true;
+}
+
+void CVaapi1Texture::Unmap()
+{
+ if (!m_vaapiPic)
+ return;
+
+ if (m_glSurface.vaImage.image_id == VA_INVALID_ID)
+ return;
+
+ m_interop.eglDestroyImageKHR(m_interop.eglDisplay, m_glSurface.eglImageY);
+ m_interop.eglDestroyImageKHR(m_interop.eglDisplay, m_glSurface.eglImageVU);
+
+ VAStatus status;
+ status = vaReleaseBufferHandle(m_vaapiPic->vadsp, m_glSurface.vaImage.buf);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - Error: {}({})", __FUNCTION__, vaErrorStr(status), status);
+ }
+
+ status = vaDestroyImage(m_vaapiPic->vadsp, m_glSurface.vaImage.image_id);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "VAAPI::{} - Error: {}({})", __FUNCTION__, vaErrorStr(status), status);
+ }
+
+ m_glSurface.vaImage.image_id = VA_INVALID_ID;
+
+ glDeleteTextures(1, &m_textureY);
+ glDeleteTextures(1, &m_textureVU);
+
+ m_vaapiPic->Release();
+ m_vaapiPic = nullptr;
+}
+
+GLuint CVaapi1Texture::GetTextureY()
+{
+ return m_textureY;
+}
+
+GLuint CVaapi1Texture::GetTextureVU()
+{
+ return m_textureVU;
+}
+
+CSizeInt CVaapi1Texture::GetTextureSize()
+{
+ return {m_texWidth, m_texHeight};
+}
+
+void CVaapi1Texture::TestInterop(VADisplay vaDpy, EGLDisplay eglDisplay, bool &general, bool &deepColor)
+{
+ general = false;
+ deepColor = false;
+
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ if (!eglCreateImageKHR || !eglDestroyImageKHR)
+ {
+ return;
+ }
+
+ int width = 1920;
+ int height = 1080;
+
+ // create surfaces
+ VASurfaceID surface;
+ VAStatus status;
+ VAImage image;
+ VABufferInfo bufferInfo;
+
+ if (vaCreateSurfaces(vaDpy, VA_RT_FORMAT_YUV420,
+ width, height,
+ &surface, 1, NULL, 0) != VA_STATUS_SUCCESS)
+ {
+ return;
+ }
+
+ // check interop
+
+ status = vaDeriveImage(vaDpy, surface, &image);
+ if (status == VA_STATUS_SUCCESS)
+ {
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
+ status = vaAcquireBufferHandle(vaDpy, image.buf, &bufferInfo);
+ if (status == VA_STATUS_SUCCESS)
+ {
+ EGLImageKHR eglImage;
+ EGLint attribs[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8,
+ EGL_WIDTH, image.width,
+ EGL_HEIGHT, image.height,
+ EGL_DMA_BUF_PLANE0_FD_EXT, static_cast<EGLint> (bufferInfo.handle),
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint> (image.offsets[0]),
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint> (image.pitches[0]),
+ EGL_NONE
+ };
+
+ eglImage = eglCreateImageKHR(eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (eglImage)
+ {
+ eglDestroyImageKHR(eglDisplay, eglImage);
+ general = true;
+ }
+ }
+ vaDestroyImage(vaDpy, image.image_id);
+ }
+ vaDestroySurfaces(vaDpy, &surface, 1);
+
+ if (general)
+ {
+ deepColor = TestInteropDeepColor(vaDpy, eglDisplay);
+ }
+}
+
+bool CVaapi1Texture::TestInteropDeepColor(VADisplay vaDpy, EGLDisplay eglDisplay)
+{
+ bool ret = false;
+
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ if (!eglCreateImageKHR || !eglDestroyImageKHR)
+ {
+ return false;
+ }
+
+ int width = 1920;
+ int height = 1080;
+
+ // create surfaces
+ VASurfaceID surface;
+ VAStatus status;
+ VAImage image;
+ VABufferInfo bufferInfo;
+
+ VASurfaceAttrib attribs = {};
+ attribs.flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attribs.type = VASurfaceAttribPixelFormat;
+ attribs.value.type = VAGenericValueTypeInteger;
+ attribs.value.value.i = VA_FOURCC_P010;
+
+ if (vaCreateSurfaces(vaDpy, VA_RT_FORMAT_YUV420_10BPP,
+ width, height,
+ &surface, 1, &attribs, 1) != VA_STATUS_SUCCESS)
+ {
+ return false;
+ }
+
+ // check interop
+ status = vaDeriveImage(vaDpy, surface, &image);
+ if (status == VA_STATUS_SUCCESS)
+ {
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
+ status = vaAcquireBufferHandle(vaDpy, image.buf, &bufferInfo);
+ if (status == VA_STATUS_SUCCESS)
+ {
+ EGLImageKHR eglImage;
+ EGLint attribs[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_GR1616,
+ EGL_WIDTH, (image.width + 1) >> 1,
+ EGL_HEIGHT, (image.height + 1) >> 1,
+ EGL_DMA_BUF_PLANE0_FD_EXT, static_cast<EGLint> (bufferInfo.handle),
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint> (image.offsets[1]),
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint> (image.pitches[1]),
+ EGL_NONE
+ };
+
+ eglImage = eglCreateImageKHR(eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL,
+ attribs);
+ if (eglImage)
+ {
+ eglDestroyImageKHR(eglDisplay, eglImage);
+ ret = true;
+ }
+
+ }
+ vaDestroyImage(vaDpy, image.image_id);
+ }
+
+ vaDestroySurfaces(vaDpy, &surface, 1);
+
+ return ret;
+}
+
+void CVaapi2Texture::Init(InteropInfo& interop)
+{
+ m_interop = interop;
+ m_hasPlaneModifiers = CEGLUtils::HasExtension(m_interop.eglDisplay, "EGL_EXT_image_dma_buf_import_modifiers");
+}
+
+bool CVaapi2Texture::Map(CVaapiRenderPicture* pic)
+{
+#if HAVE_VAEXPORTSURFACHEHANDLE
+ if (m_vaapiPic)
+ return true;
+
+ m_vaapiPic = pic;
+ m_vaapiPic->Acquire();
+
+ VAStatus status;
+
+ VADRMPRIMESurfaceDescriptor surface;
+
+ status = vaExportSurfaceHandle(pic->vadsp, pic->procPic.videoSurface,
+ VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+ VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
+ &surface);
+
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGWARNING, "CVaapi2Texture::Map: vaExportSurfaceHandle failed - Error: {} ({})",
+ vaErrorStr(status), status);
+ return false;
+ }
+
+ // Remember fds to close them later
+ if (surface.num_objects > m_drmFDs.size())
+ throw std::logic_error("Too many fds returned by vaExportSurfaceHandle");
+
+ for (uint32_t object = 0; object < surface.num_objects; object++)
+ {
+ m_drmFDs[object].attach(surface.objects[object].fd);
+ }
+
+ status = vaSyncSurface(pic->vadsp, pic->procPic.videoSurface);
+ if (status != VA_STATUS_SUCCESS)
+ {
+ CLog::Log(LOGERROR, "CVaapi2Texture::Map: vaSyncSurface - Error: {} ({})", vaErrorStr(status),
+ status);
+ return false;
+ }
+
+ m_textureSize.Set(pic->DVDPic.iWidth, pic->DVDPic.iHeight);
+
+ for (uint32_t layerNo = 0; layerNo < surface.num_layers; layerNo++)
+ {
+ int plane = 0;
+ auto const& layer = surface.layers[layerNo];
+ if (layer.num_planes != 1)
+ {
+ CLog::Log(LOGDEBUG,
+ "CVaapi2Texture::Map: DRM-exported layer has {} planes - only 1 supported",
+ layer.num_planes);
+ return false;
+ }
+ auto const& object = surface.objects[layer.object_index[plane]];
+
+ MappedTexture* texture{};
+ EGLint width{m_textureSize.Width()};
+ EGLint height{m_textureSize.Height()};
+
+ switch (surface.num_layers)
+ {
+ case 2:
+ switch (layerNo)
+ {
+ case 0:
+ texture = &m_y;
+ break;
+ case 1:
+ texture = &m_vu;
+ if (surface.fourcc == VA_FOURCC_NV12 || surface.fourcc == VA_FOURCC_P010 || surface.fourcc == VA_FOURCC_P016)
+ {
+ // Adjust w/h for 4:2:0 subsampling on UV plane
+ width = (width + 1) >> 1;
+ height = (height + 1) >> 1;
+ }
+ break;
+ default:
+ throw std::logic_error("Impossible layer number");
+ }
+ break;
+ default:
+ CLog::Log(LOGDEBUG,
+ "CVaapi2Texture::Map: DRM-exported surface {} layers - only 2 supported",
+ surface.num_layers);
+ return false;
+ }
+
+ CEGLAttributes<8> attribs; // 6 static + 2 modifiers
+ attribs.Add({{EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(layer.drm_format)},
+ {EGL_WIDTH, width},
+ {EGL_HEIGHT, height},
+ {EGL_DMA_BUF_PLANE0_FD_EXT, object.fd},
+ {EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(layer.offset[plane])},
+ {EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(layer.pitch[plane])}});
+
+ if (m_hasPlaneModifiers)
+ {
+ attribs.Add({{EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, static_cast<EGLint>(object.drm_format_modifier)},
+ {EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, static_cast<EGLint>(object.drm_format_modifier >> 32)}});
+ }
+
+ texture->eglImage = m_interop.eglCreateImageKHR(m_interop.eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr,
+ attribs.Get());
+ if (!texture->eglImage)
+ {
+ CEGLUtils::Log(LOGERROR, "Failed to import VA DRM surface into EGL image");
+ return false;
+ }
+
+ glGenTextures(1, &texture->glTexture);
+ glBindTexture(m_interop.textureTarget, texture->glTexture);
+ m_interop.glEGLImageTargetTexture2DOES(m_interop.textureTarget, texture->eglImage);
+ glBindTexture(m_interop.textureTarget, 0);
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+void CVaapi2Texture::Unmap()
+{
+ if (!m_vaapiPic)
+ return;
+
+ for (auto texture : {&m_y, &m_vu})
+ {
+ if (texture->eglImage != EGL_NO_IMAGE_KHR)
+ {
+ m_interop.eglDestroyImageKHR(m_interop.eglDisplay, texture->eglImage);
+ texture->eglImage = EGL_NO_IMAGE_KHR;
+ glDeleteTextures(1, &texture->glTexture);
+ }
+ }
+
+ for (auto& fd : m_drmFDs)
+ {
+ fd.reset();
+ }
+
+ m_vaapiPic->Release();
+ m_vaapiPic = nullptr;
+}
+
+GLuint CVaapi2Texture::GetTextureY()
+{
+ return m_y.glTexture;
+}
+
+GLuint CVaapi2Texture::GetTextureVU()
+{
+ return m_vu.glTexture;
+}
+
+CSizeInt CVaapi2Texture::GetTextureSize()
+{
+ return m_textureSize;
+}
+
+bool CVaapi2Texture::TestEsh(VADisplay vaDpy, EGLDisplay eglDisplay, std::uint32_t rtFormat, std::int32_t pixelFormat)
+{
+#if HAVE_VAEXPORTSURFACHEHANDLE
+ int width = 1920;
+ int height = 1080;
+
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
+ if (!eglCreateImageKHR || !eglDestroyImageKHR)
+ {
+ return false;
+ }
+
+ // create surfaces
+ VASurfaceID surface;
+ VAStatus status;
+
+ VASurfaceAttrib attribs = {};
+ attribs.flags = VA_SURFACE_ATTRIB_SETTABLE;
+ attribs.type = VASurfaceAttribPixelFormat;
+ attribs.value.type = VAGenericValueTypeInteger;
+ attribs.value.value.i = pixelFormat;
+
+ if (vaCreateSurfaces(vaDpy, rtFormat,
+ width, height,
+ &surface, 1, &attribs, 1) != VA_STATUS_SUCCESS)
+ {
+ return false;
+ }
+
+ // check interop
+ VADRMPRIMESurfaceDescriptor drmPrimeSurface;
+ status = vaExportSurfaceHandle(vaDpy, surface,
+ VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+ VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
+ &drmPrimeSurface);
+
+ bool result = false;
+
+ if (status == VA_STATUS_SUCCESS)
+ {
+ auto const& layer = drmPrimeSurface.layers[0];
+ auto const& object = drmPrimeSurface.objects[layer.object_index[0]];
+ EGLint attribs[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(drmPrimeSurface.layers[0].drm_format),
+ EGL_WIDTH, width,
+ EGL_HEIGHT, height,
+ EGL_DMA_BUF_PLANE0_FD_EXT, static_cast<EGLint>(object.fd),
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(layer.offset[0]),
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(layer.pitch[0]),
+ EGL_NONE};
+
+ EGLImageKHR eglImage = eglCreateImageKHR(eglDisplay,
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr,
+ attribs);
+ if (eglImage)
+ {
+ eglDestroyImageKHR(eglDisplay, eglImage);
+ result = true;
+ }
+
+ for (uint32_t object = 0; object < drmPrimeSurface.num_objects; object++)
+ {
+ close(drmPrimeSurface.objects[object].fd);
+ }
+ }
+
+ vaDestroySurfaces(vaDpy, &surface, 1);
+
+ return result;
+#else
+ return false;
+#endif
+}
+
+void CVaapi2Texture::TestInterop(VADisplay vaDpy, EGLDisplay eglDisplay, bool& general, bool& deepColor)
+{
+ general = false;
+ deepColor = false;
+
+ general = TestInteropGeneral(vaDpy, eglDisplay);
+ if (general)
+ {
+ deepColor = TestEsh(vaDpy, eglDisplay, VA_RT_FORMAT_YUV420_10BPP, VA_FOURCC_P010);
+ }
+}
+
+bool CVaapi2Texture::TestInteropGeneral(VADisplay vaDpy, EGLDisplay eglDisplay)
+{
+ return TestEsh(vaDpy, eglDisplay, VA_RT_FORMAT_YUV420, VA_FOURCC_NV12);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.h
new file mode 100644
index 0000000..a8102cc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+#if defined(HAS_GL)
+// always define GL_GLEXT_PROTOTYPES before include gl headers
+#if !defined(GL_GLEXT_PROTOTYPES)
+#define GL_GLEXT_PROTOTYPES
+#endif
+#include <GL/gl.h>
+#elif defined(HAS_GLES)
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#endif
+
+#include "system_egl.h"
+#include "utils/Geometry.h"
+
+#include "platform/posix/utils/FileHandle.h"
+
+#include <EGL/eglext.h>
+#include <va/va.h>
+
+namespace VAAPI
+{
+
+class CVaapiRenderPicture;
+
+struct InteropInfo
+{
+ EGLDisplay eglDisplay = nullptr;
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+ GLenum textureTarget;
+};
+
+class CVaapiTexture
+{
+public:
+ CVaapiTexture() = default;
+ virtual ~CVaapiTexture() = default;
+
+ virtual void Init(InteropInfo &interop) = 0;
+ virtual bool Map(CVaapiRenderPicture *pic) = 0;
+ virtual void Unmap() = 0;
+
+ virtual GLuint GetTextureY() = 0;
+ virtual GLuint GetTextureVU() = 0;
+ virtual CSizeInt GetTextureSize() = 0;
+};
+
+class CVaapi1Texture : public CVaapiTexture
+{
+public:
+ CVaapi1Texture() = default;
+
+ bool Map(CVaapiRenderPicture *pic) override;
+ void Unmap() override;
+ void Init(InteropInfo &interop) override;
+
+ GLuint GetTextureY() override;
+ GLuint GetTextureVU() override;
+ CSizeInt GetTextureSize() override;
+
+ static void TestInterop(VADisplay vaDpy, EGLDisplay eglDisplay, bool &general, bool &deepColor);
+
+ GLuint m_texture = 0;
+ GLuint m_textureY = 0;
+ GLuint m_textureVU = 0;
+ int m_texWidth = 0;
+ int m_texHeight = 0;
+
+protected:
+ static bool TestInteropDeepColor(VADisplay vaDpy, EGLDisplay eglDisplay);
+
+ InteropInfo m_interop;
+ CVaapiRenderPicture *m_vaapiPic = nullptr;
+ struct GLSurface
+ {
+ VAImage vaImage;
+ VABufferInfo vBufInfo;
+ EGLImageKHR eglImage;
+ EGLImageKHR eglImageY, eglImageVU;
+ } m_glSurface;
+};
+
+class CVaapi2Texture : public CVaapiTexture
+{
+public:
+ bool Map(CVaapiRenderPicture *pic) override;
+ void Unmap() override;
+ void Init(InteropInfo &interop) override;
+
+ GLuint GetTextureY() override;
+ GLuint GetTextureVU() override;
+ CSizeInt GetTextureSize() override;
+
+ static void TestInterop(VADisplay vaDpy, EGLDisplay eglDisplay, bool &general, bool &deepColor);
+ static bool TestInteropGeneral(VADisplay vaDpy, EGLDisplay eglDisplay);
+
+private:
+ static bool TestEsh(VADisplay vaDpy, EGLDisplay eglDisplay, std::uint32_t rtFormat, std::int32_t pixelFormat);
+
+ struct MappedTexture
+ {
+ EGLImageKHR eglImage{EGL_NO_IMAGE_KHR};
+ GLuint glTexture{};
+ };
+
+ InteropInfo m_interop;
+ CVaapiRenderPicture* m_vaapiPic{};
+ bool m_hasPlaneModifiers{false};
+ std::array<KODI::UTILS::POSIX::CFileHandle, 4> m_drmFDs;
+ MappedTexture m_y, m_vu;
+ CSizeInt m_textureSize;
+};
+
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.cpp
new file mode 100644
index 0000000..2037acb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VdpauGL.h"
+
+#include "cores/VideoPlayer/DVDCodecs/Video/VDPAU.h"
+#include "utils/log.h"
+
+#include <GL/glx.h>
+
+using namespace VDPAU;
+
+//-----------------------------------------------------------------------------
+// interop state
+//-----------------------------------------------------------------------------
+
+bool CInteropState::Init(void *device, void *procFunc, int64_t ident)
+{
+ m_device = device;
+ m_procFunc = procFunc;
+ m_ident = ident;
+
+ m_interop.glVDPAUInitNV = (PFNGLVDPAUINITNVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUInitNV");
+ m_interop.glVDPAUFiniNV = (PFNGLVDPAUFININVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUFiniNV");
+ m_interop.glVDPAURegisterOutputSurfaceNV = (PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC)glXGetProcAddress((const GLubyte *) "glVDPAURegisterOutputSurfaceNV");
+ m_interop.glVDPAURegisterVideoSurfaceNV = (PFNGLVDPAUREGISTERVIDEOSURFACENVPROC)glXGetProcAddress((const GLubyte *) "glVDPAURegisterVideoSurfaceNV");
+ m_interop.glVDPAUIsSurfaceNV = (PFNGLVDPAUISSURFACENVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUIsSurfaceNV");
+ m_interop.glVDPAUUnregisterSurfaceNV = (PFNGLVDPAUUNREGISTERSURFACENVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUUnregisterSurfaceNV");
+ m_interop.glVDPAUSurfaceAccessNV = (PFNGLVDPAUSURFACEACCESSNVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUSurfaceAccessNV");
+ m_interop.glVDPAUMapSurfacesNV = (PFNGLVDPAUMAPSURFACESNVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUMapSurfacesNV");
+ m_interop.glVDPAUUnmapSurfacesNV = (PFNGLVDPAUUNMAPSURFACESNVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUUnmapSurfacesNV");
+ m_interop.glVDPAUGetSurfaceivNV = (PFNGLVDPAUGETSURFACEIVNVPROC)glXGetProcAddress((const GLubyte *) "glVDPAUGetSurfaceivNV");
+
+ while (glGetError() != GL_NO_ERROR);
+ m_interop.glVDPAUInitNV(m_device, m_procFunc);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CInteropState::Init - GLInitInterop glVDPAUInitNV failed");
+ return false;
+ }
+ CLog::Log(LOGINFO, "CInteropState::Init: vdpau gl interop initialized");
+
+ m_interop.textureTarget = GL_TEXTURE_2D;
+
+ return true;
+}
+
+void CInteropState::Finish()
+{
+ m_interop.glVDPAUFiniNV();
+ m_device = nullptr;
+ m_procFunc = nullptr;
+}
+
+InteropInfo &CInteropState::GetInterop()
+{
+ return m_interop;
+}
+
+bool CInteropState::NeedInit(void *device, void *procFunc, int64_t ident)
+{
+ if (m_device != device)
+ return true;
+ if (m_procFunc != procFunc)
+ return true;
+ if (m_ident != ident)
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// textures
+//-----------------------------------------------------------------------------
+
+void CVdpauTexture::Init(InteropInfo &interop)
+{
+ m_interop = interop;
+}
+
+bool CVdpauTexture::Map(CVdpauRenderPicture *pic)
+{
+
+ if (m_vdpauPic)
+ return true;
+
+ m_vdpauPic = pic;
+ m_vdpauPic->Acquire();
+
+ m_texWidth = pic->width;
+ m_texHeight = pic->height;
+
+ bool success = false;
+ if (m_vdpauPic->procPic.isYuv)
+ success = MapNV12();
+ else
+ success = MapRGB();
+
+ if (!success)
+ {
+ m_vdpauPic->Release();
+ m_vdpauPic = nullptr;
+ }
+
+ return success;
+}
+
+void CVdpauTexture::Unmap()
+{
+ if (!m_vdpauPic)
+ return;
+
+ if (m_vdpauPic->procPic.isYuv)
+ UnmapNV12();
+ else
+ UnmapRGB();
+
+ m_vdpauPic->Release();
+ m_vdpauPic = nullptr;
+}
+
+bool CVdpauTexture::MapNV12()
+{
+ GLuint textures[4];
+ while (glGetError() != GL_NO_ERROR) ;
+ glGenTextures(4, textures);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapNV12 error creating texture");
+ return false;
+ }
+
+ const void *videoSurface = reinterpret_cast<void*>(m_vdpauPic->procPic.videoSurface);
+ m_glSurface.glVdpauSurface = m_interop.glVDPAURegisterVideoSurfaceNV(videoSurface,
+ m_interop.textureTarget, 4, textures);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapNV12 error register video surface");
+ glDeleteTextures(4, textures);
+ return false;
+ }
+
+ m_interop.glVDPAUSurfaceAccessNV(m_glSurface.glVdpauSurface, GL_READ_ONLY);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapNV12 error setting access");
+ glDeleteTextures(4, textures);
+ return false;
+ }
+
+ m_interop.glVDPAUMapSurfacesNV(1, &m_glSurface.glVdpauSurface);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapNV12 error mapping surface");
+ glDeleteTextures(4, textures);
+ return false;
+ }
+
+ m_interop.glVDPAUUnregisterSurfaceNV(m_glSurface.glVdpauSurface);
+
+ m_textureTopY = textures[0];
+ m_textureTopUV = textures[2];
+ m_textureBotY = textures[1];
+ m_textureBotUV = textures[3];
+
+ return true;
+}
+
+void CVdpauTexture::UnmapNV12()
+{
+ m_interop.glVDPAUUnmapSurfacesNV(1, &m_glSurface.glVdpauSurface);
+ glDeleteTextures(1, &m_textureTopY);
+ glDeleteTextures(1, &m_textureTopUV);
+ glDeleteTextures(1, &m_textureBotY);
+ glDeleteTextures(1, &m_textureBotUV);
+}
+
+bool CVdpauTexture::MapRGB()
+{
+ glGenTextures(1, &m_texture);
+ const void *outSurface = reinterpret_cast<void*>(m_vdpauPic->procPic.outputSurface);
+ m_glSurface.glVdpauSurface = m_interop.glVDPAURegisterOutputSurfaceNV(outSurface,
+ m_interop.textureTarget, 1, &m_texture);
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapRGB error register output surface: {}", err);
+ return false;
+ }
+
+ m_interop.glVDPAUSurfaceAccessNV(m_glSurface.glVdpauSurface, GL_READ_ONLY);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "CVdpauTexture::MapRGB error setting access");
+ glDeleteTextures(1, &m_texture);
+ return false;
+ }
+
+ while (glGetError() != GL_NO_ERROR) ;
+ m_interop.glVDPAUMapSurfacesNV(1, &m_glSurface.glVdpauSurface);
+ if (glGetError() != GL_NO_ERROR)
+ {
+ CLog::Log(LOGERROR, "VDPAU::COutput error mapping surface");
+ glDeleteTextures(1, &m_texture);
+ return false;
+ }
+
+ return true;
+}
+
+void CVdpauTexture::UnmapRGB()
+{
+ m_interop.glVDPAUUnmapSurfacesNV(1, &m_glSurface.glVdpauSurface);
+ m_interop.glVDPAUUnregisterSurfaceNV(m_glSurface.glVdpauSurface);
+ glDeleteTextures(1, &m_texture);
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.h
new file mode 100644
index 0000000..2583816
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VdpauGL.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// always define GL_GLEXT_PROTOTYPES before include gl headers
+#if !defined(GL_GLEXT_PROTOTYPES)
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include <GL/gl.h>
+#include <GL/glext.h>
+
+namespace VDPAU
+{
+class CVdpauRenderPicture;
+
+
+struct InteropInfo
+{
+ PFNGLVDPAUINITNVPROC glVDPAUInitNV;
+ PFNGLVDPAUFININVPROC glVDPAUFiniNV;
+ PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC glVDPAURegisterOutputSurfaceNV;
+ PFNGLVDPAUREGISTERVIDEOSURFACENVPROC glVDPAURegisterVideoSurfaceNV;
+ PFNGLVDPAUISSURFACENVPROC glVDPAUIsSurfaceNV;
+ PFNGLVDPAUUNREGISTERSURFACENVPROC glVDPAUUnregisterSurfaceNV;
+ PFNGLVDPAUSURFACEACCESSNVPROC glVDPAUSurfaceAccessNV;
+ PFNGLVDPAUMAPSURFACESNVPROC glVDPAUMapSurfacesNV;
+ PFNGLVDPAUUNMAPSURFACESNVPROC glVDPAUUnmapSurfacesNV;
+ PFNGLVDPAUGETSURFACEIVNVPROC glVDPAUGetSurfaceivNV;
+ GLenum textureTarget;
+};
+
+class CInteropState
+{
+public:
+ bool Init(void *device, void *procFunc, int64_t ident);
+ void Finish();
+ InteropInfo &GetInterop();
+ bool NeedInit(void *device, void *procFunc, int64_t ident);
+
+protected:
+ void *m_device = nullptr;
+ void *m_procFunc = nullptr;
+ int64_t m_ident = 0;
+ InteropInfo m_interop;
+};
+
+class CVdpauTexture
+{
+public:
+ bool Map(VDPAU::CVdpauRenderPicture *pic);
+ void Unmap();
+ void Init(InteropInfo &interop);
+
+ GLuint m_texture = 0;
+ GLuint m_textureTopY = 0;
+ GLuint m_textureTopUV = 0;
+ GLuint m_textureBotY = 0;
+ GLuint m_textureBotUV = 0;
+ int m_texWidth = 0;
+ int m_texHeight = 0;
+
+protected:
+ bool MapNV12();
+ void UnmapNV12();
+ bool MapRGB();
+ void UnmapRGB();
+ InteropInfo m_interop;
+ CVdpauRenderPicture *m_vdpauPic = nullptr;
+ struct GLSurface
+ {
+ GLvdpauSurfaceNV glVdpauSurface;
+ } m_glSurface;
+};
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp
new file mode 100644
index 0000000..4b8ee5a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoLayerBridgeDRMPRIME.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+#include "windowing/gbm/drm/DRMAtomic.h"
+
+#include <utility>
+
+using namespace KODI::WINDOWING::GBM;
+using namespace DRMPRIME;
+
+CVideoLayerBridgeDRMPRIME::CVideoLayerBridgeDRMPRIME(std::shared_ptr<CDRMAtomic> drm)
+ : m_DRM(std::move(drm))
+{
+}
+
+CVideoLayerBridgeDRMPRIME::~CVideoLayerBridgeDRMPRIME()
+{
+ Release(m_prev_buffer);
+ Release(m_buffer);
+}
+
+void CVideoLayerBridgeDRMPRIME::Disable()
+{
+ // disable video plane
+ auto plane = m_DRM->GetVideoPlane();
+ m_DRM->AddProperty(plane, "FB_ID", 0);
+ m_DRM->AddProperty(plane, "CRTC_ID", 0);
+
+ auto winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+ // disable HDR metadata
+ winSystem->SetHDR(nullptr);
+}
+
+void CVideoLayerBridgeDRMPRIME::Acquire(CVideoBufferDRMPRIME* buffer)
+{
+ // release the buffer that is no longer presented on screen
+ Release(m_prev_buffer);
+
+ // release the buffer currently being presented next call
+ m_prev_buffer = m_buffer;
+
+ // reference count the buffer that is going to be presented on screen
+ m_buffer = buffer;
+ m_buffer->Acquire();
+}
+
+void CVideoLayerBridgeDRMPRIME::Release(CVideoBufferDRMPRIME* buffer)
+{
+ if (!buffer)
+ return;
+
+ Unmap(buffer);
+ buffer->Release();
+}
+
+bool CVideoLayerBridgeDRMPRIME::Map(CVideoBufferDRMPRIME* buffer)
+{
+ if (buffer->m_fb_id)
+ return true;
+
+ if (!buffer->AcquireDescriptor())
+ {
+ CLog::Log(LOGERROR, "CVideoLayerBridgeDRMPRIME::{} - failed to acquire descriptor",
+ __FUNCTION__);
+ return false;
+ }
+
+ AVDRMFrameDescriptor* descriptor = buffer->GetDescriptor();
+ uint32_t handles[4] = {}, pitches[4] = {}, offsets[4] = {}, flags = 0;
+ uint64_t modifier[4] = {};
+ int ret;
+
+ // convert Prime FD to GEM handle
+ for (int object = 0; object < descriptor->nb_objects; object++)
+ {
+ ret = drmPrimeFDToHandle(m_DRM->GetFileDescriptor(), descriptor->objects[object].fd,
+ &buffer->m_handles[object]);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR,
+ "CVideoLayerBridgeDRMPRIME::{} - failed to convert prime fd {} to gem handle {}, "
+ "ret = {}",
+ __FUNCTION__, descriptor->objects[object].fd, buffer->m_handles[object], ret);
+ return false;
+ }
+ }
+
+ AVDRMLayerDescriptor* layer = &descriptor->layers[0];
+
+ for (int plane = 0; plane < layer->nb_planes; plane++)
+ {
+ int object = layer->planes[plane].object_index;
+ uint32_t handle = buffer->m_handles[object];
+ if (handle && layer->planes[plane].pitch)
+ {
+ handles[plane] = handle;
+ pitches[plane] = layer->planes[plane].pitch;
+ offsets[plane] = layer->planes[plane].offset;
+ modifier[plane] = descriptor->objects[object].format_modifier;
+ }
+ }
+
+ if (modifier[0] && modifier[0] != DRM_FORMAT_MOD_INVALID)
+ flags = DRM_MODE_FB_MODIFIERS;
+
+ // add the video frame FB
+ ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), buffer->GetWidth(),
+ buffer->GetHeight(), layer->format, handles, pitches, offsets,
+ modifier, &buffer->m_fb_id, flags);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CVideoLayerBridgeDRMPRIME::{} - failed to add fb {}, ret = {}",
+ __FUNCTION__, buffer->m_fb_id, ret);
+ return false;
+ }
+
+ Acquire(buffer);
+ return true;
+}
+
+void CVideoLayerBridgeDRMPRIME::Unmap(CVideoBufferDRMPRIME* buffer)
+{
+ if (buffer->m_fb_id)
+ {
+ drmModeRmFB(m_DRM->GetFileDescriptor(), buffer->m_fb_id);
+ buffer->m_fb_id = 0;
+ }
+
+ for (int i = 0; i < AV_DRM_MAX_PLANES; i++)
+ {
+ if (buffer->m_handles[i])
+ {
+ struct drm_gem_close gem_close;
+ gem_close.handle = buffer->m_handles[i];
+ drmIoctl(m_DRM->GetFileDescriptor(), DRM_IOCTL_GEM_CLOSE, &gem_close);
+ buffer->m_handles[i] = 0;
+ }
+ }
+
+ buffer->ReleaseDescriptor();
+}
+
+void CVideoLayerBridgeDRMPRIME::Configure(CVideoBufferDRMPRIME* buffer)
+{
+ auto winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+ const VideoPicture& picture = buffer->GetPicture();
+
+ winSystem->SetHDR(&picture);
+
+ auto plane = m_DRM->GetVideoPlane();
+
+ bool result;
+ uint64_t value;
+ std::tie(result, value) = plane->GetPropertyValue("COLOR_ENCODING", GetColorEncoding(picture));
+ if (result)
+ m_DRM->AddProperty(plane, "COLOR_ENCODING", value);
+
+ std::tie(result, value) = plane->GetPropertyValue("COLOR_RANGE", GetColorRange(picture));
+ if (result)
+ m_DRM->AddProperty(plane, "COLOR_RANGE", value);
+}
+
+void CVideoLayerBridgeDRMPRIME::SetVideoPlane(CVideoBufferDRMPRIME* buffer, const CRect& destRect)
+{
+ if (!Map(buffer))
+ {
+ Unmap(buffer);
+ return;
+ }
+
+ auto plane = m_DRM->GetVideoPlane();
+ m_DRM->AddProperty(plane, "FB_ID", buffer->m_fb_id);
+ m_DRM->AddProperty(plane, "CRTC_ID", m_DRM->GetCrtc()->GetCrtcId());
+ m_DRM->AddProperty(plane, "SRC_X", 0);
+ m_DRM->AddProperty(plane, "SRC_Y", 0);
+ m_DRM->AddProperty(plane, "SRC_W", buffer->GetWidth() << 16);
+ m_DRM->AddProperty(plane, "SRC_H", buffer->GetHeight() << 16);
+ m_DRM->AddProperty(plane, "CRTC_X", static_cast<int32_t>(destRect.x1) & ~1);
+ m_DRM->AddProperty(plane, "CRTC_Y", static_cast<int32_t>(destRect.y1) & ~1);
+ m_DRM->AddProperty(plane, "CRTC_W", (static_cast<uint32_t>(destRect.Width()) + 1) & ~1);
+ m_DRM->AddProperty(plane, "CRTC_H", (static_cast<uint32_t>(destRect.Height()) + 1) & ~1);
+}
+
+void CVideoLayerBridgeDRMPRIME::UpdateVideoPlane()
+{
+ if (!m_buffer || !m_buffer->m_fb_id)
+ return;
+
+ auto plane = m_DRM->GetVideoPlane();
+ m_DRM->AddProperty(plane, "FB_ID", m_buffer->m_fb_id);
+ m_DRM->AddProperty(plane, "CRTC_ID", m_DRM->GetCrtc()->GetCrtcId());
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.h
new file mode 100644
index 0000000..8d9758f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Interface/StreamInfo.h"
+#include "windowing/gbm/VideoLayerBridge.h"
+
+#include <memory>
+
+#include <drm_mode.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+class CDRMAtomic;
+}
+} // namespace WINDOWING
+} // namespace KODI
+
+class CVideoBufferDRMPRIME;
+
+class CVideoLayerBridgeDRMPRIME : public KODI::WINDOWING::GBM::CVideoLayerBridge
+{
+public:
+ CVideoLayerBridgeDRMPRIME(std::shared_ptr<KODI::WINDOWING::GBM::CDRMAtomic> drm);
+ ~CVideoLayerBridgeDRMPRIME() override;
+ void Disable() override;
+
+ virtual void Configure(CVideoBufferDRMPRIME* buffer);
+ virtual void SetVideoPlane(CVideoBufferDRMPRIME* buffer, const CRect& destRect);
+ virtual void UpdateVideoPlane();
+
+protected:
+ std::shared_ptr<KODI::WINDOWING::GBM::CDRMAtomic> m_DRM;
+
+private:
+ void Acquire(CVideoBufferDRMPRIME* buffer);
+ void Release(CVideoBufferDRMPRIME* buffer);
+ bool Map(CVideoBufferDRMPRIME* buffer);
+ void Unmap(CVideoBufferDRMPRIME* buffer);
+
+ CVideoBufferDRMPRIME* m_buffer = nullptr;
+ CVideoBufferDRMPRIME* m_prev_buffer = nullptr;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp
new file mode 100644
index 0000000..f0c9496
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp
@@ -0,0 +1,2757 @@
+/*
+ * Copyright (c) 2007 Frodo/jcmarshall/vulkanr/d4rk
+ * Based on XBoxRenderer by Frodo/jcmarshall
+ * Portions Copyright (c) by the authors of ffmpeg / xvid /mplayer
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LinuxRendererGL.h"
+
+#include "RenderCapture.h"
+#include "RenderCaptureGL.h"
+#include "RenderFactory.h"
+#include "ServiceBroker.h"
+#include "VideoShaders/VideoFilterShaderGL.h"
+#include "VideoShaders/YUV2RGBShaderGL.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h"
+#include "rendering/MatrixGL.h"
+#include "rendering/gl/RenderSystemGL.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <locale.h>
+#include <mutex>
+
+#ifdef TARGET_DARWIN_OSX
+#include "platform/darwin/osx/CocoaInterface.h"
+#include <CoreVideo/CoreVideo.h>
+#include <OpenGL/CGLIOSurface.h>
+#endif
+
+//! @bug
+//! due to a bug on osx nvidia, using gltexsubimage2d with a pbo bound and a null pointer
+//! screws up the alpha, an offset fixes this, there might still be a problem if stride + PBO_OFFSET
+//! is a multiple of 128 and deinterlacing is on
+#define PBO_OFFSET 16
+
+using namespace Shaders;
+using namespace Shaders::GL;
+
+static const GLubyte stipple_weave[] = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+CLinuxRendererGL::CPictureBuffer::CPictureBuffer()
+{
+ memset(&fields, 0, sizeof(fields));
+ memset(&image , 0, sizeof(image));
+ memset(&pbo , 0, sizeof(pbo));
+ videoBuffer = nullptr;
+ loaded = false;
+}
+
+CBaseRenderer* CLinuxRendererGL::Create(CVideoBuffer *buffer)
+{
+ return new CLinuxRendererGL();
+}
+
+bool CLinuxRendererGL::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("default", CLinuxRendererGL::Create);
+ return true;
+}
+
+CLinuxRendererGL::CLinuxRendererGL()
+{
+ m_iFlags = 0;
+ m_format = AV_PIX_FMT_NONE;
+
+ m_useDithering = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("videoscreen.dither");
+ m_ditherDepth = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("videoscreen.ditherdepth");
+ m_fullRange = !CServiceBroker::GetWinSystem()->UseLimitedColor();
+
+ m_fbo.width = 0.0;
+ m_fbo.height = 0.0;
+
+ m_ColorManager.reset(new CColorManager());
+ m_tCLUTTex = 0;
+ m_CLUT = NULL;
+ m_CLUTsize = 0;
+ m_cmsToken = -1;
+ m_cmsOn = false;
+
+ m_renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+}
+
+CLinuxRendererGL::~CLinuxRendererGL()
+{
+ UnInit();
+
+ if (m_pYUVShader)
+ {
+ delete m_pYUVShader;
+ m_pYUVShader = nullptr;
+ }
+
+ if (m_pVideoFilterShader)
+ {
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+ }
+}
+
+bool CLinuxRendererGL::ValidateRenderer()
+{
+ if (!m_bConfigured)
+ return false;
+
+ // if its first pass, just init textures and return
+ if (ValidateRenderTarget())
+ return false;
+
+ int index = m_iYV12RenderBuffer;
+ const CPictureBuffer& buf = m_buffers[index];
+
+ if (!buf.fields[FIELD_FULL][0].id)
+ return false;
+
+ return true;
+}
+
+bool CLinuxRendererGL::ValidateRenderTarget()
+{
+ if (!m_bValidated)
+ {
+ // function pointer for texture might change in
+ // call to LoadShaders
+ glFinish();
+ for (int i = 0 ; i < NUM_BUFFERS ; i++)
+ {
+ DeleteTexture(i);
+ }
+
+ // trigger update of video filters
+ m_scalingMethodGui = (ESCALINGMETHOD)-1;
+
+ // create the yuv textures
+ UpdateVideoFilter();
+ LoadShaders();
+ if (m_renderMethod < 0)
+ return false;
+
+ if (m_textureTarget == GL_TEXTURE_RECTANGLE)
+ CLog::Log(LOGINFO, "Using GL_TEXTURE_RECTANGLE");
+ else
+ CLog::Log(LOGINFO, "Using GL_TEXTURE_2D");
+
+ for (int i = 0 ; i < m_NumYV12Buffers ; i++)
+ CreateTexture(i);
+
+ m_bValidated = true;
+ return true;
+ }
+ return false;
+}
+
+bool CLinuxRendererGL::Configure(const VideoPicture &picture, float fps, unsigned int orientation)
+{
+ m_format = picture.videoBuffer->GetFormat();
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+ m_fps = fps;
+
+ m_iFlags = GetFlagsChromaPosition(picture.chroma_position) |
+ GetFlagsStereoMode(picture.stereoMode);
+
+ m_srcPrimaries = GetSrcPrimaries(static_cast<AVColorPrimaries>(picture.color_primaries),
+ picture.iWidth, picture.iHeight);
+ m_toneMap = false;
+
+ // Calculate the input frame aspect ratio.
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+ ManageRenderArea();
+
+ m_bConfigured = true;
+ m_scalingMethodGui = (ESCALINGMETHOD)-1;
+
+ // Ensure that textures are recreated and rendering starts only after the 1st
+ // frame is loaded after every call to Configure().
+ m_bValidated = false;
+
+ m_nonLinStretch = false;
+ m_nonLinStretchGui = false;
+ m_pixelRatio = 1.0;
+
+ m_pboSupported = CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_pixel_buffer_object");
+
+ // setup the background colour
+ m_clearColour = CServiceBroker::GetWinSystem()->UseLimitedColor() ? (16.0f / 0xff) : 0.0f;
+
+ // load 3DLUT
+ if (m_ColorManager->IsEnabled())
+ {
+ if (!m_ColorManager->CheckConfiguration(m_cmsToken, m_srcPrimaries))
+ {
+ CLog::Log(LOGDEBUG, "CMS configuration changed, reload LUT");
+ if (!LoadCLUT())
+ return false;
+ }
+ m_cmsOn = true;
+ }
+ else
+ {
+ m_cmsOn = false;
+ }
+
+ return true;
+}
+
+bool CLinuxRendererGL::ConfigChanged(const VideoPicture &picture)
+{
+ if (picture.videoBuffer->GetFormat() != m_format)
+ return true;
+
+ return false;
+}
+
+void CLinuxRendererGL::AddVideoPicture(const VideoPicture &picture, int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ if (buf.videoBuffer)
+ {
+ CLog::LogF(LOGERROR, "unreleased video buffer");
+ buf.videoBuffer->Release();
+ }
+ buf.videoBuffer = picture.videoBuffer;
+ buf.videoBuffer->Acquire();
+ buf.loaded = false;
+ buf.m_srcPrimaries = static_cast<AVColorPrimaries>(picture.color_primaries);
+ buf.m_srcColSpace = static_cast<AVColorSpace>(picture.color_space);
+ buf.m_srcFullRange = picture.color_range == 1;
+ buf.m_srcBits = picture.colorBits;
+
+ buf.hasDisplayMetadata = picture.hasDisplayMetadata;
+ buf.displayMetadata = picture.displayMetadata;
+ buf.lightMetadata = picture.lightMetadata;
+ if (picture.hasLightMetadata && picture.lightMetadata.MaxCLL)
+ buf.hasLightMetadata = picture.hasLightMetadata;
+}
+
+void CLinuxRendererGL::ReleaseBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ if (buf.videoBuffer)
+ {
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+void CLinuxRendererGL::GetPlaneTextureSize(CYuvPlane& plane)
+{
+ /* texture is assumed to be bound */
+ GLint width = 0
+ , height = 0
+ , border = 0;
+ glGetTexLevelParameteriv(m_textureTarget, 0, GL_TEXTURE_WIDTH , &width);
+ glGetTexLevelParameteriv(m_textureTarget, 0, GL_TEXTURE_HEIGHT, &height);
+ glGetTexLevelParameteriv(m_textureTarget, 0, GL_TEXTURE_BORDER, &border);
+ plane.texwidth = width - 2 * border;
+ plane.texheight = height - 2 * border;
+ if(plane.texwidth <= 0 || plane.texheight <= 0)
+ {
+ CLog::Log(LOGDEBUG, "CLinuxRendererGL::GetPlaneTextureSize - invalid size {}x{} - {}", width,
+ height, border);
+ /* to something that avoid division by zero */
+ plane.texwidth = 1;
+ plane.texheight = 1;
+ }
+}
+
+void CLinuxRendererGL::CalculateTextureSourceRects(int source, int num_planes)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ // calculate the source rectangle
+ for(int field = 0; field < 3; field++)
+ {
+ for(int plane = 0; plane < num_planes; plane++)
+ {
+ CYuvPlane& p = buf.fields[field][plane];
+
+ p.rect = m_sourceRect;
+ p.width = im->width;
+ p.height = im->height;
+
+ if(field != FIELD_FULL)
+ {
+ /* correct for field offsets and chroma offsets */
+ float offset_y = 0.5f;
+ if(plane != 0)
+ offset_y += 0.5f;
+ if(field == FIELD_BOT)
+ offset_y *= -1;
+
+ p.rect.y1 += offset_y;
+ p.rect.y2 += offset_y;
+
+ /* half the height if this is a field */
+ p.height *= 0.5f;
+ p.rect.y1 *= 0.5f;
+ p.rect.y2 *= 0.5f;
+ }
+
+ if(plane != 0)
+ {
+ p.width /= 1 << im->cshift_x;
+ p.height /= 1 << im->cshift_y;
+
+ p.rect.x1 /= 1 << im->cshift_x;
+ p.rect.x2 /= 1 << im->cshift_x;
+ p.rect.y1 /= 1 << im->cshift_y;
+ p.rect.y2 /= 1 << im->cshift_y;
+ }
+
+ // protect against division by zero
+ if (p.texheight == 0 || p.texwidth == 0 ||
+ p.pixpertex_x == 0 || p.pixpertex_y == 0)
+ {
+ continue;
+ }
+
+ p.height /= p.pixpertex_y;
+ p.rect.y1 /= p.pixpertex_y;
+ p.rect.y2 /= p.pixpertex_y;
+ p.width /= p.pixpertex_x;
+ p.rect.x1 /= p.pixpertex_x;
+ p.rect.x2 /= p.pixpertex_x;
+
+ if (m_textureTarget == GL_TEXTURE_2D)
+ {
+ p.height /= p.texheight;
+ p.rect.y1 /= p.texheight;
+ p.rect.y2 /= p.texheight;
+ p.width /= p.texwidth;
+ p.rect.x1 /= p.texwidth;
+ p.rect.x2 /= p.texwidth;
+ }
+ }
+ }
+}
+
+void CLinuxRendererGL::LoadPlane(CYuvPlane& plane, int type,
+ unsigned width, unsigned height,
+ int stride, int bpp, void* data)
+{
+
+ if (plane.pbo)
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, plane.pbo);
+
+ int bps = bpp * KODI::UTILS::GL::glFormatElementByteCount(type);
+
+ unsigned datatype;
+ if (bpp == 2)
+ datatype = GL_UNSIGNED_SHORT;
+ else
+ datatype = GL_UNSIGNED_BYTE;
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / bps);
+ glBindTexture(m_textureTarget, plane.id);
+ glTexSubImage2D(m_textureTarget, 0, 0, 0, width, height, type, datatype, data);
+
+ /* check if we need to load any border pixels */
+ if (height < plane.texheight)
+ glTexSubImage2D( m_textureTarget, 0
+ , 0, height, width, 1
+ , type, datatype
+ , (unsigned char*)data + stride * (height-1));
+
+ if (width < plane.texwidth)
+ glTexSubImage2D( m_textureTarget, 0
+ , width, 0, 1, height
+ , type, datatype
+ , (unsigned char*)data + bps * (width-1));
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glBindTexture(m_textureTarget, 0);
+ if (plane.pbo)
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+bool CLinuxRendererGL::Flush(bool saveBuffers)
+{
+ bool safe = saveBuffers && CanSaveBuffers();
+ glFinish();
+
+ for (int i = 0 ; i < m_NumYV12Buffers ; i++)
+ {
+ if (!safe)
+ ReleaseBuffer(i);
+ DeleteTexture(i);
+ }
+
+ delete m_pYUVShader;
+ m_pYUVShader = nullptr;
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+
+ glFinish();
+ m_bValidated = false;
+ m_fbo.fbo.Cleanup();
+ m_iYV12RenderBuffer = 0;
+
+ return safe;
+}
+
+void CLinuxRendererGL::Update()
+{
+ if (!m_bConfigured)
+ return;
+ ManageRenderArea();
+ m_scalingMethodGui = (ESCALINGMETHOD)-1;
+
+ ValidateRenderTarget();
+}
+
+void CLinuxRendererGL::RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ if (index2 >= 0)
+ m_iYV12RenderBuffer = index2;
+ else
+ m_iYV12RenderBuffer = index;
+
+ if (!ValidateRenderer())
+ {
+ if (clear) //if clear is set, we're expected to overwrite all backbuffer pixels, even if we have nothing to render
+ ClearBackBuffer();
+
+ return;
+ }
+
+ ManageRenderArea();
+
+ if (clear)
+ {
+ //draw black bars when video is not transparent, clear the entire backbuffer when it is
+ if (alpha == 255)
+ DrawBlackBars();
+ else
+ ClearBackBuffer();
+ }
+
+ if (alpha < 255)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+
+ if (m_pYUVShader)
+ m_pYUVShader->SetAlpha(alpha/255);
+ if (m_pVideoFilterShader)
+ m_pVideoFilterShader->SetAlpha(alpha/255);
+
+ if (!Render(flags, m_iYV12RenderBuffer) && clear)
+ ClearBackBuffer();
+
+ if (index2 >= 0)
+ {
+ m_iYV12RenderBuffer = index;
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (m_pYUVShader)
+ m_pYUVShader->SetAlpha(alpha/255/2);
+ if (m_pVideoFilterShader)
+ m_pVideoFilterShader->SetAlpha(alpha/255/2);
+
+ Render(flags, m_iYV12RenderBuffer);
+ }
+
+ VerifyGLState();
+ glEnable(GL_BLEND);
+ glFlush();
+}
+
+void CLinuxRendererGL::ClearBackBuffer()
+{
+ //set the entire backbuffer to black
+ glClearColor(m_clearColour, m_clearColour, m_clearColour, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0,0,0,0);
+}
+
+//draw black bars around the video quad, this is more efficient than glClear()
+//since it only sets pixels to black that aren't going to be overwritten by the video
+void CLinuxRendererGL::DrawBlackBars()
+{
+ glDisable(GL_BLEND);
+
+ struct Svertex
+ {
+ float x,y,z;
+ };
+ Svertex vertices[24];
+ GLubyte count = 0;
+
+ m_renderSystem->EnableShader(ShaderMethodGL::SM_DEFAULT);
+ GLint posLoc = m_renderSystem->ShaderGetPos();
+ GLint uniCol = m_renderSystem->ShaderGetUniCol();
+
+ glUniform4f(uniCol, m_clearColour / 255.0f, m_clearColour / 255.0f, m_clearColour / 255.0f, 1.0f);
+
+ int osWindowWidth = CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth();
+ int osWindowHeight = CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight();
+
+ //top quad
+ if (m_destRect.y1 > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = 0.0;
+ vertices[quad].z = 0;
+ vertices[quad+1].x = osWindowWidth;
+ vertices[quad+1].y = 0;
+ vertices[quad+1].z = 0;
+ vertices[quad+2].x = osWindowWidth;
+ vertices[quad+2].y = m_destRect.y1;
+ vertices[quad+2].z = 0;
+ vertices[quad+3] = vertices[quad+2];
+ vertices[quad+4].x = 0;
+ vertices[quad+4].y = m_destRect.y1;
+ vertices[quad+4].z = 0;
+ vertices[quad+5] = vertices[quad];
+ count += 6;
+ }
+
+ // bottom quad
+ if (m_destRect.y2 < osWindowHeight)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_destRect.y2;
+ vertices[quad].z = 0;
+ vertices[quad+1].x = osWindowWidth;
+ vertices[quad+1].y = m_destRect.y2;
+ vertices[quad+1].z = 0;
+ vertices[quad+2].x = osWindowWidth;
+ vertices[quad+2].y = osWindowHeight;
+ vertices[quad+2].z = 0;
+ vertices[quad+3] = vertices[quad+2];
+ vertices[quad+4].x = 0;
+ vertices[quad+4].y = osWindowHeight;
+ vertices[quad+4].z = 0;
+ vertices[quad+5] = vertices[quad];
+ count += 6;
+ }
+
+ // left quad
+ if (m_destRect.x1 > 0.0f)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = 0.0;
+ vertices[quad].y = m_destRect.y1;
+ vertices[quad].z = 0;
+ vertices[quad+1].x = m_destRect.x1;
+ vertices[quad+1].y = m_destRect.y1;
+ vertices[quad+1].z = 0;
+ vertices[quad+2].x = m_destRect.x1;
+ vertices[quad+2].y = m_destRect.y2;
+ vertices[quad+2].z = 0;
+ vertices[quad+3] = vertices[quad+2];
+ vertices[quad+4].x = 0;
+ vertices[quad+4].y = m_destRect.y2;
+ vertices[quad+4].z = 0;
+ vertices[quad+5] = vertices[quad];
+ count += 6;
+ }
+
+ // right quad
+ if (m_destRect.x2 < osWindowWidth)
+ {
+ GLubyte quad = count;
+ vertices[quad].x = m_destRect.x2;
+ vertices[quad].y = m_destRect.y1;
+ vertices[quad].z = 0;
+ vertices[quad+1].x = osWindowWidth;
+ vertices[quad+1].y = m_destRect.y1;
+ vertices[quad+1].z = 0;
+ vertices[quad+2].x = osWindowWidth;
+ vertices[quad+2].y = m_destRect.y2;
+ vertices[quad+2].z = 0;
+ vertices[quad+3] = vertices[quad+2];
+ vertices[quad+4].x = m_destRect.x2;
+ vertices[quad+4].y = m_destRect.y2;
+ vertices[quad+4].z = 0;
+ vertices[quad+5] = vertices[quad];
+ count += 6;
+ }
+
+ GLuint vertexVBO;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Svertex)*count, &vertices[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Svertex), 0);
+ glEnableVertexAttribArray(posLoc);
+
+ glDrawArrays(GL_TRIANGLES, 0, count);
+
+ glDisableVertexAttribArray(posLoc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+
+ m_renderSystem->DisableShader();
+}
+
+void CLinuxRendererGL::UpdateVideoFilter()
+{
+ if (!m_pVideoFilterShader)
+ {
+ m_pVideoFilterShader = new DefaultFilterShader();
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "CLinuxRendererGL::UpdateVideoFilter: Error compiling and linking video filter shader");
+ return;
+ }
+ }
+
+ bool pixelRatioChanged = (CDisplaySettings::GetInstance().GetPixelRatio() > 1.001f ||
+ CDisplaySettings::GetInstance().GetPixelRatio() < 0.999f) !=
+ (m_pixelRatio > 1.001f || m_pixelRatio < 0.999f);
+ bool nonLinStretchChanged = false;
+ bool cmsChanged = (m_cmsOn != m_ColorManager->IsEnabled()) ||
+ (m_cmsOn && !m_ColorManager->CheckConfiguration(m_cmsToken, m_srcPrimaries));
+ if (m_nonLinStretchGui != CDisplaySettings::GetInstance().IsNonLinearStretched() || pixelRatioChanged)
+ {
+ m_nonLinStretchGui = CDisplaySettings::GetInstance().IsNonLinearStretched();
+ m_pixelRatio = CDisplaySettings::GetInstance().GetPixelRatio();
+ m_reloadShaders = 1;
+ nonLinStretchChanged = true;
+
+ if (m_nonLinStretchGui && (m_pixelRatio < 0.999f || m_pixelRatio > 1.001f) && Supports(RENDERFEATURE_NONLINSTRETCH))
+ {
+ m_nonLinStretch = true;
+ CLog::Log(LOGDEBUG, "GL: Enabling non-linear stretch");
+ }
+ else
+ {
+ m_nonLinStretch = false;
+ CLog::Log(LOGDEBUG, "GL: Disabling non-linear stretch");
+ }
+ }
+
+ CRect srcRect, dstRect, viewRect;
+ GetVideoRect(srcRect, dstRect, viewRect);
+
+ if (m_scalingMethodGui == m_videoSettings.m_ScalingMethod &&
+ viewRect.Height() == m_viewRect.Height() &&
+ viewRect.Width() == m_viewRect.Width() &&
+ !nonLinStretchChanged && !cmsChanged)
+ return;
+ else
+ m_reloadShaders = 1;
+
+ // recompile YUV shader when non-linear stretch is turned on/off
+ // or when it's on and the scaling method changed
+ if (m_nonLinStretch || nonLinStretchChanged)
+ m_reloadShaders = 1;
+
+ if (cmsChanged)
+ {
+ if (m_ColorManager->IsEnabled())
+ {
+ if (!m_ColorManager->CheckConfiguration(m_cmsToken, m_srcPrimaries))
+ {
+ CLog::Log(LOGDEBUG, "CMS configuration changed, reload LUT");
+ LoadCLUT();
+ }
+ m_cmsOn = true;
+ }
+ else
+ {
+ m_cmsOn = false;
+ }
+ }
+
+ m_scalingMethodGui = m_videoSettings.m_ScalingMethod;
+ m_scalingMethod = m_scalingMethodGui;
+ m_viewRect = viewRect;
+
+ if (!Supports(m_scalingMethod))
+ {
+ CLog::Log(LOGWARNING,
+ "CLinuxRendererGL::UpdateVideoFilter - chosen scaling method {}, is not supported by "
+ "renderer",
+ m_scalingMethod);
+ m_scalingMethod = VS_SCALINGMETHOD_LINEAR;
+ }
+
+ if (m_pVideoFilterShader)
+ {
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+ }
+ m_fbo.fbo.Cleanup();
+
+ VerifyGLState();
+
+ if (m_scalingMethod == VS_SCALINGMETHOD_AUTO)
+ {
+ bool scaleSD = m_sourceHeight < 720 && m_sourceWidth < 1280;
+ bool scaleUp = (int)m_sourceHeight < m_viewRect.Height() && (int)m_sourceWidth < m_viewRect.Width();
+ bool scaleFps = m_fps < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAutoScaleMaxFps + 0.01f;
+
+ if (Supports(VS_SCALINGMETHOD_LANCZOS3_FAST) && scaleSD && scaleUp && scaleFps)
+ m_scalingMethod = VS_SCALINGMETHOD_LANCZOS3_FAST;
+ else
+ m_scalingMethod = VS_SCALINGMETHOD_LINEAR;
+ }
+
+ switch (m_scalingMethod)
+ {
+ case VS_SCALINGMETHOD_NEAREST:
+ case VS_SCALINGMETHOD_LINEAR:
+ SetTextureFilter(m_scalingMethod == VS_SCALINGMETHOD_NEAREST ? GL_NEAREST : GL_LINEAR);
+ m_renderQuality = RQ_SINGLEPASS;
+ if (m_nonLinStretch)
+ {
+ m_pVideoFilterShader = new StretchFilterShader();
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "GL: Error compiling and linking video filter shader");
+ break;
+ }
+ }
+ else
+ {
+ m_pVideoFilterShader = new DefaultFilterShader();
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "GL: Error compiling and linking video filter shader");
+ break;
+ }
+ }
+ return;
+
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
+ {
+ EShaderFormat fmt = GetShaderFormat();
+ if (fmt == SHADER_NV12 || (fmt >= SHADER_YV12 && fmt <= SHADER_YV12_16))
+ {
+ unsigned int major, minor;
+ m_renderSystem->GetRenderVersion(major, minor);
+ if (major >= 4)
+ {
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_SINGLEPASS;
+ return;
+ }
+ }
+
+ [[fallthrough]];
+ }
+
+ case VS_SCALINGMETHOD_LANCZOS2:
+ case VS_SCALINGMETHOD_SPLINE36:
+ case VS_SCALINGMETHOD_LANCZOS3:
+ case VS_SCALINGMETHOD_CUBIC_B_SPLINE:
+ case VS_SCALINGMETHOD_CUBIC_MITCHELL:
+ case VS_SCALINGMETHOD_CUBIC_CATMULL:
+ case VS_SCALINGMETHOD_CUBIC_0_075:
+ case VS_SCALINGMETHOD_CUBIC_0_1:
+ if (m_renderMethod & RENDER_GLSL)
+ {
+ if (!m_fbo.fbo.Initialize())
+ {
+ CLog::Log(LOGERROR, "GL: Error initializing FBO");
+ break;
+ }
+
+ if (!m_fbo.fbo.CreateAndBindToTexture(GL_TEXTURE_2D, m_sourceWidth, m_sourceHeight, GL_RGBA16, GL_SHORT))
+ {
+ CLog::Log(LOGERROR, "GL: Error creating texture and binding to FBO");
+ break;
+ }
+ }
+
+ GLSLOutput *out;
+ out = new GLSLOutput(3,
+ m_useDithering,
+ m_ditherDepth,
+ m_cmsOn ? m_fullRange : false,
+ m_cmsOn ? m_tCLUTTex : 0,
+ m_CLUTsize);
+ m_pVideoFilterShader = new ConvolutionFilterShader(m_scalingMethod, m_nonLinStretch, out);
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "GL: Error compiling and linking video filter shader");
+ break;
+ }
+
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_MULTIPASS;
+ return;
+
+ case VS_SCALINGMETHOD_BICUBIC_SOFTWARE:
+ case VS_SCALINGMETHOD_LANCZOS_SOFTWARE:
+ case VS_SCALINGMETHOD_SINC_SOFTWARE:
+ case VS_SCALINGMETHOD_SINC8:
+ CLog::Log(LOGERROR, "GL: TODO: This scaler has not yet been implemented");
+ break;
+
+ default:
+ break;
+ }
+
+ CLog::Log(LOGERROR, "GL: Falling back to bilinear due to failure to init scaler");
+ if (m_pVideoFilterShader)
+ {
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+ }
+ m_fbo.fbo.Cleanup();
+
+ m_pVideoFilterShader = new DefaultFilterShader();
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "CLinuxRendererGL::UpdateVideoFilter: Error compiling and linking defauilt video filter shader");
+ }
+
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_SINGLEPASS;
+}
+
+void CLinuxRendererGL::LoadShaders(int field)
+{
+ m_reloadShaders = 0;
+
+ if (!LoadShadersHook())
+ {
+ int requestedMethod = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_RENDERMETHOD);
+ CLog::Log(LOGDEBUG, "GL: Requested render method: {}", requestedMethod);
+
+ if (m_pYUVShader)
+ {
+ delete m_pYUVShader;
+ m_pYUVShader = NULL;
+ }
+
+ // create regular progressive scan shader
+ // if single pass, create GLSLOutput helper and pass it to YUV2RGB shader
+ EShaderFormat shaderFormat = GetShaderFormat();
+ std::shared_ptr<GLSLOutput> out;
+ m_toneMapMethod = m_videoSettings.m_ToneMapMethod;
+ if (m_renderQuality == RQ_SINGLEPASS)
+ {
+ out = std::make_shared<GLSLOutput>(GLSLOutput(4, m_useDithering, m_ditherDepth,
+ m_cmsOn ? m_fullRange : false,
+ m_cmsOn ? m_tCLUTTex : 0,
+ m_CLUTsize));
+
+ if (m_scalingMethod == VS_SCALINGMETHOD_LANCZOS3_FAST || m_scalingMethod == VS_SCALINGMETHOD_SPLINE36_FAST)
+ {
+ m_pYUVShader = new YUV2RGBFilterShader4(m_textureTarget == GL_TEXTURE_RECTANGLE,
+ shaderFormat, m_nonLinStretch,
+ AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries,
+ m_toneMap,
+ m_toneMapMethod,
+ m_scalingMethod, out);
+ if (!m_cmsOn)
+ m_pYUVShader->SetConvertFullColorRange(m_fullRange);
+
+ CLog::Log(LOGINFO, "GL: Selecting YUV 2 RGB shader with filter");
+
+ if (m_pYUVShader && m_pYUVShader->CompileAndLink())
+ {
+ m_renderMethod = RENDER_GLSL;
+ UpdateVideoFilter();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "GL: Error enabling YUV2RGB GLSL shader");
+ delete m_pYUVShader;
+ m_pYUVShader = nullptr;
+ }
+ }
+ }
+
+ if (!m_pYUVShader)
+ {
+ m_pYUVShader = new YUV2RGBProgressiveShader(m_textureTarget == GL_TEXTURE_RECTANGLE, shaderFormat,
+ m_nonLinStretch && m_renderQuality == RQ_SINGLEPASS,
+ AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries, m_toneMap, m_toneMapMethod, out);
+
+ if (!m_cmsOn)
+ m_pYUVShader->SetConvertFullColorRange(m_fullRange);
+
+ CLog::Log(LOGINFO, "GL: Selecting YUV 2 RGB shader");
+
+ if (m_pYUVShader && m_pYUVShader->CompileAndLink())
+ {
+ m_renderMethod = RENDER_GLSL;
+ UpdateVideoFilter();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "GL: Error enabling YUV2RGB GLSL shader");
+ }
+ }
+ }
+
+ if (m_pboSupported)
+ {
+ CLog::Log(LOGINFO, "GL: Using GL_ARB_pixel_buffer_object");
+ m_pboUsed = true;
+ }
+ else
+ m_pboUsed = false;
+}
+
+void CLinuxRendererGL::UnInit()
+{
+ CLog::Log(LOGDEBUG, "LinuxRendererGL: Cleaning up GL resources");
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ glFinish();
+
+ // YV12 textures
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ ReleaseBuffer(i);
+ DeleteTexture(i);
+ }
+
+ DeleteCLUT();
+
+ // cleanup framebuffer object if it was in use
+ m_fbo.fbo.Cleanup();
+ m_bValidated = false;
+ m_bConfigured = false;
+}
+
+bool CLinuxRendererGL::Render(unsigned int flags, int renderBuffer)
+{
+ // obtain current field, if interlaced
+ if( flags & RENDER_FLAG_TOP)
+ m_currentField = FIELD_TOP;
+
+ else if (flags & RENDER_FLAG_BOT)
+ m_currentField = FIELD_BOT;
+
+ else
+ m_currentField = FIELD_FULL;
+
+ // call texture load function
+ if (!UploadTexture(renderBuffer))
+ {
+ return false;
+ }
+
+ if (RenderHook(renderBuffer))
+ ;
+ else if (m_renderMethod & RENDER_GLSL)
+ {
+ UpdateVideoFilter();
+ switch(m_renderQuality)
+ {
+ case RQ_LOW:
+ case RQ_SINGLEPASS:
+ RenderSinglePass(renderBuffer, m_currentField);
+ VerifyGLState();
+ break;
+
+ case RQ_MULTIPASS:
+ RenderToFBO(renderBuffer, m_currentField);
+ RenderFromFBO();
+ VerifyGLState();
+ break;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ AfterRenderHook(renderBuffer);
+ return true;
+}
+
+void CLinuxRendererGL::RenderSinglePass(int index, int field)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[field];
+
+ CheckVideoParameters(index);
+
+ if (m_reloadShaders)
+ {
+ m_reloadShaders = 0;
+ LoadShaders(field);
+ }
+
+ glDisable(GL_DEPTH_TEST);
+
+ // Y
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(m_textureTarget, planes[0].id);
+
+ // U
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(m_textureTarget, planes[1].id);
+
+ // V
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(m_textureTarget, planes[2].id);
+
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ m_pYUVShader->SetBlack(m_videoSettings.m_Brightness * 0.01f - 0.5f);
+ m_pYUVShader->SetContrast(m_videoSettings.m_Contrast * 0.02f);
+ m_pYUVShader->SetWidth(planes[0].texwidth);
+ m_pYUVShader->SetHeight(planes[0].texheight);
+ m_pYUVShader->SetColParams(buf.m_srcColSpace, buf.m_srcBits, !buf.m_srcFullRange, buf.m_srcTextureBits);
+ m_pYUVShader->SetDisplayMetadata(buf.hasDisplayMetadata, buf.displayMetadata,
+ buf.hasLightMetadata, buf.lightMetadata);
+ m_pYUVShader->SetToneMapParam(m_toneMapMethod, m_videoSettings.m_ToneMapParam);
+
+ //disable non-linear stretch when a dvd menu is shown, parts of the menu are rendered through the overlay renderer
+ //having non-linear stretch on breaks the alignment
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsInMenu())
+ m_pYUVShader->SetNonLinStretch(1.0);
+ else
+ m_pYUVShader->SetNonLinStretch(pow(CDisplaySettings::GetInstance().GetPixelRatio(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoNonLinStretchRatio));
+
+ if (field == FIELD_TOP)
+ m_pYUVShader->SetField(1);
+ else if(field == FIELD_BOT)
+ m_pYUVShader->SetField(0);
+
+ m_pYUVShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ m_pYUVShader->Enable();
+
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ float u2, v2;
+ float u3, v3;
+ }vertex[4];
+
+ GLint vertLoc = m_pYUVShader->GetVertexLoc();
+ GLint Yloc = m_pYUVShader->GetYcoordLoc();
+ GLint Uloc = m_pYUVShader->GetUcoordLoc();
+ GLint Vloc = m_pYUVShader->GetVcoordLoc();
+
+ // Setup vertex position values
+ for(int i = 0; i < 4; i++)
+ {
+ vertex[i].x = m_rotatedDestCoords[i].x;
+ vertex[i].y = m_rotatedDestCoords[i].y;
+ vertex[i].z = 0.0f;// set z to 0
+ }
+
+ // bottom left
+ vertex[0].u1 = planes[0].rect.x1;
+ vertex[0].v1 = planes[0].rect.y1;
+ vertex[0].u2 = planes[1].rect.x1;
+ vertex[0].v2 = planes[1].rect.y1;
+ vertex[0].u3 = planes[2].rect.x1;
+ vertex[0].v3 = planes[2].rect.y1;
+
+ // bottom right
+ vertex[1].u1 = planes[0].rect.x2;
+ vertex[1].v1 = planes[0].rect.y1;
+ vertex[1].u2 = planes[1].rect.x2;
+ vertex[1].v2 = planes[1].rect.y1;
+ vertex[1].u3 = planes[2].rect.x2;
+ vertex[1].v3 = planes[2].rect.y1;
+
+ // top right
+ vertex[2].u1 = planes[0].rect.x2;
+ vertex[2].v1 = planes[0].rect.y2;
+ vertex[2].u2 = planes[1].rect.x2;
+ vertex[2].v2 = planes[1].rect.y2;
+ vertex[2].u3 = planes[2].rect.x2;
+ vertex[2].v3 = planes[2].rect.y2;
+
+ // top left
+ vertex[3].u1 = planes[0].rect.x1;
+ vertex[3].v1 = planes[0].rect.y2;
+ vertex[3].u2 = planes[1].rect.x1;
+ vertex[3].v2 = planes[1].rect.y2;
+ vertex[3].u3 = planes[2].rect.x1;
+ vertex[3].v3 = planes[2].rect.y2;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(Yloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+ glVertexAttribPointer(Uloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u2)));
+ if (Vloc != -1)
+ glVertexAttribPointer(Vloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u3)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(Yloc);
+ glEnableVertexAttribArray(Uloc);
+ if (Vloc != -1)
+ glEnableVertexAttribArray(Vloc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+ VerifyGLState();
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(Yloc);
+ glDisableVertexAttribArray(Uloc);
+ if (Vloc != -1)
+ glDisableVertexAttribArray(Vloc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ m_pYUVShader->Disable();
+ VerifyGLState();
+
+ VerifyGLState();
+}
+
+void CLinuxRendererGL::RenderToFBO(int index, int field, bool weave /*= false*/)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[field];
+
+ CheckVideoParameters(index);
+
+ if (m_reloadShaders)
+ {
+ m_reloadShaders = 0;
+ LoadShaders(m_currentField);
+ }
+
+ if (!m_fbo.fbo.IsValid())
+ {
+ if (!m_fbo.fbo.Initialize())
+ {
+ CLog::Log(LOGERROR, "GL: Error initializing FBO");
+ return;
+ }
+
+ if (!m_fbo.fbo.CreateAndBindToTexture(GL_TEXTURE_2D, m_sourceWidth, m_sourceHeight, GL_RGBA, GL_SHORT))
+ {
+ CLog::Log(LOGERROR, "GL: Error creating texture and binding to FBO");
+ return;
+ }
+ }
+
+ glDisable(GL_DEPTH_TEST);
+
+ // Y
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(m_textureTarget, planes[0].id);
+ VerifyGLState();
+
+ // U
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(m_textureTarget, planes[1].id);
+ VerifyGLState();
+
+ // V
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(m_textureTarget, planes[2].id);
+ VerifyGLState();
+
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ // make sure the yuv shader is loaded and ready to go
+ if (!m_pYUVShader || (!m_pYUVShader->OK()))
+ {
+ CLog::Log(LOGERROR, "GL: YUV shader not active, cannot do multipass render");
+ return;
+ }
+
+ m_fbo.fbo.BeginRender();
+ VerifyGLState();
+
+ m_pYUVShader->SetBlack(m_videoSettings.m_Brightness * 0.01f - 0.5f);
+ m_pYUVShader->SetContrast(m_videoSettings.m_Contrast * 0.02f);
+ m_pYUVShader->SetWidth(planes[0].texwidth);
+ m_pYUVShader->SetHeight(planes[0].texheight);
+ m_pYUVShader->SetNonLinStretch(1.0);
+ m_pYUVShader->SetColParams(buf.m_srcColSpace, buf.m_srcBits, !buf.m_srcFullRange, buf.m_srcTextureBits);
+ m_pYUVShader->SetDisplayMetadata(buf.hasDisplayMetadata, buf.displayMetadata,
+ buf.hasLightMetadata, buf.lightMetadata);
+ m_pYUVShader->SetToneMapParam(m_toneMapMethod, m_videoSettings.m_ToneMapParam);
+
+ if (field == FIELD_TOP)
+ m_pYUVShader->SetField(1);
+ else if (field == FIELD_BOT)
+ m_pYUVShader->SetField(0);
+
+ VerifyGLState();
+
+ glMatrixModview.Push();
+ glMatrixModview->LoadIdentity();
+ glMatrixModview.Load();
+
+ glMatrixProject.Push();
+ glMatrixProject->LoadIdentity();
+ glMatrixProject->Ortho2D(0, m_sourceWidth, 0, m_sourceHeight);
+ glMatrixProject.Load();
+
+ CRect viewport;
+ m_renderSystem->GetViewPort(viewport);
+ glViewport(0, 0, m_sourceWidth, m_sourceHeight);
+ glScissor (0, 0, m_sourceWidth, m_sourceHeight);
+
+ m_pYUVShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ if (!m_pYUVShader->Enable())
+ {
+ CLog::Log(LOGERROR, "GL: Error enabling YUV shader");
+ }
+
+ m_fbo.width = planes[0].rect.x2 - planes[0].rect.x1;
+ m_fbo.height = planes[0].rect.y2 - planes[0].rect.y1;
+
+ if (m_textureTarget == GL_TEXTURE_2D)
+ {
+ m_fbo.width *= planes[0].texwidth;
+ m_fbo.height *= planes[0].texheight;
+ }
+ m_fbo.width *= planes[0].pixpertex_x;
+ m_fbo.height *= planes[0].pixpertex_y;
+ if (weave)
+ m_fbo.height *= 2;
+
+ // 1st Pass to video frame size
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ float u2, v2;
+ float u3, v3;
+ } vertex[4];
+
+ GLint vertLoc = m_pYUVShader->GetVertexLoc();
+ GLint Yloc = m_pYUVShader->GetYcoordLoc();
+ GLint Uloc = m_pYUVShader->GetUcoordLoc();
+ GLint Vloc = m_pYUVShader->GetVcoordLoc();
+
+ // top left
+ vertex[0].x = 0.0f;
+ vertex[0].y = 0.0f;
+ vertex[0].z = 0.0f;
+ vertex[0].u1 = planes[0].rect.x1;
+ vertex[0].v1 = planes[0].rect.y1;
+ vertex[0].u2 = planes[1].rect.x1;
+ vertex[0].v2 = planes[1].rect.y1;
+ vertex[0].u3 = planes[2].rect.x1;
+ vertex[0].v3 = planes[2].rect.y1;
+
+ // top right
+ vertex[1].x = m_fbo.width;
+ vertex[1].y = 0.0f;
+ vertex[1].z = 0.0f;
+ vertex[1].u1 = planes[0].rect.x2;
+ vertex[1].v1 = planes[0].rect.y1;
+ vertex[1].u2 = planes[1].rect.x2;
+ vertex[1].v2 = planes[1].rect.y1;
+ vertex[1].u3 = planes[2].rect.x2;
+ vertex[1].v3 = planes[2].rect.y1;
+
+ // bottom right
+ vertex[2].x = m_fbo.width;
+ vertex[2].y = m_fbo.height;
+ vertex[2].z = 0.0f;
+ vertex[2].u1 = planes[0].rect.x2;
+ vertex[2].v1 = planes[0].rect.y2;
+ vertex[2].u2 = planes[1].rect.x2;
+ vertex[2].v2 = planes[1].rect.y2;
+ vertex[2].u3 = planes[2].rect.x2;
+ vertex[2].v3 = planes[2].rect.y2;
+
+ // bottom left
+ vertex[3].x = 0.0f;
+ vertex[3].y = m_fbo.height;
+ vertex[3].z = 0.0f;
+ vertex[3].u1 = planes[0].rect.x1;
+ vertex[3].v1 = planes[0].rect.y2;
+ vertex[3].u2 = planes[1].rect.x1;
+ vertex[3].v2 = planes[1].rect.y2;
+ vertex[3].u3 = planes[2].rect.x1;
+ vertex[3].v3 = planes[2].rect.y2;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(Yloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+ glVertexAttribPointer(Uloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u2)));
+ if (Vloc != -1)
+ glVertexAttribPointer(Vloc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u3)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(Yloc);
+ glEnableVertexAttribArray(Uloc);
+ if (Vloc != -1)
+ glEnableVertexAttribArray(Vloc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+ VerifyGLState();
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(Yloc);
+ glDisableVertexAttribArray(Uloc);
+ if (Vloc != -1)
+ glDisableVertexAttribArray(Vloc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ m_pYUVShader->Disable();
+
+ glMatrixModview.PopLoad();
+ glMatrixProject.PopLoad();
+
+ m_renderSystem->SetViewPort(viewport);
+
+ m_fbo.fbo.EndRender();
+
+ VerifyGLState();
+}
+
+void CLinuxRendererGL::RenderFromFBO()
+{
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ // Use regular normalized texture coordinates
+ // 2nd Pass to screen size with optional video filter
+
+ if (!m_pVideoFilterShader)
+ {
+ CLog::Log(LOGERROR, "CLinuxRendererGL::RenderFromFBO - no videofilter shader");
+ return;
+ }
+
+ GLint filter;
+ if (!m_pVideoFilterShader->GetTextureFilter(filter))
+ filter = m_scalingMethod == VS_SCALINGMETHOD_NEAREST ? GL_NEAREST : GL_LINEAR;
+
+ m_fbo.fbo.SetFiltering(GL_TEXTURE_2D, filter);
+ m_pVideoFilterShader->SetSourceTexture(0);
+ m_pVideoFilterShader->SetWidth(m_sourceWidth);
+ m_pVideoFilterShader->SetHeight(m_sourceHeight);
+
+ //disable non-linear stretch when a dvd menu is shown, parts of the menu are rendered through the overlay renderer
+ //having non-linear stretch on breaks the alignment
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsInMenu())
+ m_pVideoFilterShader->SetNonLinStretch(1.0);
+ else
+ m_pVideoFilterShader->SetNonLinStretch(pow(CDisplaySettings::GetInstance().GetPixelRatio(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoNonLinStretchRatio));
+
+ m_pVideoFilterShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ m_pVideoFilterShader->Enable();
+
+ VerifyGLState();
+
+ float imgwidth = m_fbo.width / m_sourceWidth;
+ float imgheight = m_fbo.height / m_sourceHeight;
+
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ GLint vertLoc = m_pVideoFilterShader->GetVertexLoc();
+ GLint loc = m_pVideoFilterShader->GetCoordLoc();
+
+ // Setup vertex position values
+ // top left
+ vertex[0].x = m_rotatedDestCoords[0].x;
+ vertex[0].y = m_rotatedDestCoords[0].y;
+ vertex[0].z = 0.0f;
+ vertex[0].u1 = 0.0;
+ vertex[0].v1 = 0.0;
+
+ // top right
+ vertex[1].x = m_rotatedDestCoords[1].x;
+ vertex[1].y = m_rotatedDestCoords[1].y;
+ vertex[1].z = 0.0f;
+ vertex[1].u1 = imgwidth;
+ vertex[1].v1 = 0.0f;
+
+ // bottom right
+ vertex[2].x = m_rotatedDestCoords[2].x;
+ vertex[2].y = m_rotatedDestCoords[2].y;
+ vertex[2].z = 0.0f;
+ vertex[2].u1 = imgwidth;
+ vertex[2].v1 = imgheight;
+
+ // bottom left
+ vertex[3].x = m_rotatedDestCoords[3].x;
+ vertex[3].y = m_rotatedDestCoords[3].y;
+ vertex[3].z = 0.0f;
+ vertex[3].u1 = 0.0f;
+ vertex[3].v1 = imgheight;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+ VerifyGLState();
+
+ glDisableVertexAttribArray(loc);
+ glDisableVertexAttribArray(vertLoc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ m_pVideoFilterShader->Disable();
+
+ VerifyGLState();
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ VerifyGLState();
+}
+
+void CLinuxRendererGL::RenderProgressiveWeave(int index, int field)
+{
+ bool scale = (int)m_sourceHeight != m_destRect.Height() ||
+ (int)m_sourceWidth != m_destRect.Width();
+
+ if (m_fbo.fbo.IsSupported() && (scale || m_renderQuality == RQ_MULTIPASS))
+ {
+ glEnable(GL_POLYGON_STIPPLE);
+ glPolygonStipple(stipple_weave);
+ RenderToFBO(index, FIELD_TOP, true);
+ glPolygonStipple(stipple_weave+4);
+ RenderToFBO(index, FIELD_BOT, true);
+ glDisable(GL_POLYGON_STIPPLE);
+ RenderFromFBO();
+ }
+ else
+ {
+ glEnable(GL_POLYGON_STIPPLE);
+ glPolygonStipple(stipple_weave);
+ RenderSinglePass(index, FIELD_TOP);
+ glPolygonStipple(stipple_weave+4);
+ RenderSinglePass(index, FIELD_BOT);
+ glDisable(GL_POLYGON_STIPPLE);
+ }
+}
+
+void CLinuxRendererGL::RenderRGB(int index, int field)
+{
+ CYuvPlane &plane = m_buffers[index].fields[FIELD_FULL][0];
+
+ glActiveTexture(GL_TEXTURE0);
+
+ glBindTexture(m_textureTarget, plane.id);
+
+ // make sure we know the correct texture size
+ GetPlaneTextureSize(plane);
+
+ if (!m_pVideoFilterShader)
+ {
+ CLog::Log(LOGERROR, "CLinuxRendererGL::RenderRGB - no videofilter shader");
+ return;
+ }
+
+ GLint filter;
+ if (!m_pVideoFilterShader->GetTextureFilter(filter))
+ filter = m_scalingMethod == VS_SCALINGMETHOD_NEAREST ? GL_NEAREST : GL_LINEAR;
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, filter);
+ m_pVideoFilterShader->SetSourceTexture(0);
+ m_pVideoFilterShader->SetWidth(m_sourceWidth);
+ m_pVideoFilterShader->SetHeight(m_sourceHeight);
+
+ //disable non-linear stretch when a dvd menu is shown, parts of the menu are rendered through the overlay renderer
+ //having non-linear stretch on breaks the alignment
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsInMenu())
+ m_pVideoFilterShader->SetNonLinStretch(1.0);
+ else
+ m_pVideoFilterShader->SetNonLinStretch(pow(CDisplaySettings::GetInstance().GetPixelRatio(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoNonLinStretchRatio));
+
+ m_pVideoFilterShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ m_pVideoFilterShader->Enable();
+
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ GLint vertLoc = m_pVideoFilterShader->GetVertexLoc();
+ GLint loc = m_pVideoFilterShader->GetCoordLoc();
+
+ // Setup vertex position values
+ // top left
+ vertex[0].x = m_rotatedDestCoords[0].x;
+ vertex[0].y = m_rotatedDestCoords[0].y;
+ vertex[0].z = 0.0f;
+ vertex[0].u1 = plane.rect.x1;
+ vertex[0].v1 = plane.rect.y1;
+
+ // top right
+ vertex[1].x = m_rotatedDestCoords[1].x;
+ vertex[1].y = m_rotatedDestCoords[1].y;
+ vertex[1].z = 0.0f;
+ vertex[1].u1 = plane.rect.x2;
+ vertex[1].v1 = plane.rect.y1;
+
+ // bottom right
+ vertex[2].x = m_rotatedDestCoords[2].x;
+ vertex[2].y = m_rotatedDestCoords[2].y;
+ vertex[2].z = 0.0f;
+ vertex[2].u1 = plane.rect.x2;
+ vertex[2].v1 = plane.rect.y2;
+
+ // bottom left
+ vertex[3].x = m_rotatedDestCoords[3].x;
+ vertex[3].y = m_rotatedDestCoords[3].y;
+ vertex[3].z = 0.0f;
+ vertex[3].u1 = plane.rect.x1;
+ vertex[3].v1 = plane.rect.y2;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ VerifyGLState();
+
+ m_pVideoFilterShader->Disable();
+
+ glBindTexture(m_textureTarget, 0);
+}
+
+bool CLinuxRendererGL::RenderCapture(CRenderCapture* capture)
+{
+ if (!m_bValidated)
+ return false;
+
+ // save current video rect
+ CRect saveSize = m_destRect;
+
+ saveRotatedCoords();//backup current m_rotatedDestCoords
+
+ // new video rect is capture size
+ m_destRect.SetRect(0, 0, (float)capture->GetWidth(), (float)capture->GetHeight());
+ MarkDirty();
+ syncDestRectToRotatedPoints();//syncs the changed destRect to m_rotatedDestCoords
+
+ //invert Y axis to get non-inverted image
+ glDisable(GL_BLEND);
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ glMatrixModview.Push();
+ glMatrixModview->Translatef(0.0f, capture->GetHeight(), 0.0f);
+ glMatrixModview->Scalef(1.0f, -1.0f, 1.0f);
+ glMatrixModview.Load();
+
+ capture->BeginRender();
+
+ Render(RENDER_FLAG_NOOSD, m_iYV12RenderBuffer);
+ // read pixels
+ glReadPixels(0, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() - capture->GetHeight(), capture->GetWidth(), capture->GetHeight(),
+ GL_BGRA, GL_UNSIGNED_BYTE, capture->GetRenderBuffer());
+
+ capture->EndRender();
+
+ // revert model view matrix
+ glMatrixModview.PopLoad();
+
+ // restore original video rect
+ m_destRect = saveSize;
+ restoreRotatedCoords();//restores the previous state of the rotated dest coords
+
+ return true;
+}
+
+
+GLint CLinuxRendererGL::GetInternalFormat(GLint format, int bpp)
+{
+ unsigned int major, minor;
+ m_renderSystem->GetRenderVersion(major, minor);
+ if (bpp == 2)
+ {
+ if (format == GL_RED)
+ {
+ if (major > 2)
+ return GL_R16;
+ else
+ return GL_LUMINANCE16;
+ }
+ }
+ else
+ {
+ if (format == GL_RED)
+ {
+ if (major > 2)
+ return GL_RED;
+ else
+ return GL_LUMINANCE;
+ }
+ }
+
+ return format;
+}
+
+//-----------------------------------------------------------------------------
+// Textures
+//-----------------------------------------------------------------------------
+
+bool CLinuxRendererGL::CreateTexture(int index)
+{
+ if (m_format == AV_PIX_FMT_NV12)
+ return CreateNV12Texture(index);
+ else if (m_format == AV_PIX_FMT_YUYV422 ||
+ m_format == AV_PIX_FMT_UYVY422)
+ return CreateYUV422PackedTexture(index);
+ else
+ return CreateYV12Texture(index);
+}
+
+void CLinuxRendererGL::DeleteTexture(int index)
+{
+ CPictureBuffer& buf = m_buffers[index];
+ buf.loaded = false;
+
+ if (m_format == AV_PIX_FMT_NV12)
+ DeleteNV12Texture(index);
+ else if (m_format == AV_PIX_FMT_YUYV422 ||
+ m_format == AV_PIX_FMT_UYVY422)
+ DeleteYUV422PackedTexture(index);
+ else
+ DeleteYV12Texture(index);
+}
+
+bool CLinuxRendererGL::UploadTexture(int index)
+{
+ if (!m_buffers[index].videoBuffer)
+ return false;
+
+ bool ret = true;
+
+ if (!m_buffers[index].loaded)
+ {
+ YuvImage &dst = m_buffers[index].image;
+ YuvImage src;
+ m_buffers[index].videoBuffer->GetPlanes(src.plane);
+ m_buffers[index].videoBuffer->GetStrides(src.stride);
+
+ UnBindPbo(m_buffers[index]);
+
+ if (m_format == AV_PIX_FMT_NV12)
+ {
+ CVideoBuffer::CopyNV12Picture(&dst, &src);
+ BindPbo(m_buffers[index]);
+ ret = UploadNV12Texture(index);
+ }
+ else if (m_format == AV_PIX_FMT_YUYV422 ||
+ m_format == AV_PIX_FMT_UYVY422)
+ {
+ CVideoBuffer::CopyYUV422PackedPicture(&dst, &src);
+ BindPbo(m_buffers[index]);
+ ret = UploadYUV422PackedTexture(index);
+ }
+ else
+ {
+ CVideoBuffer::CopyPicture(&dst, &src);
+ BindPbo(m_buffers[index]);
+ ret = UploadYV12Texture(index);
+ }
+
+ if (ret)
+ m_buffers[index].loaded = true;
+ }
+
+ if (ret)
+ CalculateTextureSourceRects(index, 3);
+
+ return ret;
+}
+
+//********************************************************************************************************
+// YV12 Texture creation, deletion, copying + clearing
+//********************************************************************************************************
+
+bool CLinuxRendererGL::CreateYV12Texture(int index)
+{
+ /* since we also want the field textures, pitch must be texture aligned */
+ unsigned p;
+
+ CPictureBuffer &buf = m_buffers[index];
+ YuvImage &im = m_buffers[index].image;
+ GLuint *pbo = m_buffers[index].pbo;
+
+ DeleteYV12Texture(index);
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+
+ switch (m_format)
+ {
+ case AV_PIX_FMT_YUV420P16:
+ buf.m_srcTextureBits = 16;
+ break;
+ case AV_PIX_FMT_YUV420P14:
+ buf.m_srcTextureBits = 14;
+ break;
+ case AV_PIX_FMT_YUV420P12:
+ buf.m_srcTextureBits = 12;
+ break;
+ case AV_PIX_FMT_YUV420P10:
+ buf.m_srcTextureBits = 10;
+ break;
+ case AV_PIX_FMT_YUV420P9:
+ buf.m_srcTextureBits = 9;
+ break;
+ default:
+ break;
+ }
+ if (buf.m_srcTextureBits > 8)
+ im.bpp = 2;
+ else
+ im.bpp = 1;
+
+ im.stride[0] = im.bpp * im.width;
+ im.stride[1] = im.bpp * (im.width >> im.cshift_x);
+ im.stride[2] = im.bpp * (im.width >> im.cshift_x);
+
+ im.planesize[0] = im.stride[0] * im.height;
+ im.planesize[1] = im.stride[1] * (im.height >> im.cshift_y);
+ im.planesize[2] = im.stride[2] * (im.height >> im.cshift_y);
+
+ bool pboSetup = false;
+ if (m_pboUsed)
+ {
+ pboSetup = true;
+ glGenBuffers(3, pbo);
+
+ for (int i = 0; i < 3; i++)
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[i]);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, im.planesize[i] + PBO_OFFSET, 0, GL_STREAM_DRAW);
+ void* pboPtr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+ if (pboPtr)
+ {
+ im.plane[i] = (uint8_t*) pboPtr + PBO_OFFSET;
+ memset(im.plane[i], 0, im.planesize[i]);
+ }
+ else
+ {
+ CLog::Log(LOGWARNING,"GL: failed to set up pixel buffer object");
+ pboSetup = false;
+ break;
+ }
+ }
+
+ if (!pboSetup)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[i]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ }
+ glDeleteBuffers(3, pbo);
+ memset(m_buffers[index].pbo, 0, sizeof(m_buffers[index].pbo));
+ }
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ if (!pboSetup)
+ {
+ for (int i = 0; i < 3; i++)
+ im.plane[i] = new uint8_t[im.planesize[i]];
+ }
+
+ for(int f = 0;f<MAX_FIELDS;f++)
+ {
+ for(p = 0;p<YuvImage::MAX_PLANES;p++)
+ {
+ if (!glIsTexture(m_buffers[index].fields[f][p].id))
+ {
+ glGenTextures(1, &m_buffers[index].fields[f][p].id);
+ VerifyGLState();
+ }
+ m_buffers[index].fields[f][p].pbo = pbo[p];
+ }
+ }
+
+ // YUV
+ for (int f = FIELD_FULL; f<=FIELD_BOT ; f++)
+ {
+ int fieldshift = (f==FIELD_FULL) ? 0 : 1;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[f];
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height >> fieldshift;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[2].texheight = planes[0].texheight >> im.cshift_y;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ for (int p = 0; p < 3; p++)
+ {
+ CYuvPlane &plane = planes[p];
+ if (plane.texwidth * plane.texheight == 0)
+ continue;
+
+ glBindTexture(m_textureTarget, plane.id);
+ GLint internalformat;
+ internalformat = GetInternalFormat(GL_RED, im.bpp);
+ if (im.bpp == 2)
+ glTexImage2D(m_textureTarget, 0, internalformat, plane.texwidth, plane.texheight, 0, GL_RED, GL_UNSIGNED_SHORT, NULL);
+ else
+ glTexImage2D(m_textureTarget, 0, internalformat, plane.texwidth, plane.texheight, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ VerifyGLState();
+ }
+ }
+ return true;
+}
+
+bool CLinuxRendererGL::UploadYV12Texture(int source)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ bool deinterlacing;
+ if (m_currentField == FIELD_FULL)
+ deinterlacing = false;
+ else
+ deinterlacing = true;
+
+ VerifyGLState();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+
+ if (deinterlacing)
+ {
+ // Load Even Y Field
+ LoadPlane(buf.fields[FIELD_TOP][0] , GL_RED,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0] );
+
+ //load Odd Y Field
+ LoadPlane(buf.fields[FIELD_BOT][0], GL_RED,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0] + im->stride[0]);
+
+ // Load Even U & V Fields
+ LoadPlane(buf.fields[FIELD_TOP][1], GL_RED,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1]);
+
+ LoadPlane(buf.fields[FIELD_TOP][2], GL_RED,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[2]*2, im->bpp, im->plane[2]);
+
+ // Load Odd U & V Fields
+ LoadPlane(buf.fields[FIELD_BOT][1], GL_RED,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1] + im->stride[1]);
+
+ LoadPlane(buf.fields[FIELD_BOT][2], GL_RED,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[2]*2, im->bpp, im->plane[2] + im->stride[2]);
+ }
+ else
+ {
+ //Load Y plane
+ LoadPlane(buf.fields[FIELD_FULL][0], GL_RED,
+ im->width, im->height,
+ im->stride[0], im->bpp, im->plane[0]);
+
+ //load U plane
+ LoadPlane(buf.fields[FIELD_FULL][1], GL_RED,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[1], im->bpp, im->plane[1]);
+
+ //load V plane
+ LoadPlane(buf.fields[FIELD_FULL][2], GL_RED,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[2], im->bpp, im->plane[2]);
+ }
+
+ VerifyGLState();
+
+ return true;
+}
+
+void CLinuxRendererGL::DeleteYV12Texture(int index)
+{
+ YuvImage &im = m_buffers[index].image;
+ GLuint *pbo = m_buffers[index].pbo;
+
+ if (m_buffers[index].fields[FIELD_FULL][0].id == 0)
+ return;
+
+ /* finish up all textures, and delete them */
+ for(int f = 0;f<MAX_FIELDS;f++)
+ {
+ for(int p = 0;p<YuvImage::MAX_PLANES;p++)
+ {
+ if (m_buffers[index].fields[f][p].id)
+ {
+ if (glIsTexture(m_buffers[index].fields[f][p].id))
+ glDeleteTextures(1, &m_buffers[index].fields[f][p].id);
+ m_buffers[index].fields[f][p].id = 0;
+ }
+ }
+ }
+
+ for(int p = 0;p<YuvImage::MAX_PLANES;p++)
+ {
+ if (pbo[p])
+ {
+ if (im.plane[p])
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[p]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ im.plane[p] = NULL;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ glDeleteBuffers(1, pbo + p);
+ pbo[p] = 0;
+ }
+ else
+ {
+ if (im.plane[p])
+ {
+ delete[] im.plane[p];
+ im.plane[p] = NULL;
+ }
+ }
+ }
+}
+
+//********************************************************************************************************
+// NV12 Texture loading, creation and deletion
+//********************************************************************************************************
+bool CLinuxRendererGL::UploadNV12Texture(int source)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ bool deinterlacing;
+ if (m_currentField == FIELD_FULL)
+ deinterlacing = false;
+ else
+ deinterlacing = true;
+
+ VerifyGLState();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, im->bpp);
+
+ if (deinterlacing)
+ {
+ // Load Odd Y field
+ LoadPlane(buf.fields[FIELD_TOP][0] , GL_RED,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0]);
+
+ // Load Even Y field
+ LoadPlane(buf.fields[FIELD_BOT][0], GL_RED,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0] + im->stride[0]) ;
+
+ // Load Odd UV Fields
+ LoadPlane(buf.fields[FIELD_TOP][1], GL_RG,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1]);
+
+ // Load Even UV Fields
+ LoadPlane(buf.fields[FIELD_BOT][1], GL_RG,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1] + im->stride[1]);
+
+ }
+ else
+ {
+ // Load Y plane
+ LoadPlane(buf. fields[FIELD_FULL][0], GL_RED,
+ im->width, im->height,
+ im->stride[0], im->bpp, im->plane[0]);
+
+ // Load UV plane
+ LoadPlane(buf.fields[FIELD_FULL][1], GL_RG,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[1], im->bpp, im->plane[1]);
+ }
+
+ VerifyGLState();
+
+ return true;
+}
+
+bool CLinuxRendererGL::CreateNV12Texture(int index)
+{
+ // since we also want the field textures, pitch must be texture aligned
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ GLuint *pbo = buf.pbo;
+
+ // Delete any old texture
+ DeleteNV12Texture(index);
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+ im.bpp = 1;
+
+ im.stride[0] = im.width;
+ im.stride[1] = im.width;
+ im.stride[2] = 0;
+
+ im.plane[0] = NULL;
+ im.plane[1] = NULL;
+ im.plane[2] = NULL;
+
+ // Y plane
+ im.planesize[0] = im.stride[0] * im.height;
+ // packed UV plane
+ im.planesize[1] = im.stride[1] * im.height / 2;
+ // third plane is not used
+ im.planesize[2] = 0;
+
+ bool pboSetup = false;
+ if (m_pboUsed)
+ {
+ pboSetup = true;
+ glGenBuffers(2, pbo);
+
+ for (int i = 0; i < 2; i++)
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[i]);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, im.planesize[i] + PBO_OFFSET, 0, GL_STREAM_DRAW);
+ void* pboPtr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+ if (pboPtr)
+ {
+ im.plane[i] = (uint8_t*)pboPtr + PBO_OFFSET;
+ memset(im.plane[i], 0, im.planesize[i]);
+ }
+ else
+ {
+ CLog::Log(LOGWARNING,"GL: failed to set up pixel buffer object");
+ pboSetup = false;
+ break;
+ }
+ }
+
+ if (!pboSetup)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[i]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ }
+ glDeleteBuffers(2, pbo);
+ memset(m_buffers[index].pbo, 0, sizeof(m_buffers[index].pbo));
+ }
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ if (!pboSetup)
+ {
+ for (int i = 0; i < 2; i++)
+ im.plane[i] = new uint8_t[im.planesize[i]];
+ }
+
+ for(int f = 0;f<MAX_FIELDS;f++)
+ {
+ for(int p = 0;p<2;p++)
+ {
+ if (!glIsTexture(buf.fields[f][p].id))
+ {
+ glGenTextures(1, &buf.fields[f][p].id);
+ VerifyGLState();
+ }
+ buf.fields[f][p].pbo = pbo[p];
+ }
+ buf.fields[f][2].id = buf.fields[f][1].id;
+ }
+
+ // YUV
+ for (int f = FIELD_FULL; f<=FIELD_BOT ; f++)
+ {
+ int fieldshift = (f==FIELD_FULL) ? 0 : 1;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[f];
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height >> fieldshift;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ for(int p = 0; p < 2; p++)
+ {
+ CYuvPlane &plane = planes[p];
+ if (plane.texwidth * plane.texheight == 0)
+ continue;
+
+ glBindTexture(m_textureTarget, plane.id);
+ if (p == 1)
+ glTexImage2D(m_textureTarget, 0, GL_RG, plane.texwidth, plane.texheight, 0, GL_RG, GL_UNSIGNED_BYTE, NULL);
+ else
+ glTexImage2D(m_textureTarget, 0, GL_RED, plane.texwidth, plane.texheight, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ VerifyGLState();
+ }
+ }
+
+ return true;
+}
+
+void CLinuxRendererGL::DeleteNV12Texture(int index)
+{
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ GLuint *pbo = buf.pbo;
+
+ if (buf.fields[FIELD_FULL][0].id == 0)
+ return;
+
+ // finish up all textures, and delete them
+ for(int f = 0;f<MAX_FIELDS;f++)
+ {
+ for(int p = 0;p<2;p++)
+ {
+ if (buf.fields[f][p].id)
+ {
+ if (glIsTexture(buf.fields[f][p].id))
+ {
+ glDeleteTextures(1, &buf.fields[f][p].id);
+ }
+ buf.fields[f][p].id = 0;
+ }
+ }
+ buf.fields[f][2].id = 0;
+ }
+
+ for(int p = 0;p<2;p++)
+ {
+ if (pbo[p])
+ {
+ if (im.plane[p])
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[p]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ im.plane[p] = NULL;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ glDeleteBuffers(1, pbo + p);
+ pbo[p] = 0;
+ }
+ else
+ {
+ if (im.plane[p])
+ {
+ delete[] im.plane[p];
+ im.plane[p] = NULL;
+ }
+ }
+ }
+}
+
+bool CLinuxRendererGL::UploadYUV422PackedTexture(int source)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ bool deinterlacing;
+ if (m_currentField == FIELD_FULL)
+ deinterlacing = false;
+ else
+ deinterlacing = true;
+
+ VerifyGLState();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+
+ if (deinterlacing)
+ {
+ // Load YUYV fields
+ LoadPlane(buf.fields[FIELD_TOP][0], GL_BGRA,
+ im->width / 2, im->height >> 1,
+ im->stride[0] * 2, im->bpp, im->plane[0]);
+
+ LoadPlane(buf.fields[FIELD_BOT][0], GL_BGRA,
+ im->width / 2, im->height >> 1,
+ im->stride[0] * 2, im->bpp, im->plane[0] + im->stride[0]);
+ }
+ else
+ {
+ // Load YUYV plane
+ LoadPlane(buf.fields[FIELD_FULL][0], GL_BGRA,
+ im->width / 2, im->height,
+ im->stride[0], im->bpp, im->plane[0]);
+ }
+
+ VerifyGLState();
+
+ return true;
+}
+
+void CLinuxRendererGL::DeleteYUV422PackedTexture(int index)
+{
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ GLuint *pbo = buf.pbo;
+
+ if (buf.fields[FIELD_FULL][0].id == 0)
+ return;
+
+ // finish up all textures, and delete them
+ for (int f = 0;f<MAX_FIELDS;f++)
+ {
+ if (buf.fields[f][0].id)
+ {
+ if (glIsTexture(buf.fields[f][0].id))
+ {
+ glDeleteTextures(1, &buf.fields[f][0].id);
+ }
+ buf.fields[f][0].id = 0;
+ }
+ buf.fields[f][1].id = 0;
+ buf.fields[f][2].id = 0;
+ }
+
+ if (pbo[0])
+ {
+ if (im.plane[0])
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[0]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ im.plane[0] = NULL;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ glDeleteBuffers(1, pbo);
+ pbo[0] = 0;
+ }
+ else
+ {
+ if (im.plane[0])
+ {
+ delete[] im.plane[0];
+ im.plane[0] = NULL;
+ }
+ }
+}
+
+bool CLinuxRendererGL::CreateYUV422PackedTexture(int index)
+{
+ // since we also want the field textures, pitch must be texture aligned
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+ GLuint *pbo = buf.pbo;
+
+ // Delete any old texture
+ DeleteYUV422PackedTexture(index);
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 0;
+ im.cshift_y = 0;
+ im.bpp = 1;
+
+ im.stride[0] = im.width * 2;
+ im.stride[1] = 0;
+ im.stride[2] = 0;
+
+ im.plane[0] = NULL;
+ im.plane[1] = NULL;
+ im.plane[2] = NULL;
+
+ // packed YUYV plane
+ im.planesize[0] = im.stride[0] * im.height;
+ // second plane is not used
+ im.planesize[1] = 0;
+ // third plane is not used
+ im.planesize[2] = 0;
+
+ bool pboSetup = false;
+ if (m_pboUsed)
+ {
+ pboSetup = true;
+ glGenBuffers(1, pbo);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[0]);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, im.planesize[0] + PBO_OFFSET, 0, GL_STREAM_DRAW);
+ void* pboPtr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+ if (pboPtr)
+ {
+ im.plane[0] = (uint8_t*)pboPtr + PBO_OFFSET;
+ memset(im.plane[0], 0, im.planesize[0]);
+ }
+ else
+ {
+ CLog::Log(LOGWARNING,"GL: failed to set up pixel buffer object");
+ pboSetup = false;
+ }
+
+ if (!pboSetup)
+ {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, *pbo);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ glDeleteBuffers(1, pbo);
+ memset(m_buffers[index].pbo, 0, sizeof(m_buffers[index].pbo));
+ }
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ if (!pboSetup)
+ {
+ im.plane[0] = new uint8_t[im.planesize[0]];
+ }
+
+ for(int f = 0;f<MAX_FIELDS;f++)
+ {
+ if (!glIsTexture(buf.fields[f][0].id))
+ {
+ glGenTextures(1, &buf.fields[f][0].id);
+ VerifyGLState();
+ }
+ buf.fields[f][0].pbo = pbo[0];
+ buf.fields[f][1].id = buf.fields[f][0].id;
+ buf.fields[f][2].id = buf.fields[f][1].id;
+ }
+
+ // YUV
+ for (int f = FIELD_FULL; f<=FIELD_BOT ; f++)
+ {
+ int fieldshift = (f==FIELD_FULL) ? 0 : 1;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[f];
+
+ planes[0].texwidth = im.width / 2;
+ planes[0].texheight = im.height >> fieldshift;
+ planes[1].texwidth = planes[0].texwidth;
+ planes[1].texheight = planes[0].texheight;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 2;
+ planes[p].pixpertex_y = 1;
+ }
+
+ CYuvPlane &plane = planes[0];
+ if (plane.texwidth * plane.texheight == 0)
+ continue;
+
+ glBindTexture(m_textureTarget, plane.id);
+
+ glTexImage2D(m_textureTarget, 0, GL_RGBA, plane.texwidth, plane.texheight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ VerifyGLState();
+ }
+
+ return true;
+}
+
+void CLinuxRendererGL::SetTextureFilter(GLenum method)
+{
+ for (int i = 0 ; i<m_NumYV12Buffers ; i++)
+ {
+ CPictureBuffer& buf = m_buffers[i];
+
+ for (int f = FIELD_FULL; f<=FIELD_BOT ; f++)
+ {
+ for (int p = 0; p < 3; p++)
+ {
+ if(glIsTexture(buf.fields[f][p].id))
+ {
+ glBindTexture(m_textureTarget, buf.fields[f][p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, method);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, method);
+ VerifyGLState();
+ }
+ }
+ }
+ }
+}
+
+bool CLinuxRendererGL::Supports(ERENDERFEATURE feature) const
+{
+ if (feature == RENDERFEATURE_STRETCH ||
+ feature == RENDERFEATURE_NONLINSTRETCH ||
+ feature == RENDERFEATURE_ZOOM ||
+ feature == RENDERFEATURE_VERTICAL_SHIFT ||
+ feature == RENDERFEATURE_PIXEL_RATIO ||
+ feature == RENDERFEATURE_POSTPROCESS ||
+ feature == RENDERFEATURE_ROTATION ||
+ feature == RENDERFEATURE_BRIGHTNESS ||
+ feature == RENDERFEATURE_CONTRAST ||
+ feature == RENDERFEATURE_TONEMAP)
+ return true;
+
+ return false;
+}
+
+bool CLinuxRendererGL::SupportsMultiPassRendering()
+{
+ return m_renderSystem->IsExtSupported("GL_EXT_framebuffer_object");
+}
+
+bool CLinuxRendererGL::Supports(ESCALINGMETHOD method) const
+{
+ //nearest neighbor doesn't work on YUY2 and UYVY
+ if (method == VS_SCALINGMETHOD_NEAREST &&
+ m_format != AV_PIX_FMT_YUYV422 &&
+ m_format != AV_PIX_FMT_UYVY422)
+ return true;
+
+ if (method == VS_SCALINGMETHOD_LINEAR ||
+ method == VS_SCALINGMETHOD_AUTO)
+ return true;
+
+ if (method == VS_SCALINGMETHOD_CUBIC_B_SPLINE ||
+ method == VS_SCALINGMETHOD_CUBIC_MITCHELL ||
+ method == VS_SCALINGMETHOD_CUBIC_CATMULL ||
+ method == VS_SCALINGMETHOD_CUBIC_0_075 ||
+ method == VS_SCALINGMETHOD_CUBIC_0_1 ||
+ method == VS_SCALINGMETHOD_LANCZOS2 ||
+ method == VS_SCALINGMETHOD_SPLINE36_FAST ||
+ method == VS_SCALINGMETHOD_LANCZOS3_FAST ||
+ method == VS_SCALINGMETHOD_SPLINE36 ||
+ method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ // if scaling is below level, avoid hq scaling
+ float scaleX = fabs(((float)m_sourceWidth - m_destRect.Width())/m_sourceWidth)*100;
+ float scaleY = fabs(((float)m_sourceHeight - m_destRect.Height())/m_sourceHeight)*100;
+ int minScale = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_HQSCALERS);
+ if (scaleX < minScale && scaleY < minScale)
+ return false;
+
+ bool hasFramebuffer = false;
+ unsigned int major, minor;
+ m_renderSystem->GetRenderVersion(major, minor);
+ if (major > 3 ||
+ (major == 3 && minor >= 2))
+ hasFramebuffer = true;
+ if (m_renderSystem->IsExtSupported("GL_EXT_framebuffer_object"))
+ hasFramebuffer = true;
+ if (hasFramebuffer && (m_renderMethod & RENDER_GLSL))
+ return true;
+ }
+
+ return false;
+}
+
+void CLinuxRendererGL::BindPbo(CPictureBuffer& buff)
+{
+ bool pbo = false;
+ for(int plane = 0; plane < YuvImage::MAX_PLANES; plane++)
+ {
+ if(!buff.pbo[plane] || buff.image.plane[plane] == (uint8_t*)PBO_OFFSET)
+ continue;
+ pbo = true;
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buff.pbo[plane]);
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ buff.image.plane[plane] = (uint8_t*)PBO_OFFSET;
+ }
+ if (pbo)
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+void CLinuxRendererGL::UnBindPbo(CPictureBuffer& buff)
+{
+ bool pbo = false;
+ for(int plane = 0; plane < YuvImage::MAX_PLANES; plane++)
+ {
+ if(!buff.pbo[plane] || buff.image.plane[plane] != (uint8_t*)PBO_OFFSET)
+ continue;
+ pbo = true;
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buff.pbo[plane]);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, buff.image.planesize[plane] + PBO_OFFSET, NULL, GL_STREAM_DRAW);
+ buff.image.plane[plane] = (uint8_t*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) + PBO_OFFSET;
+ }
+ if (pbo)
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+CRenderInfo CLinuxRendererGL::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = NUM_BUFFERS;
+ return info;
+}
+
+// Color management helpers
+
+bool CLinuxRendererGL::LoadCLUT()
+{
+ DeleteCLUT();
+
+ int clutSize, dataSize;
+ if (!CColorManager::Get3dLutSize(CMS_DATA_FMT_RGB, &clutSize, &dataSize))
+ return false;
+
+ // allocate buffer
+ m_CLUTsize = clutSize;
+ m_CLUT = static_cast<uint16_t*>(malloc(dataSize));
+
+ // load 3DLUT
+ if (!m_ColorManager->GetVideo3dLut(m_srcPrimaries, &m_cmsToken, CMS_DATA_FMT_RGB, m_CLUTsize,
+ m_CLUT))
+ {
+ free(m_CLUT);
+ CLog::Log(LOGERROR, "Error loading the LUT");
+ return false;
+ }
+
+ // create 3DLUT texture
+ CLog::Log(LOGDEBUG, "LinuxRendererGL: creating 3DLUT");
+ glGenTextures(1, &m_tCLUTTex);
+ glActiveTexture(GL_TEXTURE4);
+ if (m_tCLUTTex <= 0)
+ {
+ CLog::Log(LOGERROR, "Error creating 3DLUT texture");
+ return false;
+ }
+
+ // bind and set 3DLUT texture parameters
+ glBindTexture(GL_TEXTURE_3D, m_tCLUTTex);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ // load 3DLUT data
+ glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16, m_CLUTsize, m_CLUTsize, m_CLUTsize, 0, GL_RGB,
+ GL_UNSIGNED_SHORT, m_CLUT);
+ free(m_CLUT);
+ glActiveTexture(GL_TEXTURE0);
+ return true;
+}
+
+void CLinuxRendererGL::DeleteCLUT()
+{
+ if (m_tCLUTTex)
+ {
+ CLog::Log(LOGDEBUG, "LinuxRendererGL: deleting 3DLUT");
+ glDeleteTextures(1, &m_tCLUTTex);
+ m_tCLUTTex = 0;
+ }
+}
+
+void CLinuxRendererGL::CheckVideoParameters(int index)
+{
+ const CPictureBuffer& buf = m_buffers[index];
+ ETONEMAPMETHOD method = m_videoSettings.m_ToneMapMethod;
+
+ AVColorPrimaries srcPrim = GetSrcPrimaries(buf.m_srcPrimaries, buf.image.width, buf.image.height);
+ if (srcPrim != m_srcPrimaries)
+ {
+ m_srcPrimaries = srcPrim;
+ m_reloadShaders = true;
+ }
+
+ bool toneMap = false;
+ if (method != VS_TONEMAPMETHOD_OFF)
+ {
+ if (buf.hasLightMetadata || (buf.hasDisplayMetadata && buf.displayMetadata.has_luminance))
+ {
+ toneMap = true;
+ }
+ }
+
+ if (toneMap != m_toneMap || (m_toneMapMethod != method))
+ {
+ m_reloadShaders = true;
+ }
+ m_toneMap = toneMap;
+ m_toneMapMethod = method;
+}
+
+AVColorPrimaries CLinuxRendererGL::GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height)
+{
+ AVColorPrimaries ret = srcPrimaries;
+ if (ret == AVCOL_PRI_UNSPECIFIED)
+ {
+ if (width > 1024 || height >= 600)
+ ret = AVCOL_PRI_BT709;
+ else
+ ret = AVCOL_PRI_BT470BG;
+ }
+ return ret;
+}
+
+CRenderCapture* CLinuxRendererGL::GetRenderCapture()
+{
+ return new CRenderCaptureGL;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h
new file mode 100644
index 0000000..026d951
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "system_gl.h"
+
+#include "FrameBufferObject.h"
+#include "cores/VideoSettings.h"
+#include "RenderInfo.h"
+#include "BaseRenderer.h"
+#include "ColorManager.h"
+#include "utils/Geometry.h"
+
+extern "C" {
+#include <libavutil/mastering_display_metadata.h>
+}
+
+class CRenderCapture;
+class CRenderSystemGL;
+
+class CTexture;
+namespace Shaders
+{
+namespace GL
+{
+class BaseYUV2RGBGLSLShader;
+class BaseVideoFilterShader;
+}
+} // namespace Shaders
+
+enum RenderMethod
+{
+ RENDER_GLSL=0x01,
+ RENDER_CUSTOM=0x02
+};
+
+enum RenderQuality
+{
+ RQ_LOW=1,
+ RQ_SINGLEPASS,
+ RQ_MULTIPASS,
+};
+
+#define FIELD_FULL 0
+#define FIELD_TOP 1
+#define FIELD_BOT 2
+
+class CLinuxRendererGL : public CBaseRenderer
+{
+public:
+ CLinuxRendererGL();
+ ~CLinuxRendererGL() override;
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ // Player functions
+ bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override;
+ bool IsConfigured() override { return m_bConfigured; }
+ void AddVideoPicture(const VideoPicture &picture, int index) override;
+ void UnInit() override;
+ bool Flush(bool saveBuffers) override;
+ void SetBufferSize(int numBuffers) override { m_NumYV12Buffers = numBuffers; }
+ void ReleaseBuffer(int idx) override;
+ void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ void Update() override;
+ bool RenderCapture(CRenderCapture* capture) override;
+ CRenderInfo GetRenderInfo() override;
+ bool ConfigChanged(const VideoPicture &picture) override;
+
+ // Feature support
+ bool SupportsMultiPassRendering() override;
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+ CRenderCapture* GetRenderCapture() override;
+
+protected:
+
+ bool Render(unsigned int flags, int renderBuffer);
+ void ClearBackBuffer();
+ void DrawBlackBars();
+
+ bool ValidateRenderer();
+ virtual bool ValidateRenderTarget();
+ virtual void LoadShaders(int field=FIELD_FULL);
+ void SetTextureFilter(GLenum method);
+ void UpdateVideoFilter();
+ void CheckVideoParameters(int index);
+ AVColorPrimaries GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height);
+
+ // textures
+ virtual bool UploadTexture(int index);
+ virtual void DeleteTexture(int index);
+ virtual bool CreateTexture(int index);
+
+ bool UploadYV12Texture(int index);
+ void DeleteYV12Texture(int index);
+ bool CreateYV12Texture(int index);
+
+ bool UploadNV12Texture(int index);
+ void DeleteNV12Texture(int index);
+ bool CreateNV12Texture(int index);
+
+ bool UploadYUV422PackedTexture(int index);
+ void DeleteYUV422PackedTexture(int index);
+ bool CreateYUV422PackedTexture(int index);
+
+ void CalculateTextureSourceRects(int source, int num_planes);
+
+ // renderers
+ void RenderToFBO(int renderBuffer, int field, bool weave = false);
+ void RenderFromFBO();
+ void RenderSinglePass(int renderBuffer, int field); // single pass glsl renderer
+ void RenderRGB(int renderBuffer, int field); // render using vdpau/vaapi hardware
+ void RenderProgressiveWeave(int renderBuffer, int field); // render using vdpau hardware
+
+ struct CYuvPlane;
+ struct CPictureBuffer;
+
+ void BindPbo(CPictureBuffer& buff);
+ void UnBindPbo(CPictureBuffer& buff);
+ void LoadPlane(CYuvPlane& plane, int type,
+ unsigned width, unsigned height,
+ int stride, int bpp, void* data);
+ void GetPlaneTextureSize(CYuvPlane& plane);
+ GLint GetInternalFormat(GLint format, int bpp);
+
+ // hooks for HwDec Renderer
+ virtual bool LoadShadersHook() { return false; }
+ virtual bool RenderHook(int idx) { return false; }
+ virtual void AfterRenderHook(int idx) {}
+ virtual bool CanSaveBuffers() { return true; }
+
+ struct
+ {
+ CFrameBufferObject fbo;
+ float width, height;
+ } m_fbo;
+
+ int m_iYV12RenderBuffer = 0;
+ int m_NumYV12Buffers = 0;
+
+ bool m_bConfigured = false;
+ bool m_bValidated = false;
+ GLenum m_textureTarget = GL_TEXTURE_2D;
+ int m_renderMethod = RENDER_GLSL;
+ RenderQuality m_renderQuality = RQ_SINGLEPASS;
+ CRenderSystemGL *m_renderSystem = nullptr;
+
+ // Raw data used by renderer
+ int m_currentField = FIELD_FULL;
+ int m_reloadShaders = 0;
+
+ struct CYuvPlane
+ {
+ GLuint id;
+ GLuint pbo;
+ CRect rect;
+ float width;
+ float height;
+ unsigned texwidth;
+ unsigned texheight;
+ //pixels per texel
+ unsigned pixpertex_x;
+ unsigned pixpertex_y;
+ };
+
+ struct CPictureBuffer
+ {
+ CPictureBuffer();
+ ~CPictureBuffer() = default;
+
+ CYuvPlane fields[MAX_FIELDS][YuvImage::MAX_PLANES];
+ YuvImage image;
+ GLuint pbo[3]; // one pbo for 3 planes
+
+ CVideoBuffer *videoBuffer;
+ bool loaded;
+
+ AVColorPrimaries m_srcPrimaries;
+ AVColorSpace m_srcColSpace;
+ int m_srcBits = 8;
+ int m_srcTextureBits = 8;
+ bool m_srcFullRange;
+
+ bool hasDisplayMetadata = false;
+ AVMasteringDisplayMetadata displayMetadata;
+ bool hasLightMetadata = false;
+ AVContentLightMetadata lightMetadata;
+ };
+
+ // YV12 decoder textures
+ // field index 0 is full image, 1 is odd scanlines, 2 is even scanlines
+ CPictureBuffer m_buffers[NUM_BUFFERS];
+
+ Shaders::GL::BaseYUV2RGBGLSLShader* m_pYUVShader = nullptr;
+ Shaders::GL::BaseVideoFilterShader* m_pVideoFilterShader = nullptr;
+ ESCALINGMETHOD m_scalingMethod = VS_SCALINGMETHOD_LINEAR;
+ ESCALINGMETHOD m_scalingMethodGui = VS_SCALINGMETHOD_MAX;
+ bool m_useDithering;
+ unsigned int m_ditherDepth;
+ bool m_fullRange;
+ AVColorPrimaries m_srcPrimaries;
+ bool m_toneMap = false;
+ ETONEMAPMETHOD m_toneMapMethod = VS_TONEMAPMETHOD_OFF;
+ float m_clearColour = 0.0f;
+ bool m_pboSupported = true;
+ bool m_pboUsed = false;
+ bool m_nonLinStretch = false;
+ bool m_nonLinStretchGui = false;
+ float m_pixelRatio = 0.0f;
+ CRect m_viewRect;
+
+ // color management
+ std::unique_ptr<CColorManager> m_ColorManager;
+ GLuint m_tCLUTTex;
+ uint16_t *m_CLUT;
+ int m_CLUTsize;
+ int m_cmsToken;
+ bool m_cmsOn;
+
+ bool LoadCLUT();
+ void DeleteCLUT();
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp
new file mode 100644
index 0000000..e164f4c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp
@@ -0,0 +1,1782 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LinuxRendererGLES.h"
+
+#include "RenderCapture.h"
+#include "RenderCaptureGLES.h"
+#include "RenderFactory.h"
+#include "ServiceBroker.h"
+#include "VideoShaders/VideoFilterShaderGLES.h"
+#include "VideoShaders/YUV2RGBShaderGLES.h"
+#include "application/Application.h"
+#include "cores/IPlayer.h"
+#include "guilib/Texture.h"
+#include "rendering/MatrixGL.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/MediaSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/GLUtils.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+using namespace Shaders;
+using namespace Shaders::GLES;
+
+CLinuxRendererGLES::CLinuxRendererGLES()
+{
+ m_format = AV_PIX_FMT_NONE;
+
+ m_fullRange = !CServiceBroker::GetWinSystem()->UseLimitedColor();
+
+ m_renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+
+#if defined (GL_UNPACK_ROW_LENGTH_EXT)
+ if (m_renderSystem->IsExtSupported("GL_EXT_unpack_subimage"))
+ {
+ m_pixelStoreKey = GL_UNPACK_ROW_LENGTH_EXT;
+ }
+#endif
+}
+
+CLinuxRendererGLES::~CLinuxRendererGLES()
+{
+ UnInit();
+
+ ReleaseShaders();
+
+ free(m_planeBuffer);
+ m_planeBuffer = nullptr;
+}
+
+CBaseRenderer* CLinuxRendererGLES::Create(CVideoBuffer *buffer)
+{
+ return new CLinuxRendererGLES();
+}
+
+bool CLinuxRendererGLES::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("default", CLinuxRendererGLES::Create);
+ return true;
+}
+
+bool CLinuxRendererGLES::ValidateRenderTarget()
+{
+ if (!m_bValidated)
+ {
+ // function pointer for texture might change in
+ // call to LoadShaders
+ glFinish();
+
+ for (int i = 0 ; i < NUM_BUFFERS ; i++)
+ {
+ DeleteTexture(i);
+ }
+
+ // create the yuv textures
+ UpdateVideoFilter();
+ LoadShaders();
+
+ if (m_renderMethod < 0)
+ {
+ return false;
+ }
+
+ for (int i = 0 ; i < m_NumYV12Buffers ; i++)
+ {
+ CreateTexture(i);
+ }
+
+ m_bValidated = true;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CLinuxRendererGLES::Configure(const VideoPicture &picture, float fps, unsigned int orientation)
+{
+ CLog::Log(LOGDEBUG, "LinuxRendererGLES::Configure: fps: {:0.3f}", fps);
+ m_format = picture.videoBuffer->GetFormat();
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+
+ m_srcPrimaries = GetSrcPrimaries(static_cast<AVColorPrimaries>(picture.color_primaries),
+ picture.iWidth, picture.iHeight);
+ m_toneMap = false;
+
+ // Calculate the input frame aspect ratio.
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+ ManageRenderArea();
+
+ m_bConfigured = true;
+ m_scalingMethodGui = (ESCALINGMETHOD)-1;
+
+ // Ensure that textures are recreated and rendering starts only after the 1st
+ // frame is loaded after every call to Configure().
+ m_bValidated = false;
+
+ // setup the background colour
+ m_clearColour = CServiceBroker::GetWinSystem()->UseLimitedColor() ? (16.0f / 0xff) : 0.0f;
+
+ if (picture.hasDisplayMetadata && picture.hasLightMetadata)
+ {
+ m_passthroughHDR = CServiceBroker::GetWinSystem()->SetHDR(&picture);
+ CLog::Log(LOGDEBUG, "LinuxRendererGLES::Configure: HDR passthrough: {}",
+ m_passthroughHDR ? "on" : "off");
+ }
+
+ return true;
+}
+
+bool CLinuxRendererGLES::ConfigChanged(const VideoPicture &picture)
+{
+ if (picture.videoBuffer->GetFormat() != m_format)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+int CLinuxRendererGLES::NextYV12Texture()
+{
+ return (m_iYV12RenderBuffer + 1) % m_NumYV12Buffers;
+}
+
+void CLinuxRendererGLES::AddVideoPicture(const VideoPicture &picture, int index)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ if (buf.videoBuffer)
+ {
+ CLog::LogF(LOGERROR, "unreleased video buffer");
+ buf.videoBuffer->Release();
+ }
+ buf.videoBuffer = picture.videoBuffer;
+ buf.videoBuffer->Acquire();
+ buf.loaded = false;
+ buf.m_srcPrimaries = static_cast<AVColorPrimaries>(picture.color_primaries);
+ buf.m_srcColSpace = static_cast<AVColorSpace>(picture.color_space);
+ buf.m_srcFullRange = picture.color_range == 1;
+ buf.m_srcBits = picture.colorBits;
+
+ buf.hasDisplayMetadata = picture.hasDisplayMetadata;
+ buf.displayMetadata = picture.displayMetadata;
+ buf.lightMetadata = picture.lightMetadata;
+ if (picture.hasLightMetadata && picture.lightMetadata.MaxCLL)
+ {
+ buf.hasLightMetadata = picture.hasLightMetadata;
+ }
+}
+
+void CLinuxRendererGLES::ReleaseBuffer(int idx)
+{
+ CPictureBuffer &buf = m_buffers[idx];
+ if (buf.videoBuffer)
+ {
+ buf.videoBuffer->Release();
+ buf.videoBuffer = nullptr;
+ }
+}
+
+void CLinuxRendererGLES::CalculateTextureSourceRects(int source, int num_planes)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ // calculate the source rectangle
+ for(int field = 0; field < 3; field++)
+ {
+ for(int plane = 0; plane < num_planes; plane++)
+ {
+ CYuvPlane& p = buf.fields[field][plane];
+
+ p.rect = m_sourceRect;
+ p.width = im->width;
+ p.height = im->height;
+
+ if(field != FIELD_FULL)
+ {
+ // correct for field offsets and chroma offsets
+ float offset_y = 0.5;
+ if(plane != 0)
+ {
+ offset_y += 0.5f;
+ }
+
+ if(field == FIELD_BOT)
+ {
+ offset_y *= -1;
+ }
+
+ p.rect.y1 += offset_y;
+ p.rect.y2 += offset_y;
+
+ // half the height if this is a field
+ p.height *= 0.5f;
+ p.rect.y1 *= 0.5f;
+ p.rect.y2 *= 0.5f;
+ }
+
+ if(plane != 0)
+ {
+ p.width /= 1 << im->cshift_x;
+ p.height /= 1 << im->cshift_y;
+
+ p.rect.x1 /= 1 << im->cshift_x;
+ p.rect.x2 /= 1 << im->cshift_x;
+ p.rect.y1 /= 1 << im->cshift_y;
+ p.rect.y2 /= 1 << im->cshift_y;
+ }
+
+ // protect against division by zero
+ if (p.texheight == 0 || p.texwidth == 0 ||
+ p.pixpertex_x == 0 || p.pixpertex_y == 0)
+ {
+ continue;
+ }
+
+ p.height /= p.pixpertex_y;
+ p.rect.y1 /= p.pixpertex_y;
+ p.rect.y2 /= p.pixpertex_y;
+ p.width /= p.pixpertex_x;
+ p.rect.x1 /= p.pixpertex_x;
+ p.rect.x2 /= p.pixpertex_x;
+
+ if (m_textureTarget == GL_TEXTURE_2D)
+ {
+ p.height /= p.texheight;
+ p.rect.y1 /= p.texheight;
+ p.rect.y2 /= p.texheight;
+ p.width /= p.texwidth;
+ p.rect.x1 /= p.texwidth;
+ p.rect.x2 /= p.texwidth;
+ }
+ }
+ }
+}
+
+void CLinuxRendererGLES::LoadPlane(CYuvPlane& plane, int type,
+ unsigned width, unsigned height,
+ int stride, int bpp, void* data)
+{
+ const GLvoid *pixelData = data;
+ int bps = bpp * KODI::UTILS::GL::glFormatElementByteCount(type);
+
+ glBindTexture(m_textureTarget, plane.id);
+
+ bool pixelStoreChanged = false;
+ if (stride != static_cast<int>(width * bps))
+ {
+ if (m_pixelStoreKey > 0)
+ {
+ pixelStoreChanged = true;
+ glPixelStorei(m_pixelStoreKey, stride);
+ }
+ else
+ {
+ size_t planeSize = width * height * bps;
+ if (m_planeBufferSize < planeSize)
+ {
+ m_planeBuffer = static_cast<unsigned char*>(realloc(m_planeBuffer, planeSize));
+ m_planeBufferSize = planeSize;
+ }
+
+ unsigned char *src(static_cast<unsigned char*>(data)),
+ *dst(m_planeBuffer);
+
+ for (unsigned int y = 0; y < height; ++y, src += stride, dst += width * bps)
+ memcpy(dst, src, width * bps);
+
+ pixelData = m_planeBuffer;
+ }
+ }
+ glTexSubImage2D(m_textureTarget, 0, 0, 0, width, height, type, GL_UNSIGNED_BYTE, pixelData);
+
+ if (m_pixelStoreKey > 0 && pixelStoreChanged)
+ glPixelStorei(m_pixelStoreKey, 0);
+
+ // check if we need to load any border pixels
+ if (height < plane.texheight)
+ {
+ glTexSubImage2D(m_textureTarget, 0,
+ 0, height, width, 1,
+ type, GL_UNSIGNED_BYTE,
+ static_cast<const unsigned char*>(pixelData) + stride * (height - 1));
+ }
+
+ if (width < plane.texwidth)
+ {
+ glTexSubImage2D(m_textureTarget, 0,
+ width, 0, 1, height,
+ type, GL_UNSIGNED_BYTE,
+ static_cast<const unsigned char*>(pixelData) + bps * (width - 1));
+ }
+
+ glBindTexture(m_textureTarget, 0);
+}
+
+bool CLinuxRendererGLES::Flush(bool saveBuffers)
+{
+ glFinish();
+
+ for (int i = 0 ; i < m_NumYV12Buffers ; i++)
+ {
+ DeleteTexture(i);
+ }
+
+ glFinish();
+ m_bValidated = false;
+ m_fbo.fbo.Cleanup();
+ m_iYV12RenderBuffer = 0;
+
+ return false;
+}
+
+void CLinuxRendererGLES::Update()
+{
+ if (!m_bConfigured)
+ {
+ return;
+ }
+
+ ManageRenderArea();
+ ValidateRenderTarget();
+}
+
+void CLinuxRendererGLES::DrawBlackBars()
+{
+ CRect windowRect(0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(),
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight());
+
+ auto quads = windowRect.SubtractRect(m_destRect);
+
+ struct Svertex
+ {
+ float x, y;
+ };
+
+ std::vector<Svertex> vertices(6 * quads.size());
+
+ GLubyte count = 0;
+ for (const auto& quad : quads)
+ {
+ vertices[count + 1].x = quad.x1;
+ vertices[count + 1].y = quad.y1;
+
+ vertices[count + 0].x = vertices[count + 5].x = quad.x1;
+ vertices[count + 0].y = vertices[count + 5].y = quad.y2;
+
+ vertices[count + 2].x = vertices[count + 3].x = quad.x2;
+ vertices[count + 2].y = vertices[count + 3].y = quad.y1;
+
+ vertices[count + 4].x = quad.x2;
+ vertices[count + 4].y = quad.y2;
+
+ count += 6;
+ }
+
+ glDisable(GL_BLEND);
+
+ CRenderSystemGLES* renderSystem =
+ dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ if (!renderSystem)
+ return;
+
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_DEFAULT);
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint uniCol = renderSystem->GUIShaderGetUniCol();
+
+ glUniform4f(uniCol, m_clearColour / 255.0f, m_clearColour / 255.0f, m_clearColour / 255.0f, 1.0f);
+
+ GLuint vertexVBO;
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(Svertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Svertex), 0);
+ glEnableVertexAttribArray(posLoc);
+
+ glDrawArrays(GL_TRIANGLES, 0, vertices.size());
+
+ glDisableVertexAttribArray(posLoc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+
+ renderSystem->DisableGUIShader();
+}
+
+void CLinuxRendererGLES::RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ m_iYV12RenderBuffer = index;
+
+ if (!m_bConfigured)
+ {
+ return;
+ }
+
+ // if its first pass, just init textures and return
+ if (ValidateRenderTarget())
+ {
+ return;
+ }
+
+ if (!IsGuiLayer())
+ {
+ RenderUpdateVideo(clear, flags, alpha);
+ return;
+ }
+
+ CPictureBuffer& buf = m_buffers[index];
+
+ if (!buf.fields[FIELD_FULL][0].id)
+ {
+ return;
+ }
+
+ ManageRenderArea();
+
+ if (clear)
+ {
+ if (alpha == 255)
+ DrawBlackBars();
+ else
+ {
+ glClearColor(m_clearColour, m_clearColour, m_clearColour, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0, 0, 0, 0);
+ }
+ }
+
+ if (alpha < 255)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ if (m_pYUVProgShader)
+ {
+ m_pYUVProgShader->SetAlpha(alpha / 255.0f);
+ }
+
+ if (m_pYUVBobShader)
+ {
+ m_pYUVBobShader->SetAlpha(alpha / 255.0f);
+ }
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ if (m_pYUVProgShader)
+ {
+ m_pYUVProgShader->SetAlpha(1.0f);
+ }
+
+ if (m_pYUVBobShader)
+ {
+ m_pYUVBobShader->SetAlpha(1.0f);
+ }
+ }
+
+ Render(flags, index);
+
+ VerifyGLState();
+ glEnable(GL_BLEND);
+}
+
+void CLinuxRendererGLES::RenderUpdateVideo(bool clear, unsigned int flags, unsigned int alpha)
+{
+ if (!m_bConfigured)
+ {
+ return;
+ }
+
+ if (IsGuiLayer())
+ {
+ return;
+ }
+}
+
+void CLinuxRendererGLES::UpdateVideoFilter()
+{
+ CRect srcRect;
+ CRect dstRect;
+ CRect viewRect;
+ GetVideoRect(srcRect, dstRect, viewRect);
+
+ if (m_scalingMethodGui == m_videoSettings.m_ScalingMethod &&
+ viewRect.Height() == m_viewRect.Height() &&
+ viewRect.Width() == m_viewRect.Width())
+ {
+ return;
+ }
+
+ m_scalingMethodGui = m_videoSettings.m_ScalingMethod;
+ m_scalingMethod = m_scalingMethodGui;
+ m_viewRect = viewRect;
+
+ if(!Supports(m_scalingMethod))
+ {
+ CLog::Log(LOGWARNING,
+ "CLinuxRendererGLES::UpdateVideoFilter - chosen scaling method {}, is not supported "
+ "by renderer",
+ m_scalingMethod);
+ m_scalingMethod = VS_SCALINGMETHOD_LINEAR;
+ }
+
+ if (m_pVideoFilterShader)
+ {
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+ }
+
+ m_fbo.fbo.Cleanup();
+
+ VerifyGLState();
+
+ switch (m_scalingMethod)
+ {
+ case VS_SCALINGMETHOD_NEAREST:
+ {
+ CLog::Log(LOGINFO, "GLES: Selecting single pass rendering");
+ SetTextureFilter(GL_NEAREST);
+ m_renderQuality = RQ_SINGLEPASS;
+ return;
+ }
+ case VS_SCALINGMETHOD_LINEAR:
+ {
+ CLog::Log(LOGINFO, "GLES: Selecting single pass rendering");
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_SINGLEPASS;
+ return;
+ }
+ case VS_SCALINGMETHOD_LANCZOS2:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ case VS_SCALINGMETHOD_SPLINE36:
+ case VS_SCALINGMETHOD_LANCZOS3:
+ case VS_SCALINGMETHOD_CUBIC_B_SPLINE:
+ case VS_SCALINGMETHOD_CUBIC_MITCHELL:
+ case VS_SCALINGMETHOD_CUBIC_CATMULL:
+ case VS_SCALINGMETHOD_CUBIC_0_075:
+ case VS_SCALINGMETHOD_CUBIC_0_1:
+ {
+ if (m_renderMethod & RENDER_GLSL)
+ {
+ if (!m_fbo.fbo.Initialize())
+ {
+ CLog::Log(LOGERROR, "GLES: Error initializing FBO");
+ break;
+ }
+
+ if (!m_fbo.fbo.CreateAndBindToTexture(GL_TEXTURE_2D, m_sourceWidth, m_sourceHeight, GL_RGBA))
+ {
+ CLog::Log(LOGERROR, "GLES: Error creating texture and binding to FBO");
+ break;
+ }
+ }
+
+ m_pVideoFilterShader = new ConvolutionFilterShader(m_scalingMethod);
+ if (!m_pVideoFilterShader->CompileAndLink())
+ {
+ CLog::Log(LOGERROR, "GLES: Error compiling and linking video filter shader");
+ break;
+ }
+
+ CLog::Log(LOGINFO, "GLES: Selecting multi pass rendering");
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_MULTIPASS;
+ return;
+ }
+ case VS_SCALINGMETHOD_BICUBIC_SOFTWARE:
+ case VS_SCALINGMETHOD_LANCZOS_SOFTWARE:
+ case VS_SCALINGMETHOD_SINC_SOFTWARE:
+ case VS_SCALINGMETHOD_SINC8:
+ {
+ CLog::Log(LOGERROR, "GLES: TODO: This scaler has not yet been implemented");
+ break;
+ }
+ default:
+ break;
+ }
+
+ CLog::Log(LOGERROR, "GLES: Falling back to bilinear due to failure to init scaler");
+ if (m_pVideoFilterShader)
+ {
+ delete m_pVideoFilterShader;
+ m_pVideoFilterShader = nullptr;
+ }
+
+ m_fbo.fbo.Cleanup();
+
+ SetTextureFilter(GL_LINEAR);
+ m_renderQuality = RQ_SINGLEPASS;
+}
+
+void CLinuxRendererGLES::LoadShaders(int field)
+{
+ m_reloadShaders = 0;
+
+ if (!LoadShadersHook())
+ {
+ int requestedMethod = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_RENDERMETHOD);
+ CLog::Log(LOGDEBUG, "GLES: Requested render method: {}", requestedMethod);
+
+ ReleaseShaders();
+
+ switch(requestedMethod)
+ {
+ case RENDER_METHOD_AUTO:
+ case RENDER_METHOD_GLSL:
+ {
+ // Try GLSL shaders if supported and user requested auto or GLSL.
+ if (glCreateProgram())
+ {
+ // create regular scan shader
+ CLog::Log(LOGINFO, "GLES: Selecting YUV 2 RGB shader");
+
+ EShaderFormat shaderFormat = GetShaderFormat();
+ m_toneMapMethod = m_videoSettings.m_ToneMapMethod;
+ m_pYUVProgShader = new YUV2RGBProgressiveShader(
+ shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
+ m_srcPrimaries, m_toneMap, m_toneMapMethod);
+ m_pYUVProgShader->SetConvertFullColorRange(m_fullRange);
+ m_pYUVBobShader = new YUV2RGBBobShader(
+ shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
+ m_srcPrimaries, m_toneMap, m_toneMapMethod);
+ m_pYUVBobShader->SetConvertFullColorRange(m_fullRange);
+
+ if ((m_pYUVProgShader && m_pYUVProgShader->CompileAndLink())
+ && (m_pYUVBobShader && m_pYUVBobShader->CompileAndLink()))
+ {
+ m_renderMethod = RENDER_GLSL;
+ UpdateVideoFilter();
+ break;
+ }
+ else
+ {
+ ReleaseShaders();
+ CLog::Log(LOGERROR, "GLES: Error enabling YUV2RGB GLSL shader");
+ m_renderMethod = -1;
+ break;
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ m_renderMethod = -1 ;
+ CLog::Log(LOGERROR, "GLES: render method not supported");
+ }
+ }
+ }
+}
+
+void CLinuxRendererGLES::ReleaseShaders()
+{
+ if (m_pYUVProgShader)
+ {
+ delete m_pYUVProgShader;
+ m_pYUVProgShader = nullptr;
+ }
+
+ if (m_pYUVBobShader)
+ {
+ delete m_pYUVBobShader;
+ m_pYUVBobShader = nullptr;
+ }
+}
+
+void CLinuxRendererGLES::UnInit()
+{
+ CLog::Log(LOGDEBUG, "LinuxRendererGLES: Cleaning up GLES resources");
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ glFinish();
+
+ // YV12 textures
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ DeleteTexture(i);
+ }
+
+ // cleanup framebuffer object if it was in use
+ m_fbo.fbo.Cleanup();
+ m_bValidated = false;
+ m_bConfigured = false;
+
+ CServiceBroker::GetWinSystem()->SetHDR(nullptr);
+}
+
+bool CLinuxRendererGLES::CreateTexture(int index)
+{
+ if (m_format == AV_PIX_FMT_NV12)
+ {
+ return CreateNV12Texture(index);
+ }
+ else
+ {
+ return CreateYV12Texture(index);
+ }
+}
+
+void CLinuxRendererGLES::DeleteTexture(int index)
+{
+ ReleaseBuffer(index);
+
+ if (m_format == AV_PIX_FMT_NV12)
+ {
+ DeleteNV12Texture(index);
+ }
+ else
+ {
+ DeleteYV12Texture(index);
+ }
+}
+
+bool CLinuxRendererGLES::UploadTexture(int index)
+{
+ if (!m_buffers[index].videoBuffer)
+ {
+ return false;
+ }
+
+ if (m_buffers[index].loaded)
+ {
+ return true;
+ }
+
+ bool ret{false};
+
+ YuvImage &dst = m_buffers[index].image;
+ m_buffers[index].videoBuffer->GetPlanes(dst.plane);
+ m_buffers[index].videoBuffer->GetStrides(dst.stride);
+
+ if (m_format == AV_PIX_FMT_NV12)
+ {
+ ret = UploadNV12Texture(index);
+ }
+ else
+ {
+ // default to YV12 texture handlers
+ ret = UploadYV12Texture(index);
+ }
+
+ if (ret)
+ {
+ m_buffers[index].loaded = true;
+ }
+
+ return ret;
+}
+
+void CLinuxRendererGLES::Render(unsigned int flags, int index)
+{
+ // obtain current field, if interlaced
+ if( flags & RENDER_FLAG_TOP)
+ {
+ m_currentField = FIELD_TOP;
+ }
+ else if (flags & RENDER_FLAG_BOT)
+ {
+ m_currentField = FIELD_BOT;
+ }
+ else
+ {
+ m_currentField = FIELD_FULL;
+ }
+
+ // call texture load function
+ if (!UploadTexture(index))
+ {
+ return;
+ }
+
+ if (RenderHook(index))
+ {
+ ;
+ }
+ else if (m_renderMethod & RENDER_GLSL)
+ {
+ UpdateVideoFilter();
+ switch(m_renderQuality)
+ {
+ case RQ_LOW:
+ case RQ_SINGLEPASS:
+ {
+ RenderSinglePass(index, m_currentField);
+ VerifyGLState();
+ break;
+ }
+ case RQ_MULTIPASS:
+ {
+ RenderToFBO(index, m_currentField);
+ RenderFromFBO();
+ VerifyGLState();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ AfterRenderHook(index);
+}
+
+void CLinuxRendererGLES::RenderSinglePass(int index, int field)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[field];
+
+ AVColorPrimaries srcPrim = GetSrcPrimaries(buf.m_srcPrimaries, buf.image.width, buf.image.height);
+ if (srcPrim != m_srcPrimaries)
+ {
+ m_srcPrimaries = srcPrim;
+ m_reloadShaders = true;
+ }
+
+ bool toneMap = false;
+ ETONEMAPMETHOD toneMapMethod = m_videoSettings.m_ToneMapMethod;
+
+ if (!m_passthroughHDR && toneMapMethod != VS_TONEMAPMETHOD_OFF)
+ {
+ if (buf.hasLightMetadata || (buf.hasDisplayMetadata && buf.displayMetadata.has_luminance))
+ {
+ toneMap = true;
+ }
+ }
+
+ if (toneMap != m_toneMap || toneMapMethod != m_toneMapMethod)
+ {
+ m_reloadShaders = true;
+ }
+
+ m_toneMap = toneMap;
+ m_toneMapMethod = toneMapMethod;
+
+ if (m_reloadShaders)
+ {
+ LoadShaders(field);
+ }
+
+ glDisable(GL_DEPTH_TEST);
+
+ // Y
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(m_textureTarget, planes[0].id);
+
+ // U
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(m_textureTarget, planes[1].id);
+
+ // V
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(m_textureTarget, planes[2].id);
+
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ Shaders::GLES::BaseYUV2RGBGLSLShader* pYUVShader;
+ if (field != FIELD_FULL)
+ {
+ pYUVShader = m_pYUVBobShader;
+ }
+ else
+ {
+ pYUVShader = m_pYUVProgShader;
+ }
+
+ pYUVShader->SetBlack(m_videoSettings.m_Brightness * 0.01f - 0.5f);
+ pYUVShader->SetContrast(m_videoSettings.m_Contrast * 0.02f);
+ pYUVShader->SetWidth(planes[0].texwidth);
+ pYUVShader->SetHeight(planes[0].texheight);
+ pYUVShader->SetColParams(buf.m_srcColSpace, buf.m_srcBits, !buf.m_srcFullRange, buf.m_srcTextureBits);
+ pYUVShader->SetDisplayMetadata(buf.hasDisplayMetadata, buf.displayMetadata,
+ buf.hasLightMetadata, buf.lightMetadata);
+ pYUVShader->SetToneMapParam(m_videoSettings.m_ToneMapParam);
+
+ if (field == FIELD_TOP)
+ {
+ pYUVShader->SetField(1);
+ }
+ else if(field == FIELD_BOT)
+ {
+ pYUVShader->SetField(0);
+ }
+
+ pYUVShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ pYUVShader->Enable();
+
+ GLubyte idx[4] = {0, 1, 3, 2}; // determines order of triangle strip
+ GLfloat m_vert[4][3];
+ GLfloat m_tex[3][4][2];
+
+ GLint vertLoc = pYUVShader->GetVertexLoc();
+ GLint Yloc = pYUVShader->GetYcoordLoc();
+ GLint Uloc = pYUVShader->GetUcoordLoc();
+ GLint Vloc = pYUVShader->GetVcoordLoc();
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, 0, m_vert);
+ glVertexAttribPointer(Yloc, 2, GL_FLOAT, 0, 0, m_tex[0]);
+ glVertexAttribPointer(Uloc, 2, GL_FLOAT, 0, 0, m_tex[1]);
+ glVertexAttribPointer(Vloc, 2, GL_FLOAT, 0, 0, m_tex[2]);
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(Yloc);
+ glEnableVertexAttribArray(Uloc);
+ glEnableVertexAttribArray(Vloc);
+
+ // Setup vertex position values
+ for(int i = 0; i < 4; i++)
+ {
+ m_vert[i][0] = m_rotatedDestCoords[i].x;
+ m_vert[i][1] = m_rotatedDestCoords[i].y;
+ m_vert[i][2] = 0.0f;// set z to 0
+ }
+
+ // Setup texture coordinates
+ for (int i = 0; i < 3; i++)
+ {
+ m_tex[i][0][0] = m_tex[i][3][0] = planes[i].rect.x1;
+ m_tex[i][0][1] = m_tex[i][1][1] = planes[i].rect.y1;
+ m_tex[i][1][0] = m_tex[i][2][0] = planes[i].rect.x2;
+ m_tex[i][2][1] = m_tex[i][3][1] = planes[i].rect.y2;
+ }
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ VerifyGLState();
+
+ pYUVShader->Disable();
+ VerifyGLState();
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(Yloc);
+ glDisableVertexAttribArray(Uloc);
+ glDisableVertexAttribArray(Vloc);
+
+ VerifyGLState();
+}
+
+void CLinuxRendererGLES::RenderToFBO(int index, int field)
+{
+ CPictureBuffer &buf = m_buffers[index];
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[field];
+
+ AVColorPrimaries srcPrim = GetSrcPrimaries(buf.m_srcPrimaries, buf.image.width, buf.image.height);
+ if (srcPrim != m_srcPrimaries)
+ {
+ m_srcPrimaries = srcPrim;
+ m_reloadShaders = true;
+ }
+
+ bool toneMap = false;
+ ETONEMAPMETHOD toneMapMethod = m_videoSettings.m_ToneMapMethod;
+
+ if (toneMapMethod != VS_TONEMAPMETHOD_OFF)
+ {
+ if (buf.hasLightMetadata || (buf.hasDisplayMetadata && buf.displayMetadata.has_luminance))
+ {
+ toneMap = true;
+ }
+ }
+
+ if (toneMap != m_toneMap || m_toneMapMethod != toneMapMethod)
+ {
+ m_reloadShaders = true;
+ }
+
+ m_toneMap = toneMap;
+ m_toneMapMethod = toneMapMethod;
+
+ if (m_reloadShaders)
+ {
+ m_reloadShaders = 0;
+ LoadShaders(m_currentField);
+ }
+
+ if (!m_fbo.fbo.IsValid())
+ {
+ if (!m_fbo.fbo.Initialize())
+ {
+ CLog::Log(LOGERROR, "GLES: Error initializing FBO");
+ return;
+ }
+
+ if (!m_fbo.fbo.CreateAndBindToTexture(GL_TEXTURE_2D, m_sourceWidth, m_sourceHeight, GL_RGBA))
+ {
+ CLog::Log(LOGERROR, "GLES: Error creating texture and binding to FBO");
+ return;
+ }
+ }
+
+ glDisable(GL_DEPTH_TEST);
+
+ // Y
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(m_textureTarget, planes[0].id);
+ VerifyGLState();
+
+ // U
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(m_textureTarget, planes[1].id);
+ VerifyGLState();
+
+ // V
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(m_textureTarget, planes[2].id);
+ VerifyGLState();
+
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ Shaders::GLES::BaseYUV2RGBGLSLShader* pYUVShader = m_pYUVProgShader;
+ // make sure the yuv shader is loaded and ready to go
+ if (!pYUVShader || (!pYUVShader->OK()))
+ {
+ CLog::Log(LOGERROR, "GLES: YUV shader not active, cannot do multipass render");
+ return;
+ }
+
+ m_fbo.fbo.BeginRender();
+ VerifyGLState();
+
+ pYUVShader->SetBlack(m_videoSettings.m_Brightness * 0.01f - 0.5f);
+ pYUVShader->SetContrast(m_videoSettings.m_Contrast * 0.02f);
+ pYUVShader->SetWidth(planes[0].texwidth);
+ pYUVShader->SetHeight(planes[0].texheight);
+ pYUVShader->SetColParams(buf.m_srcColSpace, buf.m_srcBits, !buf.m_srcFullRange, buf.m_srcTextureBits);
+ pYUVShader->SetDisplayMetadata(buf.hasDisplayMetadata, buf.displayMetadata,
+ buf.hasLightMetadata, buf.lightMetadata);
+ pYUVShader->SetToneMapParam(m_videoSettings.m_ToneMapParam);
+
+ if (field == FIELD_TOP)
+ {
+ pYUVShader->SetField(1);
+ }
+ else if(field == FIELD_BOT)
+ {
+ pYUVShader->SetField(0);
+ }
+
+ VerifyGLState();
+
+ glMatrixModview.Push();
+ glMatrixModview->LoadIdentity();
+ glMatrixModview.Load();
+
+ glMatrixProject.Push();
+ glMatrixProject->LoadIdentity();
+ glMatrixProject->Ortho2D(0, m_sourceWidth, 0, m_sourceHeight);
+ glMatrixProject.Load();
+
+ pYUVShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+
+ CRect viewport;
+ m_renderSystem->GetViewPort(viewport);
+ glViewport(0, 0, m_sourceWidth, m_sourceHeight);
+ glScissor(0, 0, m_sourceWidth, m_sourceHeight);
+
+ if (!pYUVShader->Enable())
+ {
+ CLog::Log(LOGERROR, "GLES: Error enabling YUV shader");
+ }
+
+ m_fbo.width = planes[0].rect.x2 - planes[0].rect.x1;
+ m_fbo.height = planes[0].rect.y2 - planes[0].rect.y1;
+
+ if (m_textureTarget == GL_TEXTURE_2D)
+ {
+ m_fbo.width *= planes[0].texwidth;
+ m_fbo.height *= planes[0].texheight;
+ }
+
+ m_fbo.width *= planes[0].pixpertex_x;
+ m_fbo.height *= planes[0].pixpertex_y;
+
+ // 1st Pass to video frame size
+ GLubyte idx[4] = {0, 1, 3, 2}; // determines order of triangle strip
+ GLfloat vert[4][3];
+ GLfloat tex[3][4][2];
+
+ GLint vertLoc = pYUVShader->GetVertexLoc();
+ GLint Yloc = pYUVShader->GetYcoordLoc();
+ GLint Uloc = pYUVShader->GetUcoordLoc();
+ GLint Vloc = pYUVShader->GetVcoordLoc();
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, 0, vert);
+ glVertexAttribPointer(Yloc, 2, GL_FLOAT, 0, 0, tex[0]);
+ glVertexAttribPointer(Uloc, 2, GL_FLOAT, 0, 0, tex[1]);
+ glVertexAttribPointer(Vloc, 2, GL_FLOAT, 0, 0, tex[2]);
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(Yloc);
+ glEnableVertexAttribArray(Uloc);
+ glEnableVertexAttribArray(Vloc);
+
+ // Setup vertex position values
+ // Set vertex coordinates
+ vert[0][0] = vert[3][0] = 0.0f;
+ vert[0][1] = vert[1][1] = 0.0f;
+ vert[1][0] = vert[2][0] = m_fbo.width;
+ vert[2][1] = vert[3][1] = m_fbo.height;
+ vert[0][2] = vert[1][2] = vert[2][2] = vert[3][2] = 0.0f;
+
+ // Setup texture coordinates
+ for (int i = 0; i < 3; i++)
+ {
+ tex[i][0][0] = tex[i][3][0] = planes[i].rect.x1;
+ tex[i][0][1] = tex[i][1][1] = planes[i].rect.y1;
+ tex[i][1][0] = tex[i][2][0] = planes[i].rect.x2;
+ tex[i][2][1] = tex[i][3][1] = planes[i].rect.y2;
+ }
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ VerifyGLState();
+
+ pYUVShader->Disable();
+
+ glMatrixModview.PopLoad();
+ glMatrixProject.PopLoad();
+
+ VerifyGLState();
+
+ glDisableVertexAttribArray(vertLoc);
+ glDisableVertexAttribArray(Yloc);
+ glDisableVertexAttribArray(Uloc);
+ glDisableVertexAttribArray(Vloc);
+
+ m_renderSystem->SetViewPort(viewport);
+
+ m_fbo.fbo.EndRender();
+
+ VerifyGLState();
+}
+
+void CLinuxRendererGLES::RenderFromFBO()
+{
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_fbo.fbo.Texture());
+ VerifyGLState();
+
+ // Use regular normalized texture coordinates
+
+ // 2nd Pass to screen size with optional video filter
+
+ if (m_pVideoFilterShader)
+ {
+ GLint filter;
+ if (!m_pVideoFilterShader->GetTextureFilter(filter))
+ {
+ filter = m_scalingMethod == VS_SCALINGMETHOD_NEAREST ? GL_NEAREST : GL_LINEAR;
+ }
+
+ m_fbo.fbo.SetFiltering(GL_TEXTURE_2D, filter);
+ m_pVideoFilterShader->SetSourceTexture(0);
+ m_pVideoFilterShader->SetWidth(m_sourceWidth);
+ m_pVideoFilterShader->SetHeight(m_sourceHeight);
+ m_pVideoFilterShader->SetAlpha(1.0f);
+ m_pVideoFilterShader->SetMatrices(glMatrixProject.Get(), glMatrixModview.Get());
+ m_pVideoFilterShader->Enable();
+ }
+ else
+ {
+ GLint filter = m_scalingMethod == VS_SCALINGMETHOD_NEAREST ? GL_NEAREST : GL_LINEAR;
+ m_fbo.fbo.SetFiltering(GL_TEXTURE_2D, filter);
+ }
+
+ VerifyGLState();
+
+ float imgwidth = m_fbo.width / m_sourceWidth;
+ float imgheight = m_fbo.height / m_sourceHeight;
+
+ GLubyte idx[4] = {0, 1, 3, 2}; // determines order of triangle strip
+ GLfloat vert[4][3];
+ GLfloat tex[4][2];
+
+ GLint vertLoc = m_pVideoFilterShader->GetVertexLoc();
+ GLint loc = m_pVideoFilterShader->GetcoordLoc();
+
+ glVertexAttribPointer(vertLoc, 3, GL_FLOAT, 0, 0, vert);
+ glVertexAttribPointer(loc, 2, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(vertLoc);
+ glEnableVertexAttribArray(loc);
+
+ // Setup vertex position values
+ for(int i = 0; i < 4; i++)
+ {
+ vert[i][0] = m_rotatedDestCoords[i].x;
+ vert[i][1] = m_rotatedDestCoords[i].y;
+ vert[i][2] = 0.0f; // set z to 0
+ }
+
+ // Setup texture coordinates
+ tex[0][0] = tex[3][0] = 0.0f;
+ tex[0][1] = tex[1][1] = 0.0f;
+ tex[1][0] = tex[2][0] = imgwidth;
+ tex[2][1] = tex[3][1] = imgheight;
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ VerifyGLState();
+
+ if (m_pVideoFilterShader)
+ {
+ m_pVideoFilterShader->Disable();
+ }
+
+ VerifyGLState();
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ VerifyGLState();
+}
+
+bool CLinuxRendererGLES::RenderCapture(CRenderCapture* capture)
+{
+ if (!m_bValidated)
+ {
+ return false;
+ }
+
+ // save current video rect
+ CRect saveSize = m_destRect;
+ saveRotatedCoords(); // backup current m_rotatedDestCoords
+
+ // new video rect is thumbnail size
+ m_destRect.SetRect(0, 0, static_cast<float>(capture->GetWidth()), static_cast<float>(capture->GetHeight()));
+ MarkDirty();
+ syncDestRectToRotatedPoints(); // syncs the changed destRect to m_rotatedDestCoords
+
+ // clear framebuffer and invert Y axis to get non-inverted image
+ glDisable(GL_BLEND);
+
+ glMatrixModview.Push();
+ glMatrixModview->Translatef(0.0f, capture->GetHeight(), 0.0f);
+ glMatrixModview->Scalef(1.0f, -1.0f, 1.0f);
+ glMatrixModview.Load();
+
+ capture->BeginRender();
+
+ Render(RENDER_FLAG_NOOSD, m_iYV12RenderBuffer);
+ // read pixels
+ glReadPixels(0, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() - capture->GetHeight(), capture->GetWidth(), capture->GetHeight(),
+ GL_RGBA, GL_UNSIGNED_BYTE, capture->GetRenderBuffer());
+
+ // OpenGLES returns in RGBA order but CRenderCapture needs BGRA order
+ // XOR Swap RGBA -> BGRA
+ unsigned char* pixels = static_cast<unsigned char*>(capture->GetRenderBuffer());
+ for (unsigned int i = 0; i < capture->GetWidth() * capture->GetHeight(); i++, pixels += 4)
+ {
+ std::swap(pixels[0], pixels[2]);
+ }
+
+ capture->EndRender();
+
+ // revert model view matrix
+ glMatrixModview.PopLoad();
+
+ // restore original video rect
+ m_destRect = saveSize;
+ restoreRotatedCoords(); // restores the previous state of the rotated dest coords
+
+ return true;
+}
+
+//********************************************************************************************************/
+// YV12 Texture creation, deletion, copying + clearing
+//********************************************************************************************************/
+bool CLinuxRendererGLES::UploadYV12Texture(int source)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ VerifyGLState();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+
+ // load Y plane
+ LoadPlane(buf.fields[FIELD_FULL][0], GL_LUMINANCE,
+ im->width, im->height,
+ im->stride[0], im->bpp, im->plane[0]);
+
+ // load U plane
+ LoadPlane(buf.fields[FIELD_FULL][1], GL_LUMINANCE,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[1], im->bpp, im->plane[1]);
+
+ // load V plane
+ LoadPlane(buf.fields[FIELD_FULL][2], GL_ALPHA,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[2], im->bpp, im->plane[2]);
+
+ VerifyGLState();
+
+ CalculateTextureSourceRects(source, 3);
+
+ return true;
+}
+
+void CLinuxRendererGLES::DeleteYV12Texture(int index)
+{
+ YuvImage &im = m_buffers[index].image;
+
+ if (m_buffers[index].fields[FIELD_FULL][0].id == 0)
+ {
+ return;
+ }
+
+ // finish up all textures, and delete them
+ for(int f = 0; f < MAX_FIELDS; f++)
+ {
+ for(int p = 0; p < YuvImage::MAX_PLANES; p++)
+ {
+ if (m_buffers[index].fields[f][p].id)
+ {
+ if (glIsTexture(m_buffers[index].fields[f][p].id))
+ {
+ glDeleteTextures(1, &m_buffers[index].fields[f][p].id);
+ }
+
+ m_buffers[index].fields[f][p].id = 0;
+ }
+ }
+ }
+
+ for(int p = 0; p < YuvImage::MAX_PLANES; p++)
+ {
+ im.plane[p] = nullptr;
+ }
+}
+
+bool CLinuxRendererGLES::CreateYV12Texture(int index)
+{
+ // since we also want the field textures, pitch must be texture aligned
+ unsigned p;
+ YuvImage &im = m_buffers[index].image;
+
+ DeleteYV12Texture(index);
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+ im.bpp = 1;
+
+ im.stride[0] = im.bpp * im.width;
+ im.stride[1] = im.bpp * (im.width >> im.cshift_x);
+ im.stride[2] = im.bpp * (im.width >> im.cshift_x);
+
+ im.planesize[0] = im.stride[0] * im.height;
+ im.planesize[1] = im.stride[1] * (im.height >> im.cshift_y);
+ im.planesize[2] = im.stride[2] * (im.height >> im.cshift_y);
+
+ for (int i = 0; i < 3; i++)
+ {
+ im.plane[i] = nullptr; // will be set in UploadTexture()
+ }
+
+ for(int f = 0; f < MAX_FIELDS; f++)
+ {
+ for(p = 0; p < YuvImage::MAX_PLANES; p++)
+ {
+ if (!glIsTexture(m_buffers[index].fields[f][p].id))
+ {
+ glGenTextures(1, &m_buffers[index].fields[f][p].id);
+ VerifyGLState();
+ }
+ }
+ }
+
+ // YUV
+ for (int f = FIELD_FULL; f <= FIELD_BOT ; f++)
+ {
+ int fieldshift = (f == FIELD_FULL) ? 0 : 1;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = m_buffers[index].fields[f];
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height >> fieldshift;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[2].texheight = planes[0].texheight >> im.cshift_y;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ for(int p = 0; p < 3; p++)
+ {
+ CYuvPlane &plane = planes[p];
+ if (plane.texwidth * plane.texheight == 0)
+ {
+ continue;
+ }
+
+ glBindTexture(m_textureTarget, plane.id);
+
+ GLint format;
+ if (p == 2) // V plane needs an alpha texture
+ {
+ format = GL_ALPHA;
+ }
+ else
+ {
+ format = GL_LUMINANCE;
+ }
+
+ glTexImage2D(m_textureTarget, 0, format, plane.texwidth, plane.texheight, 0, format, GL_UNSIGNED_BYTE, nullptr);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ VerifyGLState();
+ }
+ }
+ return true;
+}
+
+//********************************************************************************************************
+// NV12 Texture loading, creation and deletion
+//********************************************************************************************************
+bool CLinuxRendererGLES::UploadNV12Texture(int source)
+{
+ CPictureBuffer& buf = m_buffers[source];
+ YuvImage* im = &buf.image;
+
+ bool deinterlacing;
+ if (m_currentField == FIELD_FULL)
+ {
+ deinterlacing = false;
+ }
+ else
+ {
+ deinterlacing = true;
+ }
+
+ VerifyGLState();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, im->bpp);
+
+ if (deinterlacing)
+ {
+ // Load Odd Y field
+ LoadPlane(buf.fields[FIELD_TOP][0] , GL_LUMINANCE,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0]);
+
+ // Load Even Y field
+ LoadPlane(buf.fields[FIELD_BOT][0], GL_LUMINANCE,
+ im->width, im->height >> 1,
+ im->stride[0]*2, im->bpp, im->plane[0] + im->stride[0]) ;
+
+ // Load Odd UV Fields
+ LoadPlane(buf.fields[FIELD_TOP][1], GL_LUMINANCE_ALPHA,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1]);
+
+ // Load Even UV Fields
+ LoadPlane(buf.fields[FIELD_BOT][1], GL_LUMINANCE_ALPHA,
+ im->width >> im->cshift_x, im->height >> (im->cshift_y + 1),
+ im->stride[1]*2, im->bpp, im->plane[1] + im->stride[1]);
+
+ }
+ else
+ {
+ // Load Y plane
+ LoadPlane(buf. fields[FIELD_FULL][0], GL_LUMINANCE,
+ im->width, im->height,
+ im->stride[0], im->bpp, im->plane[0]);
+
+ // Load UV plane
+ LoadPlane(buf.fields[FIELD_FULL][1], GL_LUMINANCE_ALPHA,
+ im->width >> im->cshift_x, im->height >> im->cshift_y,
+ im->stride[1], im->bpp, im->plane[1]);
+ }
+
+ VerifyGLState();
+
+ CalculateTextureSourceRects(source, 3);
+
+ return true;
+}
+
+bool CLinuxRendererGLES::CreateNV12Texture(int index)
+{
+ // since we also want the field textures, pitch must be texture aligned
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+
+ // Delete any old texture
+ DeleteNV12Texture(index);
+
+ im.height = m_sourceHeight;
+ im.width = m_sourceWidth;
+ im.cshift_x = 1;
+ im.cshift_y = 1;
+ im.bpp = 1;
+
+ im.stride[0] = im.width;
+ im.stride[1] = im.width;
+ im.stride[2] = 0;
+
+ im.plane[0] = nullptr;
+ im.plane[1] = nullptr;
+ im.plane[2] = nullptr;
+
+ // Y plane
+ im.planesize[0] = im.stride[0] * im.height;
+ // packed UV plane
+ im.planesize[1] = im.stride[1] * im.height / 2;
+ // third plane is not used
+ im.planesize[2] = 0;
+
+ for (int i = 0; i < 2; i++)
+ {
+ im.plane[i] = nullptr; // will be set in UploadTexture()
+ }
+
+ for(int f = 0; f < MAX_FIELDS; f++)
+ {
+ for(int p = 0; p < 2; p++)
+ {
+ if (!glIsTexture(buf.fields[f][p].id))
+ {
+ glGenTextures(1, &buf.fields[f][p].id);
+ VerifyGLState();
+ }
+ }
+
+ buf.fields[f][2].id = buf.fields[f][1].id;
+ }
+
+ // YUV
+ for (int f = FIELD_FULL; f <= FIELD_BOT; f++)
+ {
+ int fieldshift = (f == FIELD_FULL) ? 0 : 1;
+ CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[f];
+
+ planes[0].texwidth = im.width;
+ planes[0].texheight = im.height >> fieldshift;
+
+ planes[1].texwidth = planes[0].texwidth >> im.cshift_x;
+ planes[1].texheight = planes[0].texheight >> im.cshift_y;
+ planes[2].texwidth = planes[1].texwidth;
+ planes[2].texheight = planes[1].texheight;
+
+ for (int p = 0; p < 3; p++)
+ {
+ planes[p].pixpertex_x = 1;
+ planes[p].pixpertex_y = 1;
+ }
+
+ for(int p = 0; p < 2; p++)
+ {
+ CYuvPlane &plane = planes[p];
+ if (plane.texwidth * plane.texheight == 0)
+ {
+ continue;
+ }
+
+ glBindTexture(m_textureTarget, plane.id);
+
+ if (p == 1)
+ {
+ glTexImage2D(m_textureTarget, 0, GL_LUMINANCE_ALPHA, plane.texwidth, plane.texheight, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, nullptr);
+ }
+ else
+ {
+ glTexImage2D(m_textureTarget, 0, GL_LUMINANCE, plane.texwidth, plane.texheight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
+ }
+
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ VerifyGLState();
+ }
+ }
+
+ return true;
+}
+
+void CLinuxRendererGLES::DeleteNV12Texture(int index)
+{
+ CPictureBuffer& buf = m_buffers[index];
+ YuvImage &im = buf.image;
+
+ if (buf.fields[FIELD_FULL][0].id == 0)
+ {
+ return;
+ }
+
+ // finish up all textures, and delete them
+ for(int f = 0; f < MAX_FIELDS; f++)
+ {
+ for(int p = 0; p < 2; p++)
+ {
+ if (buf.fields[f][p].id)
+ {
+ if (glIsTexture(buf.fields[f][p].id))
+ {
+ glDeleteTextures(1, &buf.fields[f][p].id);
+ }
+
+ buf.fields[f][p].id = 0;
+ }
+ }
+
+ buf.fields[f][2].id = 0;
+ }
+
+ for(int p = 0; p < 2; p++)
+ {
+ im.plane[p] = nullptr;
+ }
+}
+
+//********************************************************************************************************
+// SurfaceTexture creation, deletion, copying + clearing
+//********************************************************************************************************
+void CLinuxRendererGLES::SetTextureFilter(GLenum method)
+{
+ for (int i = 0 ; i < m_NumYV12Buffers; i++)
+ {
+ CPictureBuffer& buf = m_buffers[i];
+
+ for (int f = FIELD_FULL; f <= FIELD_BOT; f++)
+ {
+ for (int p = 0; p < 3; p++)
+ {
+ if(glIsTexture(buf.fields[f][p].id))
+ {
+ glBindTexture(m_textureTarget, buf.fields[f][p].id);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, method);
+ glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, method);
+ VerifyGLState();
+ }
+ }
+ }
+ }
+}
+
+bool CLinuxRendererGLES::Supports(ERENDERFEATURE feature) const
+{
+ if (feature == RENDERFEATURE_GAMMA ||
+ feature == RENDERFEATURE_NOISE ||
+ feature == RENDERFEATURE_SHARPNESS ||
+ feature == RENDERFEATURE_NONLINSTRETCH)
+ {
+ return false;
+ }
+
+ if (feature == RENDERFEATURE_STRETCH ||
+ feature == RENDERFEATURE_ZOOM ||
+ feature == RENDERFEATURE_VERTICAL_SHIFT ||
+ feature == RENDERFEATURE_PIXEL_RATIO ||
+ feature == RENDERFEATURE_POSTPROCESS ||
+ feature == RENDERFEATURE_ROTATION ||
+ feature == RENDERFEATURE_BRIGHTNESS ||
+ feature == RENDERFEATURE_CONTRAST ||
+ feature == RENDERFEATURE_TONEMAP)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool CLinuxRendererGLES::SupportsMultiPassRendering()
+{
+ return true;
+}
+
+bool CLinuxRendererGLES::Supports(ESCALINGMETHOD method) const
+{
+ if(method == VS_SCALINGMETHOD_NEAREST ||
+ method == VS_SCALINGMETHOD_LINEAR)
+ {
+ return true;
+ }
+
+ if (method == VS_SCALINGMETHOD_CUBIC_B_SPLINE ||
+ method == VS_SCALINGMETHOD_CUBIC_MITCHELL ||
+ method == VS_SCALINGMETHOD_CUBIC_CATMULL ||
+ method == VS_SCALINGMETHOD_CUBIC_0_075 ||
+ method == VS_SCALINGMETHOD_CUBIC_0_1 ||
+ method == VS_SCALINGMETHOD_LANCZOS2 ||
+ method == VS_SCALINGMETHOD_SPLINE36_FAST ||
+ method == VS_SCALINGMETHOD_LANCZOS3_FAST ||
+ method == VS_SCALINGMETHOD_SPLINE36 ||
+ method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ // if scaling is below level, avoid hq scaling
+ float scaleX = fabs((static_cast<float>(m_sourceWidth) - m_destRect.Width()) / m_sourceWidth) * 100;
+ float scaleY = fabs((static_cast<float>(m_sourceHeight) - m_destRect.Height()) / m_sourceHeight) * 100;
+ int minScale = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_HQSCALERS);
+ if (scaleX < minScale && scaleY < minScale)
+ {
+ return false;
+ }
+
+ if (m_renderMethod & RENDER_GLSL)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+CRenderInfo CLinuxRendererGLES::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.max_buffer_size = NUM_BUFFERS;
+
+ return info;
+}
+
+bool CLinuxRendererGLES::IsGuiLayer()
+{
+ return true;
+}
+
+AVColorPrimaries CLinuxRendererGLES::GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height)
+{
+ AVColorPrimaries ret = srcPrimaries;
+ if (ret == AVCOL_PRI_UNSPECIFIED)
+ {
+ if (width > 1024 || height >= 600)
+ {
+ ret = AVCOL_PRI_BT709;
+ }
+ else
+ {
+ ret = AVCOL_PRI_BT470BG;
+ }
+ }
+
+ return ret;
+}
+
+CRenderCapture* CLinuxRendererGLES::GetRenderCapture()
+{
+ return new CRenderCaptureGLES;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h
new file mode 100644
index 0000000..8db3ed3
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "system_gl.h"
+
+#include "BaseRenderer.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "cores/VideoSettings.h"
+#include "FrameBufferObject.h"
+#include "guilib/Shader.h"
+#include "RenderFlags.h"
+#include "RenderInfo.h"
+#include "windowing/GraphicContext.h"
+
+extern "C" {
+#include <libavutil/mastering_display_metadata.h>
+}
+
+class CRenderCapture;
+class CRenderSystemGLES;
+
+class CTexture;
+namespace Shaders
+{
+namespace GLES
+{
+class BaseYUV2RGBGLSLShader;
+class BaseVideoFilterShader;
+}
+} // namespace Shaders
+
+enum RenderMethod
+{
+ RENDER_GLSL = 0x01,
+ RENDER_CUSTOM = 0x02,
+};
+
+enum RenderQuality
+{
+ RQ_LOW = 1,
+ RQ_SINGLEPASS,
+ RQ_MULTIPASS,
+ RQ_SOFTWARE
+};
+
+class CEvent;
+
+class CLinuxRendererGLES : public CBaseRenderer
+{
+public:
+ CLinuxRendererGLES();
+ ~CLinuxRendererGLES() override;
+
+ // Registration
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ // Player functions
+ bool Configure(const VideoPicture& picture, float fps, unsigned int orientation) override;
+ bool IsConfigured() override { return m_bConfigured; }
+ void AddVideoPicture(const VideoPicture& picture, int index) override;
+ void UnInit() override;
+ bool Flush(bool saveBuffers) override;
+ void SetBufferSize(int numBuffers) override { m_NumYV12Buffers = numBuffers; }
+ bool IsGuiLayer() override;
+ void ReleaseBuffer(int idx) override;
+ void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ void Update() override;
+ bool RenderCapture(CRenderCapture* capture) override;
+ CRenderInfo GetRenderInfo() override;
+ bool ConfigChanged(const VideoPicture& picture) override;
+
+ // Feature support
+ bool SupportsMultiPassRendering() override;
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+ CRenderCapture* GetRenderCapture() override;
+
+protected:
+ static const int FIELD_FULL{0};
+ static const int FIELD_TOP{1};
+ static const int FIELD_BOT{2};
+
+ virtual void Render(unsigned int flags, int index);
+ virtual void RenderUpdateVideo(bool clear, unsigned int flags = 0, unsigned int alpha = 255);
+
+ int NextYV12Texture();
+ virtual bool ValidateRenderTarget();
+ virtual void LoadShaders(int field=FIELD_FULL);
+ virtual void ReleaseShaders();
+ void SetTextureFilter(GLenum method);
+ void UpdateVideoFilter();
+ AVColorPrimaries GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height);
+
+ // textures
+ virtual bool UploadTexture(int index);
+ virtual void DeleteTexture(int index);
+ virtual bool CreateTexture(int index);
+
+ bool UploadYV12Texture(int index);
+ void DeleteYV12Texture(int index);
+ bool CreateYV12Texture(int index);
+ virtual bool SkipUploadYV12(int index) { return false; }
+
+ bool UploadNV12Texture(int index);
+ void DeleteNV12Texture(int index);
+ bool CreateNV12Texture(int index);
+
+ void CalculateTextureSourceRects(int source, int num_planes);
+
+ // renderers
+ void RenderToFBO(int index, int field);
+ void RenderFromFBO();
+ void RenderSinglePass(int index, int field); // single pass glsl renderer
+
+ // hooks for HwDec Renderered
+ virtual bool LoadShadersHook() { return false; }
+ virtual bool RenderHook(int idx) { return false; }
+ virtual void AfterRenderHook(int idx) {}
+
+ struct
+ {
+ CFrameBufferObject fbo;
+ float width{0.0};
+ float height{0.0};
+ } m_fbo;
+
+ int m_iYV12RenderBuffer{0};
+ int m_NumYV12Buffers{0};
+
+ bool m_bConfigured{false};
+ bool m_bValidated{false};
+ GLenum m_textureTarget = GL_TEXTURE_2D;
+ int m_renderMethod{RENDER_GLSL};
+ RenderQuality m_renderQuality{RQ_SINGLEPASS};
+
+ // Raw data used by renderer
+ int m_currentField{FIELD_FULL};
+ int m_reloadShaders{0};
+ CRenderSystemGLES *m_renderSystem{nullptr};
+ GLenum m_pixelStoreKey{0};
+
+ struct CYuvPlane
+ {
+ GLuint id{0};
+ CRect rect{0, 0, 0, 0};
+
+ float width{0.0};
+ float height{0.0};
+
+ unsigned texwidth{0};
+ unsigned texheight{0};
+
+ //pixels per texel
+ unsigned pixpertex_x{0};
+ unsigned pixpertex_y{0};
+ };
+
+ struct CPictureBuffer
+ {
+ CYuvPlane fields[MAX_FIELDS][YuvImage::MAX_PLANES];
+ YuvImage image;
+
+ CVideoBuffer *videoBuffer{nullptr};
+ bool loaded{false};
+
+ AVColorPrimaries m_srcPrimaries;
+ AVColorSpace m_srcColSpace;
+ int m_srcBits{8};
+ int m_srcTextureBits{8};
+ bool m_srcFullRange;
+
+ bool hasDisplayMetadata{false};
+ AVMasteringDisplayMetadata displayMetadata;
+ bool hasLightMetadata{false};
+ AVContentLightMetadata lightMetadata;
+ };
+
+ // YV12 decoder textures
+ // field index 0 is full image, 1 is odd scanlines, 2 is even scanlines
+ CPictureBuffer m_buffers[NUM_BUFFERS];
+
+ void LoadPlane(CYuvPlane& plane, int type,
+ unsigned width, unsigned height,
+ int stride, int bpp, void* data);
+
+ Shaders::GLES::BaseYUV2RGBGLSLShader* m_pYUVProgShader{nullptr};
+ Shaders::GLES::BaseYUV2RGBGLSLShader* m_pYUVBobShader{nullptr};
+ Shaders::GLES::BaseVideoFilterShader* m_pVideoFilterShader{nullptr};
+ ESCALINGMETHOD m_scalingMethod{VS_SCALINGMETHOD_LINEAR};
+ ESCALINGMETHOD m_scalingMethodGui{VS_SCALINGMETHOD_MAX};
+ bool m_fullRange;
+ AVColorPrimaries m_srcPrimaries;
+ bool m_toneMap = false;
+ ETONEMAPMETHOD m_toneMapMethod = VS_TONEMAPMETHOD_OFF;
+ bool m_passthroughHDR = false;
+ unsigned char* m_planeBuffer = nullptr;
+ size_t m_planeBufferSize = 0;
+
+ // clear colour for "black" bars
+ float m_clearColour{0.0f};
+ CRect m_viewRect;
+
+private:
+ void DrawBlackBars();
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp
new file mode 100644
index 0000000..b6f5e5e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp
@@ -0,0 +1,638 @@
+/*
+ * Initial code sponsored by: Voddler Inc (voddler.com)
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverlayRenderer.h"
+
+#include "OverlayRendererUtil.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlay.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayLibass.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+#if defined(HAS_GL) || defined(HAS_GLES)
+#include "OverlayRendererGL.h"
+#elif defined(HAS_DX)
+#include "OverlayRendererDX.h"
+#endif
+
+#include <algorithm>
+
+using namespace KODI;
+using namespace OVERLAY;
+
+COverlay::COverlay()
+{
+ m_x = 0.0f;
+ m_y = 0.0f;
+ m_width = 0.0f;
+ m_height = 0.0f;
+ m_type = TYPE_NONE;
+ m_align = ALIGN_SCREEN;
+ m_pos = POSITION_RELATIVE;
+}
+
+COverlay::~COverlay() = default;
+
+unsigned int CRenderer::m_textureid = 1;
+
+CRenderer::CRenderer()
+{
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->RegisterObserver(this);
+}
+
+CRenderer::~CRenderer()
+{
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->UnregisterObserver(this);
+ Flush();
+}
+
+void CRenderer::AddOverlay(CDVDOverlay* o, double pts, int index)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ SElement e;
+ e.pts = pts;
+ e.overlay_dvd = o->Acquire();
+ m_buffers[index].push_back(e);
+}
+
+void CRenderer::Release(std::vector<SElement>& list)
+{
+ std::vector<SElement> l = list;
+ list.clear();
+
+ for (auto &elem : l)
+ {
+ if (elem.overlay_dvd)
+ elem.overlay_dvd->Release();
+ }
+}
+
+void CRenderer::UnInit()
+{
+ if (m_saveSubtitlePosition)
+ {
+ m_saveSubtitlePosition = false;
+ CDisplaySettings::GetInstance().UpdateCalibrations();
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+ }
+
+ Flush();
+}
+
+void CRenderer::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ for(std::vector<SElement>& buffer : m_buffers)
+ Release(buffer);
+
+ ReleaseCache();
+ Reset();
+}
+
+void CRenderer::Reset()
+{
+ m_subtitlePosition = 0;
+ m_subtitlePosResInfo = -1;
+}
+
+void CRenderer::Release(int idx)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ Release(m_buffers[idx]);
+}
+
+void CRenderer::ReleaseCache()
+{
+ for (auto& overlay : m_textureCache)
+ {
+ delete overlay.second;
+ }
+ m_textureCache.clear();
+ m_textureid++;
+}
+
+void CRenderer::ReleaseUnused()
+{
+ for (auto it = m_textureCache.begin(); it != m_textureCache.end(); )
+ {
+ bool found = false;
+ for (auto& buffer : m_buffers)
+ {
+ for (auto& dvdoverlay : buffer)
+ {
+ if (dvdoverlay.overlay_dvd && dvdoverlay.overlay_dvd->m_textureid == it->first)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+ if (!found)
+ {
+ delete it->second;
+ it = m_textureCache.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void CRenderer::Render(int idx)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ std::vector<SElement>& list = m_buffers[idx];
+ for(std::vector<SElement>::iterator it = list.begin(); it != list.end(); ++it)
+ {
+ if (it->overlay_dvd)
+ {
+ COverlay* o = Convert(it->overlay_dvd, it->pts);
+
+ if (o)
+ Render(o);
+ }
+ }
+
+ ReleaseUnused();
+}
+
+void CRenderer::Render(COverlay* o)
+{
+ SRenderState state;
+ state.x = o->m_x;
+ state.y = o->m_y;
+ state.width = o->m_width;
+ state.height = o->m_height;
+
+ COverlay::EPosition pos = o->m_pos;
+ COverlay::EAlign align = o->m_align;
+
+ if (pos == COverlay::POSITION_RELATIVE)
+ {
+ float scale_x = 1.0;
+ float scale_y = 1.0;
+ float scale_w = 1.0;
+ float scale_h = 1.0;
+
+ if (align == COverlay::ALIGN_SCREEN || align == COverlay::ALIGN_SUBTITLE)
+ {
+ scale_x = m_rv.Width();
+ scale_y = m_rv.Height();
+ scale_w = scale_x;
+ scale_h = scale_y;
+ }
+ else if (align == COverlay::ALIGN_SCREEN_AR)
+ {
+ // Align to screen by keeping aspect ratio to fit into the screen area
+ float source_width = o->m_source_width > 0 ? o->m_source_width : m_rs.Width();
+ float source_height = o->m_source_height > 0 ? o->m_source_height : m_rs.Height();
+ float ratio = std::min<float>(m_rv.Width() / source_width, m_rv.Height() / source_height);
+ scale_x = m_rv.Width();
+ scale_y = m_rv.Height();
+ scale_w = ratio;
+ scale_h = ratio;
+ }
+ else if (align == COverlay::ALIGN_VIDEO)
+ {
+ scale_x = m_rs.Width();
+ scale_y = m_rs.Height();
+ scale_w = scale_x;
+ scale_h = scale_y;
+ }
+
+ state.x *= scale_x;
+ state.y *= scale_y;
+ state.width *= scale_w;
+ state.height *= scale_h;
+
+ pos = COverlay::POSITION_ABSOLUTE;
+ }
+
+ if (pos == COverlay::POSITION_ABSOLUTE)
+ {
+ if (align == COverlay::ALIGN_SCREEN || align == COverlay::ALIGN_SCREEN_AR ||
+ align == COverlay::ALIGN_SUBTITLE)
+ {
+ if (align == COverlay::ALIGN_SUBTITLE)
+ {
+ RESOLUTION_INFO resInfo = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ state.x += m_rv.x1 + m_rv.Width() * 0.5f;
+ state.y += m_rv.y1 + (resInfo.iSubtitles - resInfo.Overscan.top);
+ }
+ else
+ {
+ state.x += m_rv.x1;
+ state.y += m_rv.y1;
+ }
+ }
+ else if (align == COverlay::ALIGN_VIDEO)
+ {
+ float scale_x = m_rd.Width() / m_rs.Width();
+ float scale_y = m_rd.Height() / m_rs.Height();
+
+ state.x *= scale_x;
+ state.y *= scale_y;
+ state.width *= scale_x;
+ state.height *= scale_y;
+
+ state.x += m_rd.x1;
+ state.y += m_rd.y1;
+ }
+ }
+
+ state.x += GetStereoscopicDepth();
+
+ o->Render(state);
+}
+
+bool CRenderer::HasOverlay(int idx)
+{
+ bool hasOverlay = false;
+
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ std::vector<SElement>& list = m_buffers[idx];
+ for(std::vector<SElement>::iterator it = list.begin(); it != list.end(); ++it)
+ {
+ if (it->overlay_dvd)
+ {
+ hasOverlay = true;
+ break;
+ }
+ }
+ return hasOverlay;
+}
+
+void CRenderer::SetVideoRect(CRect &source, CRect &dest, CRect &view)
+{
+ if (m_rv != view) // Screen resolution is changed
+ {
+ m_rv = view;
+ OnViewChange();
+ }
+ m_rs = source;
+ m_rd = dest;
+}
+
+void CRenderer::OnViewChange()
+{
+ m_isSettingsChanged = true;
+}
+
+void CRenderer::SetStereoMode(const std::string &stereomode)
+{
+ m_stereomode = stereomode;
+}
+
+void CRenderer::SetSubtitleVerticalPosition(const int value, bool save)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_subtitlePosition = value;
+
+ if (save && m_subtitleAlign == SUBTITLES::Align::MANUAL)
+ {
+ m_subtitlePosResInfo = POSRESINFO_SAVE_CHANGES;
+ // We save the value to XML file settings when playback is stopped
+ // to avoid saving to disk too many times
+ m_saveSubtitlePosition = true;
+ }
+}
+
+void CRenderer::ResetSubtitlePosition()
+{
+ // In the 'pos' var the vertical margin has been substracted because
+ // we need to know the actual text baseline position on screen
+ int pos{0};
+ m_saveSubtitlePosition = false;
+ RESOLUTION_INFO resInfo = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+
+ if (m_subtitleAlign == SUBTITLES::Align::MANUAL)
+ {
+ // The position must be fixed to match the subtitle calibration bar
+ m_subtitleVerticalMargin = static_cast<int>(
+ static_cast<float>(resInfo.iHeight) / 100 *
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetVerticalMarginPerc());
+
+ m_subtitlePosResInfo = resInfo.iSubtitles;
+ pos = resInfo.iSubtitles - m_subtitleVerticalMargin;
+ }
+ else
+ {
+ // The position must be relative to the screen frame
+ m_subtitleVerticalMargin = static_cast<int>(
+ static_cast<float>(m_rv.Height()) / 100 *
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetVerticalMarginPerc());
+
+ m_subtitlePosResInfo = static_cast<int>(m_rv.Height());
+ pos = static_cast<int>(m_rv.Height()) - m_subtitleVerticalMargin + resInfo.Overscan.top;
+ }
+
+ // Update player value (and callback to CRenderer::SetSubtitleVerticalPosition)
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ appPlayer->SetSubtitleVerticalPosition(pos, false);
+}
+
+void CRenderer::CreateSubtitlesStyle()
+{
+ m_overlayStyle = std::make_shared<SUBTITLES::STYLE::style>();
+ const auto settings{CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()};
+
+ m_overlayStyle->fontName = settings->GetFontName();
+ m_overlayStyle->fontSize = static_cast<double>(settings->GetFontSize());
+
+ SUBTITLES::FontStyle fontStyle = settings->GetFontStyle();
+ if (fontStyle == SUBTITLES::FontStyle::BOLD_ITALIC)
+ m_overlayStyle->fontStyle = SUBTITLES::STYLE::FontStyle::BOLD_ITALIC;
+ else if (fontStyle == SUBTITLES::FontStyle::BOLD)
+ m_overlayStyle->fontStyle = SUBTITLES::STYLE::FontStyle::BOLD;
+ else if (fontStyle == SUBTITLES::FontStyle::ITALIC)
+ m_overlayStyle->fontStyle = SUBTITLES::STYLE::FontStyle::ITALIC;
+
+ m_overlayStyle->fontColor = settings->GetFontColor();
+ m_overlayStyle->fontBorderSize = settings->GetBorderSize();
+ m_overlayStyle->fontBorderColor = settings->GetBorderColor();
+ m_overlayStyle->fontOpacity = settings->GetFontOpacity();
+
+ SUBTITLES::BackgroundType backgroundType = settings->GetBackgroundType();
+ if (backgroundType == SUBTITLES::BackgroundType::NONE)
+ m_overlayStyle->borderStyle = SUBTITLES::STYLE::BorderType::OUTLINE_NO_SHADOW;
+ else if (backgroundType == SUBTITLES::BackgroundType::SHADOW)
+ m_overlayStyle->borderStyle = SUBTITLES::STYLE::BorderType::OUTLINE;
+ else if (backgroundType == SUBTITLES::BackgroundType::BOX)
+ m_overlayStyle->borderStyle = SUBTITLES::STYLE::BorderType::BOX;
+ else if (backgroundType == SUBTITLES::BackgroundType::SQUAREBOX)
+ m_overlayStyle->borderStyle = SUBTITLES::STYLE::BorderType::SQUARE_BOX;
+
+ m_overlayStyle->backgroundColor = settings->GetBackgroundColor();
+ m_overlayStyle->backgroundOpacity = settings->GetBackgroundOpacity();
+
+ m_overlayStyle->shadowColor = settings->GetShadowColor();
+ m_overlayStyle->shadowOpacity = settings->GetShadowOpacity();
+ m_overlayStyle->shadowSize = settings->GetShadowSize();
+
+ SUBTITLES::Align subAlign = settings->GetAlignment();
+ if (subAlign == SUBTITLES::Align::TOP_INSIDE || subAlign == SUBTITLES::Align::TOP_OUTSIDE)
+ m_overlayStyle->alignment = SUBTITLES::STYLE::FontAlign::TOP_CENTER;
+ else
+ m_overlayStyle->alignment = SUBTITLES::STYLE::FontAlign::SUB_CENTER;
+
+ m_overlayStyle->assOverrideFont = settings->IsOverrideFonts();
+
+ SUBTITLES::OverrideStyles overrideStyles = settings->GetOverrideStyles();
+ if (overrideStyles == SUBTITLES::OverrideStyles::POSITIONS)
+ m_overlayStyle->assOverrideStyles = SUBTITLES::STYLE::OverrideStyles::POSITIONS;
+ else if (overrideStyles == SUBTITLES::OverrideStyles::STYLES)
+ m_overlayStyle->assOverrideStyles = SUBTITLES::STYLE::OverrideStyles::STYLES;
+ else if (overrideStyles == SUBTITLES::OverrideStyles::STYLES_POSITIONS)
+ m_overlayStyle->assOverrideStyles = SUBTITLES::STYLE::OverrideStyles::STYLES_POSITIONS;
+ else
+ m_overlayStyle->assOverrideStyles = SUBTITLES::STYLE::OverrideStyles::DISABLED;
+
+ // Changing vertical margin while in playback causes side effects when you
+ // rewind the video, displaying the previous text position (test Libass 15.2)
+ // for now vertical margin setting will be disabled during playback
+ m_overlayStyle->marginVertical =
+ static_cast<int>(SUBTITLES::STYLE::VIEWPORT_HEIGHT / 100 *
+ static_cast<double>(settings->GetVerticalMarginPerc()));
+
+ m_overlayStyle->blur = settings->GetBlurSize();
+}
+
+COverlay* CRenderer::ConvertLibass(
+ CDVDOverlayLibass* o,
+ double pts,
+ bool updateStyle,
+ const std::shared_ptr<struct SUBTITLES::STYLE::style>& overlayStyle)
+{
+ SUBTITLES::STYLE::renderOpts rOpts;
+
+ // libass render in a target area which named as frame. the frame size may bigger than video size,
+ // and including margins between video to frame edge. libass allow to render subtitles into the margins.
+ // this has been used to show subtitles in the top or bottom "black bar" between video to frame border.
+ rOpts.sourceWidth = m_rs.Width();
+ rOpts.sourceHeight = m_rs.Height();
+ rOpts.videoWidth = m_rd.Width();
+ rOpts.videoHeight = m_rd.Height();
+ rOpts.frameWidth = m_rv.Width();
+ rOpts.frameHeight = m_rv.Height();
+
+ // Render subtitle of half-sbs and half-ou video in full screen, not in half screen
+ if (m_stereomode == "left_right" || m_stereomode == "right_left")
+ {
+ // only half-sbs video, sbs video don't need to change source size
+ if (rOpts.sourceWidth / rOpts.sourceHeight < 1.2f)
+ rOpts.sourceWidth = m_rs.Width() * 2;
+ }
+ else if (m_stereomode == "top_bottom" || m_stereomode == "bottom_top")
+ {
+ // only half-ou video, ou video don't need to change source size
+ if (rOpts.sourceWidth / rOpts.sourceHeight > 2.5f)
+ rOpts.sourceHeight = m_rs.Height() * 2;
+ }
+
+ // Set position of subtitles based on video calibration settings
+ RESOLUTION_INFO resInfo = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ // Keep track of subtitle position value change,
+ // can be changed by GUI Calibration or by window mode/resolution change or
+ // by user manual change (e.g. keyboard shortcut)
+ if (m_subtitlePosResInfo != resInfo.iSubtitles)
+ {
+ if (m_subtitlePosResInfo == POSRESINFO_SAVE_CHANGES)
+ {
+ // m_subtitlePosition has been changed
+ // and has been requested to save the value to resInfo
+ resInfo.iSubtitles = m_subtitlePosition + m_subtitleVerticalMargin;
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetResInfo(
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), resInfo);
+ m_subtitlePosResInfo = m_subtitlePosition + m_subtitleVerticalMargin;
+ }
+ else
+ ResetSubtitlePosition();
+ }
+
+ rOpts.m_par = resInfo.fPixelRatio;
+
+ // rOpts.position and margins (set to style) can invalidate the text
+ // positions to subtitles type that make use of margins to position text on
+ // the screen (e.g. ASS/WebVTT) then we allow to set them when position
+ // override setting is enabled only
+ if (o->IsForcedMargins())
+ {
+ rOpts.marginsMode = SUBTITLES::STYLE::MarginsMode::DISABLED;
+ }
+ else if (m_subtitleAlign == SUBTITLES::Align::MANUAL)
+ {
+ // When vertical margins are used Libass apply a displacement in percentage
+ // of the height available to line position, this displacement causes
+ // problems with subtitle calibration bar on Video Calibration window,
+ // so when you moving the subtitle bar of the GUI the text will no longer
+ // match the bar, this calculation compensates for the displacement.
+ // Note also that the displacement compensation will cause a different
+ // default position of the text, different from the other alignment positions
+ double posPx = static_cast<double>(m_subtitlePosition - resInfo.Overscan.top);
+
+ int assPlayResY = o->GetLibassHandler()->GetPlayResY();
+ double assVertMargin = static_cast<double>(overlayStyle->marginVertical) *
+ (static_cast<double>(assPlayResY) / 720);
+ double vertMarginScaled = assVertMargin / assPlayResY * static_cast<double>(rOpts.frameHeight);
+
+ double pos = posPx / (static_cast<double>(rOpts.frameHeight) - vertMarginScaled);
+ rOpts.position = 100 - pos * 100;
+ }
+ else if (m_subtitleAlign == SUBTITLES::Align::BOTTOM_OUTSIDE)
+ {
+ // To keep consistent the position of text as other alignment positions
+ // we avoid apply the displacement compensation
+ double posPx =
+ static_cast<double>(m_subtitlePosition + m_subtitleVerticalMargin - resInfo.Overscan.top);
+ rOpts.position = 100 - posPx / static_cast<double>(rOpts.frameHeight) * 100;
+ }
+ else if (m_subtitleAlign == SUBTITLES::Align::BOTTOM_INSIDE ||
+ m_subtitleAlign == SUBTITLES::Align::TOP_INSIDE)
+ {
+ rOpts.marginsMode = SUBTITLES::STYLE::MarginsMode::INSIDE_VIDEO;
+ }
+
+ // Set the horizontal text alignment (currently used to improve readability on CC subtitles only)
+ // This setting influence style->alignment property
+ if (o->IsTextAlignEnabled())
+ {
+ if (m_subtitleHorizontalAlign == SUBTITLES::HorizontalAlign::LEFT)
+ rOpts.horizontalAlignment = SUBTITLES::STYLE::HorizontalAlign::LEFT;
+ else if (m_subtitleHorizontalAlign == SUBTITLES::HorizontalAlign::RIGHT)
+ rOpts.horizontalAlignment = SUBTITLES::STYLE::HorizontalAlign::RIGHT;
+ else
+ rOpts.horizontalAlignment = SUBTITLES::STYLE::HorizontalAlign::CENTER;
+ }
+
+ // changes: Detect changes from previously rendered images, if > 0 they are changed
+ int changes = 0;
+ ASS_Image* images =
+ o->GetLibassHandler()->RenderImage(pts, rOpts, updateStyle, overlayStyle, &changes);
+
+ // If no images not execute the renderer
+ if (!images)
+ return nullptr;
+
+ if (o->m_textureid)
+ {
+ if (changes == 0)
+ {
+ std::map<unsigned int, COverlay*>::iterator it = m_textureCache.find(o->m_textureid);
+ if (it != m_textureCache.end())
+ return it->second;
+ }
+ }
+
+ COverlay* overlay = NULL;
+#if defined(HAS_GL) || defined(HAS_GLES)
+ overlay = new COverlayGlyphGL(images, rOpts.frameWidth, rOpts.frameHeight);
+#elif defined(HAS_DX)
+ overlay = new COverlayQuadsDX(images, rOpts.frameWidth, rOpts.frameHeight);
+#endif
+
+ m_textureCache[m_textureid] = overlay;
+ o->m_textureid = m_textureid;
+ m_textureid++;
+ return overlay;
+}
+
+COverlay* CRenderer::Convert(CDVDOverlay* o, double pts)
+{
+ COverlay* r = NULL;
+
+ if (o->IsOverlayType(DVDOVERLAY_TYPE_TEXT) || o->IsOverlayType(DVDOVERLAY_TYPE_SSA))
+ {
+ CDVDOverlayLibass* ovAss = static_cast<CDVDOverlayLibass*>(o);
+ if (!ovAss || !ovAss->GetLibassHandler())
+ return nullptr;
+ bool updateStyle = !m_overlayStyle || m_isSettingsChanged;
+ if (updateStyle)
+ {
+ m_isSettingsChanged = false;
+ LoadSettings();
+ CreateSubtitlesStyle();
+ }
+
+ r = ConvertLibass(ovAss, pts, updateStyle, m_overlayStyle);
+
+ if (!r)
+ return nullptr;
+ }
+ else if (o->m_textureid)
+ {
+ std::map<unsigned int, COverlay*>::iterator it = m_textureCache.find(o->m_textureid);
+ if (it != m_textureCache.end())
+ r = it->second;
+ }
+
+ if (r)
+ {
+ return r;
+ }
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+ if (o->IsOverlayType(DVDOVERLAY_TYPE_IMAGE))
+ r = new COverlayTextureGL(static_cast<CDVDOverlayImage*>(o), m_rs);
+ else if (o->IsOverlayType(DVDOVERLAY_TYPE_SPU))
+ r = new COverlayTextureGL(static_cast<CDVDOverlaySpu*>(o));
+#elif defined(HAS_DX)
+ if (o->IsOverlayType(DVDOVERLAY_TYPE_IMAGE))
+ r = new COverlayImageDX(static_cast<CDVDOverlayImage*>(o), m_rs);
+ else if (o->IsOverlayType(DVDOVERLAY_TYPE_SPU))
+ r = new COverlayImageDX(static_cast<CDVDOverlaySpu*>(o));
+#endif
+
+ m_textureCache[m_textureid] = r;
+ o->m_textureid = m_textureid;
+ m_textureid++;
+
+ return r;
+}
+
+void CRenderer::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ switch (msg)
+ {
+ case ObservableMessageSettingsChanged:
+ {
+ m_isSettingsChanged = true;
+ break;
+ }
+ case ObservableMessagePositionChanged:
+ {
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_subtitlePosResInfo = POSRESINFO_UNSET;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CRenderer::LoadSettings()
+{
+ const auto settings{CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()};
+ m_subtitleHorizontalAlign = settings->GetHorizontalAlignment();
+ m_subtitleAlign = settings->GetAlignment();
+ ResetSubtitlePosition();
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.h
new file mode 100644
index 0000000..54e48c0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.h
@@ -0,0 +1,198 @@
+/*
+ * Initial code sponsored by: Voddler Inc (voddler.com)
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BaseRenderer.h"
+#include "cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h"
+#include "settings/SubtitlesSettings.h"
+#include "threads/CriticalSection.h"
+#include "utils/Observer.h"
+
+#include <atomic>
+#include <map>
+#include <memory>
+#include <vector>
+
+class CDVDOverlay;
+class CDVDOverlayLibass;
+class CDVDOverlayImage;
+class CDVDOverlaySpu;
+class CDVDOverlaySSA;
+class CDVDOverlayText;
+
+namespace OVERLAY {
+
+ struct SRenderState
+ {
+ float x;
+ float y;
+ float width;
+ float height;
+ };
+
+ class COverlay
+ {
+ public:
+ COverlay();
+ virtual ~COverlay();
+
+ virtual void Render(SRenderState& state) = 0;
+
+ enum EType
+ {
+ TYPE_NONE,
+ TYPE_TEXTURE
+ } m_type;
+
+ enum EAlign
+ {
+ ALIGN_SCREEN,
+ ALIGN_SCREEN_AR,
+ ALIGN_VIDEO,
+ ALIGN_SUBTITLE
+ } m_align;
+
+ enum EPosition
+ {
+ POSITION_ABSOLUTE,
+ POSITION_ABSOLUTE_SCREEN,
+ POSITION_RELATIVE
+ } m_pos;
+
+ float m_x{0};
+ float m_y{0};
+ float m_width{1.0f};
+ float m_height{1.0f};
+ float m_source_width{0}; // Video source width resolution used to calculate aspect ratio
+ float m_source_height{0}; // Video source height resolution used to calculate aspect ratio
+
+ protected:
+ /*!
+ * \brief Given the resolution ratio determines if it is a 4/3 resolution
+ * \param resRatio The resolution ratio (the results of width / height)
+ * \return True if the ratio refer to a 4/3 resolution, otherwise false
+ */
+ bool IsSquareResolution(float resRatio) { return resRatio > 1.22f && resRatio < 1.34f; }
+ };
+
+ class CRenderer : public Observer
+ {
+ public:
+ CRenderer();
+ virtual ~CRenderer();
+
+ // Implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
+ void AddOverlay(CDVDOverlay* o, double pts, int index);
+ virtual void Render(int idx);
+
+ /*!
+ * \brief Release resources
+ */
+ void UnInit();
+
+ void Flush();
+
+ /*!
+ * \brief Reset to default values
+ */
+ void Reset();
+
+ void Release(int idx);
+ bool HasOverlay(int idx);
+ void SetVideoRect(CRect &source, CRect &dest, CRect &view);
+ void SetStereoMode(const std::string &stereomode);
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ void SetSubtitleVerticalPosition(const int value, bool save);
+
+ protected:
+ /*!
+ * \brief Reset the subtitle position to default value
+ */
+ void ResetSubtitlePosition();
+
+ /*!
+ * \brief Called when the screen resolution is changed
+ */
+ void OnViewChange();
+
+ struct SElement
+ {
+ SElement()
+ {
+ overlay_dvd = NULL;
+ pts = 0.0;
+ }
+ double pts;
+ CDVDOverlay* overlay_dvd;
+ };
+
+ void Render(COverlay* o);
+ COverlay* Convert(CDVDOverlay* o, double pts);
+ /*!
+ * \brief Convert the overlay to a overlay renderer
+ * \param o The overlay to convert
+ * \param pts The current PTS time
+ * \param subStyle The style to be used, MUST BE SET ONLY at the first call or when user change settings
+ * \return True if success, false if error
+ */
+ COverlay* ConvertLibass(
+ CDVDOverlayLibass* o,
+ double pts,
+ bool updateStyle,
+ const std::shared_ptr<struct KODI::SUBTITLES::STYLE::style>& overlayStyle);
+
+ void CreateSubtitlesStyle();
+
+ void Release(std::vector<SElement>& list);
+ void ReleaseCache();
+ void ReleaseUnused();
+
+ /*!
+ * \brief Load and store settings locally
+ */
+ void LoadSettings();
+
+ enum PositonResInfoState
+ {
+ POSRESINFO_UNSET = -1,
+ POSRESINFO_SAVE_CHANGES = -2,
+ };
+
+ CCriticalSection m_section;
+ std::vector<SElement> m_buffers[NUM_BUFFERS];
+ std::map<unsigned int, COverlay*> m_textureCache;
+ static unsigned int m_textureid;
+ CRect m_rv; // Frame size
+ CRect m_rs; // Source size
+ CRect m_rd; // Video size, may be influenced by video settings (e.g. zoom)
+ std::string m_stereomode;
+ // Current subtitle position
+ int m_subtitlePosition{0};
+ // Current subtitle position from resolution info,
+ // or PositonResInfoState enum values for deferred processing
+ int m_subtitlePosResInfo{POSRESINFO_UNSET};
+ int m_subtitleVerticalMargin{0};
+ bool m_saveSubtitlePosition{false}; // To save subtitle position permanently
+ KODI::SUBTITLES::HorizontalAlign m_subtitleHorizontalAlign{
+ KODI::SUBTITLES::HorizontalAlign::CENTER};
+ KODI::SUBTITLES::Align m_subtitleAlign{KODI::SUBTITLES::Align::BOTTOM_OUTSIDE};
+
+ std::shared_ptr<struct KODI::SUBTITLES::STYLE::style> m_overlayStyle;
+ std::atomic<bool> m_isSettingsChanged{false};
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.cpp
new file mode 100644
index 0000000..7cc3d19
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.cpp
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverlayRendererDX.h"
+
+#include "OverlayRenderer.h"
+#include "OverlayRendererUtil.h"
+#include "application/Application.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "guilib/D3DResource.h"
+#include "guilib/GUIShaderDX.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#ifndef ASSERT
+#include <crtdbg.h>
+#define ASSERT(f) _ASSERTE((f))
+#endif
+
+#include <cmath>
+
+using namespace OVERLAY;
+using namespace DirectX;
+
+#define USE_PREMULTIPLIED_ALPHA 1
+#define ALPHA_CHANNEL_OFFSET 3
+
+static bool LoadTexture(int width, int height, int stride
+ , DXGI_FORMAT format
+ , const void* pixels
+ , float* u, float* v
+ , CD3DTexture* texture)
+{
+ if (!texture->Create(width, height, 1, D3D11_USAGE_IMMUTABLE, format, pixels, stride))
+ {
+ CLog::Log(LOGERROR, "{} - failed to allocate texture.", __FUNCTION__);
+ return false;
+ }
+
+ D3D11_TEXTURE2D_DESC desc = {};
+ if (!texture->GetDesc(&desc))
+ {
+ CLog::Log(LOGERROR, "{} - failed to get texture description.", __FUNCTION__);
+ texture->Release();
+ return false;
+ }
+ ASSERT(format == desc.Format || (format == DXGI_FORMAT_R8_UNORM && desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM));
+
+ *u = float(width) / desc.Width;
+ *v = float(height) / desc.Height;
+
+ return true;
+}
+
+COverlayQuadsDX::COverlayQuadsDX(ASS_Image* images, float width, float height)
+{
+ m_width = 1.0;
+ m_height = 1.0;
+ m_align = ALIGN_SCREEN;
+ m_pos = POSITION_RELATIVE;
+ m_x = 0.0f;
+ m_y = 0.0f;
+ m_count = 0;
+
+ SQuads quads;
+ if (!convert_quad(images, quads, static_cast<int>(width)))
+ return;
+
+ float u, v;
+ if (!LoadTexture(quads.size_x, quads.size_y, quads.size_x, DXGI_FORMAT_R8_UNORM,
+ quads.texture.data(), &u, &v, &m_texture))
+ {
+ return;
+ }
+
+ Vertex* vt = new Vertex[6 * quads.quad.size()];
+ Vertex* vt_orig = vt;
+ SQuad* vs = quads.quad.data();
+
+ float scale_u = u / quads.size_x;
+ float scale_v = v / quads.size_y;
+
+ float scale_x = 1.0f / width;
+ float scale_y = 1.0f / height;
+
+ for (size_t i = 0; i < quads.quad.size(); i++)
+ {
+ for (int s = 0; s < 6; s++)
+ {
+ CD3DHelper::XMStoreColor(&vt[s].color, vs->a, vs->r, vs->g, vs->b);
+ vt[s].pos = XMFLOAT3(scale_x, scale_y, 0.0f);
+ vt[s].texCoord = XMFLOAT2(scale_u, scale_v);
+ vt[s].texCoord2 = XMFLOAT2(0.0f, 0.0f);
+ }
+
+ vt[0].pos.x *= vs->x;
+ vt[0].texCoord.x *= vs->u;
+ vt[0].pos.y *= vs->y;
+ vt[0].texCoord.y *= vs->v;
+
+ vt[1].pos.x *= vs->x + vs->w;
+ vt[1].texCoord.x *= vs->u + vs->w;
+ vt[1].pos.y *= vs->y;
+ vt[1].texCoord.y *= vs->v;
+
+ vt[2].pos.x *= vs->x;
+ vt[2].texCoord.x *= vs->u;
+ vt[2].pos.y *= vs->y + vs->h;
+ vt[2].texCoord.y *= vs->v + vs->h;
+
+ vt[3] = vt[1];
+
+ vt[4].pos.x *= vs->x + vs->w;
+ vt[4].texCoord.x *= vs->u + vs->w;
+ vt[4].pos.y *= vs->y + vs->h;
+ vt[4].texCoord.y *= vs->v + vs->h;
+
+ vt[5] = vt[2];
+
+ vs += 1;
+ vt += 6;
+ }
+
+ vt = vt_orig;
+ m_count = static_cast<unsigned int>(quads.quad.size());
+
+ if (!m_vertex.Create(D3D11_BIND_VERTEX_BUFFER, 6 * m_count, sizeof(Vertex), DXGI_FORMAT_UNKNOWN,
+ D3D11_USAGE_IMMUTABLE, vt))
+ {
+ CLog::Log(LOGERROR, "{} - failed to create vertex buffer", __FUNCTION__);
+ m_texture.Release();
+ }
+
+ delete[] vt;
+}
+
+COverlayQuadsDX::~COverlayQuadsDX()
+{
+}
+
+void COverlayQuadsDX::Render(SRenderState &state)
+{
+ if (m_count == 0)
+ return;
+
+ ID3D11Buffer* vertexBuffer = m_vertex.Get();
+ if (vertexBuffer == nullptr)
+ return;
+
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+
+ XMMATRIX world, view, proj;
+ pGUIShader->GetWVP(world, view, proj);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_SPLIT_HORIZONTAL
+ || CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_SPLIT_VERTICAL)
+ {
+ CRect rect;
+ DX::Windowing()->GetViewPort(rect);
+ DX::Windowing()->SetCameraPosition(CPoint(rect.Width() * 0.5f, rect.Height() * 0.5f),
+ static_cast<int>(rect.Width()),
+ static_cast<int>(rect.Height()));
+ }
+
+ XMMATRIX trans = XMMatrixTranslation(state.x, state.y, 0.0f);
+ XMMATRIX scale = XMMatrixScaling(state.width, state.height, 1.0f);
+
+ pGUIShader->SetWorld(XMMatrixMultiply(XMMatrixMultiply(world, scale), trans));
+
+ const unsigned stride = sizeof(Vertex);
+ const unsigned offset = 0;
+
+ // Set the vertex buffer to active in the input assembler so it can be rendered.
+ pContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
+ // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
+ pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ DX::Windowing()->SetAlphaBlendEnable(true);
+ pGUIShader->Begin(SHADER_METHOD_RENDER_FONT);
+
+ pGUIShader->SetShaderViews(1, m_texture.GetAddressOfSRV());
+ pGUIShader->Draw(m_count * 6, 0);
+
+ // restoring transformation
+ pGUIShader->SetWVP(world, view, proj);
+ pGUIShader->RestoreBuffers();
+}
+
+COverlayImageDX::~COverlayImageDX()
+{
+}
+
+COverlayImageDX::COverlayImageDX(CDVDOverlayImage* o, CRect& rSource)
+{
+ if (o->palette.empty())
+ {
+ m_pma = false;
+ uint32_t* rgba = reinterpret_cast<uint32_t*>(o->pixels.data());
+ Load(rgba, o->width, o->height, o->linesize);
+ }
+ else
+ {
+ std::vector<uint32_t> rgba(o->width * o->height);
+ m_pma = !!USE_PREMULTIPLIED_ALPHA;
+ convert_rgba(o, m_pma, rgba);
+ Load(rgba.data(), o->width, o->height, o->width * 4);
+ }
+
+ if (o->source_width > 0 && o->source_height > 0)
+ {
+ m_pos = POSITION_RELATIVE;
+ m_x = (0.5f * o->width + o->x) / o->source_width;
+ m_y = (0.5f * o->height + o->y) / o->source_height;
+
+ const float subRatio{static_cast<float>(o->source_width) / o->source_height};
+ const float vidRatio{rSource.Width() / rSource.Height()};
+
+ // We always consider aligning 4/3 subtitles to the video,
+ // for example SD DVB subtitles (4/3) must be stretched on fullhd video
+
+ if (std::fabs(subRatio - vidRatio) < 0.001f || IsSquareResolution(subRatio))
+ {
+ m_align = ALIGN_VIDEO;
+ m_width = static_cast<float>(o->width) / o->source_width;
+ m_height = static_cast<float>(o->height) / o->source_height;
+ }
+ else
+ {
+ // We should have a re-encoded/cropped (removed black bars) video source.
+ // Then we cannot align to video otherwise the subtitles will be deformed
+ // better align to screen by keeping the aspect-ratio.
+ m_align = ALIGN_SCREEN_AR;
+ m_width = static_cast<float>(o->width);
+ m_height = static_cast<float>(o->height);
+ m_source_width = static_cast<float>(o->source_width);
+ m_source_height = static_cast<float>(o->source_height);
+ }
+ }
+ else
+ {
+ m_align = ALIGN_VIDEO;
+ m_pos = POSITION_ABSOLUTE;
+ m_x = static_cast<float>(o->x);
+ m_y = static_cast<float>(o->y);
+ m_width = static_cast<float>(o->width);
+ m_height = static_cast<float>(o->height);
+ }
+}
+
+COverlayImageDX::COverlayImageDX(CDVDOverlaySpu* o)
+{
+ int min_x, max_x, min_y, max_y;
+ std::vector<uint32_t> rgba(o->width * o->height);
+
+ convert_rgba(o, USE_PREMULTIPLIED_ALPHA, min_x, max_x, min_y, max_y, rgba);
+ Load(rgba.data() + min_x + min_y * o->width, max_x - min_x, max_y - min_y, o->width * 4);
+
+ m_align = ALIGN_VIDEO;
+ m_pos = POSITION_ABSOLUTE;
+ m_x = static_cast<float>(min_x + o->x);
+ m_y = static_cast<float>(min_y + o->y);
+ m_width = static_cast<float>(max_x - min_x);
+ m_height = static_cast<float>(max_y - min_y);
+}
+
+void COverlayImageDX::Load(uint32_t* rgba, int width, int height, int stride)
+{
+ float u, v;
+ if(!LoadTexture(width
+ , height
+ , stride
+ , DXGI_FORMAT_B8G8R8A8_UNORM
+ , rgba
+ , &u, &v
+ , &m_texture))
+ return;
+
+ Vertex vt[4];
+
+ vt[0].texCoord = XMFLOAT2(u, 0.0f);
+ vt[0].pos = XMFLOAT3(1.0f, 0.0f, 0.0f);
+
+ vt[1].texCoord = XMFLOAT2(u, v);
+ vt[1].pos = XMFLOAT3(1.0f, 1.0f, 0.0f);
+
+ vt[2].texCoord = XMFLOAT2(0.0f, 0.0f);
+ vt[2].pos = XMFLOAT3(0.0f, 0.0f, 0.0f);
+
+ vt[3].texCoord = XMFLOAT2(0.0f, v);
+ vt[3].pos = XMFLOAT3(0.0f, 1.0f, 0.0f);
+
+ if (!m_vertex.Create(D3D11_BIND_VERTEX_BUFFER, 4, sizeof(Vertex), DXGI_FORMAT_UNKNOWN, D3D11_USAGE_IMMUTABLE, vt))
+ {
+ CLog::Log(LOGERROR, "{} - failed to create vertex buffer", __FUNCTION__);
+ m_texture.Release();
+ }
+}
+
+void COverlayImageDX::Render(SRenderState &state)
+{
+ ID3D11Buffer* vertexBuffer = m_vertex.Get();
+ if (vertexBuffer == nullptr)
+ return;
+
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+
+ XMMATRIX world, view, proj;
+ pGUIShader->GetWVP(world, view, proj);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_SPLIT_HORIZONTAL
+ || CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_SPLIT_VERTICAL)
+ {
+ CRect rect;
+ DX::Windowing()->GetViewPort(rect);
+ DX::Windowing()->SetCameraPosition(CPoint(rect.Width() * 0.5f, rect.Height() * 0.5f),
+ static_cast<int>(rect.Width()),
+ static_cast<int>(rect.Height()));
+ }
+
+ XMMATRIX trans = m_pos == POSITION_RELATIVE
+ ? XMMatrixTranslation(state.x - state.width * 0.5f, state.y - state.height * 0.5f, 0.0f)
+ : XMMatrixTranslation(state.x, state.y, 0.0f),
+ scale = XMMatrixScaling(state.width, state.height, 1.0f);
+
+ pGUIShader->SetWorld(XMMatrixMultiply(XMMatrixMultiply(world, scale), trans));
+
+ const unsigned stride = m_vertex.GetStride();
+ const unsigned offset = 0;
+
+ pContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
+ pContext->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
+
+ pGUIShader->Begin(SHADER_METHOD_RENDER_TEXTURE_NOBLEND);
+ DX::Windowing()->SetAlphaBlendEnable(true);
+
+ pGUIShader->SetShaderViews(1, m_texture.GetAddressOfSRV());
+ pGUIShader->Draw(4, 0);
+
+ // restoring transformation
+ pGUIShader->SetWVP(world, view, proj);
+ pGUIShader->RestoreBuffers();
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.h b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.h
new file mode 100644
index 0000000..fa1a96c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererDX.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "OverlayRenderer.h"
+#include "guilib/D3DResource.h"
+
+class CDVDOverlay;
+class CDVDOverlayImage;
+class CDVDOverlaySpu;
+class CDVDOverlaySSA;
+typedef struct ass_image ASS_Image;
+
+namespace OVERLAY {
+
+ class COverlayQuadsDX
+ : public COverlay
+ {
+ public:
+ COverlayQuadsDX(ASS_Image* images, float width, float height);
+ virtual ~COverlayQuadsDX();
+
+ void Render(SRenderState& state);
+
+ unsigned int m_count;
+ CD3DTexture m_texture;
+ CD3DBuffer m_vertex;
+ };
+
+ class COverlayImageDX
+ : public COverlay
+ {
+ public:
+ /*! \brief Create the overlay for rendering
+ * \param o The overlay image
+ * \param rSource The video source rect size
+ */
+ explicit COverlayImageDX(CDVDOverlayImage* o, CRect& rSource);
+ explicit COverlayImageDX(CDVDOverlaySpu* o);
+ virtual ~COverlayImageDX();
+
+ void Load(uint32_t* rgba, int width, int height, int stride);
+ void Render(SRenderState& state);
+
+ CD3DTexture m_texture;
+ CD3DBuffer m_vertex;
+ bool m_pma{false};
+ };
+
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.cpp
new file mode 100644
index 0000000..12d6fb4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.cpp
@@ -0,0 +1,619 @@
+/*
+ * Initial code sponsored by: Voddler Inc (voddler.com)
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverlayRenderer.h"
+#include "OverlayRendererUtil.h"
+#include "OverlayRendererGL.h"
+#ifdef HAS_GL
+#include "LinuxRendererGL.h"
+#include "rendering/gl/RenderSystemGL.h"
+#elif HAS_GLES >= 2
+#include "LinuxRendererGLES.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#endif
+#include "rendering/MatrixGL.h"
+#include "RenderManager.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h"
+#include "windowing/WinSystem.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "utils/GLUtils.h"
+
+#include <cmath>
+
+#if HAS_GLES >= 2
+// GLES2.0 cant do CLAMP, but can do CLAMP_TO_EDGE.
+#define GL_CLAMP GL_CLAMP_TO_EDGE
+#endif
+
+#define USE_PREMULTIPLIED_ALPHA 1
+
+using namespace OVERLAY;
+
+static void LoadTexture(GLenum target
+ , GLsizei width, GLsizei height, GLsizei stride
+ , GLfloat* u, GLfloat* v
+ , bool alpha, const GLvoid* pixels)
+{
+ int width2 = width;
+ int height2 = height;
+ char *pixelVector = NULL;
+ const GLvoid *pixelData = pixels;
+
+#ifdef HAS_GLES
+ GLenum internalFormat = alpha ? GL_ALPHA : GL_RGBA;
+ GLenum externalFormat = alpha ? GL_ALPHA : GL_RGBA;
+#else
+ GLenum internalFormat = alpha ? GL_RED : GL_RGBA;
+ GLenum externalFormat = alpha ? GL_RED : GL_BGRA;
+#endif
+
+ int bytesPerPixel = KODI::UTILS::GL::glFormatElementByteCount(externalFormat);
+
+#ifdef HAS_GLES
+ bool bgraSupported = false;
+ CRenderSystemGLES* renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+
+ if (!alpha)
+ {
+ if (renderSystem->IsExtSupported("GL_EXT_texture_format_BGRA8888") ||
+ renderSystem->IsExtSupported("GL_IMG_texture_format_BGRA8888"))
+ {
+ bgraSupported = true;
+ internalFormat = externalFormat = GL_BGRA_EXT;
+ }
+ else if (renderSystem->IsExtSupported("GL_APPLE_texture_format_BGRA8888"))
+ {
+ // Apple's implementation does not conform to spec. Instead, they require
+ // differing format/internalformat, more like GL.
+ bgraSupported = true;
+ externalFormat = GL_BGRA_EXT;
+ }
+ }
+
+ int bytesPerLine = bytesPerPixel * width;
+
+ if (!alpha && !bgraSupported)
+ {
+ pixelVector = (char *)malloc(bytesPerLine * height);
+
+ const char *src = (const char*)pixels;
+ char *dst = pixelVector;
+ for (int y = 0;y < height;++y)
+ {
+ src = (const char*)pixels + y * stride;
+ dst = pixelVector + y * bytesPerLine;
+
+ for (GLsizei i = 0; i < width; i++, src+=4, dst+=4)
+ {
+ dst[0] = src[2];
+ dst[1] = src[1];
+ dst[2] = src[0];
+ dst[3] = src[3];
+ }
+ }
+
+ pixelData = pixelVector;
+ stride = width;
+ }
+ /** OpenGL ES does not support strided texture input. Make a copy without stride **/
+ else if (stride != bytesPerLine)
+ {
+ pixelVector = (char *)malloc(bytesPerLine * height);
+
+ const char *src = (const char*)pixels;
+ char *dst = pixelVector;
+ for (int y = 0;y < height;++y)
+ {
+ memcpy(dst, src, bytesPerLine);
+ src += stride;
+ dst += bytesPerLine;
+ }
+
+ pixelData = pixelVector;
+ stride = bytesPerLine;
+ }
+#else
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / bytesPerPixel);
+#endif
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexImage2D (target, 0, internalFormat
+ , width2, height2, 0
+ , externalFormat, GL_UNSIGNED_BYTE, NULL);
+
+ glTexSubImage2D(target, 0
+ , 0, 0, width, height
+ , externalFormat, GL_UNSIGNED_BYTE
+ , pixelData);
+
+ if(height < height2)
+ glTexSubImage2D( target, 0
+ , 0, height, width, 1
+ , externalFormat, GL_UNSIGNED_BYTE
+ , (const unsigned char*)pixelData + stride * (height-1));
+
+ if(width < width2)
+ glTexSubImage2D( target, 0
+ , width, 0, 1, height
+ , externalFormat, GL_UNSIGNED_BYTE
+ , (const unsigned char*)pixelData + bytesPerPixel * (width-1));
+
+#ifndef HAS_GLES
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+#endif
+
+ free(pixelVector);
+
+ *u = (GLfloat)width / width2;
+ *v = (GLfloat)height / height2;
+}
+
+COverlayTextureGL::COverlayTextureGL(CDVDOverlayImage* o, CRect& rSource)
+{
+ glGenTextures(1, &m_texture);
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ if (o->palette.empty())
+ {
+ m_pma = false;
+ uint32_t* rgba = reinterpret_cast<uint32_t*>(o->pixels.data());
+ LoadTexture(GL_TEXTURE_2D, o->width, o->height, o->linesize, &m_u, &m_v, false, rgba);
+ }
+ else
+ {
+ std::vector<uint32_t> rgba(o->width * o->height);
+ m_pma = !!USE_PREMULTIPLIED_ALPHA;
+ convert_rgba(o, m_pma, rgba);
+ LoadTexture(GL_TEXTURE_2D, o->width, o->height, o->width * 4, &m_u, &m_v, false, rgba.data());
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ if (o->source_width > 0 && o->source_height > 0)
+ {
+ m_pos = POSITION_RELATIVE;
+ m_x = (0.5f * o->width + o->x) / o->source_width;
+ m_y = (0.5f * o->height + o->y) / o->source_height;
+
+ const float subRatio{static_cast<float>(o->source_width) / o->source_height};
+ const float vidRatio{rSource.Width() / rSource.Height()};
+
+ // We always consider aligning 4/3 subtitles to the video,
+ // for example SD DVB subtitles (4/3) must be stretched on fullhd video
+
+ if (std::fabs(subRatio - vidRatio) < 0.001f || IsSquareResolution(subRatio))
+ {
+ m_align = ALIGN_VIDEO;
+ m_width = static_cast<float>(o->width) / o->source_width;
+ m_height = static_cast<float>(o->height) / o->source_height;
+ }
+ else
+ {
+ // We should have a re-encoded/cropped (removed black bars) video source.
+ // Then we cannot align to video otherwise the subtitles will be deformed
+ // better align to screen by keeping the aspect-ratio.
+ m_align = ALIGN_SCREEN_AR;
+ m_width = static_cast<float>(o->width);
+ m_height = static_cast<float>(o->height);
+ m_source_width = static_cast<float>(o->source_width);
+ m_source_height = static_cast<float>(o->source_height);
+ }
+ }
+ else
+ {
+ m_align = ALIGN_VIDEO;
+ m_pos = POSITION_ABSOLUTE;
+ m_x = static_cast<float>(o->x);
+ m_y = static_cast<float>(o->y);
+ m_width = static_cast<float>(o->width);
+ m_height = static_cast<float>(o->height);
+ }
+}
+
+COverlayTextureGL::COverlayTextureGL(CDVDOverlaySpu* o)
+{
+ int min_x, max_x, min_y, max_y;
+ std::vector<uint32_t> rgba(o->width * o->height);
+
+ convert_rgba(o, USE_PREMULTIPLIED_ALPHA, min_x, max_x, min_y, max_y, rgba);
+
+ glGenTextures(1, &m_texture);
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ LoadTexture(GL_TEXTURE_2D, max_x - min_x, max_y - min_y, o->width * 4, &m_u, &m_v, false,
+ rgba.data() + min_x + min_y * o->width);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ m_align = ALIGN_VIDEO;
+ m_pos = POSITION_ABSOLUTE;
+ m_x = static_cast<float>(min_x + o->x);
+ m_y = static_cast<float>(min_y + o->y);
+ m_width = static_cast<float>(max_x - min_x);
+ m_height = static_cast<float>(max_y - min_y);
+ m_pma = !!USE_PREMULTIPLIED_ALPHA;
+}
+
+COverlayGlyphGL::COverlayGlyphGL(ASS_Image* images, float width, float height)
+{
+ m_width = 1.0;
+ m_height = 1.0;
+ m_align = ALIGN_SCREEN;
+ m_pos = POSITION_RELATIVE;
+ m_x = 0.0f;
+ m_y = 0.0f;
+
+ SQuads quads;
+ if (!convert_quad(images, quads, static_cast<int>(width)))
+ return;
+
+ glGenTextures(1, &m_texture);
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+
+ LoadTexture(GL_TEXTURE_2D, quads.size_x, quads.size_y, quads.size_x, &m_u, &m_v, true,
+ quads.texture.data());
+
+
+ float scale_u = m_u / quads.size_x;
+ float scale_v = m_v / quads.size_y;
+
+ float scale_x = 1.0f / width;
+ float scale_y = 1.0f / height;
+
+ m_vertex.resize(quads.quad.size() * 4);
+
+ VERTEX* vt = m_vertex.data();
+ SQuad* vs = quads.quad.data();
+
+ for (size_t i = 0; i < quads.quad.size(); i++)
+ {
+ for(int s = 0; s < 4; s++)
+ {
+ vt[s].a = vs->a;
+ vt[s].r = vs->r;
+ vt[s].g = vs->g;
+ vt[s].b = vs->b;
+
+ vt[s].x = scale_x;
+ vt[s].y = scale_y;
+ vt[s].z = 0.0f;
+ vt[s].u = scale_u;
+ vt[s].v = scale_v;
+ }
+
+ vt[0].x *= vs->x;
+ vt[0].u *= vs->u;
+ vt[0].y *= vs->y;
+ vt[0].v *= vs->v;
+
+ vt[1].x *= vs->x;
+ vt[1].u *= vs->u;
+ vt[1].y *= vs->y + vs->h;
+ vt[1].v *= vs->v + vs->h;
+
+ vt[2].x *= vs->x + vs->w;
+ vt[2].u *= vs->u + vs->w;
+ vt[2].y *= vs->y;
+ vt[2].v *= vs->v;
+
+ vt[3].x *= vs->x + vs->w;
+ vt[3].u *= vs->u + vs->w;
+ vt[3].y *= vs->y + vs->h;
+ vt[3].v *= vs->v + vs->h;
+
+ vs += 1;
+ vt += 4;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+COverlayGlyphGL::~COverlayGlyphGL()
+{
+ glDeleteTextures(1, &m_texture);
+}
+
+void COverlayGlyphGL::Render(SRenderState& state)
+{
+ if ((m_texture == 0) || (m_vertex.size() == 0))
+ return;
+
+ glEnable(GL_BLEND);
+
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ glMatrixModview.Push();
+ glMatrixModview->Translatef(state.x, state.y, 0.0f);
+ glMatrixModview->Scalef(state.width, state.height, 1.0f);
+ glMatrixModview.Load();
+
+#ifdef HAS_GL
+ CRenderSystemGL* renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+ renderSystem->EnableShader(ShaderMethodGL::SM_FONTS);
+ GLint posLoc = renderSystem->ShaderGetPos();
+ GLint colLoc = renderSystem->ShaderGetCol();
+ GLint tex0Loc = renderSystem->ShaderGetCoord0();
+
+ std::vector<VERTEX> vecVertices(6 * m_vertex.size() / 4);
+ VERTEX* vertices = vecVertices.data();
+
+ for (size_t i = 0; i < m_vertex.size(); i += 4)
+ {
+ *vertices++ = m_vertex[i];
+ *vertices++ = m_vertex[i+1];
+ *vertices++ = m_vertex[i+2];
+
+ *vertices++ = m_vertex[i+1];
+ *vertices++ = m_vertex[i+3];
+ *vertices++ = m_vertex[i+2];
+ }
+ GLuint VertexVBO;
+
+ glGenBuffers(1, &VertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, VertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(VERTEX) * vecVertices.size(), vecVertices.data(),
+ GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(VERTEX),
+ reinterpret_cast<const GLvoid*>(offsetof(VERTEX, x)));
+ glVertexAttribPointer(colLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(VERTEX),
+ reinterpret_cast<const GLvoid*>(offsetof(VERTEX, r)));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(VERTEX),
+ reinterpret_cast<const GLvoid*>(offsetof(VERTEX, u)));
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(colLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ glDrawArrays(GL_TRIANGLES, 0, vecVertices.size());
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(colLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &VertexVBO);
+
+ renderSystem->DisableShader();
+
+#else
+ CRenderSystemGLES* renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_FONTS);
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint colLoc = renderSystem->GUIShaderGetCol();
+ GLint tex0Loc = renderSystem->GUIShaderGetCoord0();
+
+ // stack object until VBOs will be used
+ std::vector<VERTEX> vecVertices(6 * m_vertex.size() / 4);
+ VERTEX* vertices = vecVertices.data();
+
+ for (size_t i = 0; i < m_vertex.size(); i += 4)
+ {
+ *vertices++ = m_vertex[i];
+ *vertices++ = m_vertex[i+1];
+ *vertices++ = m_vertex[i+2];
+
+ *vertices++ = m_vertex[i+1];
+ *vertices++ = m_vertex[i+3];
+ *vertices++ = m_vertex[i+2];
+ }
+
+ vertices = vecVertices.data();
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (char*)vertices + offsetof(VERTEX, x));
+ glVertexAttribPointer(colLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(VERTEX), (char*)vertices + offsetof(VERTEX, r));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (char*)vertices + offsetof(VERTEX, u));
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(colLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ glDrawArrays(GL_TRIANGLES, 0, vecVertices.size());
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(colLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ renderSystem->DisableGUIShader();
+#endif
+
+ glMatrixModview.PopLoad();
+
+ glDisable(GL_BLEND);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+
+COverlayTextureGL::~COverlayTextureGL()
+{
+ glDeleteTextures(1, &m_texture);
+}
+
+void COverlayTextureGL::Render(SRenderState& state)
+{
+ glEnable(GL_BLEND);
+
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+ if(m_pma)
+ glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ else
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ CRect rd;
+ if (m_pos == POSITION_RELATIVE)
+ {
+ float top = state.y - state.height * 0.5f;
+ float bottom = state.y + state.height * 0.5f;
+ float left = state.x - state.width * 0.5f;
+ float right = state.x + state.width * 0.5f;
+
+ rd.SetRect(left, top, right, bottom);
+ }
+ else
+ {
+ float top = state.y;
+ float bottom = state.y + state.height;
+ float left = state.x;
+ float right = state.x + state.width;
+
+ rd.SetRect(left, top, right, bottom);
+ }
+
+#if defined(HAS_GL)
+ CRenderSystemGL* renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+
+ int glslMajor, glslMinor;
+ renderSystem->GetGLSLVersion(glslMajor, glslMinor);
+ if (glslMajor >= 2 || (glslMajor == 1 && glslMinor >= 50))
+ renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE_LIM);
+ else
+ renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE);
+
+ GLint posLoc = renderSystem->ShaderGetPos();
+ GLint tex0Loc = renderSystem->ShaderGetCoord0();
+ GLint uniColLoc = renderSystem->ShaderGetUniCol();
+
+ GLfloat col[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+
+ glUniform4f(uniColLoc,(col[0]), (col[1]), (col[2]), (col[3]));
+
+ // Setup vertex position values
+ vertex[0].x = rd.x1;
+ vertex[0].y = rd.y1;
+ vertex[0].z = 0;
+ vertex[0].u1 = 0.0f;
+ vertex[0].v1 = 0.0;
+
+ vertex[1].x = rd.x2;
+ vertex[1].y = rd.y1;
+ vertex[1].z = 0;
+ vertex[1].u1 = m_u;
+ vertex[1].v1 = 0.0f;
+
+ vertex[2].x = rd.x2;
+ vertex[2].y = rd.y2;
+ vertex[2].z = 0;
+ vertex[2].u1 = m_u;
+ vertex[2].v1 = m_v;
+
+ vertex[3].x = rd.x1;
+ vertex[3].y = rd.y2;
+ vertex[3].z = 0;
+ vertex[3].u1 = 0.0f;
+ vertex[3].v1 = m_v;
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ renderSystem->DisableShader();
+
+#else
+ CRenderSystemGLES* renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE);
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint colLoc = renderSystem->GUIShaderGetCol();
+ GLint tex0Loc = renderSystem->GUIShaderGetCoord0();
+ GLint uniColLoc = renderSystem->GUIShaderGetUniCol();
+
+ GLfloat col[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+ GLfloat ver[4][2];
+ GLfloat tex[4][2];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of triangle strip
+
+ glVertexAttribPointer(posLoc, 2, GL_FLOAT, 0, 0, ver);
+ glVertexAttribPointer(colLoc, 4, GL_FLOAT, 0, 0, col);
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(colLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ glUniform4f(uniColLoc,(col[0]), (col[1]), (col[2]), (col[3]));
+ // Setup vertex position values
+ ver[0][0] = ver[3][0] = rd.x1;
+ ver[0][1] = ver[1][1] = rd.y1;
+ ver[1][0] = ver[2][0] = rd.x2;
+ ver[2][1] = ver[3][1] = rd.y2;
+
+ // Setup texture coordinates
+ tex[0][0] = tex[0][1] = tex[1][1] = tex[3][0] = 0.0f;
+ tex[1][0] = tex[2][0] = m_u;
+ tex[2][1] = tex[3][1] = m_v;
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(colLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ renderSystem->DisableGUIShader();
+#endif
+
+ glDisable(GL_BLEND);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.h
new file mode 100644
index 0000000..cd06e22
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererGL.h
@@ -0,0 +1,66 @@
+/*
+ * Initial code sponsored by: Voddler Inc (voddler.com)
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "OverlayRenderer.h"
+
+#include "system_gl.h"
+
+class CDVDOverlay;
+class CDVDOverlayImage;
+class CDVDOverlaySpu;
+class CDVDOverlaySSA;
+typedef struct ass_image ASS_Image;
+
+namespace OVERLAY {
+
+ class COverlayTextureGL : public COverlay
+ {
+ public:
+ /*! \brief Create the overlay for rendering
+ * \param o The overlay image
+ * \param rSource The video source rect size
+ */
+ explicit COverlayTextureGL(CDVDOverlayImage* o, CRect& rSource);
+ explicit COverlayTextureGL(CDVDOverlaySpu* o);
+ ~COverlayTextureGL() override;
+
+ void Render(SRenderState& state) override;
+
+ GLuint m_texture = 0;
+ float m_u;
+ float m_v;
+ bool m_pma; /*< is alpha in texture premultiplied in the values */
+ };
+
+ class COverlayGlyphGL : public COverlay
+ {
+ public:
+ COverlayGlyphGL(ASS_Image* images, float width, float height);
+
+ ~COverlayGlyphGL() override;
+
+ void Render(SRenderState& state) override;
+
+ struct VERTEX
+ {
+ GLfloat u, v;
+ GLubyte r, g, b, a;
+ GLfloat x, y, z;
+ };
+
+ std::vector<VERTEX> m_vertex;
+
+ GLuint m_texture = 0;
+ float m_u;
+ float m_v;
+ };
+
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.cpp
new file mode 100644
index 0000000..a21800e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverlayRendererUtil.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayImage.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySSA.h"
+#include "cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlaySpu.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "windowing/GraphicContext.h"
+
+namespace OVERLAY
+{
+
+static uint32_t build_rgba(int a, int r, int g, int b, bool mergealpha)
+{
+ if(mergealpha)
+ return a << PIXEL_ASHIFT
+ | (r * a / 255) << PIXEL_RSHIFT
+ | (g * a / 255) << PIXEL_GSHIFT
+ | (b * a / 255) << PIXEL_BSHIFT;
+ else
+ return a << PIXEL_ASHIFT
+ | r << PIXEL_RSHIFT
+ | g << PIXEL_GSHIFT
+ | b << PIXEL_BSHIFT;
+}
+
+#define clamp(x) (x) > 255.0 ? 255 : ((x) < 0.0 ? 0 : (int)(x + 0.5))
+static uint32_t build_rgba(int yuv[3], int alpha, bool mergealpha)
+{
+ int a = alpha + ( (alpha << 4) & 0xff );
+ double r = 1.164 * (yuv[0] - 16) + 1.596 * (yuv[2] - 128);
+ double g = 1.164 * (yuv[0] - 16) - 0.391 * (yuv[1] - 128) - 0.813 * (yuv[2] - 128);
+ double b = 1.164 * (yuv[0] - 16) + 2.018 * (yuv[1] - 128);
+ return build_rgba(a, clamp(r), clamp(g), clamp(b), mergealpha);
+}
+#undef clamp
+
+void convert_rgba(CDVDOverlayImage* o, bool mergealpha, std::vector<uint32_t>& rgba)
+{
+ uint32_t palette[256] = {};
+ for (size_t i = 0; i < o->palette.size(); i++)
+ palette[i] = build_rgba((o->palette[i] >> PIXEL_ASHIFT) & 0xff
+ , (o->palette[i] >> PIXEL_RSHIFT) & 0xff
+ , (o->palette[i] >> PIXEL_GSHIFT) & 0xff
+ , (o->palette[i] >> PIXEL_BSHIFT) & 0xff
+ , mergealpha);
+
+ for (int row = 0; row < o->height; row++)
+ for (int col = 0; col < o->width; col++)
+ rgba[row * o->width + col] = palette[o->pixels[row * o->linesize + col]];
+}
+
+void convert_rgba(CDVDOverlaySpu* o,
+ bool mergealpha,
+ int& min_x,
+ int& max_x,
+ int& min_y,
+ int& max_y,
+ std::vector<uint32_t>& rgba)
+{
+ uint32_t palette[8];
+ for (int i = 0; i < 4; i++)
+ {
+ palette[i] = build_rgba(o->color[i] , o->alpha[i] , mergealpha);
+ palette[i+4] = build_rgba(o->highlight_color[i], o->highlight_alpha[i], mergealpha);
+ }
+
+ uint32_t color;
+ uint32_t* trg;
+ uint16_t* src;
+
+ int len, idx, draw;
+
+ int btn_x_start = 0
+ , btn_x_end = 0
+ , btn_y_start = 0
+ , btn_y_end = 0;
+
+ if(o->bForced)
+ {
+ btn_x_start = o->crop_i_x_start - o->x;
+ btn_x_end = o->crop_i_x_end - o->x;
+ btn_y_start = o->crop_i_y_start - o->y;
+ btn_y_end = o->crop_i_y_end - o->y;
+ }
+
+ min_x = o->width;
+ max_x = 0;
+ min_y = o->height;
+ max_y = 0;
+
+ trg = rgba.data();
+ src = (uint16_t*)o->result;
+
+ for (int y = 0; y < o->height; y++)
+ {
+ for (int x = 0; x < o->width ; x += len)
+ {
+ /* Get the RLE part, then draw the line */
+ idx = *src & 0x3;
+ len = *src++ >> 2;
+
+ while( len > 0 )
+ {
+ draw = len;
+ color = palette[idx];
+
+ if (y >= btn_y_start && y <= btn_y_end)
+ {
+ if ( x < btn_x_start && x + len >= btn_x_start) // starts outside
+ draw = btn_x_start - x;
+ else if( x >= btn_x_start && x <= btn_x_end) // starts inside
+ {
+ color = palette[idx + 4];
+ draw = btn_x_end - x + 1;
+ }
+ }
+ /* make sure we are not requested to draw to far */
+ /* that part will be taken care of in next pass */
+ if( draw > len )
+ draw = len;
+
+ /* calculate cropping */
+ if(color & 0xff000000)
+ {
+ if(x < min_x)
+ min_x = x;
+ if(y < min_y)
+ min_y = y;
+ if(x + draw > max_x)
+ max_x = x + draw;
+ if(y + 1 > max_y)
+ max_y = y + 1;
+ }
+
+ for(int i = 0; i < draw; i++)
+ trg[x + i] = color;
+
+ len -= draw;
+ x += draw;
+ }
+ }
+ trg += o->width;
+ }
+
+ /* if nothing visible, just output a dummy pixel */
+ if(max_x <= min_x
+ || max_y <= min_y)
+ {
+ max_y = max_x = 1;
+ min_y = min_x = 0;
+ }
+}
+
+bool convert_quad(ASS_Image* images, SQuads& quads, int max_x)
+{
+ ASS_Image* img;
+ int count = 0;
+
+ if (!images)
+ return false;
+
+ // first calculate how many glyph we have and the total x length
+
+ for(img = images; img; img = img->next)
+ {
+ // fully transparent or width or height is 0 -> not displayed
+ if((img->color & 0xff) == 0xff || img->w == 0 || img->h == 0)
+ continue;
+
+ quads.size_x += img->w + 1;
+ count++;
+ }
+
+ if (count == 0)
+ return false;
+
+ if (quads.size_x > max_x)
+ quads.size_x = max_x;
+
+ int curr_x = 0;
+ int curr_y = 0;
+
+ // calculate the y size of the texture
+
+ for(img = images; img; img = img->next)
+ {
+ if((img->color & 0xff) == 0xff || img->w == 0 || img->h == 0)
+ continue;
+
+ // check if we need to split to new line
+ if (curr_x + img->w >= quads.size_x)
+ {
+ quads.size_y += curr_y + 1;
+ curr_x = 0;
+ curr_y = 0;
+ }
+
+ curr_x += img->w + 1;
+
+ if (img->h > curr_y)
+ curr_y = img->h;
+ }
+
+ quads.size_y += curr_y + 1;
+
+ // allocate space for the glyph positions and texturedata
+ quads.quad.resize(count);
+ quads.texture.resize(quads.size_x * quads.size_y);
+
+ SQuad* v = quads.quad.data();
+ uint8_t* data = quads.texture.data();
+
+ int y = 0;
+
+ curr_x = 0;
+ curr_y = 0;
+
+ for (img = images; img; img = img->next)
+ {
+ if ((img->color & 0xff) == 0xff || img->w == 0 || img->h == 0)
+ continue;
+
+ unsigned int color = img->color;
+ unsigned int alpha = (color & 0xff);
+
+ if (curr_x + img->w >= quads.size_x)
+ {
+ curr_y += y + 1;
+ curr_x = 0;
+ y = 0;
+ data = quads.texture.data() + curr_y * quads.size_x;
+ }
+
+ unsigned int r = ((color >> 24) & 0xff);
+ unsigned int g = ((color >> 16) & 0xff);
+ unsigned int b = ((color >> 8 ) & 0xff);
+
+ v->a = 255 - alpha;
+ v->r = r;
+ v->g = g;
+ v->b = b;
+
+ v->u = curr_x;
+ v->v = curr_y;
+
+ v->x = img->dst_x;
+ v->y = img->dst_y;
+
+ v->w = img->w;
+ v->h = img->h;
+
+ v++;
+
+ for (int i = 0; i < img->h; i++)
+ memcpy(data + quads.size_x * i, img->bitmap + img->stride * i, img->w);
+
+ if (img->h > y)
+ y = img->h;
+
+ curr_x += img->w + 1;
+ data += img->w + 1;
+ }
+ return true;
+}
+
+int GetStereoscopicDepth()
+{
+ int depth = 0;
+
+ if(CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_MONO
+ && CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_OFF)
+ {
+ depth = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SUBTITLES_STEREOSCOPICDEPTH);
+ depth *= (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoView() == RENDER_STEREO_VIEW_LEFT ? 1 : -1);
+ }
+
+ return depth;
+}
+
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.h b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.h
new file mode 100644
index 0000000..96db739
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRendererUtil.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <vector>
+
+class CDVDOverlayImage;
+class CDVDOverlaySpu;
+class CDVDOverlaySSA;
+typedef struct ass_image ASS_Image;
+
+namespace OVERLAY
+{
+
+struct SQuad
+{
+ int u, v;
+ unsigned char r, g, b, a;
+ int x, y;
+ int w, h;
+};
+
+struct SQuads
+{
+ int size_x{0};
+ int size_y{0};
+ std::vector<uint8_t> texture;
+ std::vector<SQuad> quad;
+};
+
+void convert_rgba(CDVDOverlayImage* o, bool mergealpha, std::vector<uint32_t>& rgba);
+void convert_rgba(CDVDOverlaySpu* o,
+ bool mergealpha,
+ int& min_x,
+ int& max_x,
+ int& min_y,
+ int& max_y,
+ std::vector<uint32_t>& rgba);
+bool convert_quad(ASS_Image* images, SQuads& quads, int max_x);
+int GetStereoscopicDepth();
+
+} // namespace OVERLAY
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.cpp
new file mode 100644
index 0000000..461d413
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderCapture.h"
+
+#include "ServiceBroker.h"
+#include "cores/IPlayer.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+
+bool CRenderCapture::UseOcclusionQuery()
+{
+ if (m_flags & CAPTUREFLAG_IMMEDIATELY)
+ return false;
+ else if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoCaptureUseOcclusionQuery == 0)
+ return false;
+ else
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.h
new file mode 100644
index 0000000..3f01189
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCapture.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Event.h"
+
+enum ECAPTURESTATE
+{
+ CAPTURESTATE_WORKING,
+ CAPTURESTATE_NEEDSRENDER,
+ CAPTURESTATE_NEEDSREADOUT,
+ CAPTURESTATE_DONE,
+ CAPTURESTATE_FAILED,
+ CAPTURESTATE_NEEDSDELETE
+};
+
+class CRenderCapture
+{
+ public:
+ CRenderCapture() = default;
+ virtual ~CRenderCapture() = default;
+
+ virtual void BeginRender() = 0;
+ virtual void EndRender() = 0;
+
+ virtual void ReadOut() {}
+ virtual void* GetRenderBuffer() { return m_pixels; }
+
+ /* \brief Called by the rendermanager to set the state, should not be called by anything else */
+ void SetState(ECAPTURESTATE state) { m_state = state; }
+
+ /* \brief Called by the rendermanager to get the state, should not be called by anything else */
+ ECAPTURESTATE GetState() { return m_state;}
+
+ /* \brief Called by the rendermanager to set the userstate, should not be called by anything else */
+ void SetUserState(ECAPTURESTATE state) { m_userState = state; }
+
+ /* \brief Called by the code requesting the capture
+ \return CAPTURESTATE_WORKING when the capture is in progress,
+ CAPTURESTATE_DONE when the capture has succeeded,
+ CAPTURESTATE_FAILED when the capture has failed
+ */
+ ECAPTURESTATE GetUserState() { return m_userState; }
+
+ /* \brief The internal event will be set when the rendermanager has captured and read a videoframe, or when it has failed
+ \return A reference to m_event
+ */
+ CEvent& GetEvent() { return m_event; }
+
+ /* \brief Called by the rendermanager to set the flags, should not be called by anything else */
+ void SetFlags(int flags) { m_flags = flags; }
+
+ /* \brief Called by the rendermanager to get the flags, should not be called by anything else */
+ int GetFlags() { return m_flags; }
+
+ /* \brief Called by the rendermanager to set the width, should not be called by anything else */
+ void SetWidth(unsigned int width) { m_width = width; }
+
+ /* \brief Called by the rendermanager to set the height, should not be called by anything else */
+ void SetHeight(unsigned int height) { m_height = height; }
+
+ /* \brief Called by the code requesting the capture to get the width */
+ unsigned int GetWidth() { return m_width; }
+
+ /* \brief Called by the code requesting the capture to get the height */
+ unsigned int GetHeight() { return m_height; }
+
+ /* \brief Called by the code requesting the capture to get the buffer where the videoframe is stored,
+ the format is BGRA, this buffer is only valid when GetUserState returns CAPTURESTATE_DONE.
+ The size of the buffer is GetWidth() * GetHeight() * 4.
+ */
+ uint8_t* GetPixels() const { return m_pixels; }
+
+ /* \brief Called by the rendermanager to know if the capture is readout async (using dma for example),
+ should not be called by anything else.
+ */
+ bool IsAsync() { return m_asyncSupported; }
+
+ protected:
+ bool UseOcclusionQuery();
+
+ ECAPTURESTATE m_state{CAPTURESTATE_FAILED}; //state for the rendermanager
+ ECAPTURESTATE m_userState{CAPTURESTATE_FAILED}; //state for the thread that wants the capture
+ int m_flags{0};
+ CEvent m_event;
+
+ uint8_t* m_pixels{nullptr};
+ unsigned int m_width{0};
+ unsigned int m_height{0};
+ unsigned int m_bufferSize{0};
+
+ // this is set after the first render
+ bool m_asyncSupported{false};
+ bool m_asyncChecked{false};
+};
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp
new file mode 100644
index 0000000..81d2260
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderCaptureDX.h"
+
+#include "cores/IPlayer.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+extern "C"
+{
+#include <libavutil/mem.h>
+}
+
+CRenderCaptureDX::CRenderCaptureDX() : CRenderCapture()
+{
+ DX::Windowing()->Register(this);
+}
+
+CRenderCaptureDX::~CRenderCaptureDX()
+{
+ CleanupDX();
+ av_freep(&m_pixels);
+ DX::Windowing()->Unregister(this);
+}
+
+void CRenderCaptureDX::BeginRender()
+{
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext =
+ DX::DeviceResources::Get()->GetD3DContext();
+ Microsoft::WRL::ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ CD3D11_QUERY_DESC queryDesc(D3D11_QUERY_EVENT);
+
+ if (!m_asyncChecked)
+ {
+ m_asyncSupported = SUCCEEDED(pDevice->CreateQuery(&queryDesc, nullptr));
+ if (m_flags & CAPTUREFLAG_CONTINUOUS)
+ {
+ if (!m_asyncSupported)
+ CLog::Log(LOGWARNING, "{}: D3D11_QUERY_OCCLUSION not supported, performance might suffer.",
+ __FUNCTION__);
+ if (!UseOcclusionQuery())
+ CLog::Log(LOGWARNING, "{}: D3D11_QUERY_OCCLUSION disabled, performance might suffer.",
+ __FUNCTION__);
+ }
+ m_asyncChecked = true;
+ }
+
+ HRESULT result;
+
+ if (m_surfaceWidth != m_width || m_surfaceHeight != m_height)
+ {
+ m_renderTex.Release();
+ m_copyTex.Release();
+
+ if (!m_renderTex.Create(m_width, m_height, 1, D3D11_USAGE_DEFAULT, DXGI_FORMAT_B8G8R8A8_UNORM))
+ {
+ CLog::LogF(LOGERROR, "CreateTexture2D (RENDER_TARGET) failed.");
+ SetState(CAPTURESTATE_FAILED);
+ return;
+ }
+
+ if (!m_copyTex.Create(m_width, m_height, 1, D3D11_USAGE_STAGING, DXGI_FORMAT_B8G8R8A8_UNORM))
+ {
+ CLog::LogF(LOGERROR, "CreateRenderTargetView failed.");
+ SetState(CAPTURESTATE_FAILED);
+ return;
+ }
+
+ m_surfaceWidth = m_width;
+ m_surfaceHeight = m_height;
+ }
+
+ if (m_bufferSize != m_width * m_height * 4)
+ {
+ m_bufferSize = m_width * m_height * 4;
+ av_freep(&m_pixels);
+ m_pixels = (uint8_t*)av_malloc(m_bufferSize);
+ }
+
+ if (m_asyncSupported && UseOcclusionQuery())
+ {
+ //generate an occlusion query if we don't have one
+ if (!m_query)
+ {
+ result = pDevice->CreateQuery(&queryDesc, m_query.ReleaseAndGetAddressOf());
+ if (FAILED(result))
+ {
+ CLog::LogF(LOGERROR, "CreateQuery failed {}", DX::GetErrorDescription(result));
+ m_asyncSupported = false;
+ m_query = nullptr;
+ }
+ }
+ }
+ else
+ {
+ //don't use an occlusion query, clean up any old one
+ m_query = nullptr;
+ }
+}
+
+void CRenderCaptureDX::EndRender()
+{
+ // send commands to the GPU queue
+ auto deviceResources = DX::DeviceResources::Get();
+ deviceResources->FinishCommandList();
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext = deviceResources->GetImmediateContext();
+
+ pContext->CopyResource(m_copyTex.Get(), m_renderTex.Get());
+
+ if (m_query)
+ {
+ pContext->End(m_query.Get());
+ }
+
+ if (m_flags & CAPTUREFLAG_IMMEDIATELY)
+ SurfaceToBuffer();
+ else
+ SetState(CAPTURESTATE_NEEDSREADOUT);
+}
+
+void CRenderCaptureDX::ReadOut()
+{
+ if (m_query)
+ {
+ //if the result of the occlusion query is available, the data is probably also written into m_copySurface
+ HRESULT result =
+ DX::DeviceResources::Get()->GetImmediateContext()->GetData(m_query.Get(), nullptr, 0, 0);
+ if (SUCCEEDED(result))
+ {
+ if (S_OK == result)
+ SurfaceToBuffer();
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{}: GetData failed.", __FUNCTION__);
+ SurfaceToBuffer();
+ }
+ }
+ else
+ {
+ SurfaceToBuffer();
+ }
+}
+
+void CRenderCaptureDX::SurfaceToBuffer()
+{
+ D3D11_MAPPED_SUBRESOURCE lockedRect;
+ if (m_copyTex.LockRect(0, &lockedRect, D3D11_MAP_READ))
+ {
+ //if pitch is same, do a direct copy, otherwise copy one line at a time
+ if (lockedRect.RowPitch == m_width * 4)
+ {
+ memcpy(m_pixels, lockedRect.pData, m_width * m_height * 4);
+ }
+ else
+ {
+ for (unsigned int y = 0; y < m_height; y++)
+ memcpy(m_pixels + y * m_width * 4, (uint8_t*)lockedRect.pData + y * lockedRect.RowPitch,
+ m_width * 4);
+ }
+ m_copyTex.UnlockRect(0);
+ SetState(CAPTURESTATE_DONE);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{}: locking m_copySurface failed.", __FUNCTION__);
+ SetState(CAPTURESTATE_FAILED);
+ }
+}
+
+void CRenderCaptureDX::OnDestroyDevice(bool fatal)
+{
+ CleanupDX();
+ SetState(CAPTURESTATE_FAILED);
+}
+
+void CRenderCaptureDX::CleanupDX()
+{
+ m_renderTex.Release();
+ m_copyTex.Release();
+ m_query = nullptr;
+ m_surfaceWidth = 0;
+ m_surfaceHeight = 0;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.h
new file mode 100644
index 0000000..ea18f5c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RenderCapture.h"
+#include "guilib/D3DResource.h"
+
+#include <wrl/client.h>
+
+class CRenderCaptureDX : public CRenderCapture, public ID3DResource
+{
+public:
+ CRenderCaptureDX();
+ ~CRenderCaptureDX() override;
+
+ void BeginRender() override;
+ void EndRender() override;
+ void ReadOut() override;
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override{};
+ CD3DTexture& GetTarget() { return m_renderTex; }
+
+private:
+ void SurfaceToBuffer();
+ void CleanupDX();
+
+ unsigned int m_surfaceWidth{0};
+ unsigned int m_surfaceHeight{0};
+ Microsoft::WRL::ComPtr<ID3D11Query> m_query{nullptr};
+ CD3DTexture m_renderTex;
+ CD3DTexture m_copyTex;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.cpp
new file mode 100644
index 0000000..bbc36ae
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderCaptureGL.h"
+
+#include "ServiceBroker.h"
+#include "cores/IPlayer.h"
+#include "rendering/RenderSystem.h"
+#include "utils/log.h"
+
+CRenderCaptureGL::~CRenderCaptureGL()
+{
+ if (m_asyncSupported)
+ {
+ if (m_pbo)
+ {
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
+ glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+ glDeleteBuffers(1, &m_pbo);
+ }
+
+ if (m_query)
+ glDeleteQueries(1, &m_query);
+ }
+
+ delete[] m_pixels;
+}
+
+void CRenderCaptureGL::BeginRender()
+{
+ if (!m_asyncChecked)
+ {
+ unsigned int major, minor, glversion;
+ CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor);
+ glversion = 10 * major + minor;
+ if (glversion >= 21)
+ {
+ m_asyncSupported = true;
+ m_occlusionQuerySupported = true;
+ }
+ else if (glversion > 14)
+ {
+ m_occlusionQuerySupported = true;
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "CRenderCaptureGL: Occlusion_query not supported, upgrade your GL "
+ "drivers to support at least GL 2.1");
+ }
+ if (m_flags & CAPTUREFLAG_CONTINUOUS)
+ {
+ if (!m_occlusionQuerySupported)
+ CLog::Log(LOGWARNING,
+ "CRenderCaptureGL: Occlusion_query not supported, performance might suffer");
+ if (!CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_pixel_buffer_object"))
+ CLog::Log(
+ LOGWARNING,
+ "CRenderCaptureGL: GL_ARB_pixel_buffer_object not supported, performance might suffer");
+ if (!UseOcclusionQuery())
+ CLog::Log(LOGWARNING,
+ "CRenderCaptureGL: GL_ARB_occlusion_query disabled, performance might suffer");
+ }
+
+ m_asyncChecked = true;
+ }
+
+ if (m_asyncSupported)
+ {
+ if (!m_pbo)
+ glGenBuffers(1, &m_pbo);
+
+ if (UseOcclusionQuery() && m_occlusionQuerySupported)
+ {
+ //generate an occlusion query if we don't have one
+ if (!m_query)
+ glGenQueries(1, &m_query);
+ }
+ else
+ {
+ //don't use an occlusion query, clean up any old one
+ if (m_query)
+ {
+ glDeleteQueries(1, &m_query);
+ m_query = 0;
+ }
+ }
+
+ //start the occlusion query
+ if (m_query)
+ glBeginQuery(GL_SAMPLES_PASSED, m_query);
+
+ //allocate data on the pbo and pixel buffer
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
+ if (m_bufferSize != m_width * m_height * 4)
+ {
+ m_bufferSize = m_width * m_height * 4;
+ glBufferData(GL_PIXEL_PACK_BUFFER, m_bufferSize, 0, GL_STREAM_READ);
+ delete[] m_pixels;
+ m_pixels = new uint8_t[m_bufferSize];
+ }
+ }
+ else
+ {
+ if (m_bufferSize != m_width * m_height * 4)
+ {
+ delete[] m_pixels;
+ m_bufferSize = m_width * m_height * 4;
+ m_pixels = new uint8_t[m_bufferSize];
+ }
+ }
+}
+
+void CRenderCaptureGL::EndRender()
+{
+ if (m_asyncSupported)
+ {
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+ if (m_query)
+ glEndQuery(GL_SAMPLES_PASSED);
+
+ if (m_flags & CAPTUREFLAG_IMMEDIATELY)
+ PboToBuffer();
+ else
+ SetState(CAPTURESTATE_NEEDSREADOUT);
+ }
+ else
+ {
+ SetState(CAPTURESTATE_DONE);
+ }
+}
+
+void* CRenderCaptureGL::GetRenderBuffer()
+{
+ if (m_asyncSupported)
+ {
+ return nullptr; //offset into the pbo
+ }
+ else
+ {
+ return m_pixels;
+ }
+}
+
+void CRenderCaptureGL::ReadOut()
+{
+ if (m_asyncSupported)
+ {
+ //we don't care about the occlusion query, we just want to know if the result is available
+ //when it is, the write into the pbo is probably done as well,
+ //so it can be mapped and read without a busy wait
+
+ GLuint readout = 1;
+ if (m_query)
+ glGetQueryObjectuiv(m_query, GL_QUERY_RESULT_AVAILABLE, &readout);
+
+ if (readout)
+ PboToBuffer();
+ }
+}
+
+void CRenderCaptureGL::PboToBuffer()
+{
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
+ GLvoid* pboPtr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
+
+ if (pboPtr)
+ {
+ memcpy(m_pixels, pboPtr, m_bufferSize);
+ SetState(CAPTURESTATE_DONE);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CRenderCaptureGL::PboToBuffer: glMapBuffer failed");
+ SetState(CAPTURESTATE_FAILED);
+ }
+
+ glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.h
new file mode 100644
index 0000000..feafccb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGL.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RenderCapture.h"
+
+#include "system_gl.h"
+
+class CRenderCaptureGL : public CRenderCapture
+{
+public:
+ CRenderCaptureGL() = default;
+ ~CRenderCaptureGL() override;
+
+ void BeginRender() override;
+ void EndRender() override;
+ void ReadOut() override;
+
+ void* GetRenderBuffer() override;
+
+private:
+ void PboToBuffer();
+ GLuint m_pbo{0};
+ GLuint m_query{0};
+ bool m_occlusionQuerySupported{false};
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.cpp
new file mode 100644
index 0000000..0c1ce14
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderCaptureGLES.h"
+
+CRenderCaptureGLES::~CRenderCaptureGLES()
+{
+ delete[] m_pixels;
+}
+
+void CRenderCaptureGLES::BeginRender()
+{
+ if (m_bufferSize != m_width * m_height * 4)
+ {
+ delete[] m_pixels;
+ m_bufferSize = m_width * m_height * 4;
+ m_pixels = new uint8_t[m_bufferSize];
+ }
+}
+
+void CRenderCaptureGLES::EndRender()
+{
+ SetState(CAPTURESTATE_DONE);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.h
new file mode 100644
index 0000000..db0e3fc
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureGLES.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "RenderCapture.h"
+
+#include "system_gl.h"
+
+class CRenderCaptureGLES : public CRenderCapture
+{
+public:
+ CRenderCaptureGLES() = default;
+ ~CRenderCaptureGLES() override;
+
+ void BeginRender() override;
+ void EndRender() override;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.cpp
new file mode 100644
index 0000000..0441697
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderFactory.h"
+
+#include <mutex>
+
+
+using namespace VIDEOPLAYER;
+
+CCriticalSection renderSection;
+std::map<std::string, VIDEOPLAYER::CreateRenderer> CRendererFactory::m_renderers;
+
+CBaseRenderer* CRendererFactory::CreateRenderer(const std::string& id, CVideoBuffer* buffer)
+{
+ std::unique_lock<CCriticalSection> lock(renderSection);
+
+ auto it = m_renderers.find(id);
+ if (it != m_renderers.end())
+ {
+ return it->second(buffer);
+ }
+
+ return nullptr;
+}
+
+std::vector<std::string> CRendererFactory::GetRenderers()
+{
+ std::unique_lock<CCriticalSection> lock(renderSection);
+
+ std::vector<std::string> ret;
+ ret.reserve(m_renderers.size());
+ for (auto &renderer : m_renderers)
+ {
+ ret.push_back(renderer.first);
+ }
+ return ret;
+}
+
+void CRendererFactory::RegisterRenderer(const std::string& id, ::CreateRenderer createFunc)
+{
+ std::unique_lock<CCriticalSection> lock(renderSection);
+
+ m_renderers[id] = createFunc;
+}
+
+void CRendererFactory::ClearRenderer()
+{
+ std::unique_lock<CCriticalSection> lock(renderSection);
+
+ m_renderers.clear();
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.h
new file mode 100644
index 0000000..59b5561
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFactory.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BaseRenderer.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace VIDEOPLAYER
+{
+
+typedef CBaseRenderer* (*CreateRenderer)(CVideoBuffer *buffer);
+
+class CRendererFactory
+{
+public:
+ static CBaseRenderer* CreateRenderer(const std::string& id, CVideoBuffer* buffer);
+
+ static void RegisterRenderer(const std::string& id, VIDEOPLAYER::CreateRenderer createFunc);
+ static std::vector<std::string> GetRenderers();
+ static void ClearRenderer();
+
+protected:
+
+ static std::map<std::string, VIDEOPLAYER::CreateRenderer> m_renderers;
+};
+
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.cpp
new file mode 100644
index 0000000..5a4a2e6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderFlags.h"
+
+#include <map>
+#include <string>
+
+unsigned int GetFlagsColorMatrix(unsigned int color_matrix, unsigned width, unsigned height)
+{
+ switch(color_matrix)
+ {
+ case 10: // BT2020_CL
+ case 9: // BT2020_NCL
+ return CONF_FLAGS_YUVCOEF_BT2020;
+ case 7: // SMPTE 240M (1987)
+ return CONF_FLAGS_YUVCOEF_240M;
+ case 6: // SMPTE 170M
+ case 5: // ITU-R BT.470-2
+ case 4: // FCC
+ return CONF_FLAGS_YUVCOEF_BT601;
+ case 1: // ITU-R Rec.709 (1990) -- BT.709
+ return CONF_FLAGS_YUVCOEF_BT709;
+ case 3: // RESERVED
+ case 2: // UNSPECIFIED
+ default:
+ if(width > 1024 || height >= 600)
+ return CONF_FLAGS_YUVCOEF_BT709;
+ else
+ return CONF_FLAGS_YUVCOEF_BT601;
+ break;
+ }
+}
+
+unsigned int GetFlagsChromaPosition(unsigned int chroma_position)
+{
+ switch(chroma_position)
+ {
+ case 1: return CONF_FLAGS_CHROMA_LEFT;
+ case 2: return CONF_FLAGS_CHROMA_CENTER;
+ case 3: return CONF_FLAGS_CHROMA_TOPLEFT;
+ }
+ return 0;
+}
+
+unsigned int GetFlagsColorPrimaries(unsigned int color_primaries)
+{
+ switch(color_primaries)
+ {
+ case 1: return CONF_FLAGS_COLPRI_BT709;
+ case 4: return CONF_FLAGS_COLPRI_BT470M;
+ case 5: return CONF_FLAGS_COLPRI_BT470BG;
+ case 6: return CONF_FLAGS_COLPRI_170M;
+ case 7: return CONF_FLAGS_COLPRI_240M;
+ case 9: return CONF_FLAGS_COLPRI_BT2020;
+ }
+ return 0;
+}
+
+unsigned int GetFlagsStereoMode(const std::string& mode)
+{
+ static std::map<std::string, unsigned int> convert;
+ if(convert.empty())
+ {
+ convert["mono"] = 0u;
+ convert["left_right"] = CONF_FLAGS_STEREO_MODE_SBS | CONF_FLAGS_STEREO_CADANCE_LEFT_RIGHT;
+ convert["bottom_top"] = CONF_FLAGS_STEREO_MODE_TAB | CONF_FLAGS_STEREO_CADANCE_RIGHT_LEFT;
+ convert["top_bottom"] = CONF_FLAGS_STEREO_MODE_TAB | CONF_FLAGS_STEREO_CADANCE_LEFT_RIGHT;
+ convert["checkerboard_rl"] = 0u;
+ convert["checkerboard_lr"] = 0u;
+ convert["row_interleaved_rl"] = 0u;
+ convert["row_interleaved_lr"] = 0u;
+ convert["col_interleaved_rl"] = 0u;
+ convert["col_interleaved_lr"] = 0u;
+ convert["anaglyph_cyan_red"] = 0u;
+ convert["right_left"] = CONF_FLAGS_STEREO_MODE_SBS | CONF_FLAGS_STEREO_CADANCE_RIGHT_LEFT;
+ convert["anaglyph_green_magenta"] = 0u;
+ convert["anaglyph_yellow_blue"] = 0u;
+ convert["block_lr"] = 0u;
+ convert["block_rl"] = 0u;
+ }
+ return convert[mode];
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.h
new file mode 100644
index 0000000..54f0524
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFlags.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#define RENDER_FLAG_BOT 0x01
+#define RENDER_FLAG_TOP 0x02
+#define RENDER_FLAG_BOTH (RENDER_FLAG_BOT | RENDER_FLAG_TOP)
+#define RENDER_FLAG_FIELDMASK 0x03
+
+#define RENDER_FLAG_FIELD0 0x80
+#define RENDER_FLAG_FIELD1 0x100
+
+// #define RENDER_FLAG_LAST 0x40
+
+#define RENDER_FLAG_NOOSD 0x04 /* don't draw any osd */
+#define RENDER_FLAG_NOOSDALPHA 0x08 /* don't allow alpha when osd is drawn */
+
+/* these two flags will be used if we need to render same image twice (bob deinterlacing) */
+#define RENDER_FLAG_NOLOCK 0x10 /* don't attempt to lock texture before rendering */
+#define RENDER_FLAG_NOUNLOCK 0x20 /* don't unlock texture after rendering */
+
+/* this defines what color translation coefficients */
+#define CONF_FLAGS_YUVCOEF_MASK(a) ((a) & 0x07)
+#define CONF_FLAGS_YUVCOEF_BT709 0x01
+#define CONF_FLAGS_YUVCOEF_BT601 0x02
+#define CONF_FLAGS_YUVCOEF_240M 0x03
+#define CONF_FLAGS_YUVCOEF_EBU 0x04
+#define CONF_FLAGS_YUVCOEF_BT2020 0x05
+
+#define CONF_FLAGS_YUV_FULLRANGE 0x08
+#define CONF_FLAGS_FULLSCREEN 0x10
+
+/* defines color primaries */
+#define CONF_FLAGS_COLPRI_MASK(a) ((a) & 0xe0)
+#define CONF_FLAGS_COLPRI_BT709 0x20 // sRGB, HDTV (ITU-R BT.709)
+#define CONF_FLAGS_COLPRI_BT470M 0x40 // NTSC (1953) (FCC 1953, ITU-R BT.470 System M)
+#define CONF_FLAGS_COLPRI_BT470BG 0x60 // PAL/SECAM (1970) (EBU Tech. 3213, ITU-R BT.470 System B, G)
+#define CONF_FLAGS_COLPRI_170M 0x80 // NTSC (1987) (SMPTE RP 145 "SMPTE C", SMPTE 170M)
+#define CONF_FLAGS_COLPRI_240M 0xa0 // SMPTE-240M
+#define CONF_FLAGS_COLPRI_BT2020 0xc0 // UHDTV (ITU-R BT.2020)
+
+/* defines chroma subsampling sample location */
+#define CONF_FLAGS_CHROMA_MASK(a) ((a) & 0x0300)
+#define CONF_FLAGS_CHROMA_LEFT 0x0100
+#define CONF_FLAGS_CHROMA_CENTER 0x0200
+#define CONF_FLAGS_CHROMA_TOPLEFT 0x0300
+
+/* defines 3d modes */
+#define CONF_FLAGS_STEREO_MODE_MASK(a) ((a) & 0x007000)
+#define CONF_FLAGS_STEREO_MODE_SBS 0x001000
+#define CONF_FLAGS_STEREO_MODE_TAB 0x002000
+
+#define CONF_FLAGS_STEREO_CADENCE(a) ((a) & 0x008000)
+#define CONF_FLAGS_STEREO_CADANCE_LEFT_RIGHT 0x000000
+#define CONF_FLAGS_STEREO_CADANCE_RIGHT_LEFT 0x008000
+
+unsigned int GetFlagsColorMatrix(unsigned int color_matrix, unsigned width, unsigned height);
+unsigned int GetFlagsChromaPosition(unsigned int chroma_position);
+unsigned int GetFlagsColorPrimaries(unsigned int color_primaries);
+unsigned int GetFlagsStereoMode(const std::string& mode);
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderInfo.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderInfo.h
new file mode 100644
index 0000000..6945ec4
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderInfo.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+#include "cores/IPlayer.h"
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+struct CRenderInfo
+{
+ CRenderInfo()
+ {
+ Reset();
+ }
+ void Reset()
+ {
+ optimal_buffer_size = 0;
+ max_buffer_size = 0;
+ opaque_pointer = nullptr;
+ m_deintMethods.clear();
+ formats.clear();
+ }
+ unsigned int optimal_buffer_size;
+ unsigned int max_buffer_size;
+ // Supported pixel formats, can be called before configure
+ std::vector<AVPixelFormat> formats;
+ std::vector<EINTERLACEMETHOD> m_deintMethods;
+ // Can be used for initialising video codec with information from renderer (e.g. a shared image pool)
+ void *opaque_pointer;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp
new file mode 100644
index 0000000..ceeded1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp
@@ -0,0 +1,1309 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RenderManager.h"
+
+#include "RenderCapture.h"
+#include "RenderFactory.h"
+#include "RenderFlags.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+/* to use the same as player */
+#include "../VideoPlayer/DVDClock.h"
+#include "../VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+
+using namespace std::chrono_literals;
+
+void CRenderManager::CClockSync::Reset()
+{
+ m_error = 0;
+ m_errCount = 0;
+ m_syncOffset = 0;
+ m_enabled = false;
+}
+
+unsigned int CRenderManager::m_nextCaptureId = 0;
+
+CRenderManager::CRenderManager(CDVDClock &clock, IRenderMsg *player) :
+ m_dvdClock(clock),
+ m_playerPort(player)
+{
+}
+
+CRenderManager::~CRenderManager()
+{
+ delete m_pRenderer;
+}
+
+void CRenderManager::GetVideoRect(CRect& source, CRect& dest, CRect& view) const
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ m_pRenderer->GetVideoRect(source, dest, view);
+}
+
+float CRenderManager::GetAspectRatio() const
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ return m_pRenderer->GetAspectRatio();
+ else
+ return 1.0f;
+}
+
+void CRenderManager::SetVideoSettings(const CVideoSettings& settings)
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ {
+ m_pRenderer->SetVideoSettings(settings);
+ }
+}
+
+bool CRenderManager::Configure(const VideoPicture& picture, float fps, unsigned int orientation, int buffers)
+{
+
+ // check if something has changed
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ if (!m_bRenderGUI)
+ return true;
+
+ if (m_width == picture.iWidth &&
+ m_height == picture.iHeight &&
+ m_dwidth == picture.iDisplayWidth &&
+ m_dheight == picture.iDisplayHeight &&
+ m_fps == fps &&
+ m_orientation == orientation &&
+ m_stereomode == picture.stereoMode &&
+ m_NumberBuffers == buffers &&
+ m_pRenderer != nullptr &&
+ !m_pRenderer->ConfigChanged(picture))
+ {
+ return true;
+ }
+ }
+
+ CLog::Log(LOGDEBUG,
+ "CRenderManager::Configure - change configuration. {}x{}. display: {}x{}. framerate: "
+ "{:4.2f}.",
+ picture.iWidth, picture.iHeight, picture.iDisplayWidth, picture.iDisplayHeight, fps);
+
+ // make sure any queued frame was fully presented
+ {
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+ XbmcThreads::EndTime<> endtime(5000ms);
+ m_forceNext = true;
+ while (m_presentstep != PRESENT_IDLE)
+ {
+ if(endtime.IsTimePast())
+ {
+ CLog::Log(LOGWARNING, "CRenderManager::Configure - timeout waiting for state");
+ m_forceNext = false;
+ return false;
+ }
+ m_presentevent.wait(lock, endtime.GetTimeLeft());
+ }
+ m_forceNext = false;
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ m_width = picture.iWidth;
+ m_height = picture.iHeight,
+ m_dwidth = picture.iDisplayWidth;
+ m_dheight = picture.iDisplayHeight;
+ m_fps = fps;
+ m_orientation = orientation;
+ m_stereomode = picture.stereoMode;
+ m_NumberBuffers = buffers;
+ m_renderState = STATE_CONFIGURING;
+ m_stateEvent.Reset();
+ m_clockSync.Reset();
+ m_dvdClock.SetVsyncAdjust(0);
+ m_pConfigPicture.reset(new VideoPicture());
+ m_pConfigPicture->CopyRef(picture);
+
+ std::unique_lock<CCriticalSection> lock2(m_presentlock);
+ m_presentstep = PRESENT_READY;
+ m_presentevent.notifyAll();
+ }
+
+ if (!m_stateEvent.Wait(1000ms))
+ {
+ CLog::Log(LOGWARNING, "CRenderManager::Configure - timeout waiting for configure");
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ return false;
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_renderState != STATE_CONFIGURED)
+ {
+ CLog::Log(LOGWARNING, "CRenderManager::Configure - failed to configure");
+ return false;
+ }
+
+ return true;
+}
+
+bool CRenderManager::Configure()
+{
+ // lock all interfaces
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ std::unique_lock<CCriticalSection> lock2(m_presentlock);
+ std::unique_lock<CCriticalSection> lock3(m_datalock);
+
+ if (m_pRenderer)
+ {
+ DeleteRenderer();
+ }
+
+ if (!m_pRenderer)
+ {
+ CreateRenderer();
+ if (!m_pRenderer)
+ return false;
+ }
+
+ m_pRenderer->SetVideoSettings(m_playerPort->GetVideoSettings());
+ bool result = m_pRenderer->Configure(*m_pConfigPicture, m_fps, m_orientation);
+ if (result)
+ {
+ CRenderInfo info = m_pRenderer->GetRenderInfo();
+ int renderbuffers = info.max_buffer_size;
+ m_QueueSize = renderbuffers;
+ if (m_NumberBuffers > 0)
+ m_QueueSize = std::min(m_NumberBuffers, renderbuffers);
+
+ if(m_QueueSize < 2)
+ {
+ m_QueueSize = 2;
+ CLog::Log(LOGWARNING, "CRenderManager::Configure - queue size too small ({}, {}, {})",
+ m_QueueSize, renderbuffers, m_NumberBuffers);
+ }
+
+ m_pRenderer->SetBufferSize(m_QueueSize);
+ m_pRenderer->Update();
+
+ m_playerPort->UpdateRenderInfo(info);
+ m_playerPort->UpdateGuiRender(true);
+ m_playerPort->UpdateVideoRender(!m_pRenderer->IsGuiLayer());
+
+ m_queued.clear();
+ m_discard.clear();
+ m_free.clear();
+ m_presentsource = 0;
+ m_presentsourcePast = -1;
+ for (int i=1; i < m_QueueSize; i++)
+ m_free.push_back(i);
+
+ m_bRenderGUI = true;
+ m_bTriggerUpdateResolution = true;
+ m_presentstep = PRESENT_IDLE;
+ m_presentpts = DVD_NOPTS_VALUE;
+ m_lateframes = -1;
+ m_presentevent.notifyAll();
+ m_renderedOverlay = false;
+ m_renderDebug = false;
+ m_clockSync.Reset();
+ m_dvdClock.SetVsyncAdjust(0);
+ m_overlays.Reset();
+ m_overlays.SetStereoMode(m_stereomode);
+
+ m_renderState = STATE_CONFIGURED;
+
+ CLog::Log(LOGDEBUG, "CRenderManager::Configure - {}", m_QueueSize);
+ }
+ else
+ m_renderState = STATE_UNCONFIGURED;
+
+ m_pConfigPicture.reset();
+
+ m_stateEvent.Set();
+ m_playerPort->VideoParamsChange();
+ return result;
+}
+
+bool CRenderManager::IsConfigured() const
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_renderState == STATE_CONFIGURED)
+ return true;
+ else
+ return false;
+}
+
+void CRenderManager::ShowVideo(bool enable)
+{
+ m_showVideo = enable;
+ if (!enable)
+ DiscardBuffer();
+}
+
+void CRenderManager::FrameWait(std::chrono::milliseconds duration)
+{
+ XbmcThreads::EndTime<> timeout{duration};
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+ while(m_presentstep == PRESENT_IDLE && !timeout.IsTimePast())
+ m_presentevent.wait(lock, timeout.GetTimeLeft());
+}
+
+bool CRenderManager::IsPresenting()
+{
+ if (!IsConfigured())
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+ if (!m_presentTimer.IsTimePast())
+ return true;
+ else
+ return false;
+}
+
+void CRenderManager::FrameMove()
+{
+ bool firstFrame = false;
+ UpdateResolution();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ if (m_renderState == STATE_UNCONFIGURED)
+ return;
+ else if (m_renderState == STATE_CONFIGURING)
+ {
+ lock.unlock();
+ if (!Configure())
+ return;
+
+ firstFrame = true;
+ FrameWait(50ms);
+ }
+
+ CheckEnableClockSync();
+ }
+ {
+ std::unique_lock<CCriticalSection> lock2(m_presentlock);
+
+ if (m_queued.empty())
+ {
+ m_presentstep = PRESENT_IDLE;
+ }
+ else
+ {
+ m_presentTimer.Set(1000ms);
+ }
+
+ if (m_presentstep == PRESENT_READY)
+ PrepareNextRender();
+
+ if (m_presentstep == PRESENT_FLIP)
+ {
+ m_presentstep = PRESENT_FRAME;
+ m_presentevent.notifyAll();
+ }
+
+ // release all previous
+ for (std::deque<int>::iterator it = m_discard.begin(); it != m_discard.end(); )
+ {
+ // renderer may want to keep the frame for postprocessing
+ if (!m_pRenderer->NeedBuffer(*it) || !m_bRenderGUI)
+ {
+ m_pRenderer->ReleaseBuffer(*it);
+ m_overlays.Release(*it);
+ m_free.push_back(*it);
+ it = m_discard.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ m_playerPort->UpdateRenderBuffers(m_queued.size(), m_discard.size(), m_free.size());
+ m_bRenderGUI = true;
+ }
+
+ m_playerPort->UpdateGuiRender(IsGuiLayer() || firstFrame);
+
+ ManageCaptures();
+}
+
+void CRenderManager::PreInit()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_renderState != STATE_UNCONFIGURED)
+ return;
+ }
+
+ if (!CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ m_initEvent.Reset();
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RENDERER_PREINIT);
+ if (!m_initEvent.Wait(2000ms))
+ {
+ CLog::Log(LOGERROR, "{} - timed out waiting for renderer to preinit", __FUNCTION__);
+ }
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ if (!m_pRenderer)
+ {
+ CreateRenderer();
+ }
+
+ UpdateLatencyTweak();
+
+ m_QueueSize = 2;
+ m_QueueSkip = 0;
+ m_presentstep = PRESENT_IDLE;
+ m_bRenderGUI = true;
+
+ m_initEvent.Set();
+}
+
+void CRenderManager::UnInit()
+{
+ if (!CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ m_initEvent.Reset();
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RENDERER_UNINIT);
+ if (!m_initEvent.Wait(2000ms))
+ {
+ CLog::Log(LOGERROR, "{} - timed out waiting for renderer to uninit", __FUNCTION__);
+ }
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ m_overlays.UnInit();
+ m_debugRenderer.Dispose();
+
+ DeleteRenderer();
+
+ m_renderState = STATE_UNCONFIGURED;
+ m_width = 0;
+ m_height = 0;
+ m_bRenderGUI = false;
+ RemoveCaptures();
+
+ m_initEvent.Set();
+}
+
+bool CRenderManager::Flush(bool wait, bool saveBuffers)
+{
+ if (!m_pRenderer)
+ return true;
+
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ CLog::Log(LOGDEBUG, "{} - flushing renderer", __FUNCTION__);
+
+// fix deadlock on Windows only when is enabled 'Sync playback to display'
+#ifndef TARGET_WINDOWS
+ CSingleExit exitlock(CServiceBroker::GetWinSystem()->GetGfxContext());
+#endif
+
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ std::unique_lock<CCriticalSection> lock2(m_presentlock);
+ std::unique_lock<CCriticalSection> lock3(m_datalock);
+
+ if (m_pRenderer)
+ {
+ m_overlays.Flush();
+ m_debugRenderer.Flush();
+
+ if (!m_pRenderer->Flush(saveBuffers))
+ {
+ m_queued.clear();
+ m_discard.clear();
+ m_free.clear();
+ m_presentsource = 0;
+ m_presentsourcePast = -1;
+ m_presentstep = PRESENT_IDLE;
+ for (int i = 1; i < m_QueueSize; i++)
+ m_free.push_back(i);
+ }
+
+ m_flushEvent.Set();
+ }
+ }
+ else
+ {
+ m_flushEvent.Reset();
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RENDERER_FLUSH);
+ if (wait)
+ {
+ if (!m_flushEvent.Wait(1000ms))
+ {
+ CLog::Log(LOGERROR, "{} - timed out waiting for renderer to flush", __FUNCTION__);
+ return false;
+ }
+ else
+ return true;
+ }
+ }
+ return true;
+}
+
+void CRenderManager::CreateRenderer()
+{
+ if (!m_pRenderer)
+ {
+ CVideoBuffer *buffer = nullptr;
+ if (m_pConfigPicture)
+ buffer = m_pConfigPicture->videoBuffer;
+
+ auto renderers = VIDEOPLAYER::CRendererFactory::GetRenderers();
+ for (auto &id : renderers)
+ {
+ if (id == "default")
+ continue;
+
+ m_pRenderer = VIDEOPLAYER::CRendererFactory::CreateRenderer(id, buffer);
+ if (m_pRenderer)
+ {
+ return;
+ }
+ }
+ m_pRenderer = VIDEOPLAYER::CRendererFactory::CreateRenderer("default", buffer);
+ }
+}
+
+void CRenderManager::DeleteRenderer()
+{
+ if (m_pRenderer)
+ {
+ CLog::Log(LOGDEBUG, "{} - deleting renderer", __FUNCTION__);
+
+ delete m_pRenderer;
+ m_pRenderer = NULL;
+ }
+}
+
+unsigned int CRenderManager::AllocRenderCapture()
+{
+ if (m_pRenderer)
+ {
+ CRenderCapture* capture = m_pRenderer->GetRenderCapture();
+ if (capture)
+ {
+ m_captures[m_nextCaptureId] = capture;
+ return m_nextCaptureId++;
+ }
+ }
+
+ return m_nextCaptureId;
+}
+
+void CRenderManager::ReleaseRenderCapture(unsigned int captureId)
+{
+ std::unique_lock<CCriticalSection> lock(m_captCritSect);
+
+ std::map<unsigned int, CRenderCapture*>::iterator it;
+ it = m_captures.find(captureId);
+
+ if (it != m_captures.end())
+ it->second->SetState(CAPTURESTATE_NEEDSDELETE);
+}
+
+void CRenderManager::StartRenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags)
+{
+ std::unique_lock<CCriticalSection> lock(m_captCritSect);
+
+ std::map<unsigned int, CRenderCapture*>::iterator it;
+ it = m_captures.find(captureId);
+ if (it == m_captures.end())
+ {
+ CLog::Log(LOGERROR, "CRenderManager::Capture - unknown capture id: {}", captureId);
+ return;
+ }
+
+ CRenderCapture *capture = it->second;
+
+ capture->SetState(CAPTURESTATE_NEEDSRENDER);
+ capture->SetUserState(CAPTURESTATE_WORKING);
+ capture->SetWidth(width);
+ capture->SetHeight(height);
+ capture->SetFlags(flags);
+ capture->GetEvent().Reset();
+
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ if (flags & CAPTUREFLAG_IMMEDIATELY)
+ {
+ //render capture and read out immediately
+ RenderCapture(capture);
+ capture->SetUserState(capture->GetState());
+ capture->GetEvent().Set();
+ }
+ }
+
+ if (!m_captures.empty())
+ m_hasCaptures = true;
+}
+
+bool CRenderManager::RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size)
+{
+ std::unique_lock<CCriticalSection> lock(m_captCritSect);
+
+ std::map<unsigned int, CRenderCapture*>::iterator it;
+ it = m_captures.find(captureId);
+ if (it == m_captures.end())
+ return false;
+
+ m_captureWaitCounter++;
+
+ {
+ if (!millis)
+ millis = 1000;
+
+ CSingleExit exitlock(m_captCritSect);
+ if (!it->second->GetEvent().Wait(std::chrono::milliseconds(millis)))
+ {
+ m_captureWaitCounter--;
+ return false;
+ }
+ }
+
+ m_captureWaitCounter--;
+
+ if (it->second->GetUserState() != CAPTURESTATE_DONE)
+ return false;
+
+ unsigned int srcSize = it->second->GetWidth() * it->second->GetHeight() * 4;
+ unsigned int bytes = std::min(srcSize, size);
+
+ memcpy(buffer, it->second->GetPixels(), bytes);
+ return true;
+}
+
+void CRenderManager::ManageCaptures()
+{
+ //no captures, return here so we don't do an unnecessary lock
+ if (!m_hasCaptures)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(m_captCritSect);
+
+ std::map<unsigned int, CRenderCapture*>::iterator it = m_captures.begin();
+ while (it != m_captures.end())
+ {
+ CRenderCapture* capture = it->second;
+
+ if (capture->GetState() == CAPTURESTATE_NEEDSDELETE)
+ {
+ delete capture;
+ it = m_captures.erase(it);
+ continue;
+ }
+
+ if (capture->GetState() == CAPTURESTATE_NEEDSRENDER)
+ RenderCapture(capture);
+ else if (capture->GetState() == CAPTURESTATE_NEEDSREADOUT)
+ capture->ReadOut();
+
+ if (capture->GetState() == CAPTURESTATE_DONE || capture->GetState() == CAPTURESTATE_FAILED)
+ {
+ //tell the thread that the capture is done or has failed
+ capture->SetUserState(capture->GetState());
+ capture->GetEvent().Set();
+
+ if (capture->GetFlags() & CAPTUREFLAG_CONTINUOUS)
+ {
+ capture->SetState(CAPTURESTATE_NEEDSRENDER);
+
+ //if rendering this capture continuously, and readout is async, render a new capture immediately
+ if (capture->IsAsync() && !(capture->GetFlags() & CAPTUREFLAG_IMMEDIATELY))
+ RenderCapture(capture);
+ }
+ ++it;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ if (m_captures.empty())
+ m_hasCaptures = false;
+}
+
+void CRenderManager::RenderCapture(CRenderCapture* capture)
+{
+ if (!m_pRenderer || !m_pRenderer->RenderCapture(capture))
+ capture->SetState(CAPTURESTATE_FAILED);
+}
+
+void CRenderManager::RemoveCaptures()
+{
+ std::unique_lock<CCriticalSection> lock(m_captCritSect);
+
+ while (m_captureWaitCounter > 0)
+ {
+ for (auto entry : m_captures)
+ {
+ entry.second->GetEvent().Set();
+ }
+ CSingleExit lockexit(m_captCritSect);
+ KODI::TIME::Sleep(10ms);
+ }
+
+ for (auto entry : m_captures)
+ {
+ delete entry.second;
+ }
+ m_captures.clear();
+}
+
+void CRenderManager::SetViewMode(int iViewMode)
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ m_pRenderer->SetViewMode(iViewMode);
+ m_playerPort->VideoParamsChange();
+}
+
+RESOLUTION CRenderManager::GetResolution()
+{
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_renderState == STATE_UNCONFIGURED)
+ return res;
+
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE) != ADJUST_REFRESHRATE_OFF)
+ res = CResolutionUtils::ChooseBestResolution(m_fps, m_width, m_height, !m_stereomode.empty());
+
+ return res;
+}
+
+void CRenderManager::Render(bool clear, DWORD flags, DWORD alpha, bool gui)
+{
+ CSingleExit exitLock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_renderState != STATE_CONFIGURED)
+ return;
+ }
+
+ if (!gui && m_pRenderer->IsGuiLayer())
+ return;
+
+ if (!gui || m_pRenderer->IsGuiLayer())
+ {
+ SPresent& m = m_Queue[m_presentsource];
+
+ if( m.presentmethod == PRESENT_METHOD_BOB )
+ PresentFields(clear, flags, alpha);
+ else if( m.presentmethod == PRESENT_METHOD_BLEND )
+ PresentBlend(clear, flags, alpha);
+ else
+ PresentSingle(clear, flags, alpha);
+ }
+
+ if (gui)
+ {
+ if (!m_pRenderer->IsGuiLayer())
+ m_pRenderer->Update();
+
+ m_renderedOverlay = m_overlays.HasOverlay(m_presentsource);
+ CRect src, dst, view;
+ m_pRenderer->GetVideoRect(src, dst, view);
+ m_overlays.SetVideoRect(src, dst, view);
+ m_overlays.Render(m_presentsource);
+
+ if (m_renderDebug)
+ {
+ if (m_renderDebugVideo)
+ {
+ DEBUG_INFO_VIDEO video = m_pRenderer->GetDebugInfo(m_presentsource);
+ DEBUG_INFO_RENDER render = CServiceBroker::GetWinSystem()->GetDebugInfo();
+
+ m_debugRenderer.SetInfo(video, render);
+ }
+ else
+ {
+ DEBUG_INFO_PLAYER info;
+
+ m_playerPort->GetDebugInfo(info.audio, info.video, info.player);
+
+ double refreshrate, clockspeed;
+ int missedvblanks;
+ info.vsync = StringUtils::Format("VSyncOff: {:.1f} latency: {:.3f} ",
+ m_clockSync.m_syncOffset / 1000,
+ DVD_TIME_TO_MSEC(m_displayLatency) / 1000.0f);
+ if (m_dvdClock.GetClockInfo(missedvblanks, clockspeed, refreshrate))
+ {
+ info.vsync += StringUtils::Format("VSync: refresh:{:.3f} missed:{} speed:{:.3f}%",
+ refreshrate, missedvblanks, clockspeed * 100);
+ }
+
+ m_debugRenderer.SetInfo(info);
+ }
+
+ m_debugRenderer.Render(src, dst, view);
+
+ m_debugTimer.Set(1000ms);
+ m_renderedOverlay = true;
+ }
+ }
+
+ const SPresent& m = m_Queue[m_presentsource];
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+
+ if (m_presentstep == PRESENT_FRAME)
+ {
+ if (m.presentmethod == PRESENT_METHOD_BOB)
+ m_presentstep = PRESENT_FRAME2;
+ else
+ m_presentstep = PRESENT_IDLE;
+ }
+ else if (m_presentstep == PRESENT_FRAME2)
+ m_presentstep = PRESENT_IDLE;
+
+ if (m_presentstep == PRESENT_IDLE)
+ {
+ if (!m_queued.empty())
+ m_presentstep = PRESENT_READY;
+ }
+
+ m_presentevent.notifyAll();
+ }
+}
+
+bool CRenderManager::IsGuiLayer()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ if (!m_pRenderer)
+ return false;
+
+ if ((m_pRenderer->IsGuiLayer() && IsPresenting()) ||
+ m_renderedOverlay || m_overlays.HasOverlay(m_presentsource))
+ return true;
+
+ if (m_renderDebug && m_debugTimer.IsTimePast())
+ return true;
+ }
+ return false;
+}
+
+bool CRenderManager::IsVideoLayer()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+
+ if (!m_pRenderer)
+ return false;
+
+ if (!m_pRenderer->IsGuiLayer())
+ return true;
+ }
+ return false;
+}
+
+/* simple present method */
+void CRenderManager::PresentSingle(bool clear, DWORD flags, DWORD alpha)
+{
+ const SPresent& m = m_Queue[m_presentsource];
+
+ if (m.presentfield == FS_BOT)
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_BOT, alpha);
+ else if (m.presentfield == FS_TOP)
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_TOP, alpha);
+ else
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags, alpha);
+}
+
+/* new simpler method of handling interlaced material, *
+ * we just render the two fields right after eachother */
+void CRenderManager::PresentFields(bool clear, DWORD flags, DWORD alpha)
+{
+ const SPresent& m = m_Queue[m_presentsource];
+
+ if(m_presentstep == PRESENT_FRAME)
+ {
+ if( m.presentfield == FS_BOT)
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_BOT | RENDER_FLAG_FIELD0, alpha);
+ else
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_TOP | RENDER_FLAG_FIELD0, alpha);
+ }
+ else
+ {
+ if( m.presentfield == FS_TOP)
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_BOT | RENDER_FLAG_FIELD1, alpha);
+ else
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_TOP | RENDER_FLAG_FIELD1, alpha);
+ }
+}
+
+void CRenderManager::PresentBlend(bool clear, DWORD flags, DWORD alpha)
+{
+ const SPresent& m = m_Queue[m_presentsource];
+
+ if( m.presentfield == FS_BOT )
+ {
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_BOT | RENDER_FLAG_NOOSD, alpha);
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, false, flags | RENDER_FLAG_TOP, alpha / 2);
+ }
+ else
+ {
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, clear, flags | RENDER_FLAG_TOP | RENDER_FLAG_NOOSD, alpha);
+ m_pRenderer->RenderUpdate(m_presentsource, m_presentsourcePast, false, flags | RENDER_FLAG_BOT, alpha / 2);
+ }
+}
+
+void CRenderManager::UpdateLatencyTweak()
+{
+ float fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ float refresh = fps;
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution() == RES_WINDOW)
+ refresh = 0; // No idea about refresh rate when windowed, just get the default latency
+ m_latencyTweak = static_cast<double>(
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->GetLatencyTweak(refresh));
+}
+
+void CRenderManager::UpdateResolution()
+{
+ if (m_bTriggerUpdateResolution)
+ {
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() && CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot())
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE) != ADJUST_REFRESHRATE_OFF && m_fps > 0.0f)
+ {
+ RESOLUTION res = CResolutionUtils::ChooseBestResolution(m_fps, m_width, m_height, !m_stereomode.empty());
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(res, false);
+ UpdateLatencyTweak();
+ if (m_pRenderer)
+ m_pRenderer->Update();
+ }
+ m_bTriggerUpdateResolution = false;
+ m_playerPort->VideoParamsChange();
+ }
+ }
+}
+
+void CRenderManager::TriggerUpdateResolution(float fps, int width, int height, std::string &stereomode)
+{
+ if (width)
+ {
+ m_fps = fps;
+ m_width = width;
+ m_height = height;
+ m_stereomode = stereomode;
+ }
+ m_bTriggerUpdateResolution = true;
+}
+
+void CRenderManager::ToggleDebug()
+{
+ bool isEnabled = !m_renderDebug;
+ if (isEnabled)
+ m_debugRenderer.Initialize();
+ else
+ m_debugRenderer.Dispose();
+
+ m_renderDebug = isEnabled;
+ m_debugTimer.SetExpired();
+ m_renderDebugVideo = false;
+}
+
+void CRenderManager::ToggleDebugVideo()
+{
+ bool isEnabled = !m_renderDebug;
+ if (isEnabled)
+ m_debugRenderer.Initialize();
+ else
+ m_debugRenderer.Dispose();
+
+ m_renderDebug = isEnabled;
+ m_debugTimer.SetExpired();
+ m_renderDebugVideo = true;
+}
+
+void CRenderManager::SetSubtitleVerticalPosition(int value, bool save)
+{
+ m_overlays.SetSubtitleVerticalPosition(value, save);
+}
+
+bool CRenderManager::AddVideoPicture(const VideoPicture& picture, volatile std::atomic_bool& bStop, EINTERLACEMETHOD deintMethod, bool wait)
+{
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+
+ if (m_free.empty())
+ return false;
+
+ int index = m_free.front();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_datalock);
+ if (!m_pRenderer)
+ return false;
+
+ m_pRenderer->AddVideoPicture(picture, index);
+ }
+
+
+ // set fieldsync if picture is interlaced
+ EFIELDSYNC displayField = FS_NONE;
+ if (picture.iFlags & DVP_FLAG_INTERLACED)
+ {
+ if (deintMethod != EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE)
+ {
+ if (picture.iFlags & DVP_FLAG_TOP_FIELD_FIRST)
+ displayField = FS_TOP;
+ else
+ displayField = FS_BOT;
+ }
+ }
+
+ EPRESENTMETHOD presentmethod = PRESENT_METHOD_SINGLE;
+ if (deintMethod == VS_INTERLACEMETHOD_NONE)
+ {
+ presentmethod = PRESENT_METHOD_SINGLE;
+ displayField = FS_NONE;
+ }
+ else
+ {
+ if (displayField == FS_NONE)
+ presentmethod = PRESENT_METHOD_SINGLE;
+ else
+ {
+ if (deintMethod == VS_INTERLACEMETHOD_RENDER_BLEND)
+ presentmethod = PRESENT_METHOD_BLEND;
+ else if (deintMethod == VS_INTERLACEMETHOD_RENDER_BOB)
+ presentmethod = PRESENT_METHOD_BOB;
+ else
+ {
+ if (!m_pRenderer->WantsDoublePass())
+ presentmethod = PRESENT_METHOD_SINGLE;
+ else
+ presentmethod = PRESENT_METHOD_BOB;
+ }
+ }
+ }
+
+
+ SPresent& m = m_Queue[index];
+ m.presentfield = displayField;
+ m.presentmethod = presentmethod;
+ m.pts = picture.pts;
+ m_queued.push_back(m_free.front());
+ m_free.pop_front();
+ m_playerPort->UpdateRenderBuffers(m_queued.size(), m_discard.size(), m_free.size());
+
+ // signal to any waiters to check state
+ if (m_presentstep == PRESENT_IDLE)
+ {
+ m_presentstep = PRESENT_READY;
+ m_presentevent.notifyAll();
+ }
+
+ if (wait)
+ {
+ m_forceNext = true;
+ XbmcThreads::EndTime<> endtime(200ms);
+ while (m_presentstep == PRESENT_READY)
+ {
+ m_presentevent.wait(lock, 20ms);
+ if(endtime.IsTimePast() || bStop)
+ {
+ if (!bStop)
+ {
+ CLog::Log(LOGWARNING, "CRenderManager::AddVideoPicture - timeout waiting for render");
+ }
+ break;
+ }
+ }
+ m_forceNext = false;
+ }
+
+ return true;
+}
+
+void CRenderManager::AddOverlay(CDVDOverlay* o, double pts)
+{
+ int idx;
+ {
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+ if (m_free.empty())
+ return;
+ idx = m_free.front();
+ }
+ std::unique_lock<CCriticalSection> lock(m_datalock);
+ m_overlays.AddOverlay(o, pts, idx);
+}
+
+bool CRenderManager::Supports(ERENDERFEATURE feature) const
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ return m_pRenderer->Supports(feature);
+ else
+ return false;
+}
+
+bool CRenderManager::Supports(ESCALINGMETHOD method) const
+{
+ std::unique_lock<CCriticalSection> lock(m_statelock);
+ if (m_pRenderer)
+ return m_pRenderer->Supports(method);
+ else
+ return false;
+}
+
+int CRenderManager::WaitForBuffer(volatile std::atomic_bool& bStop,
+ std::chrono::milliseconds timeout)
+{
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+
+ // check if gui is active and discard buffer if not
+ // this keeps videoplayer going
+ if (!m_bRenderGUI || !g_application.GetRenderGUI())
+ {
+ m_bRenderGUI = false;
+ double presenttime = 0;
+ double clock = m_dvdClock.GetClock();
+ if (!m_queued.empty())
+ {
+ int idx = m_queued.front();
+ presenttime = m_Queue[idx].pts;
+ }
+ else
+ presenttime = clock + 0.02;
+
+ auto sleeptime = std::chrono::milliseconds(static_cast<int>((presenttime - clock) * 1000));
+ if (sleeptime < 0ms)
+ sleeptime = 0ms;
+ sleeptime = std::min(sleeptime, 20ms);
+ m_presentevent.wait(lock, sleeptime);
+ DiscardBuffer();
+ return 0;
+ }
+
+ XbmcThreads::EndTime<> endtime{timeout};
+ while(m_free.empty())
+ {
+ m_presentevent.wait(lock, std::min(50ms, timeout));
+ if (endtime.IsTimePast() || bStop)
+ {
+ return -1;
+ }
+ }
+
+ // make sure overlay buffer is released, this won't happen on AddOverlay
+ m_overlays.Release(m_free.front());
+
+ // return buffer level
+ return m_queued.size() + m_discard.size();
+}
+
+void CRenderManager::PrepareNextRender()
+{
+ if (m_queued.empty())
+ {
+ CLog::Log(LOGERROR, "CRenderManager::PrepareNextRender - asked to prepare with nothing available");
+ m_presentstep = PRESENT_IDLE;
+ m_presentevent.notifyAll();
+ return;
+ }
+
+ if (!m_showVideo && !m_forceNext)
+ return;
+
+ double frameOnScreen = m_dvdClock.GetClock();
+ double frametime = 1.0 /
+ static_cast<double>(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS()) *
+ DVD_TIME_BASE;
+
+ m_displayLatency = DVD_MSEC_TO_TIME(
+ m_latencyTweak +
+ static_cast<double>(CServiceBroker::GetWinSystem()->GetGfxContext().GetDisplayLatency()) -
+ m_videoDelay -
+ static_cast<double>(CServiceBroker::GetWinSystem()->GetFrameLatencyAdjustment()));
+
+ double renderPts = frameOnScreen + m_displayLatency;
+
+ double nextFramePts = m_Queue[m_queued.front()].pts;
+ if (m_dvdClock.GetClockSpeed() < 0)
+ nextFramePts = renderPts;
+
+ if (m_clockSync.m_enabled)
+ {
+ double err = fmod(renderPts - nextFramePts, frametime);
+ m_clockSync.m_error += err;
+ m_clockSync.m_errCount ++;
+ if (m_clockSync.m_errCount > 30)
+ {
+ double average = m_clockSync.m_error / m_clockSync.m_errCount;
+ m_clockSync.m_syncOffset = average;
+ m_clockSync.m_error = 0;
+ m_clockSync.m_errCount = 0;
+
+ m_dvdClock.SetVsyncAdjust(-average);
+ }
+ renderPts += frametime / 2 - m_clockSync.m_syncOffset;
+ }
+ else
+ {
+ m_dvdClock.SetVsyncAdjust(0);
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGAVTIMING,
+ "frameOnScreen: {:f} renderPts: {:f} nextFramePts: {:f} -> diff: {:f} render: {} "
+ "forceNext: {}",
+ frameOnScreen, renderPts, nextFramePts, (renderPts - nextFramePts),
+ renderPts >= nextFramePts, m_forceNext);
+
+ bool combined = false;
+ if (m_presentsourcePast >= 0)
+ {
+ m_discard.push_back(m_presentsourcePast);
+ m_presentsourcePast = -1;
+ combined = true;
+ }
+
+ if (renderPts >= nextFramePts || m_forceNext)
+ {
+ // see if any future queued frames are already due
+ auto iter = m_queued.begin();
+ int idx = *iter;
+ ++iter;
+ while (iter != m_queued.end())
+ {
+ // the slot for rendering in time is [pts .. (pts + x * frametime)]
+ // renderer/drivers have internal queues, being slightly late here does not mean that
+ // we are really late. The likelihood that we recover decreases the greater m_lateframes
+ // get. Skipping a frame is easier than having decoder dropping one (lateframes > 10)
+ double x = (m_lateframes <= 6) ? 0.98 : 0;
+ if (renderPts < m_Queue[*iter].pts + x * frametime)
+ break;
+ idx = *iter;
+ ++iter;
+ }
+
+ // skip late frames
+ while (m_queued.front() != idx)
+ {
+ if (m_presentsourcePast >= 0)
+ {
+ m_discard.push_back(m_presentsourcePast);
+ m_QueueSkip++;
+ }
+ m_presentsourcePast = m_queued.front();
+ m_queued.pop_front();
+ }
+
+ int lateframes = static_cast<int>((renderPts - m_Queue[idx].pts) *
+ static_cast<double>(m_fps / DVD_TIME_BASE));
+ if (lateframes)
+ m_lateframes += lateframes;
+ else
+ m_lateframes = 0;
+
+ m_presentstep = PRESENT_FLIP;
+ m_discard.push_back(m_presentsource);
+ m_presentsource = idx;
+ m_queued.pop_front();
+ m_presentpts = m_Queue[idx].pts - m_displayLatency;
+ m_presentevent.notifyAll();
+
+ m_playerPort->UpdateRenderBuffers(m_queued.size(), m_discard.size(), m_free.size());
+ }
+ else if (!combined && renderPts > (nextFramePts - frametime))
+ {
+ m_lateframes = 0;
+ m_presentstep = PRESENT_FLIP;
+ m_presentsourcePast = m_presentsource;
+ m_presentsource = m_queued.front();
+ m_queued.pop_front();
+ m_presentpts = m_Queue[m_presentsource].pts - m_displayLatency - frametime / 2;
+ m_presentevent.notifyAll();
+ }
+}
+
+void CRenderManager::DiscardBuffer()
+{
+ std::unique_lock<CCriticalSection> lock2(m_presentlock);
+
+ while(!m_queued.empty())
+ {
+ m_discard.push_back(m_queued.front());
+ m_queued.pop_front();
+ }
+
+ if(m_presentstep == PRESENT_READY)
+ m_presentstep = PRESENT_IDLE;
+ m_presentevent.notifyAll();
+}
+
+bool CRenderManager::GetStats(int &lateframes, double &pts, int &queued, int &discard)
+{
+ std::unique_lock<CCriticalSection> lock(m_presentlock);
+ lateframes = m_lateframes / 10;
+ pts = m_presentpts;
+ queued = m_queued.size();
+ discard = m_discard.size();
+ return true;
+}
+
+void CRenderManager::CheckEnableClockSync()
+{
+ // refresh rate can be a multiple of video fps
+ double diff = 1.0;
+
+ if (m_fps != 0)
+ {
+ double fps = static_cast<double>(m_fps);
+ double refreshrate, clockspeed;
+ int missedvblanks;
+ if (m_dvdClock.GetClockInfo(missedvblanks, clockspeed, refreshrate))
+ {
+ fps *= clockspeed;
+ }
+
+ diff = static_cast<double>(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS()) / fps;
+ if (diff < 1.0)
+ diff = 1.0 / diff;
+
+ // Calculate distance from nearest integer proportion
+ diff = std::abs(std::round(diff) - diff);
+ }
+
+ if (diff < 0.0005)
+ {
+ m_clockSync.m_enabled = true;
+ }
+ else
+ {
+ m_clockSync.m_enabled = false;
+ m_dvdClock.SetVsyncAdjust(0);
+ }
+
+ m_playerPort->UpdateClockSync(m_clockSync.m_enabled);
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h
new file mode 100644
index 0000000..cc4a00d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DVDClock.h"
+#include "DebugRenderer.h"
+#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h"
+#include "cores/VideoPlayer/VideoRenderers/OverlayRenderer.h"
+#include "cores/VideoSettings.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/SystemClock.h"
+#include "utils/Geometry.h"
+#include "windowing/Resolution.h"
+
+#include <atomic>
+#include <deque>
+#include <list>
+#include <map>
+
+#include "PlatformDefs.h"
+
+class CRenderCapture;
+struct VideoPicture;
+
+class CWinRenderer;
+class CLinuxRenderer;
+class CLinuxRendererGL;
+class CLinuxRendererGLES;
+class CRenderManager;
+
+class IRenderMsg
+{
+ friend CRenderManager;
+public:
+ virtual ~IRenderMsg() = default;
+protected:
+ virtual void VideoParamsChange() = 0;
+ virtual void GetDebugInfo(std::string &audio, std::string &video, std::string &general) = 0;
+ virtual void UpdateClockSync(bool enabled) = 0;
+ virtual void UpdateRenderInfo(CRenderInfo &info) = 0;
+ virtual void UpdateRenderBuffers(int queued, int discard, int free) = 0;
+ virtual void UpdateGuiRender(bool gui) = 0;
+ virtual void UpdateVideoRender(bool video) = 0;
+ virtual CVideoSettings GetVideoSettings() const = 0;
+};
+
+class CRenderManager
+{
+public:
+ CRenderManager(CDVDClock &clock, IRenderMsg *player);
+ virtual ~CRenderManager();
+
+ // Functions called from render thread
+ void GetVideoRect(CRect& source, CRect& dest, CRect& view) const;
+ float GetAspectRatio() const;
+ void FrameMove();
+ void FrameWait(std::chrono::milliseconds duration);
+ void Render(bool clear, DWORD flags = 0, DWORD alpha = 255, bool gui = true);
+ bool IsVideoLayer();
+ RESOLUTION GetResolution();
+ void UpdateResolution();
+ void TriggerUpdateResolution(float fps, int width, int height, std::string &stereomode);
+ void SetViewMode(int iViewMode);
+ void PreInit();
+ void UnInit();
+ bool Flush(bool wait, bool saveBuffers);
+ bool IsConfigured() const;
+ void ToggleDebug();
+ void ToggleDebugVideo();
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ void SetSubtitleVerticalPosition(const int value, bool save);
+
+ unsigned int AllocRenderCapture();
+ void ReleaseRenderCapture(unsigned int captureId);
+ void StartRenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags);
+ bool RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size);
+
+ // Functions called from GUI
+ bool Supports(ERENDERFEATURE feature) const;
+ bool Supports(ESCALINGMETHOD method) const;
+
+ int GetSkippedFrames() { return m_QueueSkip; }
+
+ bool Configure(const VideoPicture& picture, float fps, unsigned int orientation, int buffers = 0);
+ bool AddVideoPicture(const VideoPicture& picture, volatile std::atomic_bool& bStop, EINTERLACEMETHOD deintMethod, bool wait);
+ void AddOverlay(CDVDOverlay* o, double pts);
+ void ShowVideo(bool enable);
+
+ /**
+ * If player uses buffering it has to wait for a buffer before it calls
+ * AddVideoPicture and AddOverlay. It waits for max 50 ms before it returns -1
+ * in case no buffer is available. Player may call this in a loop and decides
+ * by itself when it wants to drop a frame.
+ */
+ int WaitForBuffer(volatile std::atomic_bool& bStop,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds(100));
+
+ /**
+ * Can be called by player for lateness detection. This is done best by
+ * looking at the end of the queue.
+ */
+ bool GetStats(int &lateframes, double &pts, int &queued, int &discard);
+
+ /**
+ * Video player call this on flush in oder to discard any queued frames
+ */
+ void DiscardBuffer();
+
+ void SetDelay(int delay) { m_videoDelay = delay; }
+ int GetDelay() { return m_videoDelay; }
+
+ void SetVideoSettings(const CVideoSettings& settings);
+
+protected:
+
+ void PresentSingle(bool clear, DWORD flags, DWORD alpha);
+ void PresentFields(bool clear, DWORD flags, DWORD alpha);
+ void PresentBlend(bool clear, DWORD flags, DWORD alpha);
+
+ void PrepareNextRender();
+ bool IsPresenting();
+ bool IsGuiLayer();
+
+ bool Configure();
+ void CreateRenderer();
+ void DeleteRenderer();
+ void ManageCaptures();
+
+ void UpdateLatencyTweak();
+ void CheckEnableClockSync();
+
+ CBaseRenderer *m_pRenderer = nullptr;
+ OVERLAY::CRenderer m_overlays;
+ CDebugRenderer m_debugRenderer;
+ mutable CCriticalSection m_statelock;
+ CCriticalSection m_presentlock;
+ CCriticalSection m_datalock;
+ bool m_bTriggerUpdateResolution = false;
+ bool m_bRenderGUI = true;
+ bool m_renderedOverlay = false;
+ bool m_renderDebug = false;
+ bool m_renderDebugVideo = false;
+ XbmcThreads::EndTime<> m_debugTimer;
+ std::atomic_bool m_showVideo = {false};
+
+ enum EPRESENTSTEP
+ {
+ PRESENT_IDLE = 0
+ , PRESENT_FLIP
+ , PRESENT_FRAME
+ , PRESENT_FRAME2
+ , PRESENT_READY
+ };
+
+ enum EPRESENTMETHOD
+ {
+ PRESENT_METHOD_SINGLE = 0,
+ PRESENT_METHOD_BLEND,
+ PRESENT_METHOD_BOB,
+ };
+
+ enum ERENDERSTATE
+ {
+ STATE_UNCONFIGURED = 0,
+ STATE_CONFIGURING,
+ STATE_CONFIGURED,
+ };
+ ERENDERSTATE m_renderState = STATE_UNCONFIGURED;
+ CEvent m_stateEvent;
+
+ /// Display latency tweak value from AdvancedSettings for the current refresh rate
+ /// in milliseconds
+ double m_latencyTweak = 0.0;
+ /// Display latency updated in PrepareNextRender in DVD clock units, includes m_latencyTweak
+ double m_displayLatency = 0.0;
+ std::atomic_int m_videoDelay = {};
+
+ int m_QueueSize = 2;
+ int m_QueueSkip = 0;
+
+ struct SPresent
+ {
+ double pts;
+ EFIELDSYNC presentfield;
+ EPRESENTMETHOD presentmethod;
+ } m_Queue[NUM_BUFFERS]{};
+
+ std::deque<int> m_free;
+ std::deque<int> m_queued;
+ std::deque<int> m_discard;
+
+ std::unique_ptr<VideoPicture> m_pConfigPicture;
+ unsigned int m_width = 0;
+ unsigned int m_height = 0;
+ unsigned int m_dwidth = 0;
+ unsigned int m_dheight = 0;
+ float m_fps = 0.0;
+ unsigned int m_orientation = 0;
+ int m_NumberBuffers = 0;
+ std::string m_stereomode;
+
+ int m_lateframes = -1;
+ double m_presentpts = 0.0;
+ EPRESENTSTEP m_presentstep = PRESENT_IDLE;
+ XbmcThreads::EndTime<> m_presentTimer;
+ bool m_forceNext = false;
+ int m_presentsource = 0;
+ int m_presentsourcePast = -1;
+ XbmcThreads::ConditionVariable m_presentevent;
+ CEvent m_flushEvent;
+ CEvent m_initEvent;
+ CDVDClock &m_dvdClock;
+ IRenderMsg *m_playerPort;
+
+ struct CClockSync
+ {
+ void Reset();
+ double m_error;
+ int m_errCount;
+ double m_syncOffset;
+ bool m_enabled;
+ };
+ CClockSync m_clockSync;
+
+ void RenderCapture(CRenderCapture* capture);
+ void RemoveCaptures();
+ CCriticalSection m_captCritSect;
+ std::map<unsigned int, CRenderCapture*> m_captures;
+ static unsigned int m_nextCaptureId;
+ unsigned int m_captureWaitCounter = 0;
+ //set to true when adding something to m_captures, set to false when m_captures is made empty
+ //std::list::empty() isn't thread safe, using an extra bool will save a lock per render when no captures are requested
+ bool m_hasCaptures = false;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt
new file mode 100644
index 0000000..6b71389
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt
@@ -0,0 +1,41 @@
+set(SOURCES ConvolutionKernels.cpp)
+
+set(HEADERS ConvolutionKernels.h
+ dither.h
+ ShaderFormats.h)
+
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES ConversionMatrix.cpp
+ WinVideoFilter.cpp)
+ list(APPEND HEADERS ConversionMatrix.h
+ WinVideoFilter.h)
+endif()
+
+
+if(OPENGL_FOUND OR OPENGLES_FOUND)
+ list(APPEND SOURCES ConversionMatrix.cpp)
+ list(APPEND HEADERS ConversionMatrix.h)
+endif()
+
+if(OPENGL_FOUND)
+ list(APPEND SOURCES GLSLOutput.cpp
+ VideoFilterShaderGL.cpp
+ YUV2RGBShaderGL.cpp)
+ list(APPEND HEADERS GLSLOutput.h
+ VideoFilterShaderGL.h
+ YUV2RGBShaderGL.h)
+endif()
+
+if(OPENGLES_FOUND AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "ios" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "x11" IN_LIST CORE_PLATFORM_NAME_LC OR
+ "wayland" IN_LIST CORE_PLATFORM_NAME_LC))
+ list(APPEND SOURCES VideoFilterShaderGLES.cpp
+ YUV2RGBShaderGLES.cpp)
+ list(APPEND HEADERS VideoFilterShaderGLES.h
+ YUV2RGBShaderGLES.h)
+endif()
+
+core_add_library(videoshaders)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.cpp
new file mode 100644
index 0000000..270c263
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.cpp
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ConversionMatrix.h"
+
+#include <stdexcept>
+#include <string>
+
+//------------------------------------------------------------------------------
+// constants for primaries and transfers functions and color models
+//------------------------------------------------------------------------------
+
+// source: https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html#PRIMARY_CONVERSION
+
+namespace
+{
+struct ConvYCbCr
+{
+ float Kr, Kb;
+};
+
+struct Primaries
+{
+ float primaries[3][2];
+ float whitepoint[2];
+};
+
+constexpr ConvYCbCr BT709YCbCr = {0.2126, 0.0722};
+constexpr ConvYCbCr BT601YCbCr = {0.299, 0.114};
+constexpr ConvYCbCr BT2020YCbCr = {0.2627, 0.0593};
+constexpr ConvYCbCr ST240YCbCr = {0.212, 0.087};
+
+constexpr Primaries PrimariesBT709 = {{{0.640, 0.330}, {0.300, 0.600}, {0.150, 0.060}},
+ {0.3127, 0.3290}};
+constexpr Primaries PrimariesBT610_525 = {{{0.640, 0.340}, {0.310, 0.595}, {0.155, 0.070}},
+ {0.3127, 0.3290}};
+constexpr Primaries PrimariesBT610_625 = {{{0.640, 0.330}, {0.290, 0.600}, {0.150, 0.060}},
+ {0.3127, 0.3290}};
+constexpr Primaries PrimariesBT2020 = {{{0.708, 0.292}, {0.170, 0.797}, {0.131, 0.046}},
+ {0.3127, 0.3290}};
+} // namespace
+
+//------------------------------------------------------------------------------
+// Matrix helpers
+//------------------------------------------------------------------------------
+// source: http://timjones.io/blog/archive/2014/10/20/the-matrix-inverted
+
+template<uint8_t Order>
+float CalculateDeterminant(const std::array<std::array<float, Order>, Order>& src);
+
+template<uint8_t Order>
+std::array<std::array<float, Order - 1>, Order - 1> GetSubmatrix(
+ const std::array<std::array<float, Order>, Order>& src, uint8_t row, uint8_t col)
+{
+ uint8_t colCount = 0;
+ uint8_t rowCount = 0;
+
+ std::array<std::array<float, Order - 1>, Order - 1> dest;
+
+ for (int i = 0; i < Order; i++)
+ {
+ if (i != row)
+ {
+ colCount = 0;
+ for (int j = 0; j < Order; j++)
+ {
+ if (j != col)
+ {
+ dest[rowCount][colCount] = src[i][j];
+ colCount++;
+ }
+ }
+ rowCount++;
+ }
+ }
+
+ return dest;
+}
+
+template<uint8_t Order>
+float CalculateMinor(const std::array<std::array<float, Order>, Order>& src,
+ uint8_t row,
+ uint8_t col)
+{
+ const std::array<std::array<float, Order - 1>, Order - 1> sub =
+ GetSubmatrix<Order>(src, row, col);
+ return CalculateDeterminant<Order - 1>(sub);
+}
+
+template<uint8_t Order>
+float CalculateDeterminant(const std::array<std::array<float, Order>, Order>& src)
+{
+ float det = 0.0f;
+
+ for (int i = 0; i < Order; i++)
+ {
+ // Get minor of element (0, i)
+ float minor = CalculateMinor<Order>(src, 0, i);
+
+ // If this is an odd-numbered row, negate the value.
+ float factor = (i % 2 == 1) ? -1.0f : 1.0f;
+
+ det += factor * src[0][i] * minor;
+ }
+ return det;
+}
+
+template<>
+float CalculateDeterminant<2>(const std::array<std::array<float, 2>, 2>& src)
+{
+ return src[0][0] * src[1][1] - src[0][1] * src[1][0];
+}
+
+//------------------------------------------------------------------------------
+// Matrix classes
+//------------------------------------------------------------------------------
+
+template<uint8_t Order>
+CMatrix<Order>::CMatrix(const std::array<std::array<float, Order - 1>, Order - 1>& other)
+{
+ *this = other;
+}
+
+template<uint8_t Order>
+CMatrix<Order>::CMatrix(const CMatrix<Order - 1>& other)
+{
+ *this = other.Get();
+}
+
+template<uint8_t Order>
+CMatrix<Order>& CMatrix<Order>::operator=(
+ const std::array<std::array<float, Order - 1>, Order - 1>& other)
+{
+ for (int i = 0; i < Order - 1; ++i)
+ for (int j = 0; j < Order - 1; ++j)
+ m_mat[i][j] = other[i][j];
+
+ for (int i = 0; i < Order; ++i)
+ m_mat[i][Order-1] = 0;
+
+ for (int i = 0; i < Order; ++i)
+ m_mat[Order-1][i] = 0;
+
+ return *this;
+}
+
+template<uint8_t Order>
+std::array<std::array<float, Order>, Order>& CMatrix<Order>::Get()
+{
+ return m_mat;
+}
+
+template<uint8_t Order>
+const std::array<std::array<float, Order>, Order>& CMatrix<Order>::Get() const
+{
+ return m_mat;
+}
+
+template<uint8_t Order>
+CMatrix<Order> CMatrix<Order>::operator*(const CMatrix& other)
+{
+ return *this * other.m_mat;
+}
+
+template<uint8_t Order>
+CMatrix<Order>& CMatrix<Order>::operator*=(const CMatrix& other)
+{
+ CMatrix<Order> tmp = *this * other.m_mat;
+ *this = tmp;
+ return *this;
+}
+
+template<uint8_t Order>
+CMatrix<Order> CMatrix<Order>::operator*(const std::array<std::array<float, Order>, Order>& other)
+{
+ CMatrix<Order> ret;
+ for (int i = 0; i < Order; ++i)
+ for (int j = 0; j < Order; ++j)
+ for (int k = 0; k < Order; ++k)
+ ret.m_mat[i][j] += m_mat[i][k] * other[k][j];
+
+ return ret;
+}
+
+template<uint8_t Order>
+CMatrix<Order>& CMatrix<Order>::Invert()
+{
+ CMatrix<Order> tmp;
+ tmp.m_mat = Invert(m_mat);
+ *this = tmp;
+ return *this;
+}
+
+template<uint8_t Order>
+std::array<std::array<float, Order>, Order> CMatrix<Order>::Invert(
+ std::array<std::array<float, Order>, Order>& other) const
+{
+ // Calculate the inverse of the determinant of src.
+ float det = CalculateDeterminant<Order>(other);
+ float inverseDet = 1.0f / det;
+
+ std::array<std::array<float, Order>, Order> dst;
+
+ for (int j = 0; j < Order; j++)
+ {
+ for (int i = 0; i < Order; i++)
+ {
+ // Get minor of element (j, i) - not (i, j) because
+ // this is where the transpose happens.
+ float minor = CalculateMinor<Order>(other, j, i);
+
+ // Multiply by (−1)^{i+j}
+ float factor = ((i + j) % 2 == 1) ? -1.0f : 1.0f;
+ float cofactor = minor * factor;
+
+ dst[i][j] = inverseDet * cofactor;
+ }
+ }
+
+ return dst;
+}
+
+CGlMatrix::CGlMatrix(const CMatrix<3>& other) : CMatrix<4>(other)
+{
+}
+
+CGlMatrix::CGlMatrix(const std::array<std::array<float, 3>, 3>& other) : CMatrix<4>(other)
+{
+}
+
+CGlMatrix::CMatrix CGlMatrix::operator*(const std::array<std::array<float, 4>, 4>& other)
+{
+ CGlMatrix ret;
+
+ std::array<std::array<float, 4>, 4>& left = m_mat;
+ const std::array<std::array<float, 4>, 4>& right = other;
+
+ ret.m_mat[0][0] = left[0][0] * right[0][0] + left[0][1] * right[1][0] + left[0][2] * right[2][0];
+ ret.m_mat[0][1] = left[0][0] * right[0][1] + left[0][1] * right[1][1] + left[0][2] * right[2][1];
+ ret.m_mat[0][2] = left[0][0] * right[0][2] + left[0][1] * right[1][2] + left[0][2] * right[2][2];
+ ret.m_mat[0][3] = left[0][0] * right[0][3] + left[0][1] * right[1][3] + left[0][2] * right[2][3] + left[0][3];
+ ret.m_mat[1][0] = left[1][0] * right[0][0] + left[1][1] * right[1][0] + left[1][2] * right[2][0];
+ ret.m_mat[1][1] = left[1][0] * right[0][1] + left[1][1] * right[1][1] + left[1][2] * right[2][1];
+ ret.m_mat[1][2] = left[1][0] * right[0][2] + left[1][1] * right[1][2] + left[1][2] * right[2][2];
+ ret.m_mat[1][3] = left[1][0] * right[0][3] + left[1][1] * right[1][3] + left[1][2] * right[2][3] + left[1][3];
+ ret.m_mat[2][0] = left[2][0] * right[0][0] + left[2][1] * right[1][0] + left[2][2] * right[2][0];
+ ret.m_mat[2][1] = left[2][0] * right[0][1] + left[2][1] * right[1][1] + left[2][2] * right[2][1];
+ ret.m_mat[2][2] = left[2][0] * right[0][2] + left[2][1] * right[1][2] + left[2][2] * right[2][2];
+ ret.m_mat[2][3] = left[2][0] * right[0][3] + left[2][1] * right[1][3] + left[2][2] * right[2][3] + left[2][3];
+
+ return ret;
+}
+
+CScale::CScale(float x, float y, float z)
+{
+ m_mat[0][0] = x;
+ m_mat[1][1] = y;
+ m_mat[2][2] = z;
+ m_mat[3][3] = 1;
+}
+
+CTranslate::CTranslate(float x, float y, float z)
+{
+ m_mat[0][0] = 1;
+ m_mat[1][1] = 1;
+ m_mat[2][2] = 1;
+ m_mat[3][3] = 1;
+ m_mat[0][3] = x;
+ m_mat[1][3] = y;
+ m_mat[2][3] = z;
+}
+
+//------------------------------------------------------------------------------
+// Conversion classes
+//------------------------------------------------------------------------------
+
+ConversionToRGB::ConversionToRGB(float Kr, float Kb)
+{
+ float Kg = 1-Kr-Kb;
+ a11 = Kr;
+ a12 = Kg;
+ a13 = Kb;
+ CbDen = 2*(1-Kb);
+ CrDen = 2*(1-Kr);
+
+ m_mat[0][0] = a11; m_mat[0][1] = a12; m_mat[0][2] = a13;
+ m_mat[1][0] = -Kr/CbDen; m_mat[1][1] = -Kg/CbDen; m_mat[1][2] = 0.5;
+ m_mat[2][0] = 0.5; m_mat[2][1] = -Kg/CrDen; m_mat[2][2] = -Kb/CrDen;
+
+ m_mat = Invert(m_mat);
+};
+
+PrimaryToXYZ::PrimaryToXYZ(const float (&primaries)[3][2], const float (&whitepoint)[2])
+{
+ float By = CalcBy(primaries, whitepoint);
+ float Gy = CalcGy(primaries, whitepoint, By);
+ float Ry = CalcRy(By, Gy);
+
+ m_mat[0][0] = Ry*primaries[0][0]/primaries[0][1];
+ m_mat[0][1] = Gy*primaries[1][0]/primaries[1][1];
+ m_mat[0][2] = By*primaries[2][0]/primaries[2][1];
+ m_mat[1][0] = Ry;
+ m_mat[1][1] = Gy;
+ m_mat[1][2] = By;
+ m_mat[2][0] = Ry/primaries[0][1] * (1- primaries[0][0] - primaries[0][1]);
+ m_mat[2][1] = Gy/primaries[1][1] * (1- primaries[1][0] - primaries[1][1]);
+ m_mat[2][2] = By/primaries[2][1] * (1- primaries[2][0] - primaries[2][1]);
+}
+
+float PrimaryToXYZ::CalcBy(const float p[3][2], const float w[2])
+{
+ float val = ((1-w[0])/w[1] - (1-p[0][0])/p[0][1]) * (p[1][0]/p[1][1] - p[0][0]/p[0][1]) -
+ (w[0]/w[1] - p[0][0]/p[0][1]) * ((1-p[1][0])/p[1][1] - (1-p[0][0])/p[0][1]);
+
+ val /= ((1-p[2][0])/p[2][1] - (1-p[0][0])/p[0][1]) * (p[1][0]/p[1][1] - p[0][0]/p[0][1]) -
+ (p[2][0]/p[2][1] - p[0][0]/p[0][1]) * ((1-p[1][0])/p[1][1] - (1-p[0][0])/p[0][1]);
+
+ return val;
+}
+
+float PrimaryToXYZ::CalcGy(const float p[3][2], const float w[2], const float By)
+{
+ float val = w[0]/w[1] - p[0][0]/p[0][1] - By * (p[2][0]/p[2][1] - p[0][0]/p[0][1]);
+ val /= p[1][0]/p[1][1] - p[0][0]/p[0][1];
+
+ return val;
+}
+
+float PrimaryToXYZ::CalcRy(const float By, const float Gy)
+{
+ return 1.0f - Gy - By;
+}
+
+PrimaryToRGB::PrimaryToRGB(float (&primaries)[3][2], float (&whitepoint)[2]) : PrimaryToXYZ(primaries, whitepoint)
+{
+ m_mat = Invert(m_mat);
+}
+
+//------------------------------------------------------------------------------
+
+CConvertMatrix& CConvertMatrix::SetSourceColorSpace(AVColorSpace colorSpace)
+{
+ if (m_colSpace != colorSpace)
+ m_mat.reset();
+
+ m_colSpace = colorSpace;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetSourceBitDepth(int bits)
+{
+ if (m_srcBits != bits)
+ m_mat.reset();
+
+ m_srcBits = bits;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetSourceLimitedRange(bool limited)
+{
+ if (m_limitedSrc != limited)
+ m_mat.reset();
+
+ m_limitedSrc = limited;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetSourceTextureBitDepth(int textureBits)
+{
+ if (m_srcTextureBits != textureBits)
+ m_mat.reset();
+
+ m_srcTextureBits = textureBits;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetSourceColorPrimaries(AVColorPrimaries src)
+{
+ if (m_colPrimariesSrc != src)
+ m_matPrim.reset();
+
+ m_colPrimariesSrc = src;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetDestinationColorPrimaries(AVColorPrimaries dst)
+{
+ if (m_colPrimariesDst != dst)
+ m_matPrim.reset();
+
+ m_colPrimariesDst = dst;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetDestinationContrast(float contrast)
+{
+ m_contrast = contrast;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetDestinationBlack(float black)
+{
+ m_black = black;
+ return *this;
+}
+
+CConvertMatrix& CConvertMatrix::SetDestinationLimitedRange(bool limited)
+{
+ m_limitedDst = limited;
+ return *this;
+}
+
+const CMatrix<3>& CConvertMatrix::GenPrimMat()
+{
+ if (m_matPrim)
+ return *m_matPrim;
+
+ Primaries primToRGB;
+ Primaries primToXYZ;
+ switch (m_colPrimariesSrc)
+ {
+ case AVCOL_PRI_BT709:
+ primToXYZ = PrimariesBT709;
+ m_gammaSrc = 2.2;
+ break;
+ case AVCOL_PRI_BT470BG:
+ primToXYZ = PrimariesBT610_625;
+ m_gammaSrc = 2.2;
+ break;
+ case AVCOL_PRI_SMPTE170M:
+ case AVCOL_PRI_SMPTE240M:
+ primToXYZ = PrimariesBT610_525;
+ m_gammaSrc = 2.2;
+ break;
+ case AVCOL_PRI_BT2020:
+ primToXYZ = PrimariesBT2020;
+ m_gammaSrc = 2.4;
+ break;
+ default:
+ primToXYZ = PrimariesBT709;
+ m_gammaSrc = 2.2;
+ break;
+ }
+ switch (m_colPrimariesDst)
+ {
+ case AVCOL_PRI_BT709:
+ primToRGB = PrimariesBT709;
+ m_gammaDst = 2.2;
+ break;
+ case AVCOL_PRI_BT470BG:
+ primToRGB = PrimariesBT610_625;
+ m_gammaDst = 2.2;
+ break;
+ case AVCOL_PRI_SMPTE170M:
+ case AVCOL_PRI_SMPTE240M:
+ primToRGB = PrimariesBT610_525;
+ m_gammaDst = 2.2;
+ break;
+ case AVCOL_PRI_BT2020:
+ primToRGB = PrimariesBT2020;
+ m_gammaDst = 2.4;
+ break;
+ default:
+ primToRGB = PrimariesBT709;
+ m_gammaDst = 2.2;
+ break;
+ }
+ PrimaryToXYZ toXYZ(primToXYZ.primaries, primToXYZ.whitepoint);
+ PrimaryToRGB toRGB(primToRGB.primaries, primToRGB.whitepoint);
+
+ m_matPrim = std::make_unique<CMatrix<3>>(toRGB * toXYZ);
+
+ return *m_matPrim;
+}
+
+const CGlMatrix& CConvertMatrix::GenMat()
+{
+ if (m_mat)
+ return *m_mat;
+
+ ConvYCbCr convYCbCr;
+ switch (m_colSpace)
+ {
+ case AVCOL_SPC_BT709:
+ convYCbCr = BT709YCbCr;
+ break;
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_SMPTE170M:
+ convYCbCr = BT601YCbCr;
+ break;
+ case AVCOL_SPC_SMPTE240M:
+ convYCbCr = ST240YCbCr;
+ break;
+ case AVCOL_SPC_BT2020_NCL:
+ case AVCOL_SPC_BT2020_CL:
+ convYCbCr = BT2020YCbCr;
+ break;
+ default:
+ convYCbCr = BT709YCbCr;
+ break;
+ }
+
+ ConversionToRGB mConvRGB(convYCbCr.Kr, convYCbCr.Kb);
+ CGlMatrix mat(mConvRGB);
+
+ CTranslate trans(0, -0.5, -0.5);
+ mat *= trans;
+
+ if (m_limitedSrc)
+ {
+ if (m_srcBits >= 12)
+ {
+ CScale scale(4080.0f / (3760 - 256), 4080.0f / (3840 - 256), 4080.0f / (3840 - 256));
+ CTranslate trans(- 256.0f / 4080, - 256.0f / 4080, - 256.0f / 4080);
+ mat *= scale;
+ mat *= trans;
+ }
+ else if (m_srcBits == 10)
+ {
+ CScale scale(1020.0f / (940 - 64), 1020.0f / (960 - 64), 1020.0f / (960 - 64));
+ CTranslate trans(- 64.0f / 1020, - 64.0f / 1020, - 64.0f / 1020);
+ mat *= scale;
+ mat *= trans;
+ }
+ else
+ {
+ CScale scale(255.0f / (235 - 16), 255.0f / (240 - 16), 255.0f / (240 - 16));
+ CTranslate trans(- 16.0f / 255, - 16.0f / 255, - 16.0f / 255);
+ mat *= scale;
+ mat *= trans;
+ }
+ }
+
+ if (m_srcTextureBits > 8)
+ {
+ float val = 65535.0f / ((1 << m_srcTextureBits) - 1);
+ CScale scale(val, val, val);
+ mat *= scale;
+ }
+
+ m_mat = std::make_unique<CGlMatrix>(mat);
+
+ return *m_mat;
+}
+
+Matrix4 CConvertMatrix::GetYuvMat()
+{
+ const CGlMatrix& mat = GenMat();
+
+ CScale contrast(m_contrast, m_contrast, m_contrast);
+ CTranslate black(m_black, m_black, m_black);
+
+ CGlMatrix ret = contrast;
+ ret *= black;
+ if (m_limitedDst)
+ {
+ float valScale = (235 - 16) / 255.0f;
+ float valTrans = 16.0f / 255;
+ CScale scale(valScale, valScale, valScale);
+ CTranslate trans(valTrans, valTrans, valTrans);
+ ret *= trans;
+ ret *= scale;
+ }
+
+ ret *= mat;
+
+ Matrix4 dst;
+
+ for (int i = 0; i < 4; ++i)
+ for (int j = 0; j < 4; ++j)
+ dst[i][j] = ret[j][i];
+
+ dst[0][3] = 0.0f;
+ dst[1][3] = 0.0f;
+ dst[2][3] = 0.0f;
+ dst[3][3] = 1.0f;
+
+ return dst;
+}
+
+Matrix3 CConvertMatrix::GetPrimMat()
+{
+ if (m_colPrimariesDst == m_colPrimariesSrc)
+ return Matrix3();
+
+ const Matrix3& matPrim = GenPrimMat();
+
+ Matrix3 dst;
+
+ for (int i = 0; i < 3; ++i)
+ for (int j = 0; j < 3; ++j)
+ dst[i][j] = matPrim[j][i];
+
+ return dst;
+}
+
+float CConvertMatrix::GetGammaSrc()
+{
+ return m_gammaSrc;
+}
+
+float CConvertMatrix::GetGammaDst()
+{
+ return m_gammaDst;
+}
+
+Matrix3x1 CConvertMatrix::GetRGBYuvCoefs(AVColorSpace colspace)
+{
+ Matrix3x1 coefs;
+
+ switch (colspace)
+ {
+ case AVCOL_SPC_BT709:
+ coefs[0] = BT709YCbCr.Kr;
+ coefs[1] = 1 - BT709YCbCr.Kr - BT709YCbCr.Kb;
+ coefs[2] = BT709YCbCr.Kb;
+ break;
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_SMPTE170M:
+ coefs[0] = BT601YCbCr.Kr;
+ coefs[1] = 1 - BT601YCbCr.Kr - BT601YCbCr.Kb;
+ coefs[2] = BT601YCbCr.Kb;
+ break;
+ case AVCOL_SPC_SMPTE240M:
+ coefs[0] = ST240YCbCr.Kr;
+ coefs[1] = 1 - ST240YCbCr.Kr - ST240YCbCr.Kb;
+ coefs[2] = ST240YCbCr.Kb;
+ break;
+ case AVCOL_SPC_BT2020_NCL:
+ case AVCOL_SPC_BT2020_CL:
+ coefs[0] = BT2020YCbCr.Kr;
+ coefs[1] = 1 - BT2020YCbCr.Kr - BT2020YCbCr.Kb;
+ coefs[2] = BT2020YCbCr.Kb;
+ break;
+ default:
+ throw std::invalid_argument("unknown colorspace: " + std::to_string(colspace));
+ }
+
+ return coefs;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h
new file mode 100644
index 0000000..b2c66a7
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <array>
+#include <cmath>
+#include <memory>
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+template<uint8_t Order>
+class CMatrix
+{
+public:
+ CMatrix() = default;
+ explicit CMatrix(const CMatrix<Order - 1>& other);
+ explicit CMatrix(const std::array<std::array<float, Order>, Order>& other) : m_mat(other) {}
+ explicit CMatrix(const std::array<std::array<float, Order - 1>, Order - 1>& other);
+ virtual ~CMatrix() = default;
+
+ virtual CMatrix operator*(const std::array<std::array<float, Order>, Order>& other);
+
+ CMatrix operator*(const CMatrix& other);
+ CMatrix& operator*=(const CMatrix& other);
+
+ CMatrix& operator=(const std::array<std::array<float, Order - 1>, Order - 1>& other);
+
+ bool operator==(const CMatrix<Order>& other) const
+ {
+ for (int i = 0; i < Order; ++i)
+ {
+ for (int j = 0; j < Order; ++j)
+ {
+ if (m_mat[i][j] == other.m_mat[i][j])
+ continue;
+
+ // some floating point comparisons should be done by checking if the difference is within a tolerance
+ if (std::abs(m_mat[i][j] - other.m_mat[i][j]) <=
+ (std::max(std::abs(other.m_mat[i][j]), std::abs(m_mat[i][j])) * 1e-2f))
+ continue;
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ std::array<float, Order>& operator[](int index) { return m_mat[index]; }
+
+ const std::array<float, Order>& operator[](int index) const { return m_mat[index]; }
+
+ std::array<std::array<float, Order>, Order>& Get();
+
+ const std::array<std::array<float, Order>, Order>& Get() const;
+
+ CMatrix& Invert();
+
+ float* ToRaw() { return &m_mat[0][0]; }
+
+protected:
+ std::array<std::array<float, Order>, Order> Invert(
+ std::array<std::array<float, Order>, Order>& other) const;
+
+ std::array<std::array<float, Order>, Order> m_mat{{}};
+};
+
+class CGlMatrix : public CMatrix<4>
+{
+public:
+ CGlMatrix() = default;
+ explicit CGlMatrix(const CMatrix<3>& other);
+ explicit CGlMatrix(const std::array<std::array<float, 3>, 3>& other);
+ ~CGlMatrix() override = default;
+ CMatrix operator*(const std::array<std::array<float, 4>, 4>& other) override;
+};
+
+class CScale : public CGlMatrix
+{
+public:
+ CScale(float x, float y, float z);
+ ~CScale() override = default;
+};
+
+class CTranslate : public CGlMatrix
+{
+public:
+ CTranslate(float x, float y, float z);
+ ~CTranslate() override = default;
+};
+
+class ConversionToRGB : public CMatrix<3>
+{
+public:
+ ConversionToRGB(float Kr, float Kb);
+ ~ConversionToRGB() override = default;
+
+protected:
+ ConversionToRGB() = default;
+
+ float a11, a12, a13;
+ float CbDen, CrDen;
+};
+
+class PrimaryToXYZ : public CMatrix<3>
+{
+public:
+ PrimaryToXYZ(const float (&primaries)[3][2], const float (&whitepoint)[2]);
+ ~PrimaryToXYZ() override = default;
+
+protected:
+ PrimaryToXYZ() = default;
+ float CalcBy(const float p[3][2], const float w[2]);
+ float CalcGy(const float p[3][2], const float w[2], const float By);
+ float CalcRy(const float By, const float Gy);
+};
+
+class PrimaryToRGB : public PrimaryToXYZ
+{
+public:
+ PrimaryToRGB(float (&primaries)[3][2], float (&whitepoint)[2]);
+ ~PrimaryToRGB() override = default;
+};
+
+//------------------------------------------------------------------------------
+
+using Matrix4 = CMatrix<4>;
+using Matrix3 = CMatrix<3>;
+using Matrix3x1 = std::array<float, 3>;
+
+/**
+ * @brief Helper class used for YUV to RGB conversions. This class can
+ * take into account different source/destination primaries and
+ * various other parameters.
+ */
+class CConvertMatrix
+{
+public:
+ CConvertMatrix() = default;
+ ~CConvertMatrix() = default;
+
+ /**
+ * @brief Set the source color space.
+ */
+ CConvertMatrix& SetSourceColorSpace(AVColorSpace colorSpace);
+
+ /**
+ * @brief Set the source bit depth.
+ */
+ CConvertMatrix& SetSourceBitDepth(int bits);
+
+ /**
+ * @brief Set the source limited range boolean.
+ */
+ CConvertMatrix& SetSourceLimitedRange(bool limited);
+
+ /**
+ * @brief Set the source texture bit depth. This is need to normalize values
+ * when using > 8 bit texture formats in OpenGL/DirectX. For example
+ * GL_R16 is a 16 bit texture which needs to normalize the 10 bit format.
+ */
+ CConvertMatrix& SetSourceTextureBitDepth(int textureBits);
+
+ /**
+ * @brief Set the source color primaries.
+ */
+ CConvertMatrix& SetSourceColorPrimaries(AVColorPrimaries src);
+
+ /**
+ * @brief Set the destination color primaries.
+ */
+ CConvertMatrix& SetDestinationColorPrimaries(AVColorPrimaries dst);
+
+ /**
+ * @brief Set the destination contrast.
+ */
+ CConvertMatrix& SetDestinationContrast(float contrast);
+
+ /**
+ * @brief Set the destination black level.
+ */
+ CConvertMatrix& SetDestinationBlack(float black);
+
+ /**
+ * @brief Set the destination limited range boolean.
+ */
+ CConvertMatrix& SetDestinationLimitedRange(bool limited);
+
+ /**
+ * @brief Get the YUV Matrix for the YUV to RGB conversion.
+ */
+ Matrix4 GetYuvMat();
+
+ /**
+ * @brief Get the Primaries Matrix for the primaries conversion.
+ */
+ Matrix3 GetPrimMat();
+
+ /**
+ * @brief Get the gamma source value. Used for color primary conversion..
+ */
+ float GetGammaSrc();
+
+ /**
+ * @brief Get the gamma destination value. Used for color primary conversion.
+ */
+ float GetGammaDst();
+
+ /**
+ * @brief Get the YUV coeffecients used for tonemapping.
+ */
+ static Matrix3x1 GetRGBYuvCoefs(AVColorSpace colspace);
+
+private:
+ const CGlMatrix& GenMat();
+ const CMatrix<3>& GenPrimMat();
+
+ std::unique_ptr<CGlMatrix> m_mat;
+ std::unique_ptr<CMatrix<3>> m_matPrim;
+
+ AVColorSpace m_colSpace = AVCOL_SPC_BT709;
+ AVColorPrimaries m_colPrimariesSrc = AVCOL_PRI_BT709;
+ float m_gammaSrc = 2.2f;
+ bool m_limitedSrc = true;
+ AVColorPrimaries m_colPrimariesDst = AVCOL_PRI_BT709;
+ float m_gammaDst = 2.2f;
+ bool m_limitedDst = false;
+ int m_srcBits = 8;
+ int m_srcTextureBits = 8;
+ float m_contrast = 1.0;
+ float m_black = 0.0;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.cpp
new file mode 100644
index 0000000..93a4f4a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#ifdef TARGET_WINDOWS
+ #define _USE_MATH_DEFINES
+#endif
+
+#include "ConvolutionKernels.h"
+#include "utils/MathUtils.h"
+
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846
+#endif
+
+#define SINC(x) (sin(M_PI * (x)) / (M_PI * (x)))
+
+CConvolutionKernel::CConvolutionKernel(ESCALINGMETHOD method, int size)
+{
+ m_size = size;
+ m_floatpixels = new float[m_size * 4];
+
+ if (method == VS_SCALINGMETHOD_LANCZOS2)
+ Lanczos2();
+ else if (method == VS_SCALINGMETHOD_SPLINE36_FAST)
+ Spline36Fast();
+ else if (method == VS_SCALINGMETHOD_LANCZOS3_FAST)
+ Lanczos3Fast();
+ else if (method == VS_SCALINGMETHOD_SPLINE36)
+ Spline36();
+ else if (method == VS_SCALINGMETHOD_LANCZOS3)
+ Lanczos3();
+ else if (method == VS_SCALINGMETHOD_CUBIC_B_SPLINE)
+ Bicubic(1.0, 0.0);
+ else if (method == VS_SCALINGMETHOD_CUBIC_MITCHELL)
+ Bicubic(1.0 / 3.0, 1.0 / 3.0);
+ else if (method == VS_SCALINGMETHOD_CUBIC_CATMULL)
+ Bicubic(0.0, 0.5);
+ else if (method == VS_SCALINGMETHOD_CUBIC_0_075)
+ Bicubic(0.0, 0.75);
+ else if (method == VS_SCALINGMETHOD_CUBIC_0_1)
+ Bicubic(0.0, 1.0);
+
+ ToIntFract();
+ ToUint8();
+}
+
+CConvolutionKernel::~CConvolutionKernel()
+{
+ delete [] m_floatpixels;
+ delete [] m_intfractpixels;
+ delete [] m_uint8pixels;
+}
+
+//generate a lanczos2 kernel which can be loaded with RGBA format
+//each value of RGBA has one tap, so a shader can load 4 taps with a single pixel lookup
+void CConvolutionKernel::Lanczos2()
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ for (int j = 0; j < 4; j++)
+ m_floatpixels[i * 4 + j] = (float)LanczosWeight(x + (double)(j - 2), 2.0);
+
+ //any collection of 4 taps added together needs to be exactly 1.0
+ //for lanczos this is not always the case, so we take each collection of 4 taps
+ //and divide those taps by the sum of the taps
+ float weight = 0.0;
+ for (int j = 0; j < 4; j++)
+ weight += m_floatpixels[i * 4 + j];
+
+ for (int j = 0; j < 4; j++)
+ m_floatpixels[i * 4 + j] /= weight;
+ }
+}
+
+//generate a lanczos3 kernel which can be loaded with RGBA format
+//each value of RGBA has one tap, so a shader can load 4 taps with a single pixel lookup
+//the two outer lobes of the lanczos3 kernel are added to the two lobes one step to the middle
+//this basically looks the same as lanczos3, but the kernel only has 4 taps,
+//so it can use the 4x4 convolution shader which is twice as fast as the 6x6 one
+void CConvolutionKernel::Lanczos3Fast()
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double a = 3.0;
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ m_floatpixels[i * 4 + 0] = (float)(LanczosWeight(x - 2.0, a) + LanczosWeight(x - 3.0, a));
+ m_floatpixels[i * 4 + 1] = (float) LanczosWeight(x - 1.0, a);
+ m_floatpixels[i * 4 + 2] = (float) LanczosWeight(x , a);
+ m_floatpixels[i * 4 + 3] = (float)(LanczosWeight(x + 1.0, a) + LanczosWeight(x + 2.0, a));
+
+ //any collection of 4 taps added together needs to be exactly 1.0
+ //for lanczos this is not always the case, so we take each collection of 4 taps
+ //and divide those taps by the sum of the taps
+ float weight = 0.0;
+ for (int j = 0; j < 4; j++)
+ weight += m_floatpixels[i * 4 + j];
+
+ for (int j = 0; j < 4; j++)
+ m_floatpixels[i * 4 + j] /= weight;
+ }
+}
+
+//generate a lanczos3 kernel which can be loaded with RGBA format
+//each value of RGB has one tap, so a shader can load 3 taps with a single pixel lookup
+void CConvolutionKernel::Lanczos3()
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ for (int j = 0; j < 3; j++)
+ m_floatpixels[i * 4 + j] = (float)LanczosWeight(x * 2.0 + (double)(j * 2 - 3), 3.0);
+
+ m_floatpixels[i * 4 + 3] = 0.0;
+ }
+
+ //any collection of 6 taps added together needs to be exactly 1.0
+ //for lanczos this is not always the case, so we take each collection of 6 taps
+ //and divide those taps by the sum of the taps
+ for (int i = 0; i < m_size / 2; i++)
+ {
+ float weight = 0.0;
+ for (int j = 0; j < 3; j++)
+ {
+ weight += m_floatpixels[i * 4 + j];
+ weight += m_floatpixels[(i + m_size / 2) * 4 + j];
+ }
+ for (int j = 0; j < 3; j++)
+ {
+ m_floatpixels[i * 4 + j] /= weight;
+ m_floatpixels[(i + m_size / 2) * 4 + j] /= weight;
+ }
+ }
+}
+
+void CConvolutionKernel::Spline36Fast()
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ m_floatpixels[i * 4 + 0] = (float)(Spline36Weight(x - 2.0) + Spline36Weight(x - 3.0));
+ m_floatpixels[i * 4 + 1] = (float) Spline36Weight(x - 1.0);
+ m_floatpixels[i * 4 + 2] = (float) Spline36Weight(x );
+ m_floatpixels[i * 4 + 3] = (float)(Spline36Weight(x + 1.0) + Spline36Weight(x + 2.0));
+
+ float weight = 0.0;
+ for (int j = 0; j < 4; j++)
+ weight += m_floatpixels[i * 4 + j];
+
+ for (int j = 0; j < 4; j++)
+ m_floatpixels[i * 4 + j] /= weight;
+ }
+}
+
+void CConvolutionKernel::Spline36()
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ for (int j = 0; j < 3; j++)
+ m_floatpixels[i * 4 + j] = (float)Spline36Weight(x * 2.0 + (double)(j * 2 - 3));
+
+ m_floatpixels[i * 4 + 3] = 0.0;
+ }
+
+ for (int i = 0; i < m_size / 2; i++)
+ {
+ float weight = 0.0;
+ for (int j = 0; j < 3; j++)
+ {
+ weight += m_floatpixels[i * 4 + j];
+ weight += m_floatpixels[(i + m_size / 2) * 4 + j];
+ }
+ for (int j = 0; j < 3; j++)
+ {
+ m_floatpixels[i * 4 + j] /= weight;
+ m_floatpixels[(i + m_size / 2) * 4 + j] /= weight;
+ }
+ }
+}
+
+//generate a bicubic kernel which can be loaded with RGBA format
+//each value of RGBA has one tap, so a shader can load 4 taps with a single pixel lookup
+void CConvolutionKernel::Bicubic(double B, double C)
+{
+ for (int i = 0; i < m_size; i++)
+ {
+ double x = (double)i / (double)m_size;
+
+ //generate taps
+ for (int j = 0; j < 4; j++)
+ m_floatpixels[i * 4 + j] = (float)BicubicWeight(x + (double)(j - 2), B, C);
+ }
+}
+
+double CConvolutionKernel::LanczosWeight(double x, double radius)
+{
+ double ax = fabs(x);
+
+ if (ax == 0.0)
+ return 1.0;
+ else if (ax < radius)
+ return SINC(ax) * SINC(ax / radius);
+ else
+ return 0.0;
+}
+
+double CConvolutionKernel::BicubicWeight(double x, double B, double C)
+{
+ double ax = fabs(x);
+
+ if (ax<1.0)
+ {
+ return ((12 - 9*B - 6*C) * ax * ax * ax +
+ (-18 + 12*B + 6*C) * ax * ax +
+ (6 - 2*B))/6;
+ }
+ else if (ax<2.0)
+ {
+ return ((-B - 6*C) * ax * ax * ax +
+ (6*B + 30*C) * ax * ax + (-12*B - 48*C) *
+ ax + (8*B + 24*C)) / 6;
+ }
+ else
+ {
+ return 0.0;
+ }
+}
+
+double CConvolutionKernel::Spline36Weight(double x)
+{
+ double ax = fabs(x);
+
+ if ( ax < 1.0 )
+ return ( ( 13.0 / 11.0 * (ax ) - 453.0 / 209.0 ) * (ax ) - 3.0 / 209.0 ) * (ax ) + 1.0;
+ else if ( ax < 2.0 )
+ return ( ( -6.0 / 11.0 * (ax - 1.0) + 270.0 / 209.0 ) * (ax - 1.0) - 156.0 / 209.0 ) * (ax - 1.0);
+ else if ( ax < 3.0 )
+ return ( ( 1.0 / 11.0 * (ax - 2.0) - 45.0 / 209.0 ) * (ax - 2.0) + 26.0 / 209.0 ) * (ax - 2.0);
+ return 0.0;
+}
+
+//convert float to high byte/low byte, so the kernel can be loaded into an 8 bit texture
+//with height 2 and converted back to real float in the shader
+//it only works when the kernel texture uses nearest neighbour, but there's almost no difference
+//between that and linear interpolation
+void CConvolutionKernel::ToIntFract()
+{
+ m_intfractpixels = new uint8_t[m_size * 8];
+
+ for (int i = 0; i < m_size * 4; i++)
+ {
+ int value = MathUtils::round_int((static_cast<double>(m_floatpixels[i]) + 1.0) / 2.0 * 65535.0);
+ if (value < 0)
+ value = 0;
+ else if (value > 65535)
+ value = 65535;
+
+ int integer = value / 256;
+ int fract = value % 256;
+
+ m_intfractpixels[i] = (uint8_t)integer;
+ m_intfractpixels[i + m_size * 4] = (uint8_t)fract;
+ }
+}
+
+//convert to 8 bits unsigned
+void CConvolutionKernel::ToUint8()
+{
+ m_uint8pixels = new uint8_t[m_size * 4];
+
+ for (int i = 0; i < m_size * 4; i++)
+ {
+ int value = MathUtils::round_int((static_cast<double>(m_floatpixels[i]) * 0.5 + 0.5) * 255.0);
+ if (value < 0)
+ value = 0;
+ else if (value > 255)
+ value = 255;
+
+ m_uint8pixels[i] = (uint8_t)value;
+ }
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.h
new file mode 100644
index 0000000..d39e0c8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConvolutionKernels.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoSettings.h"
+
+#include <stdint.h>
+
+class CConvolutionKernel
+{
+ public:
+ CConvolutionKernel(ESCALINGMETHOD method, int size);
+ ~CConvolutionKernel();
+
+ int GetSize() { return m_size; }
+ float* GetFloatPixels() { return m_floatpixels; }
+ uint8_t* GetIntFractPixels() { return m_intfractpixels; }
+ uint8_t* GetUint8Pixels() { return m_uint8pixels; }
+
+ private:
+ CConvolutionKernel(const CConvolutionKernel&) = delete;
+ CConvolutionKernel& operator=(const CConvolutionKernel&) = delete;
+ void Lanczos2();
+ void Lanczos3Fast();
+ void Lanczos3();
+ void Spline36Fast();
+ void Spline36();
+ void Bicubic(double B, double C);
+
+ static double LanczosWeight(double x, double radius);
+ static double Spline36Weight(double x);
+ static double BicubicWeight(double x, double B, double C);
+
+ void ToIntFract();
+ void ToUint8();
+
+ int m_size;
+ float* m_floatpixels;
+ uint8_t* m_intfractpixels;
+ uint8_t* m_uint8pixels;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.cpp
new file mode 100644
index 0000000..e70cd9d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2007 d4rk
+ * Copyright (C) 2007-2018 Team Kodi
+ * Copyright (C) 2015 Lauri Mylläri
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GLSLOutput.h"
+
+#include "dither.h"
+#include "utils/log.h"
+
+#include "system_gl.h"
+
+using namespace Shaders;
+
+GLSLOutput::GLSLOutput(int texunit,
+ bool useDithering,
+ unsigned int ditherDepth,
+ bool fullrange,
+ GLuint clutTex,
+ int clutSize)
+ : m_tCLUTTex(clutTex)
+{
+ // set member variable initial values
+ m_1stTexUnit = texunit;
+ m_uDither = m_1stTexUnit+0;
+ m_uCLUT = m_1stTexUnit+1;
+
+ m_dither = useDithering;
+ m_ditherDepth = ditherDepth;
+ m_fullRange = fullrange;
+ // make sure CMS is enabled - this allows us to keep the texture
+ // around to quickly switch between CMS on and off
+ m_3DLUT = clutTex > 0;
+ m_uCLUTSize = clutSize;
+}
+
+std::string GLSLOutput::GetDefines()
+{
+ std::string defines;
+ if (m_dither)
+ defines += "#define XBMC_DITHER\n";
+ if (m_fullRange)
+ defines += "#define XBMC_FULLRANGE\n";
+ if (m_3DLUT)
+ defines += "#define KODI_3DLUT\n";
+ return defines;
+}
+
+void GLSLOutput::OnCompiledAndLinked(GLuint programHandle)
+{
+ FreeTextures();
+
+ // get uniform locations
+ // dithering
+ if (m_dither)
+ {
+ m_hDither = glGetUniformLocation(programHandle, "m_dither");
+ m_hDitherQuant = glGetUniformLocation(programHandle, "m_ditherquant");
+ m_hDitherSize = glGetUniformLocation(programHandle, "m_dithersize");
+ }
+ // 3DLUT
+ if (m_3DLUT)
+ {
+ m_hCLUT = glGetUniformLocation(programHandle, "m_CLUT");
+ m_hCLUTSize = glGetUniformLocation(programHandle, "m_CLUTsize");
+ }
+
+ if (m_dither)
+ {
+ //! @todo create a dither pattern
+
+ // create a dither texture
+ glGenTextures(1, &m_tDitherTex);
+ if ( m_tDitherTex <= 0 )
+ {
+ CLog::Log(LOGERROR, "Error creating dither texture");
+ return;
+ }
+ // bind and set texture parameters
+ glActiveTexture(GL_TEXTURE0 + m_uDither);
+ glBindTexture(GL_TEXTURE_2D, m_tDitherTex);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+ // load dither texture data
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, dither_size, dither_size, 0, GL_RED, GL_UNSIGNED_SHORT, dither_matrix);
+ }
+
+ glActiveTexture(GL_TEXTURE0);
+
+ VerifyGLState();
+}
+
+bool GLSLOutput::OnEnabled()
+{
+
+ if (m_dither)
+ {
+ // set texture units
+ glUniform1i(m_hDither, m_uDither);
+ VerifyGLState();
+
+ // bind textures
+ glActiveTexture(GL_TEXTURE0 + m_uDither);
+ glBindTexture(GL_TEXTURE_2D, m_tDitherTex);
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+
+ // dither settings
+ glUniform1f(m_hDitherQuant, (1<<m_ditherDepth)-1.0);
+ VerifyGLState();
+ glUniform2f(m_hDitherSize, dither_size, dither_size);
+ VerifyGLState();
+ }
+
+ if (m_3DLUT)
+ {
+ // set texture units
+ glUniform1i(m_hCLUT, m_uCLUT);
+ glUniform1f(m_hCLUTSize, m_uCLUTSize);
+ VerifyGLState();
+
+ // bind textures
+ glActiveTexture(GL_TEXTURE0 + m_uCLUT);
+ glBindTexture(GL_TEXTURE_3D, m_tCLUTTex);
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+ }
+
+ VerifyGLState();
+ return true;
+}
+
+void GLSLOutput::OnDisabled()
+{
+ // disable textures
+ if (m_dither)
+ {
+ glActiveTexture(GL_TEXTURE0 + m_uDither);
+ }
+ if (m_3DLUT)
+ {
+ glActiveTexture(GL_TEXTURE0 + m_uCLUT);
+ glDisable(GL_TEXTURE_3D);
+ }
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+}
+
+void GLSLOutput::Free()
+{
+ FreeTextures();
+}
+
+void GLSLOutput::FreeTextures()
+{
+ if (m_tDitherTex)
+ {
+ glDeleteTextures(1, &m_tDitherTex);
+ m_tDitherTex = 0;
+ }
+}
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.h
new file mode 100644
index 0000000..3f2bd9f
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/GLSLOutput.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * Copyright (C) 2015 Lauri Mylläri
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/GLUtils.h"
+
+#include <string>
+
+namespace Shaders
+{
+ class GLSLOutput
+ {
+ public:
+ // take the 1st available texture unit as a parameter
+ GLSLOutput(
+ int texunit,
+ bool useDithering,
+ unsigned int ditherDepth,
+ bool fullrange,
+ GLuint clutTex,
+ int clutSize);
+ std::string GetDefines();
+ void OnCompiledAndLinked(GLuint programHandle);
+ bool OnEnabled();
+ void OnDisabled();
+ void Free();
+
+ private:
+ void FreeTextures();
+
+ bool m_dither;
+ unsigned int m_ditherDepth;
+ bool m_fullRange;
+ bool m_3DLUT;
+ // first texture unit available to us
+ int m_1stTexUnit;
+ int m_uDither;
+ int m_uCLUT;
+ int m_uCLUTSize;
+
+ // defines
+
+ // attribute locations
+ GLint m_hDither = -1;
+ GLint m_hDitherQuant = -1;
+ GLint m_hDitherSize = -1;
+ GLint m_hCLUT = -1;
+ GLint m_hCLUTSize = -1;
+
+ // textures
+ GLuint m_tDitherTex = 0;
+ GLuint m_tCLUTTex;
+ };
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ShaderFormats.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ShaderFormats.h
new file mode 100644
index 0000000..f5d03a2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ShaderFormats.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Map.h"
+
+#include <fmt/format.h>
+
+enum EShaderFormat
+{
+ SHADER_NONE,
+ SHADER_YV12,
+ SHADER_YV12_9,
+ SHADER_YV12_10,
+ SHADER_YV12_12,
+ SHADER_YV12_14,
+ SHADER_YV12_16,
+ SHADER_NV12,
+ SHADER_YUY2,
+ SHADER_UYVY,
+ SHADER_NV12_RRG,
+ SHADER_MAX,
+};
+
+template<>
+struct fmt::formatter<EShaderFormat> : fmt::formatter<std::string_view>
+{
+ template<typename FormatContext>
+ constexpr auto format(const EShaderFormat& shaderFormat, FormatContext& ctx)
+ {
+ const auto it = shaderFormatMap.find(shaderFormat);
+ if (it == shaderFormatMap.cend())
+ throw std::range_error("no shader format string found");
+
+ return fmt::formatter<string_view>::format(it->second, ctx);
+ }
+
+private:
+ static constexpr auto shaderFormatMap = make_map<EShaderFormat, std::string_view>({
+ {SHADER_NONE, "none"},
+ {SHADER_YV12, "YV12"},
+ {SHADER_YV12_9, "YV12 9bit"},
+ {SHADER_YV12_10, "YV12 10bit"},
+ {SHADER_YV12_12, "YV12 12bit"},
+ {SHADER_YV12_14, "YV12 14bit"},
+ {SHADER_YV12_16, "YV12 16bit"},
+ {SHADER_NV12, "NV12"},
+ {SHADER_YUY2, "YUY2"},
+ {SHADER_UYVY, "UYVY"},
+ {SHADER_NV12_RRG, "NV12 red/red/green"},
+ });
+
+ static_assert(SHADER_MAX == shaderFormatMap.size(),
+ "shaderFormatMap doesn't match the size of EShaderFormat, did you forget to "
+ "add/remove a mapping?");
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.cpp
new file mode 100644
index 0000000..0bfa324
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2007 d4rk
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoFilterShaderGL.h"
+
+#include "ConvolutionKernels.h"
+#include "ServiceBroker.h"
+#include "rendering/RenderSystem.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <math.h>
+#include <string>
+
+#define TEXTARGET GL_TEXTURE_1D
+
+using namespace Shaders::GL;
+
+//////////////////////////////////////////////////////////////////////
+// BaseVideoFilterShader - base class for video filter shaders
+//////////////////////////////////////////////////////////////////////
+
+BaseVideoFilterShader::BaseVideoFilterShader()
+{
+ m_width = 1;
+ m_height = 1;
+ m_stepX = 0;
+ m_stepY = 0;
+ m_stretch = 0.0f;
+
+ VertexShader()->LoadSource("gl_videofilter_vertex.glsl");
+
+ PixelShader()->LoadSource("gl_videofilter_frag.glsl");
+}
+
+BaseVideoFilterShader::~BaseVideoFilterShader()
+{
+ Free();
+}
+
+//////////////////////////////////////////////////////////////////////
+// ConvolutionFilterShader - base class for video filter shaders
+//////////////////////////////////////////////////////////////////////
+
+ConvolutionFilterShader::ConvolutionFilterShader(ESCALINGMETHOD method, bool stretch, GLSLOutput *output)
+{
+ m_method = method;
+
+ std::string shadername;
+ std::string defines;
+
+ m_floattex = CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_texture_float");
+
+ if (m_method == VS_SCALINGMETHOD_CUBIC_B_SPLINE ||
+ m_method == VS_SCALINGMETHOD_CUBIC_MITCHELL ||
+ m_method == VS_SCALINGMETHOD_CUBIC_CATMULL ||
+ m_method == VS_SCALINGMETHOD_CUBIC_0_075 ||
+ m_method == VS_SCALINGMETHOD_CUBIC_0_1 ||
+ m_method == VS_SCALINGMETHOD_LANCZOS2 ||
+ m_method == VS_SCALINGMETHOD_SPLINE36_FAST ||
+ m_method == VS_SCALINGMETHOD_LANCZOS3_FAST)
+ {
+ shadername = "gl_convolution-4x4.glsl";
+
+ if (m_floattex)
+ m_internalformat = GL_RGBA16F;
+ else
+ m_internalformat = GL_RGBA;
+ }
+ else if (m_method == VS_SCALINGMETHOD_SPLINE36 ||
+ m_method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ shadername = "gl_convolution-6x6.glsl";
+
+ if (m_floattex)
+ m_internalformat = GL_RGB16F;
+ else
+ m_internalformat = GL_RGB;
+ }
+
+ if (m_floattex)
+ defines = "#define HAS_FLOAT_TEXTURE\n";
+
+ //don't compile in stretch support when it's not needed
+ if (stretch)
+ defines += "#define XBMC_STRETCH 1\n";
+ else
+ defines += "#define XBMC_STRETCH 0\n";
+
+ // get defines from the output stage if used
+ m_glslOutput = output;
+ if (m_glslOutput) {
+ defines += m_glslOutput->GetDefines();
+ }
+
+ CLog::Log(LOGDEBUG, "GL: using scaling method: {}", m_method);
+ CLog::Log(LOGDEBUG, "GL: using shader: {}", shadername);
+
+ PixelShader()->LoadSource(shadername, defines);
+ PixelShader()->AppendSource("gl_output.glsl");
+}
+
+ConvolutionFilterShader::~ConvolutionFilterShader()
+{
+ Free();
+ delete m_glslOutput;
+}
+
+void ConvolutionFilterShader::OnCompiledAndLinked()
+{
+ // obtain shader attribute handles on successful compilation
+ m_hSourceTex = glGetUniformLocation(ProgramHandle(), "img");
+ m_hStepXY = glGetUniformLocation(ProgramHandle(), "stepxy");
+ m_hKernTex = glGetUniformLocation(ProgramHandle(), "kernelTex");
+ m_hStretch = glGetUniformLocation(ProgramHandle(), "m_stretch");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hCoord = glGetAttribLocation(ProgramHandle(), "m_attrcord");
+
+ CConvolutionKernel kernel(m_method, 256);
+
+ if (m_kernelTex1)
+ {
+ glDeleteTextures(1, &m_kernelTex1);
+ m_kernelTex1 = 0;
+ }
+
+ glGenTextures(1, &m_kernelTex1);
+
+ if ((m_kernelTex1<=0))
+ {
+ CLog::Log(LOGERROR, "GL: ConvolutionFilterShader: Error creating kernel texture");
+ return;
+ }
+
+ //make a kernel texture on GL_TEXTURE2 and set clamping and interpolation
+ //TEXTARGET is set to GL_TEXTURE_1D or GL_TEXTURE_2D
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(TEXTARGET, m_kernelTex1);
+ glTexParameteri(TEXTARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(TEXTARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(TEXTARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(TEXTARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //if float textures are supported, we can load the kernel as a float texture
+ //if not we load it as 8 bit unsigned which gets converted back to float in the shader
+ GLenum format;
+ GLvoid* data;
+ if (m_floattex)
+ {
+ format = GL_FLOAT;
+ data = (GLvoid*)kernel.GetFloatPixels();
+ }
+ else
+ {
+ format = GL_UNSIGNED_BYTE;
+ data = (GLvoid*)kernel.GetUint8Pixels();
+ }
+
+ glTexImage1D(TEXTARGET, 0, m_internalformat, kernel.GetSize(), 0, GL_RGBA, format, data);
+
+ glActiveTexture(GL_TEXTURE0);
+
+ VerifyGLState();
+
+ if (m_glslOutput)
+ m_glslOutput->OnCompiledAndLinked(ProgramHandle());
+}
+
+bool ConvolutionFilterShader::OnEnabled()
+{
+ // set shader attributes once enabled
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(TEXTARGET, m_kernelTex1);
+
+ glActiveTexture(GL_TEXTURE0);
+ glUniform1i(m_hSourceTex, m_sourceTexUnit);
+ glUniform1i(m_hKernTex, 2);
+ glUniform2f(m_hStepXY, m_stepX, m_stepY);
+ glUniform1f(m_hStretch, m_stretch);
+ glUniform1f(m_hAlpha, m_alpha);
+
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+
+ VerifyGLState();
+ if (m_glslOutput) m_glslOutput->OnEnabled();
+ return true;
+}
+
+void ConvolutionFilterShader::OnDisabled()
+{
+ if (m_glslOutput) m_glslOutput->OnDisabled();
+}
+
+void ConvolutionFilterShader::Free()
+{
+ if (m_kernelTex1)
+ glDeleteTextures(1, &m_kernelTex1);
+ m_kernelTex1 = 0;
+ if (m_glslOutput) m_glslOutput->Free();
+ BaseVideoFilterShader::Free();
+}
+
+//////////////////////////////////////////////////////////////////////
+// StretchFilterShader - base class for video filter shaders
+//////////////////////////////////////////////////////////////////////
+
+StretchFilterShader::StretchFilterShader()
+{
+ PixelShader()->LoadSource("gl_stretch.glsl");
+}
+
+void StretchFilterShader::OnCompiledAndLinked()
+{
+ m_hSourceTex = glGetUniformLocation(ProgramHandle(), "img");
+ m_hStretch = glGetUniformLocation(ProgramHandle(), "m_stretch");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hCoord = glGetAttribLocation(ProgramHandle(), "m_attrcord");
+}
+
+bool StretchFilterShader::OnEnabled()
+{
+ glUniform1i(m_hSourceTex, m_sourceTexUnit);
+ glUniform1f(m_hStretch, m_stretch);
+ glUniform1f(m_hAlpha, m_alpha);
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+ VerifyGLState();
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////
+// DefaultFilterShader - base class for video filter shaders
+//////////////////////////////////////////////////////////////////////
+
+void DefaultFilterShader::OnCompiledAndLinked()
+{
+ m_hSourceTex = glGetUniformLocation(ProgramHandle(), "img");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hCoord = glGetAttribLocation(ProgramHandle(), "m_attrcord");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+}
+
+bool DefaultFilterShader::OnEnabled()
+{
+ glUniform1i(m_hSourceTex, m_sourceTexUnit);
+ glUniform1f(m_hAlpha, m_alpha);
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+ VerifyGLState();
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.h
new file mode 100644
index 0000000..b23d4e2
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGL.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GLSLOutput.h"
+#include "cores/VideoSettings.h"
+#include "guilib/Shader.h"
+
+#include "system_gl.h"
+
+namespace Shaders {
+
+namespace GL
+{
+
+class BaseVideoFilterShader : public CGLSLShaderProgram
+{
+public:
+ BaseVideoFilterShader();
+ ~BaseVideoFilterShader() override;
+ virtual bool GetTextureFilter(GLint& filter) { return false; }
+
+ void SetSourceTexture(GLint ytex) { m_sourceTexUnit = ytex; }
+ void SetWidth(int w)
+ {
+ m_width = w;
+ m_stepX = w > 0 ? 1.0f / w : 0;
+ }
+ void SetHeight(int h)
+ {
+ m_height = h;
+ m_stepY = h > 0 ? 1.0f / h : 0;
+ }
+ void SetNonLinStretch(float stretch) { m_stretch = stretch; }
+ void SetAlpha(GLfloat alpha) { m_alpha = alpha; }
+
+ GLint GetVertexLoc() { return m_hVertex; }
+ GLint GetCoordLoc() { return m_hCoord; }
+
+ void SetMatrices(const GLfloat* p, const GLfloat* m)
+ {
+ m_proj = p;
+ m_model = m;
+ }
+
+protected:
+ int m_width;
+ int m_height;
+ float m_stepX;
+ float m_stepY;
+ float m_stretch;
+ GLfloat m_alpha;
+ GLint m_sourceTexUnit = 0;
+ const GLfloat* m_proj = nullptr;
+ const GLfloat* m_model = nullptr;
+
+ // shader attribute handles
+ GLint m_hSourceTex = 0;
+ GLint m_hStepXY = 0;
+ GLint m_hStretch = -1;
+ GLint m_hAlpha = -1;
+ GLint m_hVertex = -1;
+ GLint m_hCoord = -1;
+ GLint m_hProj = -1;
+ GLint m_hModel = -1;
+ };
+
+ class ConvolutionFilterShader : public BaseVideoFilterShader
+ {
+ public:
+ ConvolutionFilterShader(ESCALINGMETHOD method, bool stretch, GLSLOutput *output=NULL);
+ ~ConvolutionFilterShader() override;
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ void OnDisabled() override;
+ void Free();
+
+ bool GetTextureFilter(GLint& filter) override { filter = GL_NEAREST; return true; }
+
+ protected:
+ // kernel textures
+ GLuint m_kernelTex1 = 0;
+
+ // shader handles to kernel textures
+ GLint m_hKernTex;
+
+ ESCALINGMETHOD m_method;
+ bool m_floattex; //if float textures are supported
+ GLint m_internalformat;
+
+ GLSLOutput* m_glslOutput;
+ };
+
+ class StretchFilterShader : public BaseVideoFilterShader
+ {
+ public:
+ StretchFilterShader();
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ };
+
+ class DefaultFilterShader : public BaseVideoFilterShader
+ {
+ public:
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ };
+
+ } // namespace GL
+} // end namespace
+
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.cpp
new file mode 100644
index 0000000..4f6ffc0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2007 d4rk
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoFilterShaderGLES.h"
+
+#include "ConvolutionKernels.h"
+#include "ServiceBroker.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <math.h>
+#include <string>
+
+using namespace Shaders::GLES;
+
+//////////////////////////////////////////////////////////////////////
+// BaseVideoFilterShader - base class for video filter shaders
+//////////////////////////////////////////////////////////////////////
+
+BaseVideoFilterShader::BaseVideoFilterShader()
+{
+ m_width = 1;
+ m_height = 1;
+ m_stepX = 0;
+ m_stepY = 0;
+
+ m_proj = nullptr;
+ m_model = nullptr;
+
+ VertexShader()->LoadSource("gles_videofilter.vert");
+
+ PixelShader()->LoadSource("gles_videofilter.frag");
+}
+
+void BaseVideoFilterShader::OnCompiledAndLinked()
+{
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hcoord = glGetAttribLocation(ProgramHandle(), "m_attrcord");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+}
+
+bool BaseVideoFilterShader::OnEnabled()
+{
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+ glUniform1f(m_hAlpha, m_alpha);
+ return true;
+}
+
+ConvolutionFilterShader::ConvolutionFilterShader(ESCALINGMETHOD method)
+{
+ m_method = method;
+
+ std::string shadername;
+ std::string defines;
+
+ if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_color_buffer_float"))
+ {
+ m_floattex = true;
+ }
+ else
+ {
+ m_floattex = false;
+ }
+
+ if (m_method == VS_SCALINGMETHOD_CUBIC_B_SPLINE ||
+ m_method == VS_SCALINGMETHOD_CUBIC_MITCHELL ||
+ m_method == VS_SCALINGMETHOD_CUBIC_CATMULL ||
+ m_method == VS_SCALINGMETHOD_CUBIC_0_075 ||
+ m_method == VS_SCALINGMETHOD_CUBIC_0_1 ||
+ m_method == VS_SCALINGMETHOD_LANCZOS2 ||
+ m_method == VS_SCALINGMETHOD_SPLINE36_FAST ||
+ m_method == VS_SCALINGMETHOD_LANCZOS3_FAST)
+ {
+ shadername = "gles_convolution-4x4.frag";
+ }
+ else if (m_method == VS_SCALINGMETHOD_SPLINE36 ||
+ m_method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ shadername = "gles_convolution-6x6.frag";
+ }
+
+ if (m_floattex)
+ {
+ m_internalformat = GL_RGBA16F_EXT;
+ defines = "#define HAS_FLOAT_TEXTURE\n";
+ }
+ else
+ {
+ m_internalformat = GL_RGBA;
+ }
+
+ CLog::Log(LOGDEBUG, "GLES: using scaling method: {}", m_method);
+ CLog::Log(LOGDEBUG, "GLES: using shader: {}", shadername);
+
+ PixelShader()->LoadSource(shadername, defines);
+}
+
+ConvolutionFilterShader::~ConvolutionFilterShader()
+{
+ Free();
+}
+
+void ConvolutionFilterShader::OnCompiledAndLinked()
+{
+ BaseVideoFilterShader::OnCompiledAndLinked();
+
+ // obtain shader attribute handles on successful compilation
+ m_hSourceTex = glGetUniformLocation(ProgramHandle(), "img");
+ m_hStepXY = glGetUniformLocation(ProgramHandle(), "stepxy");
+ m_hKernTex = glGetUniformLocation(ProgramHandle(), "kernelTex");
+
+ CConvolutionKernel kernel(m_method, 256);
+
+ if (m_kernelTex1)
+ {
+ glDeleteTextures(1, &m_kernelTex1);
+ m_kernelTex1 = 0;
+ }
+
+ glGenTextures(1, &m_kernelTex1);
+
+ if ((m_kernelTex1<=0))
+ {
+ CLog::Log(LOGERROR, "GL: ConvolutionFilterShader: Error creating kernel texture");
+ return;
+ }
+
+ //make a kernel texture on GL_TEXTURE2 and set clamping and interpolation
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, m_kernelTex1);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //if float textures are supported, we can load the kernel as a float texture
+ //if not we load it as 8 bit unsigned which gets converted back to float in the shader
+ GLenum format;
+ GLvoid* data;
+ if (m_floattex)
+ {
+ format = GL_FLOAT;
+ data = (GLvoid*)kernel.GetFloatPixels();
+ }
+ else
+ {
+ format = GL_UNSIGNED_BYTE;
+ data = (GLvoid*)kernel.GetUint8Pixels();
+ }
+
+ //upload as 2D texture with height of 1
+ glTexImage2D(GL_TEXTURE_2D, 0, m_internalformat, kernel.GetSize(), 1, 0, GL_RGBA, format, data);
+
+ glActiveTexture(GL_TEXTURE0);
+
+ VerifyGLState();
+}
+
+bool ConvolutionFilterShader::OnEnabled()
+{
+ BaseVideoFilterShader::OnEnabled();
+
+ // set shader attributes once enabled
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, m_kernelTex1);
+
+ glActiveTexture(GL_TEXTURE0);
+ glUniform1i(m_hSourceTex, m_sourceTexUnit);
+ glUniform1i(m_hKernTex, 2);
+ glUniform2f(m_hStepXY, m_stepX, m_stepY);
+ VerifyGLState();
+
+ return true;
+}
+
+void ConvolutionFilterShader::OnDisabled()
+{
+}
+
+void ConvolutionFilterShader::Free()
+{
+ if (m_kernelTex1)
+ glDeleteTextures(1, &m_kernelTex1);
+ m_kernelTex1 = 0;
+ BaseVideoFilterShader::Free();
+}
+
+void DefaultFilterShader::OnCompiledAndLinked()
+{
+ BaseVideoFilterShader::OnCompiledAndLinked();
+
+ m_hSourceTex = glGetUniformLocation(ProgramHandle(), "img");
+}
+
+bool DefaultFilterShader::OnEnabled()
+{
+ BaseVideoFilterShader::OnEnabled();
+
+ glUniform1i(m_hSourceTex, m_sourceTexUnit);
+ VerifyGLState();
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.h
new file mode 100644
index 0000000..8cb424b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/VideoFilterShaderGLES.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoSettings.h"
+#include "guilib/Shader.h"
+
+#include "system_gl.h"
+
+namespace Shaders {
+
+namespace GLES
+{
+class BaseVideoFilterShader : public CGLSLShaderProgram
+{
+public:
+ BaseVideoFilterShader();
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ virtual void SetSourceTexture(GLint ytex) { m_sourceTexUnit = ytex; }
+ virtual void SetWidth(int w)
+ {
+ m_width = w;
+ m_stepX = w > 0 ? 1.0f / w : 0;
+ }
+ virtual void SetHeight(int h)
+ {
+ m_height = h;
+ m_stepY = h > 0 ? 1.0f / h : 0;
+ }
+ virtual bool GetTextureFilter(GLint& filter) { return false; }
+ virtual GLint GetVertexLoc() { return m_hVertex; }
+ virtual GLint GetcoordLoc() { return m_hcoord; }
+ virtual void SetMatrices(const GLfloat* p, const GLfloat* m)
+ {
+ m_proj = p;
+ m_model = m;
+ }
+ virtual void SetAlpha(GLfloat alpha) { m_alpha = alpha; }
+
+protected:
+ int m_width;
+ int m_height;
+ float m_stepX;
+ float m_stepY;
+ GLint m_sourceTexUnit = 0;
+
+ // shader attribute handles
+ GLint m_hSourceTex = 0;
+ GLint m_hStepXY = 0;
+
+ GLint m_hVertex = -1;
+ GLint m_hcoord = -1;
+ GLint m_hProj = -1;
+ GLint m_hModel = -1;
+ GLint m_hAlpha = -1;
+
+ const GLfloat* m_proj;
+ const GLfloat* m_model;
+ GLfloat m_alpha = -1;
+ };
+
+ class ConvolutionFilterShader : public BaseVideoFilterShader
+ {
+ public:
+ ConvolutionFilterShader(ESCALINGMETHOD method);
+ ~ConvolutionFilterShader() override;
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ void OnDisabled() override;
+ void Free();
+
+ bool GetTextureFilter(GLint& filter) override { filter = GL_NEAREST; return true; }
+
+ protected:
+ // kernel textures
+ GLuint m_kernelTex1 = 0;
+
+ // shader handles to kernel textures
+ GLint m_hKernTex = -1;
+
+ ESCALINGMETHOD m_method;
+ bool m_floattex; //if float textures are supported
+ GLint m_internalformat;
+ };
+
+ class DefaultFilterShader : public BaseVideoFilterShader
+ {
+ public:
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ };
+
+ } // namespace GLES
+} // end namespace
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp
new file mode 100644
index 0000000..511ed51
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp
@@ -0,0 +1,1228 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinVideoFilter.h"
+
+#include "ConvolutionKernels.h"
+#include "Util.h"
+#include "VideoRenderers/windows/RendererBase.h"
+#include "cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h"
+#include "filesystem/File.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include "platform/win32/WIN32Util.h"
+
+#include <map>
+
+#include <DirectXPackedVector.h>
+
+using namespace DirectX::PackedVector;
+using namespace Microsoft::WRL;
+
+//===================================================================
+
+bool CWinShader::CreateVertexBuffer(unsigned int count, unsigned int size)
+{
+ if (!m_vb.Create(D3D11_BIND_VERTEX_BUFFER, count, size, DXGI_FORMAT_UNKNOWN, D3D11_USAGE_DYNAMIC))
+ return false;
+
+ uint16_t id[4] = { 3, 0, 2, 1 };
+ if (!m_ib.Create(D3D11_BIND_INDEX_BUFFER, ARRAYSIZE(id), sizeof(uint16_t), DXGI_FORMAT_R16_UINT, D3D11_USAGE_IMMUTABLE, id))
+ return false;
+
+ m_vbsize = count * size;
+ m_vertsize = size;
+
+ return true;
+}
+
+bool CWinShader::CreateInputLayout(D3D11_INPUT_ELEMENT_DESC *layout, unsigned numElements)
+{
+ D3DX11_PASS_DESC desc = {};
+ if (FAILED(m_effect.Get()->GetTechniqueByIndex(0)->GetPassByIndex(0)->GetDesc(&desc)))
+ {
+ CLog::LogF(LOGERROR, "Failed to get first pass description.");
+ return false;
+ }
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ return SUCCEEDED(pDevice->CreateInputLayout(layout, numElements, desc.pIAInputSignature, desc.IAInputSignatureSize, &m_inputLayout));
+}
+
+void CWinShader::SetTarget(CD3DTexture* target)
+{
+ m_target = target;
+ if (m_target)
+ DX::DeviceResources::Get()->GetD3DContext()->OMSetRenderTargets(1, target->GetAddressOfRTV(), nullptr);
+}
+
+bool CWinShader::LockVertexBuffer(void **data)
+{
+ if (!m_vb.Map(data))
+ {
+ CLog::LogF(LOGERROR, "failed to lock vertex buffer");
+ return false;
+ }
+ return true;
+}
+
+bool CWinShader::UnlockVertexBuffer()
+{
+ if (!m_vb.Unmap())
+ {
+ CLog::LogF(LOGERROR, "failed to unlock vertex buffer");
+ return false;
+ }
+ return true;
+}
+
+bool CWinShader::LoadEffect(const std::string& filename, DefinesMap* defines)
+{
+ CLog::LogF(LOGDEBUG, "loading shader {}", filename);
+
+ XFILE::CFileStream file;
+ if(!file.Open(filename))
+ {
+ CLog::LogF(LOGERROR, "failed to open file {}", filename);
+ return false;
+ }
+
+ std::string pStrEffect;
+ getline(file, pStrEffect, '\0');
+
+ if (!m_effect.Create(pStrEffect, defines))
+ {
+ CLog::LogF(LOGERROR, "{} failed", pStrEffect);
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinShader::Execute(const std::vector<CD3DTexture*> &targets, unsigned int vertexIndexStep)
+{
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
+ ComPtr<ID3D11RenderTargetView> oldRT;
+
+ // The render target will be overridden: save the caller's original RT
+ if (!targets.empty())
+ pContext->OMGetRenderTargets(1, &oldRT, nullptr);
+
+ unsigned cPasses;
+ if (!m_effect.Begin(&cPasses, 0))
+ {
+ CLog::LogF(LOGERROR, "failed to begin d3d effect");
+ return false;
+ }
+
+ ID3D11Buffer* vertexBuffer = m_vb.Get();
+ ID3D11Buffer* indexBuffer = m_ib.Get();
+ unsigned int stride = m_vb.GetStride();
+ unsigned int offset = 0;
+ pContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
+ pContext->IASetIndexBuffer(indexBuffer, m_ib.GetFormat(), 0);
+ pContext->IASetInputLayout(m_inputLayout.Get());
+ pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
+
+ for (unsigned iPass = 0; iPass < cPasses; iPass++)
+ {
+ SetTarget(targets.size() > iPass ? targets.at(iPass) : nullptr);
+ SetStepParams(iPass);
+
+ if (!m_effect.BeginPass(iPass))
+ {
+ CLog::LogF(LOGERROR, "failed to begin d3d effect pass");
+ break;
+ }
+
+ pContext->DrawIndexed(4, 0, iPass * vertexIndexStep);
+
+ if (!m_effect.EndPass())
+ CLog::LogF(LOGERROR, "failed to end d3d effect pass");
+
+ CD3DHelper::PSClearShaderResources(pContext);
+ }
+ if (!m_effect.End())
+ CLog::LogF(LOGERROR, "failed to end d3d effect");
+
+ if (oldRT)
+ pContext->OMSetRenderTargets(1, oldRT.GetAddressOf(), nullptr);
+
+ return true;
+}
+
+//==================================================================================
+
+void COutputShader::ApplyEffectParameters(CD3DEffect &effect, unsigned sourceWidth, unsigned sourceHeight)
+{
+ if (m_useLut && HasLUT())
+ {
+ float lut_params[2] = {(m_lutSize - 1) / static_cast<float>(m_lutSize),
+ 0.5f / static_cast<float>(m_lutSize)};
+ effect.SetResources("m_LUT", m_pLUTView.GetAddressOf(), 1);
+ effect.SetFloatArray("m_LUTParams", lut_params, 2);
+ }
+ if (m_useDithering)
+ {
+ float ditherParams[3] =
+ {
+ static_cast<float>(sourceWidth) / dither_size,
+ static_cast<float>(sourceHeight) / dither_size,
+ static_cast<float>(1 << m_ditherDepth) - 1.0f
+ };
+ effect.SetResources("m_ditherMatrix", m_pDitherView.GetAddressOf(), 1);
+ effect.SetFloatArray("m_ditherParams", ditherParams, 3);
+ }
+ if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_REINHARD)
+ {
+ const float def_param = log10(100.0f) / log10(600.0f); // 600 nits --> 0.72
+ float param = def_param;
+
+ if (m_hasLightMetadata)
+ param = static_cast<float>(log10(100) / log10(m_lightMetadata.MaxCLL));
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance)
+ param = static_cast<float>(log10(100) / log10(m_displayMetadata.max_luminance.num /
+ m_displayMetadata.max_luminance.den));
+
+ // Sanity check
+ if (param < 0.1f || param > 5.0f)
+ param = def_param;
+
+ param *= m_toneMappingParam;
+
+ Matrix3x1 coefs = CConvertMatrix::GetRGBYuvCoefs(AVCOL_SPC_BT709);
+
+ effect.SetScalar("g_toneP1", param);
+ effect.SetFloatArray("g_coefsDst", coefs.data(), coefs.size());
+ m_toneMappingDebug = param;
+ }
+ else if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_ACES)
+ {
+ float lumin = GetLuminanceValue();
+ effect.SetScalar("g_luminance", lumin);
+ effect.SetScalar("g_toneP1", m_toneMappingParam);
+ m_toneMappingDebug = lumin;
+ }
+ else if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_HABLE)
+ {
+ float lumin = GetLuminanceValue();
+ float lumin_factor = (10000.0f / lumin) * (2.0f / m_toneMappingParam);
+ float lumin_div100 = lumin / (100.0f * m_toneMappingParam);
+ effect.SetScalar("g_toneP1", lumin_factor);
+ effect.SetScalar("g_toneP2", lumin_div100);
+ m_toneMappingDebug = lumin;
+ }
+}
+
+float COutputShader::GetLuminanceValue() const
+{
+ float lum1 = 400.0f; // default for bad quality HDR-PQ sources (with no metadata)
+ float lum2 = lum1;
+ float lum3 = lum1;
+
+ if (m_hasLightMetadata)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ if (m_lightMetadata.MaxCLL >= lum)
+ {
+ lum1 = static_cast<float>(lum);
+ lum2 = static_cast<float>(m_lightMetadata.MaxCLL);
+ }
+ else
+ {
+ lum1 = static_cast<float>(m_lightMetadata.MaxCLL);
+ lum2 = static_cast<float>(lum);
+ }
+ lum3 = static_cast<float>(m_lightMetadata.MaxFALL);
+ lum1 = (lum1 * 0.5f) + (lum2 * 0.2f) + (lum3 * 0.3f);
+ }
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ lum1 = static_cast<float>(lum);
+ }
+
+ return lum1;
+}
+
+void COutputShader::GetDefines(DefinesMap& map) const
+{
+ if (m_useLut)
+ {
+ map["KODI_3DLUT"] = "";
+ }
+ if (m_useDithering)
+ {
+ map["KODI_DITHER"] = "";
+ }
+ if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_REINHARD)
+ {
+ map["KODI_TONE_MAPPING_REINHARD"] = "";
+ }
+ else if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_ACES)
+ {
+ map["KODI_TONE_MAPPING_ACES"] = "";
+ }
+ else if (m_toneMapping && m_toneMappingMethod == VS_TONEMAPMETHOD_HABLE)
+ {
+ map["KODI_TONE_MAPPING_HABLE"] = "";
+ }
+ if (m_useHLGtoPQ)
+ {
+ map["KODI_HLG_TO_PQ"] = "";
+ }
+}
+
+bool COutputShader::Create(bool useLUT,
+ bool useDithering,
+ int ditherDepth,
+ bool toneMapping,
+ ETONEMAPMETHOD toneMethod,
+ bool HLGtoPQ)
+{
+ m_useLut = useLUT;
+ m_ditherDepth = ditherDepth;
+ m_toneMapping = toneMapping;
+ m_useHLGtoPQ = HLGtoPQ;
+ m_toneMappingMethod = toneMethod;
+
+ CWinShader::CreateVertexBuffer(4, sizeof(Vertex));
+
+ if (useDithering)
+ CreateDitherView();
+
+ DefinesMap defines;
+ defines["KODI_OUTPUT_T"] = "";
+ GetDefines(defines);
+
+ std::string effectString("special://xbmc/system/shaders/output_d3d.fx");
+
+ if (!LoadEffect(effectString, &defines))
+ {
+ CLog::LogF(LOGERROR, "Failed to load shader {}.", effectString);
+ return false;
+ }
+
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+ return CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout));
+}
+
+void COutputShader::Render(CD3DTexture& sourceTexture, CRect sourceRect, const CPoint points[4]
+ , CD3DTexture& target, unsigned range, float contrast, float brightness)
+{
+ PrepareParameters(sourceTexture.GetWidth(), sourceTexture.GetHeight(), sourceRect, points);
+ SetShaderParameters(sourceTexture, range, contrast, brightness);
+ Execute({ &target }, 4);
+}
+
+void COutputShader::Render(CD3DTexture& sourceTexture, CRect sourceRect, CRect destRect
+ , CD3DTexture& target, unsigned range, float contrast, float brightness)
+{
+ CPoint points[] =
+ {
+ { destRect.x1, destRect.y1 },
+ { destRect.x2, destRect.y1 },
+ { destRect.x2, destRect.y2 },
+ { destRect.x1, destRect.y2 },
+ };
+ Render(sourceTexture, sourceRect, points, target, range, contrast, brightness);
+}
+
+void COutputShader::SetLUT(int lutSize, ID3D11ShaderResourceView* pLUTView)
+{
+ m_lutSize = lutSize;
+ m_pLUTView = pLUTView;
+}
+
+void COutputShader::SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata, bool hasLightMetadata, AVContentLightMetadata lightMetadata)
+{
+ m_hasDisplayMetadata = hasDisplayMetadata;
+ m_displayMetadata = displayMetadata;
+ m_hasLightMetadata = hasLightMetadata;
+ m_lightMetadata = lightMetadata;
+}
+
+void COutputShader::SetToneMapParam(ETONEMAPMETHOD method, float param)
+{
+ m_toneMappingMethod = method;
+ m_toneMappingParam = param;
+}
+
+bool COutputShader::CreateLUTView(int lutSize, uint16_t* lutData, bool isRGB, ID3D11ShaderResourceView** ppLUTView)
+{
+ if (!lutSize || !lutData)
+ return false;
+
+ uint16_t* cData;
+ if (isRGB)
+ {
+ // repack data to RGBA
+ const unsigned samples = lutSize * lutSize * lutSize;
+ cData = reinterpret_cast<uint16_t*>(KODI::MEMORY::AlignedMalloc(samples * sizeof(uint16_t) * 4, 16));
+ auto rgba = static_cast<uint16_t*>(cData);
+ for (unsigned i = 0; i < samples - 1; ++i, rgba += 4, lutData += 3)
+ {
+ *reinterpret_cast<uint64_t*>(rgba) = *reinterpret_cast<uint64_t*>(lutData);
+ }
+ // and last one
+ rgba[0] = lutData[0]; rgba[1] = lutData[1]; rgba[2] = lutData[2]; rgba[3] = 0xFFFF;
+ }
+ else
+ cData = lutData;
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+ CD3D11_TEXTURE3D_DESC txDesc(DXGI_FORMAT_R16G16B16A16_UNORM, lutSize, lutSize, lutSize, 1,
+ D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_IMMUTABLE);
+
+ ComPtr<ID3D11Texture3D> pLUTTex;
+ D3D11_SUBRESOURCE_DATA texData;
+ texData.pSysMem = cData;
+ texData.SysMemPitch = lutSize * sizeof(uint16_t) * 4;
+ texData.SysMemSlicePitch = texData.SysMemPitch * lutSize;
+
+ HRESULT hr = pDevice->CreateTexture3D(&txDesc, &texData, pLUTTex.GetAddressOf());
+ if (isRGB)
+ KODI::MEMORY::AlignedFree(cData);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create 3dlut texture cube.");
+ return false;
+ }
+
+ ComPtr<ID3D11ShaderResourceView> lutView;
+ CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE3D, DXGI_FORMAT_R16G16B16A16_UNORM, 0, 1);
+ hr = pDevice->CreateShaderResourceView(pLUTTex.Get(), &srvDesc, &lutView);
+ pContext->Flush();
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create view for 3dlut texture cube.");
+ return false;
+ }
+
+ *ppLUTView = lutView.Detach();
+ return true;
+}
+
+void COutputShader::PrepareParameters(unsigned sourceWidth, unsigned sourceHeight, CRect sourceRect, const CPoint points[4])
+{
+ bool changed = false;
+ for(int i= 0; i < 4 && !changed; ++i)
+ changed = points[i] != m_destPoints[i];
+
+ if (m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight
+ || m_sourceRect != sourceRect || changed)
+ {
+ m_sourceWidth = sourceWidth;
+ m_sourceHeight = sourceHeight;
+ m_sourceRect = sourceRect;
+ for (int i = 0; i < 4; ++i)
+ m_destPoints[i] = points[i];
+
+ Vertex* v;
+ CWinShader::LockVertexBuffer(reinterpret_cast<void**>(&v));
+
+ v[0].x = m_destPoints[0].x;
+ v[0].y = m_destPoints[0].y;
+ v[0].z = 0;
+ v[0].tu = m_sourceRect.x1 / m_sourceWidth;
+ v[0].tv = m_sourceRect.y1 / m_sourceHeight;
+
+ v[1].x = m_destPoints[1].x;
+ v[1].y = m_destPoints[1].y;
+ v[1].z = 0;
+ v[1].tu = m_sourceRect.x2 / m_sourceWidth;
+ v[1].tv = m_sourceRect.y1 / m_sourceHeight;
+
+ v[2].x = m_destPoints[2].x;
+ v[2].y = m_destPoints[2].y;
+ v[2].z = 0;
+ v[2].tu = m_sourceRect.x2 / m_sourceWidth;
+ v[2].tv = m_sourceRect.y2 / m_sourceHeight;
+
+ v[3].x = m_destPoints[3].x;
+ v[3].y = m_destPoints[3].y;
+ v[3].z = 0;
+ v[3].tu = m_sourceRect.x1 / m_sourceWidth;
+ v[3].tv = m_sourceRect.y2 / m_sourceHeight;
+
+ CWinShader::UnlockVertexBuffer();
+ }
+}
+
+void COutputShader::SetShaderParameters(CD3DTexture& sourceTexture, unsigned range, float contrast, float brightness)
+{
+ m_effect.SetTechnique("OUTPUT_T");
+ m_effect.SetResources("g_Texture", sourceTexture.GetAddressOfSRV(), 1);
+
+ unsigned numPorts = 1;
+ D3D11_VIEWPORT viewPort;
+ DX::DeviceResources::Get()->GetD3DContext()->RSGetViewports(&numPorts, &viewPort);
+ m_effect.SetFloatArray("g_viewPort", &viewPort.Width, 2);
+
+ float params[3] = { static_cast<float>(range), contrast, brightness };
+ m_effect.SetFloatArray("m_params", params, 3);
+
+ ApplyEffectParameters(m_effect, sourceTexture.GetWidth(), sourceTexture.GetHeight());
+}
+
+void COutputShader::CreateDitherView()
+{
+ ID3D11Device* pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ CD3D11_TEXTURE2D_DESC txDesc(DXGI_FORMAT_R16_UNORM, dither_size, dither_size, 1, 1);
+ D3D11_SUBRESOURCE_DATA resData;
+ resData.pSysMem = dither_matrix;
+ resData.SysMemPitch = dither_size * sizeof(uint16_t);
+ resData.SysMemSlicePitch = resData.SysMemPitch * dither_size;
+
+ ComPtr<ID3D11Texture2D> pDitherTex;
+ HRESULT hr = pDevice->CreateTexture2D(&txDesc, &resData, &pDitherTex);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create dither texture cube.");
+ m_useDithering = false;
+ return;
+ }
+
+ CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R16_UNORM, 0, 1);
+ hr = pDevice->CreateShaderResourceView(pDitherTex.Get(), &srvDesc, &m_pDitherView);
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create view for dither texture cube.");
+ m_useDithering = false;
+ return;
+ }
+ m_useDithering = true;
+}
+
+std::string COutputShader::GetDebugInfo()
+{
+ std::string tone = "OFF";
+ std::string hlg = "OFF";
+ std::string lut = "OFF";
+ std::string dither = "OFF";
+
+ if (m_toneMapping)
+ {
+ std::string method;
+ switch (m_toneMappingMethod)
+ {
+ case VS_TONEMAPMETHOD_REINHARD:
+ method = "Reinhard";
+ break;
+ case VS_TONEMAPMETHOD_ACES:
+ method = "ACES";
+ break;
+ case VS_TONEMAPMETHOD_HABLE:
+ method = "Hable";
+ break;
+ }
+ tone = StringUtils::Format("ON ({}, {:.2f}, {:.2f}{})", method, m_toneMappingParam,
+ m_toneMappingDebug, (m_toneMappingMethod == 1) ? "" : " nits");
+ }
+
+ if (m_useHLGtoPQ)
+ hlg = "ON (peak 1000 nits)";
+
+ if (m_useLut)
+ lut = StringUtils::Format("ON (size {})", m_lutSize);
+
+ if (m_useDithering)
+ dither = StringUtils::Format("ON (depth {})", m_ditherDepth);
+
+ return StringUtils::Format("Tone mapping: {} | HLG to PQ: {} | 3D LUT: {} | Dithering: {}", tone,
+ hlg, lut, dither);
+}
+
+//==================================================================================
+
+bool CYUV2RGBShader::Create(AVPixelFormat fmt, AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries, const std::shared_ptr<COutputShader>& pOutputShader)
+{
+ m_format = fmt;
+ m_pOutShader = pOutputShader;
+
+ DefinesMap defines;
+
+ switch (fmt)
+ {
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_YUV420P10:
+ case AV_PIX_FMT_YUV420P16:
+ defines["XBMC_YV12"] = "";
+ break;
+ case AV_PIX_FMT_D3D11VA_VLD:
+ case AV_PIX_FMT_NV12:
+ case AV_PIX_FMT_P010:
+ case AV_PIX_FMT_P016:
+ defines["XBMC_NV12"] = "";
+ // FL 9.x doesn't support DXGI_FORMAT_R8G8_UNORM, so we have to use SNORM and correct values in shader
+ if (!DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R8G8_UNORM, D3D11_FORMAT_SUPPORT_TEXTURE2D))
+ defines["NV12_SNORM_UV"] = "";
+ break;
+ case AV_PIX_FMT_UYVY422:
+ defines["XBMC_UYVY"] = "";
+ break;
+ case AV_PIX_FMT_YUYV422:
+ defines["XBMC_YUY2"] = "";
+ break;
+ default:
+ return false;
+ }
+
+ if (srcPrimaries != dstPrimaries)
+ {
+ m_colorConversion = true;
+ defines["XBMC_COL_CONVERSION"] = "";
+ }
+
+ if (m_pOutShader)
+ m_pOutShader->GetDefines(defines);
+
+ std::string effectString = "special://xbmc/system/shaders/yuv2rgb_d3d.fx";
+
+ if(!LoadEffect(effectString, &defines))
+ {
+ CLog::LogF(LOGERROR, "Failed to load shader {}.", effectString);
+ return false;
+ }
+
+ CWinShader::CreateVertexBuffer(4, sizeof(Vertex));
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+
+ if (!CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout)))
+ {
+ CLog::LogF(LOGERROR, "Failed to create input layout for Input Assembler.");
+ return false;
+ }
+
+ m_convMatrix.SetDestinationColorPrimaries(dstPrimaries).SetSourceColorPrimaries(srcPrimaries);
+
+ return true;
+}
+
+void CYUV2RGBShader::Render(CRect sourceRect, CPoint dest[], CRenderBuffer* videoBuffer, CD3DTexture& target)
+{
+ PrepareParameters(videoBuffer, sourceRect, dest);
+ SetShaderParameters(videoBuffer);
+ Execute({ &target }, 4);
+}
+
+void CYUV2RGBShader::SetParams(float contrast, float black, bool limited)
+{
+ m_convMatrix.SetDestinationContrast(contrast * 0.02f)
+ .SetDestinationBlack(black * 0.01f - 0.5f)
+ .SetDestinationLimitedRange(limited);
+}
+
+void CYUV2RGBShader::SetColParams(AVColorSpace colSpace, int bits, bool limited, int texBits)
+{
+ if (colSpace == AVCOL_SPC_UNSPECIFIED)
+ {
+ if (m_sourceWidth > 1024 || m_sourceHeight >= 600)
+ colSpace = AVCOL_SPC_BT709;
+ else
+ colSpace = AVCOL_SPC_BT470BG;
+ }
+
+ m_convMatrix.SetSourceColorSpace(colSpace)
+ .SetSourceBitDepth(bits)
+ .SetSourceLimitedRange(limited)
+ .SetSourceTextureBitDepth(texBits);
+}
+
+void CYUV2RGBShader::PrepareParameters(CRenderBuffer* videoBuffer, CRect sourceRect, CPoint dest[])
+{
+ if (m_sourceRect != sourceRect
+ || m_dest[0] != dest[0] || m_dest[1] != dest[1]
+ || m_dest[2] != dest[2] || m_dest[3] != dest[3]
+ || videoBuffer->GetWidth() != m_sourceWidth
+ || videoBuffer->GetHeight() != m_sourceHeight)
+ {
+ m_sourceRect = sourceRect;
+ for (size_t i = 0; i < 4; ++i)
+ m_dest[i] = dest[i];
+ m_sourceWidth = videoBuffer->GetWidth();
+ m_sourceHeight = videoBuffer->GetHeight();
+
+ Vertex* v;
+ CWinShader::LockVertexBuffer(reinterpret_cast<void**>(&v));
+
+ v[0].x = m_dest[0].x;
+ v[0].y = m_dest[0].y;
+ v[0].z = 0.0f;
+ v[0].tu = sourceRect.x1 / m_sourceWidth;
+ v[0].tv = sourceRect.y1 / m_sourceHeight;
+ v[0].tu2 = (sourceRect.x1 / 2.0f) / (m_sourceWidth>>1);
+ v[0].tv2 = (sourceRect.y1 / 2.0f) / (m_sourceHeight>>1);
+
+ v[1].x = m_dest[1].x;
+ v[1].y = m_dest[1].y;
+ v[1].z = 0.0f;
+ v[1].tu = sourceRect.x2 / m_sourceWidth;
+ v[1].tv = sourceRect.y1 / m_sourceHeight;
+ v[1].tu2 = (sourceRect.x2 / 2.0f) / (m_sourceWidth>>1);
+ v[1].tv2 = (sourceRect.y1 / 2.0f) / (m_sourceHeight>>1);
+
+ v[2].x = m_dest[2].x;
+ v[2].y = m_dest[2].y;
+ v[2].z = 0.0f;
+ v[2].tu = sourceRect.x2 / m_sourceWidth;
+ v[2].tv = sourceRect.y2 / m_sourceHeight;
+ v[2].tu2 = (sourceRect.x2 / 2.0f) / (m_sourceWidth>>1);
+ v[2].tv2 = (sourceRect.y2 / 2.0f) / (m_sourceHeight>>1);
+
+ v[3].x = m_dest[3].x;
+ v[3].y = m_dest[3].y;
+ v[3].z = 0.0f;
+ v[3].tu = sourceRect.x1 / m_sourceWidth;
+ v[3].tv = sourceRect.y2 / m_sourceHeight;
+ v[3].tu2 = (sourceRect.x1 / 2.0f) / (m_sourceWidth>>1);
+ v[3].tv2 = (sourceRect.y2 / 2.0f) / (m_sourceHeight>>1);
+
+ CWinShader::UnlockVertexBuffer();
+ }
+
+ unsigned int texWidth = m_sourceWidth;
+ if ( m_format == AV_PIX_FMT_UYVY422
+ || m_format == AV_PIX_FMT_YUYV422)
+ texWidth = texWidth >> 1;
+
+ m_texSteps[0] = 1.0f / texWidth;
+ m_texSteps[1] = 1.0f / m_sourceHeight;
+}
+
+void CYUV2RGBShader::SetShaderParameters(CRenderBuffer* videoBuffer)
+{
+ m_effect.SetTechnique("YUV2RGB_T");
+ ID3D11ShaderResourceView* ppSRView[3] = {};
+ for (unsigned i = 0, max_i = videoBuffer->GetViewCount(); i < max_i; i++)
+ ppSRView[i] = reinterpret_cast<ID3D11ShaderResourceView*>(videoBuffer->GetView(i));
+ m_effect.SetResources("g_Texture", ppSRView, videoBuffer->GetViewCount());
+ m_effect.SetFloatArray("g_StepXY", m_texSteps.data(), m_texSteps.size());
+
+ Matrix4 yuvMat = m_convMatrix.GetYuvMat();
+ m_effect.SetMatrix("g_ColorMatrix", yuvMat.ToRaw());
+
+ if (m_colorConversion)
+ {
+ Matrix4 primMat(m_convMatrix.GetPrimMat());
+
+ // looks like FX11 doesn't like 3x3 matrix, so used 4x4 for its happiness
+ m_effect.SetMatrix("g_primMat", primMat.ToRaw());
+ m_effect.SetScalar("g_gammaSrc", m_convMatrix.GetGammaSrc());
+ m_effect.SetScalar("g_gammaDstInv", 1 / m_convMatrix.GetGammaDst());
+ }
+
+ unsigned numPorts = 1;
+ D3D11_VIEWPORT viewPort;
+ DX::DeviceResources::Get()->GetD3DContext()->RSGetViewports(&numPorts, &viewPort);
+ m_effect.SetFloatArray("g_viewPort", &viewPort.Width, 2);
+ if (m_pOutShader)
+ m_pOutShader->ApplyEffectParameters(m_effect, m_sourceWidth, m_sourceHeight);
+}
+
+//==================================================================================
+
+bool CConvolutionShader::ChooseKernelD3DFormat()
+{
+ if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R16G16B16A16_FLOAT, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
+ {
+ m_KernelFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
+ m_floattex = true;
+ m_rgba = true;
+ }
+ else if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R8G8B8A8_UNORM, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
+ {
+ m_KernelFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ m_floattex = false;
+ m_rgba = true;
+ }
+ else if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
+ {
+ m_KernelFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
+ m_floattex = false;
+ m_rgba = false;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CConvolutionShader::CreateHQKernel(ESCALINGMETHOD method)
+{
+ CConvolutionKernel kern(method, 256);
+ void *kernel;
+ int kernelSize;
+
+ if (m_floattex)
+ {
+ float *data = kern.GetFloatPixels();
+ const auto float16 = new uint16_t[kern.GetSize() * 4];
+
+ XMConvertFloatToHalfStream(float16, sizeof(uint16_t), data, sizeof(float), kern.GetSize()*4);
+
+ kernel = float16;
+ kernelSize = sizeof(uint16_t)*kern.GetSize() * 4;
+ }
+ else
+ {
+ kernel = kern.GetUint8Pixels();
+ kernelSize = sizeof(uint8_t)*kern.GetSize() * 4;
+ }
+
+ if (!m_HQKernelTexture.Create(kern.GetSize(), 1, 1, D3D11_USAGE_IMMUTABLE, m_KernelFormat, kernel, kernelSize))
+ {
+ CLog::LogF(LOGERROR, "Failed to create kernel texture.");
+ return false;
+ }
+
+ if (m_floattex)
+ delete[] static_cast<uint16_t*>(kernel);
+
+ return true;
+}
+//==================================================================================
+bool CConvolutionShader1Pass::Create(ESCALINGMETHOD method, const std::shared_ptr<COutputShader>& pOutputShader)
+{
+ m_pOutShader = pOutputShader;
+
+ std::string effectString;
+ switch(method)
+ {
+ case VS_SCALINGMETHOD_CUBIC_MITCHELL:
+ case VS_SCALINGMETHOD_LANCZOS2:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ effectString = "special://xbmc/system/shaders/convolution-4x4_d3d.fx";
+ break;
+ case VS_SCALINGMETHOD_SPLINE36:
+ case VS_SCALINGMETHOD_LANCZOS3:
+ effectString = "special://xbmc/system/shaders/convolution-6x6_d3d.fx";
+ break;
+ default:
+ CLog::LogF(LOGERROR, "scaling method {} not supported.", method);
+ return false;
+ }
+
+ if (!ChooseKernelD3DFormat())
+ {
+ CLog::LogF(LOGERROR, "failed to find a compatible texture format for the kernel.");
+ return false;
+ }
+
+ CWinShader::CreateVertexBuffer(4, sizeof(Vertex));
+
+ DefinesMap defines;
+ if (m_floattex)
+ defines["HAS_FLOAT_TEXTURE"] = "";
+ if (m_rgba)
+ defines["HAS_RGBA"] = "";
+
+ if (m_pOutShader)
+ m_pOutShader->GetDefines(defines);
+
+ if(!LoadEffect(effectString, &defines))
+ {
+ CLog::LogF(LOGERROR, "Failed to load shader {}.", effectString);
+ return false;
+ }
+
+ if (!CreateHQKernel(method))
+ return false;
+
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+ return CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout));
+}
+
+void CConvolutionShader1Pass::Render(CD3DTexture& sourceTexture, CD3DTexture& target,
+ CRect sourceRect, CRect destRect, bool useLimitRange)
+{
+ const unsigned int sourceWidth = sourceTexture.GetWidth();
+ const unsigned int sourceHeight = sourceTexture.GetHeight();
+
+ PrepareParameters(sourceWidth, sourceHeight, sourceRect, destRect);
+ std::array<float, 2> texSteps = {1.0f / static_cast<float>(sourceWidth),
+ 1.0f / static_cast<float>(sourceHeight)};
+ SetShaderParameters(sourceTexture, texSteps.data(), texSteps.size(), useLimitRange);
+ Execute({ &target }, 4);
+}
+
+void CConvolutionShader1Pass::PrepareParameters(unsigned int sourceWidth, unsigned int sourceHeight,
+ CRect sourceRect, CRect destRect)
+{
+ if(m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight
+ || m_sourceRect != sourceRect || m_destRect != destRect)
+ {
+ m_sourceWidth = sourceWidth;
+ m_sourceHeight = sourceHeight;
+ m_sourceRect = sourceRect;
+ m_destRect = destRect;
+
+ Vertex* v;
+ CWinShader::LockVertexBuffer(reinterpret_cast<void**>(&v));
+
+ v[0].x = destRect.x1;
+ v[0].y = destRect.y1;
+ v[0].z = 0;
+ v[0].tu = sourceRect.x1 / sourceWidth;
+ v[0].tv = sourceRect.y1 / sourceHeight;
+
+ v[1].x = destRect.x2;
+ v[1].y = destRect.y1;
+ v[1].z = 0;
+ v[1].tu = sourceRect.x2 / sourceWidth;
+ v[1].tv = sourceRect.y1 / sourceHeight;
+
+ v[2].x = destRect.x2;
+ v[2].y = destRect.y2;
+ v[2].z = 0;
+ v[2].tu = sourceRect.x2 / sourceWidth;
+ v[2].tv = sourceRect.y2 / sourceHeight;
+
+ v[3].x = destRect.x1;
+ v[3].y = destRect.y2;
+ v[3].z = 0;
+ v[3].tu = sourceRect.x1 / sourceWidth;
+ v[3].tv = sourceRect.y2 / sourceHeight;
+
+ CWinShader::UnlockVertexBuffer();
+ }
+}
+
+void CConvolutionShader1Pass::SetShaderParameters(CD3DTexture &sourceTexture, float* texSteps, int texStepsCount, bool useLimitRange)
+{
+ m_effect.SetTechnique( "SCALER_T" );
+ m_effect.SetTexture( "g_Texture", sourceTexture ) ;
+ m_effect.SetTexture( "g_KernelTexture", m_HQKernelTexture );
+ m_effect.SetFloatArray("g_StepXY", texSteps, texStepsCount);
+ unsigned numVP = 1;
+ D3D11_VIEWPORT viewPort = {};
+ DX::DeviceResources::Get()->GetD3DContext()->RSGetViewports(&numVP, &viewPort);
+ m_effect.SetFloatArray("g_viewPort", &viewPort.Width, 2);
+ float colorRange[2] =
+ {
+ (useLimitRange ? 16.f / 255.f : 0.f),
+ (useLimitRange ? 219.f / 255.f : 1.f),
+ };
+ m_effect.SetFloatArray("g_colorRange", colorRange, _countof(colorRange));
+ if (m_pOutShader)
+ m_pOutShader->ApplyEffectParameters(m_effect, sourceTexture.GetWidth(), sourceTexture.GetHeight());
+}
+
+//==================================================================================
+
+bool CConvolutionShaderSeparable::Create(ESCALINGMETHOD method, const std::shared_ptr<COutputShader>& pOutputShader)
+{
+ m_pOutShader = pOutputShader;
+
+ std::string effectString;
+ switch(method)
+ {
+ case VS_SCALINGMETHOD_CUBIC_MITCHELL:
+ case VS_SCALINGMETHOD_LANCZOS2:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ effectString = "special://xbmc/system/shaders/convolutionsep-4x4_d3d.fx";
+ break;
+ case VS_SCALINGMETHOD_SPLINE36:
+ case VS_SCALINGMETHOD_LANCZOS3:
+ effectString = "special://xbmc/system/shaders/convolutionsep-6x6_d3d.fx";
+ break;
+ default:
+ CLog::LogF(LOGERROR, "scaling method {} not supported.", method);
+ return false;
+ }
+
+ if (!ChooseIntermediateD3DFormat())
+ {
+ CLog::LogF(LOGERROR, "failed to find a compatible texture format for the intermediate render target.");
+ return false;
+ }
+
+ if (!ChooseKernelD3DFormat())
+ {
+ CLog::LogF(LOGERROR, "failed to find a compatible texture format for the kernel.");
+ return false;
+ }
+
+ CWinShader::CreateVertexBuffer(8, sizeof(Vertex));
+
+ DefinesMap defines;
+ if (m_floattex)
+ defines["HAS_FLOAT_TEXTURE"] = "";
+ if (m_rgba)
+ defines["HAS_RGBA"] = "";
+
+ if (m_pOutShader)
+ m_pOutShader->GetDefines(defines);
+
+ if(!LoadEffect(effectString, &defines))
+ {
+ CLog::LogF(LOGERROR, "Failed to load shader {}.", effectString);
+ return false;
+ }
+
+ if (!CreateHQKernel(method))
+ return false;
+
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+ return CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout));
+}
+
+void CConvolutionShaderSeparable::Render(CD3DTexture& sourceTexture, CD3DTexture& target,
+ CRect sourceRect, CRect destRect, bool useLimitRange)
+{
+ const unsigned int sourceWidth = sourceTexture.GetWidth();
+ const unsigned int sourceHeight = sourceTexture.GetHeight();
+
+ const unsigned int destWidth = target.GetWidth();
+ const unsigned int destHeight = target.GetHeight();
+
+ if(m_destWidth != destWidth || m_sourceHeight != sourceHeight)
+ CreateIntermediateRenderTarget(destWidth, sourceHeight);
+
+ PrepareParameters(sourceWidth, sourceHeight, destWidth, destHeight, sourceRect, destRect);
+ float texSteps[] =
+ {
+ 1.0f / static_cast<float>(sourceWidth),
+ 1.0f / static_cast<float>(sourceHeight),
+ 1.0f / static_cast<float>(destWidth),
+ 1.0f / static_cast<float>(sourceHeight)
+ };
+ SetShaderParameters(sourceTexture, texSteps, 4, useLimitRange);
+
+ Execute({ &m_IntermediateTarget, &target }, 4);
+}
+
+bool CConvolutionShaderSeparable::ChooseIntermediateD3DFormat()
+{
+ const D3D11_FORMAT_SUPPORT usage = D3D11_FORMAT_SUPPORT_RENDER_TARGET;
+
+ // Need a float texture, as the output of the first pass can contain negative values.
+ if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R16G16B16A16_FLOAT, usage))
+ m_IntermediateFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
+ else if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R32G32B32A32_FLOAT, usage))
+ m_IntermediateFormat = DXGI_FORMAT_R32G32B32A32_FLOAT;
+ else
+ {
+ CLog::LogF(LOGINFO, "no float format available for the intermediate render target");
+ return false;
+ }
+
+ CLog::LogF(LOGDEBUG, "format {}", m_IntermediateFormat);
+ return true;
+}
+
+bool CConvolutionShaderSeparable::CreateIntermediateRenderTarget(unsigned int width, unsigned int height)
+{
+ if (m_IntermediateTarget.Get())
+ m_IntermediateTarget.Release();
+
+ if (!m_IntermediateTarget.Create(width, height, 1, D3D11_USAGE_DEFAULT, m_IntermediateFormat))
+ {
+ CLog::LogF(LOGERROR, "render target creation failed.");
+ return false;
+ }
+ return true;
+}
+
+bool CConvolutionShaderSeparable::ClearIntermediateRenderTarget()
+{
+ float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+ DX::DeviceResources::Get()->GetD3DContext()->ClearRenderTargetView(m_IntermediateTarget.GetRenderTarget(), color);
+ return true;
+}
+
+void CConvolutionShaderSeparable::PrepareParameters(unsigned int sourceWidth, unsigned int sourceHeight,
+ unsigned int destWidth, unsigned int destHeight,
+ CRect sourceRect, CRect destRect)
+{
+ if(m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight
+ || m_destWidth != destWidth || m_destHeight != destHeight
+ || m_sourceRect != sourceRect || m_destRect != destRect)
+ {
+ ClearIntermediateRenderTarget();
+
+ m_sourceWidth = sourceWidth;
+ m_sourceHeight = sourceHeight;
+ m_destWidth = destWidth;
+ m_destHeight = destHeight;
+ m_sourceRect = sourceRect;
+ m_destRect = destRect;
+
+ Vertex* v;
+ CWinShader::LockVertexBuffer(reinterpret_cast<void**>(&v));
+
+ // Alter rectangles the destination rectangle exceeds the intermediate target width when zooming and causes artifacts.
+ // Work on the parameters rather than the members to avoid disturbing the parameter change detection the next time the function is called
+ const CRect target(0, 0, static_cast<float>(destWidth), static_cast<float>(destHeight));
+ CWIN32Util::CropSource(sourceRect, destRect, target);
+
+ // Manipulate the coordinates to work only on the active parts of the textures,
+ // and therefore avoid the need to clear surfaces/render targets
+
+ // Pass 1:
+ // Horizontal dimension: crop/zoom, so that it is completely done with the convolution shader. Scaling to display width in pass1 and
+ // cropping/zooming in pass 2 would use bilinear in pass2, which we don't want.
+ // Vertical dimension: crop using sourceRect to save memory bandwidth for high zoom values, but don't stretch/shrink in any way in this pass.
+
+ v[0].x = 0;
+ v[0].y = 0;
+ v[0].z = 0;
+ v[0].tu = sourceRect.x1 / sourceWidth;
+ v[0].tv = sourceRect.y1 / sourceHeight;
+
+ v[1].x = destRect.x2 - destRect.x1;
+ v[1].y = 0;
+ v[1].z = 0;
+ v[1].tu = sourceRect.x2 / sourceWidth;
+ v[1].tv = sourceRect.y1 / sourceHeight;
+
+ v[2].x = destRect.x2 - destRect.x1;
+ v[2].y = sourceRect.y2 - sourceRect.y1;
+ v[2].z = 0;
+ v[2].tu = sourceRect.x2 / sourceWidth;
+ v[2].tv = sourceRect.y2 / sourceHeight;
+
+ v[3].x = 0;
+ v[3].y = sourceRect.y2 - sourceRect.y1;
+ v[3].z = 0;
+ v[3].tu = sourceRect.x1 / sourceWidth;
+ v[3].tv = sourceRect.y2 / sourceHeight;
+
+ // Pass 2: pass the horizontal data untouched, resize vertical dimension for final result.
+
+ v[4].x = destRect.x1;
+ v[4].y = destRect.y1;
+ v[4].z = 0;
+ v[4].tu = 0;
+ v[4].tv = 0;
+
+ v[5].x = destRect.x2;
+ v[5].y = destRect.y1;
+ v[5].z = 0;
+ v[5].tu = (destRect.x2 - destRect.x1) / destWidth;
+ v[5].tv = 0;
+
+ v[6].x = destRect.x2;
+ v[6].y = destRect.y2;
+ v[6].z = 0;
+ v[6].tu = (destRect.x2 - destRect.x1) / destWidth;
+ v[6].tv = (sourceRect.y2 - sourceRect.y1) / sourceHeight;
+
+ v[7].x = destRect.x1;
+ v[7].y = destRect.y2;
+ v[7].z = 0;
+ v[7].tu = 0;
+ v[7].tv = (sourceRect.y2 - sourceRect.y1) / sourceHeight;
+
+ CWinShader::UnlockVertexBuffer();
+ }
+}
+
+void CConvolutionShaderSeparable::SetShaderParameters(CD3DTexture &sourceTexture, float* texSteps, int texStepsCount, bool useLimitRange)
+{
+ m_effect.SetTechnique( "SCALER_T" );
+ m_effect.SetTexture( "g_Texture", sourceTexture );
+ m_effect.SetTexture( "g_KernelTexture", m_HQKernelTexture );
+ m_effect.SetFloatArray("g_StepXY", texSteps, texStepsCount);
+ float colorRange[2] =
+ {
+ (useLimitRange ? 16.f / 255.f : 0.f),
+ (useLimitRange ? 219.f / 255.f : 1.f)
+ };
+ m_effect.SetFloatArray("g_colorRange", colorRange, _countof(colorRange));
+ if (m_pOutShader)
+ m_pOutShader->ApplyEffectParameters(m_effect, sourceTexture.GetWidth(), sourceTexture.GetHeight());
+}
+
+void CConvolutionShaderSeparable::SetStepParams(unsigned iPass)
+{
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
+
+ CD3D11_VIEWPORT viewPort = CD3D11_VIEWPORT(
+ 0.0f,
+ 0.0f,
+ static_cast<float>(m_target->GetWidth()),
+ static_cast<float>(m_target->GetHeight()));
+
+ if (iPass == 0)
+ {
+ // reset scissor
+ DX::Windowing()->ResetScissors();
+ }
+ else if (iPass == 1)
+ {
+ // at the second pass m_IntermediateTarget is a source of data
+ m_effect.SetTexture("g_Texture", m_IntermediateTarget);
+ // restore scissor
+ DX::Windowing()->SetScissors(CServiceBroker::GetWinSystem()->GetGfxContext().StereoCorrection(CServiceBroker::GetWinSystem()->GetGfxContext().GetScissors()));
+ }
+ // setting view port
+ pContext->RSSetViewports(1, &viewPort);
+ // pass viewport dimension to the shaders
+ m_effect.SetFloatArray("g_viewPort", &viewPort.Width, 2);
+}
+
+//==========================================================
+#define SHADER_SOURCE(...) #__VA_ARGS__
+
+bool CTestShader::Create()
+{
+ std::string strShader = SHADER_SOURCE(
+ float4 TEST() : SV_TARGET
+ {
+ return float4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ technique11 TEST_T
+ {
+ pass P0
+ {
+ SetPixelShader(CompileShader(ps_4_0_level_9_1, TEST()));
+ }
+ };
+ );
+
+ if (!m_effect.Create(strShader, nullptr))
+ {
+ CLog::LogF(LOGERROR, "Failed to create test shader: {}", strShader);
+ return false;
+ }
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h
new file mode 100644
index 0000000..de901af
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/IPlayer.h"
+#include "cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h"
+#include "guilib/D3DResource.h"
+#include "utils/Geometry.h"
+
+#include <array>
+#include <vector>
+
+#include <DirectXMath.h>
+#include <wrl/client.h>
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+#include <libavutil/mastering_display_metadata.h>
+}
+class CRenderBuffer;
+
+using namespace DirectX;
+
+class CWinShader
+{
+protected:
+ CWinShader() = default;
+ virtual ~CWinShader() = default;
+
+ virtual bool CreateVertexBuffer(unsigned int vertCount, unsigned int vertSize);
+ virtual bool LockVertexBuffer(void **data);
+ virtual bool UnlockVertexBuffer();
+ virtual bool LoadEffect(const std::string& filename, DefinesMap* defines);
+ virtual bool Execute(const std::vector<CD3DTexture*>& targets, unsigned int vertexIndexStep);
+ virtual void SetStepParams(unsigned stepIndex) { }
+ virtual bool CreateInputLayout(D3D11_INPUT_ELEMENT_DESC *layout, unsigned numElements);
+
+ CD3DEffect m_effect;
+ CD3DTexture* m_target = nullptr;
+
+private:
+ void SetTarget(CD3DTexture* target);
+
+ CD3DBuffer m_vb;
+ CD3DBuffer m_ib;
+ unsigned int m_vbsize = 0;
+ unsigned int m_vertsize = 0;
+ Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout = nullptr;
+};
+
+class COutputShader : public CWinShader
+{
+public:
+ explicit COutputShader() = default;
+ ~COutputShader() = default;
+
+ void ApplyEffectParameters(CD3DEffect &effect, unsigned sourceWidth, unsigned sourceHeight);
+ void GetDefines(DefinesMap &map) const;
+ bool Create(bool useLUT,
+ bool useDithering,
+ int ditherDepth,
+ bool toneMapping,
+ ETONEMAPMETHOD toneMethod,
+ bool HLGtoPQ);
+ void Render(CD3DTexture& sourceTexture, CRect sourceRect, const CPoint points[4]
+ , CD3DTexture& target, unsigned range = 0, float contrast = 0.5f, float brightness = 0.5f);
+ void Render(CD3DTexture& sourceTexture, CRect sourceRect, CRect destRect
+ , CD3DTexture& target, unsigned range = 0, float contrast = 0.5f, float brightness = 0.5f);
+ void SetLUT(int lutSize, ID3D11ShaderResourceView *pLUTView);
+ void SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata,
+ bool hasLightMetadata, AVContentLightMetadata lightMetadata);
+ void SetToneMapParam(ETONEMAPMETHOD method, float param);
+ std::string GetDebugInfo();
+
+ static bool CreateLUTView(int lutSize, uint16_t* lutData, bool isRGB, ID3D11ShaderResourceView** ppLUTView);
+
+private:
+ struct Vertex
+ {
+ float x, y, z;
+ float tu, tv;
+ };
+
+ bool HasLUT() const { return m_lutSize && m_pLUTView; }
+ void PrepareParameters(unsigned sourceWidth, unsigned sourceHeight, CRect sourceRect, const CPoint points[4]);
+ void SetShaderParameters(CD3DTexture &sourceTexture, unsigned range, float contrast, float brightness);
+ void CreateDitherView();
+ float GetLuminanceValue() const;
+
+ bool m_useLut = false;
+ bool m_useDithering = false;
+ bool m_toneMapping = false;
+ bool m_useHLGtoPQ = false;
+
+ bool m_hasDisplayMetadata = false;
+ bool m_hasLightMetadata = false;
+ unsigned m_sourceWidth = 0;
+ unsigned m_sourceHeight = 0;
+ int m_lutSize = 0;
+ int m_ditherDepth = 0;
+ ETONEMAPMETHOD m_toneMappingMethod = VS_TONEMAPMETHOD_OFF;
+ float m_toneMappingParam = 1.0f;
+ float m_toneMappingDebug = .0f;
+
+ CRect m_sourceRect = {};
+ CPoint m_destPoints[4] = {};
+ Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_pDitherView = nullptr;
+ Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_pLUTView = nullptr;
+
+ AVMasteringDisplayMetadata m_displayMetadata = {};
+ AVContentLightMetadata m_lightMetadata = {};
+};
+
+class CYUV2RGBShader : public CWinShader
+{
+public:
+ explicit CYUV2RGBShader() = default;
+ ~CYUV2RGBShader() = default;
+
+ bool Create(AVPixelFormat fmt,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ const std::shared_ptr<COutputShader>& pOutShader = nullptr);
+ void Render(CRect sourceRect, CPoint dest[], CRenderBuffer* videoBuffer, CD3DTexture& target);
+ void SetParams(float contrast, float black, bool limited);
+ void SetColParams(AVColorSpace colSpace, int bits, bool limited, int texBits);
+
+protected:
+ void PrepareParameters(CRenderBuffer* videoBuffer, CRect sourceRect, CPoint dest[]);
+ void SetShaderParameters(CRenderBuffer* videoBuffer);
+
+private:
+ struct Vertex {
+ float x, y, z;
+ float tu, tv; // Y Texture coordinates
+ float tu2, tv2; // U and V Textures coordinates
+ };
+
+ unsigned int m_sourceWidth = 0;
+ unsigned int m_sourceHeight = 0;
+ CRect m_sourceRect = {};
+ CPoint m_dest[4] = {};
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+ std::array<float, 2> m_texSteps = {};
+ std::shared_ptr<COutputShader> m_pOutShader = nullptr;
+ CConvertMatrix m_convMatrix;
+ bool m_colorConversion{false};
+};
+
+class CConvolutionShader : public CWinShader
+{
+public:
+ virtual ~CConvolutionShader() = default;
+ virtual bool Create(ESCALINGMETHOD method, const std::shared_ptr<COutputShader>& pOutShader = nullptr) = 0;
+ virtual void Render(CD3DTexture& sourceTexture, CD3DTexture& target,
+ CRect sourceRect, CRect destRect, bool useLimitRange) = 0;
+protected:
+ struct Vertex {
+ float x, y, z;
+ float tu, tv;
+ };
+
+ CConvolutionShader() = default;
+
+ virtual bool ChooseKernelD3DFormat();
+ virtual bool CreateHQKernel(ESCALINGMETHOD method);
+ virtual void SetShaderParameters(CD3DTexture &sourceTexture, float* texSteps, int texStepsCount, bool useLimitRange) = 0;
+
+ bool m_floattex = false;
+ bool m_rgba = false;
+ DXGI_FORMAT m_KernelFormat = DXGI_FORMAT_UNKNOWN;
+ CD3DTexture m_HQKernelTexture;
+ std::shared_ptr<COutputShader> m_pOutShader = nullptr;
+};
+
+class CConvolutionShader1Pass : public CConvolutionShader
+{
+public:
+ explicit CConvolutionShader1Pass() = default;
+
+ bool Create(ESCALINGMETHOD method, const std::shared_ptr<COutputShader>& pOutputShader = nullptr) override;
+ void Render(CD3DTexture& sourceTexture, CD3DTexture& target,
+ CRect sourceRect, CRect destRect, bool useLimitRange) override;
+
+protected:
+ void PrepareParameters(unsigned int sourceWidth, unsigned int sourceHeight,
+ CRect sourceRect, CRect destRect);
+ void SetShaderParameters(CD3DTexture &sourceTexture, float* texSteps,
+ int texStepsCount, bool useLimitRange) override;
+
+private:
+ unsigned int m_sourceWidth = 0;
+ unsigned int m_sourceHeight = 0;
+ CRect m_sourceRect = {};
+ CRect m_destRect = {};
+};
+
+class CConvolutionShaderSeparable : public CConvolutionShader
+{
+public:
+ explicit CConvolutionShaderSeparable() = default;
+ ~CConvolutionShaderSeparable() = default;
+
+ bool Create(ESCALINGMETHOD method, const std::shared_ptr<COutputShader>& pOutputShader = nullptr) override;
+ void Render(CD3DTexture& sourceTexture, CD3DTexture& target,
+ CRect sourceRect, CRect destRect, bool useLimitRange) override;
+
+protected:
+ bool ChooseIntermediateD3DFormat();
+ bool CreateIntermediateRenderTarget(unsigned int width, unsigned int height);
+ bool ClearIntermediateRenderTarget();
+ void PrepareParameters(unsigned int sourceWidth, unsigned int sourceHeight,
+ unsigned int destWidth, unsigned int destHeight,
+ CRect sourceRect, CRect destRect);
+ void SetShaderParameters(CD3DTexture &sourceTexture, float* texSteps,
+ int texStepsCount, bool useLimitRange) override;
+ void SetStepParams(unsigned iPass) override;
+
+private:
+ CD3DTexture m_IntermediateTarget;
+ DXGI_FORMAT m_IntermediateFormat = DXGI_FORMAT_UNKNOWN;
+ unsigned int m_sourceWidth = 0;
+ unsigned int m_sourceHeight = 0;
+ unsigned int m_destWidth = 0;
+ unsigned int m_destHeight = 0;
+ CRect m_sourceRect = {};
+ CRect m_destRect = {};
+};
+
+class CTestShader : public CWinShader
+{
+public:
+ virtual bool Create();
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.cpp
new file mode 100644
index 0000000..6753ca1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2007 d4rk
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "YUV2RGBShaderGL.h"
+
+#include "../RenderFlags.h"
+#include "ConvolutionKernels.h"
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace Shaders::GL;
+
+//////////////////////////////////////////////////////////////////////
+// BaseYUV2RGBGLSLShader - base class for GLSL YUV2RGB shaders
+//////////////////////////////////////////////////////////////////////
+
+BaseYUV2RGBGLSLShader::BaseYUV2RGBGLSLShader(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ std::shared_ptr<GLSLOutput> output)
+{
+ m_width = 1;
+ m_height = 1;
+ m_field = 0;
+ m_format = format;
+ m_black = 0.0f;
+ m_contrast = 1.0f;
+ m_stretch = 0.0f;
+
+ // get defines from the output stage if used
+ m_glslOutput = std::move(output);
+ if (m_glslOutput)
+ {
+ m_defines += m_glslOutput->GetDefines();
+ }
+
+ if (rect)
+ m_defines += "#define XBMC_texture_rectangle 1\n";
+ else
+ m_defines += "#define XBMC_texture_rectangle 0\n";
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_GLRectangleHack)
+ m_defines += "#define XBMC_texture_rectangle_hack 1\n";
+ else
+ m_defines += "#define XBMC_texture_rectangle_hack 0\n";
+
+ //don't compile in stretch support when it's not needed
+ if (stretch)
+ m_defines += "#define XBMC_STRETCH 1\n";
+ else
+ m_defines += "#define XBMC_STRETCH 0\n";
+
+ if (m_format == SHADER_YV12 ||
+ m_format == SHADER_YV12_9 ||
+ m_format == SHADER_YV12_10 ||
+ m_format == SHADER_YV12_12 ||
+ m_format == SHADER_YV12_14 ||
+ m_format == SHADER_YV12_16)
+ m_defines += "#define XBMC_YV12\n";
+ else if (m_format == SHADER_NV12)
+ m_defines += "#define XBMC_NV12\n";
+ else if (m_format == SHADER_YUY2)
+ m_defines += "#define XBMC_YUY2\n";
+ else if (m_format == SHADER_UYVY)
+ m_defines += "#define XBMC_UYVY\n";
+ else
+ CLog::Log(LOGERROR, "GL: BaseYUV2RGBGLSLShader - unsupported format {}", m_format);
+
+ if (dstPrimaries != srcPrimaries)
+ {
+ m_colorConversion = true;
+ m_defines += "#define XBMC_COL_CONVERSION\n";
+ }
+
+ if (toneMap)
+ {
+ m_toneMapping = true;
+ m_toneMappingMethod = toneMapMethod;
+ m_defines += "#define XBMC_TONE_MAPPING\n";
+ if (toneMapMethod == VS_TONEMAPMETHOD_REINHARD)
+ m_defines += "#define KODI_TONE_MAPPING_REINHARD\n";
+ else if (toneMapMethod == VS_TONEMAPMETHOD_ACES)
+ m_defines += "#define KODI_TONE_MAPPING_ACES\n";
+ else if (toneMapMethod == VS_TONEMAPMETHOD_HABLE)
+ m_defines += "#define KODI_TONE_MAPPING_HABLE\n";
+ }
+
+ VertexShader()->LoadSource("gl_yuv2rgb_vertex.glsl", m_defines);
+
+ CLog::Log(LOGDEBUG, "GL: using shader format: {}", m_format);
+ CLog::Log(LOGDEBUG, "GL: using tonemap method: {}", toneMapMethod);
+
+ m_convMatrix.SetDestinationColorPrimaries(dstPrimaries).SetSourceColorPrimaries(srcPrimaries);
+}
+
+BaseYUV2RGBGLSLShader::~BaseYUV2RGBGLSLShader()
+{
+ Free();
+ m_glslOutput.reset();
+}
+
+void BaseYUV2RGBGLSLShader::OnCompiledAndLinked()
+{
+ m_hYTex = glGetUniformLocation(ProgramHandle(), "m_sampY");
+ m_hUTex = glGetUniformLocation(ProgramHandle(), "m_sampU");
+ m_hVTex = glGetUniformLocation(ProgramHandle(), "m_sampV");
+ m_hYuvMat = glGetUniformLocation(ProgramHandle(), "m_yuvmat");
+ m_hStretch = glGetUniformLocation(ProgramHandle(), "m_stretch");
+ m_hStep = glGetUniformLocation(ProgramHandle(), "m_step");
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hYcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordY");
+ m_hUcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordU");
+ m_hVcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordV");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+ m_hPrimMat = glGetUniformLocation(ProgramHandle(), "m_primMat");
+ m_hGammaSrc = glGetUniformLocation(ProgramHandle(), "m_gammaSrc");
+ m_hGammaDstInv = glGetUniformLocation(ProgramHandle(), "m_gammaDstInv");
+ m_hCoefsDst = glGetUniformLocation(ProgramHandle(), "m_coefsDst");
+ m_hToneP1 = glGetUniformLocation(ProgramHandle(), "m_toneP1");
+ m_hLuminance = glGetUniformLocation(ProgramHandle(), "m_luminance");
+ VerifyGLState();
+
+ if (m_glslOutput)
+ m_glslOutput->OnCompiledAndLinked(ProgramHandle());
+}
+
+bool BaseYUV2RGBGLSLShader::OnEnabled()
+{
+ // set shader attributes once enabled
+ glUniform1i(m_hYTex, 0);
+ glUniform1i(m_hUTex, 1);
+ glUniform1i(m_hVTex, 2);
+ glUniform1f(m_hStretch, m_stretch);
+ glUniform2f(m_hStep, 1.0 / m_width, 1.0 / m_height);
+
+ m_convMatrix.SetDestinationContrast(m_contrast)
+ .SetDestinationBlack(m_black)
+ .SetDestinationLimitedRange(!m_convertFullRange);
+
+ Matrix4 yuvMat = m_convMatrix.GetYuvMat();
+ glUniformMatrix4fv(m_hYuvMat, 1, GL_FALSE, reinterpret_cast<GLfloat*>(yuvMat.ToRaw()));
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+ glUniform1f(m_hAlpha, m_alpha);
+
+ if (m_colorConversion)
+ {
+ Matrix3 primMat = m_convMatrix.GetPrimMat();
+ glUniformMatrix3fv(m_hPrimMat, 1, GL_FALSE, reinterpret_cast<GLfloat*>(primMat.ToRaw()));
+ glUniform1f(m_hGammaSrc, m_convMatrix.GetGammaSrc());
+ glUniform1f(m_hGammaDstInv, 1 / m_convMatrix.GetGammaDst());
+ }
+
+ if (m_toneMapping)
+ {
+ if (m_toneMappingMethod == VS_TONEMAPMETHOD_REINHARD)
+ {
+ float param = 0.7;
+ if (m_hasLightMetadata)
+ param = log10(100) / log10(m_lightMetadata.MaxCLL);
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance)
+ param = log10(100) / log10(m_displayMetadata.max_luminance.num/m_displayMetadata.max_luminance.den);
+
+ // Sanity check
+ if (param < 0.1f || param > 5.0f)
+ param = 0.7f;
+
+ param *= m_toneMappingParam;
+
+ Matrix3x1 coefs = m_convMatrix.GetRGBYuvCoefs(AVColorSpace::AVCOL_SPC_BT709);
+ glUniform3f(m_hCoefsDst, coefs[0], coefs[1], coefs[2]);
+ glUniform1f(m_hToneP1, param);
+ }
+ else if (m_toneMappingMethod == VS_TONEMAPMETHOD_ACES)
+ {
+ glUniform1f(m_hLuminance, GetLuminanceValue());
+ glUniform1f(m_hToneP1, m_toneMappingParam);
+ }
+ else if (m_toneMappingMethod == VS_TONEMAPMETHOD_HABLE)
+ {
+ float lumin = GetLuminanceValue();
+ float param = (10000.0f / lumin) * (2.0f / m_toneMappingParam);
+ glUniform1f(m_hLuminance, lumin);
+ glUniform1f(m_hToneP1, param);
+ }
+ }
+
+ VerifyGLState();
+ if (m_glslOutput)
+ m_glslOutput->OnEnabled();
+ return true;
+}
+
+void BaseYUV2RGBGLSLShader::OnDisabled()
+{
+ if (m_glslOutput)
+ m_glslOutput->OnDisabled();
+}
+
+void BaseYUV2RGBGLSLShader::Free()
+{
+ if (m_glslOutput)
+ m_glslOutput->Free();
+}
+
+void BaseYUV2RGBGLSLShader::SetColParams(AVColorSpace colSpace, int bits, bool limited,
+ int textureBits)
+{
+ if (colSpace == AVCOL_SPC_UNSPECIFIED)
+ {
+ if (m_width > 1024 || m_height >= 600)
+ colSpace = AVCOL_SPC_BT709;
+ else
+ colSpace = AVCOL_SPC_BT470BG;
+ }
+ m_convMatrix.SetSourceColorSpace(colSpace)
+ .SetSourceBitDepth(bits)
+ .SetSourceLimitedRange(limited)
+ .SetSourceTextureBitDepth(textureBits);
+}
+
+void BaseYUV2RGBGLSLShader::SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata,
+ bool hasLightMetadata, AVContentLightMetadata lightMetadata)
+{
+ m_hasDisplayMetadata = hasDisplayMetadata;
+ m_displayMetadata = displayMetadata;
+ m_hasLightMetadata = hasLightMetadata;
+ m_lightMetadata = lightMetadata;
+}
+
+
+void BaseYUV2RGBGLSLShader::SetToneMapParam(ETONEMAPMETHOD method, float param)
+{
+ m_toneMappingMethod = method;
+ m_toneMappingParam = param;
+}
+
+float BaseYUV2RGBGLSLShader::GetLuminanceValue() const //Maybe move this to linuxrenderer?! same as in baserenderer
+{
+ float lum1 = 400.0f; // default for bad quality HDR-PQ sources (with no metadata)
+ float lum2 = lum1;
+ float lum3 = lum1;
+
+ if (m_hasLightMetadata)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ if (m_lightMetadata.MaxCLL >= lum)
+ {
+ lum1 = static_cast<float>(lum);
+ lum2 = static_cast<float>(m_lightMetadata.MaxCLL);
+ }
+ else
+ {
+ lum1 = static_cast<float>(m_lightMetadata.MaxCLL);
+ lum2 = static_cast<float>(lum);
+ }
+ lum3 = static_cast<float>(m_lightMetadata.MaxFALL);
+ lum1 = (lum1 * 0.5f) + (lum2 * 0.2f) + (lum3 * 0.3f);
+ }
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance &&
+ m_displayMetadata.max_luminance.num)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ lum1 = static_cast<float>(lum);
+ }
+
+ return lum1;
+}
+
+//////////////////////////////////////////////////////////////////////
+// YUV2RGBProgressiveShader - YUV2RGB with no deinterlacing
+// Use for weave deinterlacing / progressive
+//////////////////////////////////////////////////////////////////////
+
+YUV2RGBProgressiveShader::YUV2RGBProgressiveShader(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ std::shared_ptr<GLSLOutput> output)
+ : BaseYUV2RGBGLSLShader(rect,
+ format,
+ stretch,
+ dstPrimaries,
+ srcPrimaries,
+ toneMap,
+ toneMapMethod,
+ std::move(output))
+{
+ PixelShader()->LoadSource("gl_yuv2rgb_basic.glsl", m_defines);
+ PixelShader()->AppendSource("gl_output.glsl");
+
+ PixelShader()->InsertSource("gl_tonemap.glsl", "vec4 process()");
+}
+
+//------------------------------------------------------------------------------
+// YUV2RGBFilterShader4
+//------------------------------------------------------------------------------
+
+YUV2RGBFilterShader4::YUV2RGBFilterShader4(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ ESCALINGMETHOD method,
+ std::shared_ptr<GLSLOutput> output)
+ : BaseYUV2RGBGLSLShader(rect,
+ format,
+ stretch,
+ dstPrimaries,
+ srcPrimaries,
+ toneMap,
+ toneMapMethod,
+ std::move(output))
+{
+ m_scaling = method;
+ PixelShader()->LoadSource("gl_yuv2rgb_filter4.glsl", m_defines);
+ PixelShader()->AppendSource("gl_output.glsl");
+
+ PixelShader()->InsertSource("gl_tonemap.glsl", "vec4 process()");
+}
+
+YUV2RGBFilterShader4::~YUV2RGBFilterShader4()
+{
+ if (m_kernelTex)
+ glDeleteTextures(1, &m_kernelTex);
+ m_kernelTex = 0;
+}
+
+void YUV2RGBFilterShader4::OnCompiledAndLinked()
+{
+ BaseYUV2RGBGLSLShader::OnCompiledAndLinked();
+ m_hKernTex = glGetUniformLocation(ProgramHandle(), "m_kernelTex");
+
+ if (m_scaling != VS_SCALINGMETHOD_LANCZOS3_FAST && m_scaling != VS_SCALINGMETHOD_SPLINE36_FAST)
+ {
+ CLog::Log(LOGERROR, "GL: BaseYUV2RGBGLSLShader4 - unsupported scaling {} will fallback",
+ m_scaling);
+ m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST;
+ }
+
+ CConvolutionKernel kernel(m_scaling, 256);
+
+ if (m_kernelTex)
+ {
+ glDeleteTextures(1, &m_kernelTex);
+ m_kernelTex = 0;
+ }
+ glGenTextures(1, &m_kernelTex);
+
+ //make a kernel texture on GL_TEXTURE2 and set clamping and interpolation
+ //TEXTARGET is set to GL_TEXTURE_1D or GL_TEXTURE_2D
+ glActiveTexture(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_1D, m_kernelTex);
+ glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+
+ GLvoid* data = (GLvoid*)kernel.GetFloatPixels();
+ glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, kernel.GetSize(), 0, GL_RGBA, GL_FLOAT, data);
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+}
+
+bool YUV2RGBFilterShader4::OnEnabled()
+{
+ glActiveTexture(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_1D, m_kernelTex);
+ glUniform1i(m_hKernTex, 3);
+
+ return BaseYUV2RGBGLSLShader::OnEnabled();
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.h
new file mode 100644
index 0000000..476933b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGL.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ConversionMatrix.h"
+#include "GLSLOutput.h"
+#include "ShaderFormats.h"
+#include "cores/VideoSettings.h"
+#include "guilib/Shader.h"
+#include "utils/TransformMatrix.h"
+
+#include <memory>
+
+extern "C" {
+#include <libavutil/pixfmt.h>
+#include <libavutil/mastering_display_metadata.h>
+}
+
+class CConvertMatrix;
+
+namespace Shaders {
+namespace GL
+{
+
+class BaseYUV2RGBGLSLShader : public CGLSLShaderProgram
+{
+public:
+ BaseYUV2RGBGLSLShader(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dst,
+ AVColorPrimaries src,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ std::shared_ptr<GLSLOutput> output);
+ ~BaseYUV2RGBGLSLShader() override;
+
+ void SetField(int field) { m_field = field; }
+ void SetWidth(int w) { m_width = w; }
+ void SetHeight(int h) { m_height = h; }
+
+ void SetColParams(AVColorSpace colSpace, int bits, bool limited, int textureBits);
+ void SetBlack(float black) { m_black = black; }
+ void SetContrast(float contrast) { m_contrast = contrast; }
+ void SetNonLinStretch(float stretch) { m_stretch = stretch; }
+ void SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata,
+ bool hasLightMetadata, AVContentLightMetadata lightMetadata);
+ void SetToneMapParam(ETONEMAPMETHOD method, float param);
+ float GetLuminanceValue() const;
+
+ void SetConvertFullColorRange(bool convertFullRange) { m_convertFullRange = convertFullRange; }
+
+ GLint GetVertexLoc() { return m_hVertex; }
+ GLint GetYcoordLoc() { return m_hYcoord; }
+ GLint GetUcoordLoc() { return m_hUcoord; }
+ GLint GetVcoordLoc() { return m_hVcoord; }
+
+ void SetMatrices(const GLfloat *p, const GLfloat *m) { m_proj = p; m_model = m; }
+ void SetAlpha(GLfloat alpha) { m_alpha = alpha; }
+
+protected:
+
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ void OnDisabled() override;
+ void Free();
+
+ bool m_convertFullRange;
+ EShaderFormat m_format;
+ int m_width;
+ int m_height;
+ int m_field;
+ bool m_hasDisplayMetadata = false;
+ AVMasteringDisplayMetadata m_displayMetadata;
+ bool m_hasLightMetadata = false;
+ AVContentLightMetadata m_lightMetadata;
+ bool m_toneMapping = false;
+ ETONEMAPMETHOD m_toneMappingMethod = VS_TONEMAPMETHOD_REINHARD;
+ float m_toneMappingParam = 1.0;
+
+ bool m_colorConversion{false};
+
+ float m_black;
+ float m_contrast;
+ float m_stretch;
+
+ const GLfloat *m_proj = nullptr;
+ const GLfloat *m_model = nullptr;
+ GLfloat m_alpha = 1.0f;
+
+ std::string m_defines;
+
+ std::shared_ptr<Shaders::GLSLOutput> m_glslOutput;
+ CConvertMatrix m_convMatrix;
+
+ // pixel shader attribute handles
+ GLint m_hYTex = -1;
+ GLint m_hUTex = -1;
+ GLint m_hVTex = -1;
+ GLint m_hYuvMat = -1;
+ GLint m_hStretch = -1;
+ GLint m_hStep = -1;
+ GLint m_hGammaSrc = -1;
+ GLint m_hGammaDstInv = -1;
+ GLint m_hPrimMat = -1;
+ GLint m_hToneP1 = -1;
+ GLint m_hCoefsDst = -1;
+ GLint m_hLuminance = -1;
+
+ // vertex shader attribute handles
+ GLint m_hVertex = -1;
+ GLint m_hYcoord = -1;
+ GLint m_hUcoord = -1;
+ GLint m_hVcoord = -1;
+ GLint m_hProj = -1;
+ GLint m_hModel = -1;
+ GLint m_hAlpha = -1;
+};
+
+class YUV2RGBProgressiveShader : public BaseYUV2RGBGLSLShader
+{
+public:
+ YUV2RGBProgressiveShader(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ std::shared_ptr<GLSLOutput> output);
+};
+
+class YUV2RGBFilterShader4 : public BaseYUV2RGBGLSLShader
+{
+public:
+ YUV2RGBFilterShader4(bool rect,
+ EShaderFormat format,
+ bool stretch,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ ESCALINGMETHOD method,
+ std::shared_ptr<GLSLOutput> output);
+ ~YUV2RGBFilterShader4() override;
+
+protected:
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+
+ GLuint m_kernelTex = 0;
+ GLint m_hKernTex = -1;
+ ESCALINGMETHOD m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST;
+};
+
+} // namespace GL
+} // end namespace
+
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp
new file mode 100644
index 0000000..48987e0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2007 d4rk
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "YUV2RGBShaderGLES.h"
+
+#include "../RenderFlags.h"
+#include "settings/AdvancedSettings.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+
+#include <sstream>
+#include <string>
+
+using namespace Shaders::GLES;
+
+//////////////////////////////////////////////////////////////////////
+// BaseYUV2RGBGLSLShader - base class for GLSL YUV2RGB shaders
+//////////////////////////////////////////////////////////////////////
+
+BaseYUV2RGBGLSLShader::BaseYUV2RGBGLSLShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod)
+{
+ m_width = 1;
+ m_height = 1;
+ m_field = 0;
+ m_format = format;
+
+ m_black = 0.0f;
+ m_contrast = 1.0f;
+
+ m_convertFullRange = false;
+
+ if (m_format == SHADER_YV12)
+ m_defines += "#define XBMC_YV12\n";
+ else if (m_format == SHADER_NV12)
+ m_defines += "#define XBMC_NV12\n";
+ else if (m_format == SHADER_NV12_RRG)
+ m_defines += "#define XBMC_NV12_RRG\n";
+ else
+ CLog::Log(LOGERROR, "GLES: BaseYUV2RGBGLSLShader - unsupported format {}", m_format);
+
+ if (dstPrimaries != srcPrimaries)
+ {
+ m_colorConversion = true;
+ m_defines += "#define XBMC_COL_CONVERSION\n";
+ }
+
+ if (toneMap)
+ {
+ m_toneMapping = true;
+ m_toneMappingMethod = toneMapMethod;
+ if (toneMapMethod == VS_TONEMAPMETHOD_REINHARD)
+ m_defines += "#define KODI_TONE_MAPPING_REINHARD\n";
+ else if (toneMapMethod == VS_TONEMAPMETHOD_ACES)
+ m_defines += "#define KODI_TONE_MAPPING_ACES\n";
+ else if (toneMapMethod == VS_TONEMAPMETHOD_HABLE)
+ m_defines += "#define KODI_TONE_MAPPING_HABLE\n";
+ }
+
+ VertexShader()->LoadSource("gles_yuv2rgb.vert", m_defines);
+
+ CLog::Log(LOGDEBUG, "GLES: using shader format: {}", m_format);
+ CLog::Log(LOGDEBUG, "GLES: using tonemap method: {}", m_toneMappingMethod);
+
+ m_convMatrix.SetSourceColorPrimaries(srcPrimaries).SetDestinationColorPrimaries(dstPrimaries);
+}
+
+BaseYUV2RGBGLSLShader::~BaseYUV2RGBGLSLShader()
+{
+ Free();
+}
+
+void BaseYUV2RGBGLSLShader::OnCompiledAndLinked()
+{
+ m_hVertex = glGetAttribLocation(ProgramHandle(), "m_attrpos");
+ m_hYcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordY");
+ m_hUcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordU");
+ m_hVcoord = glGetAttribLocation(ProgramHandle(), "m_attrcordV");
+ m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj");
+ m_hModel = glGetUniformLocation(ProgramHandle(), "m_model");
+ m_hAlpha = glGetUniformLocation(ProgramHandle(), "m_alpha");
+ m_hYTex = glGetUniformLocation(ProgramHandle(), "m_sampY");
+ m_hUTex = glGetUniformLocation(ProgramHandle(), "m_sampU");
+ m_hVTex = glGetUniformLocation(ProgramHandle(), "m_sampV");
+ m_hYuvMat = glGetUniformLocation(ProgramHandle(), "m_yuvmat");
+ m_hStep = glGetUniformLocation(ProgramHandle(), "m_step");
+ m_hPrimMat = glGetUniformLocation(ProgramHandle(), "m_primMat");
+ m_hGammaSrc = glGetUniformLocation(ProgramHandle(), "m_gammaSrc");
+ m_hGammaDstInv = glGetUniformLocation(ProgramHandle(), "m_gammaDstInv");
+ m_hCoefsDst = glGetUniformLocation(ProgramHandle(), "m_coefsDst");
+ m_hToneP1 = glGetUniformLocation(ProgramHandle(), "m_toneP1");
+ m_hLuminance = glGetUniformLocation(ProgramHandle(), "m_luminance");
+ VerifyGLState();
+}
+
+bool BaseYUV2RGBGLSLShader::OnEnabled()
+{
+ // set shader attributes once enabled
+ glUniform1i(m_hYTex, 0);
+ glUniform1i(m_hUTex, 1);
+ glUniform1i(m_hVTex, 2);
+ glUniform2f(m_hStep, 1.0 / m_width, 1.0 / m_height);
+
+ m_convMatrix.SetDestinationContrast(m_contrast)
+ .SetDestinationBlack(m_black)
+ .SetDestinationLimitedRange(!m_convertFullRange);
+
+ Matrix4 yuvMat = m_convMatrix.GetYuvMat();
+ glUniformMatrix4fv(m_hYuvMat, 1, GL_FALSE, yuvMat.ToRaw());
+ glUniformMatrix4fv(m_hProj, 1, GL_FALSE, m_proj);
+ glUniformMatrix4fv(m_hModel, 1, GL_FALSE, m_model);
+ glUniform1f(m_hAlpha, m_alpha);
+
+ if (m_colorConversion)
+ {
+ Matrix3 primMat = m_convMatrix.GetPrimMat();
+ glUniformMatrix3fv(m_hPrimMat, 1, GL_FALSE, primMat.ToRaw());
+ glUniform1f(m_hGammaSrc, m_convMatrix.GetGammaSrc());
+ glUniform1f(m_hGammaDstInv, 1 / m_convMatrix.GetGammaDst());
+ }
+
+ if (m_toneMapping)
+ {
+ if (m_toneMappingMethod == VS_TONEMAPMETHOD_REINHARD)
+ {
+ float param = 0.7;
+
+ if (m_hasLightMetadata)
+ {
+ param = log10(100) / log10(m_lightMetadata.MaxCLL);
+ }
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance)
+ {
+ param = log10(100) /
+ log10(m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den);
+ }
+
+ // Sanity check
+ if (param < 0.1f || param > 5.0f)
+ {
+ param = 0.7f;
+ }
+
+ param *= m_toneMappingParam;
+
+ Matrix3x1 coefs = m_convMatrix.GetRGBYuvCoefs(AVColorSpace::AVCOL_SPC_BT709);
+ glUniform3f(m_hCoefsDst, coefs[0], coefs[1], coefs[2]);
+ glUniform1f(m_hToneP1, param);
+ }
+ else if (m_toneMappingMethod == VS_TONEMAPMETHOD_ACES)
+ {
+ glUniform1f(m_hLuminance, GetLuminanceValue());
+ glUniform1f(m_hToneP1, m_toneMappingParam);
+ }
+ else if (m_toneMappingMethod == VS_TONEMAPMETHOD_HABLE)
+ {
+ float lumin = GetLuminanceValue();
+ float param = (10000.0f / lumin) * (2.0f / m_toneMappingParam);
+ glUniform1f(m_hLuminance, lumin);
+ glUniform1f(m_hToneP1, param);
+ }
+ }
+
+ VerifyGLState();
+
+ return true;
+}
+
+void BaseYUV2RGBGLSLShader::OnDisabled()
+{
+}
+
+void BaseYUV2RGBGLSLShader::Free()
+{
+}
+
+void BaseYUV2RGBGLSLShader::SetColParams(AVColorSpace colSpace, int bits, bool limited,
+ int textureBits)
+{
+ if (colSpace == AVCOL_SPC_UNSPECIFIED)
+ {
+ if (m_width > 1024 || m_height >= 600)
+ colSpace = AVCOL_SPC_BT709;
+ else
+ colSpace = AVCOL_SPC_BT470BG;
+ }
+
+ m_convMatrix.SetSourceColorSpace(colSpace)
+ .SetSourceBitDepth(bits)
+ .SetSourceLimitedRange(limited)
+ .SetSourceTextureBitDepth(textureBits);
+}
+
+void BaseYUV2RGBGLSLShader::SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata,
+ bool hasLightMetadata, AVContentLightMetadata lightMetadata)
+{
+ m_hasDisplayMetadata = hasDisplayMetadata;
+ m_displayMetadata = displayMetadata;
+ m_hasLightMetadata = hasLightMetadata;
+ m_lightMetadata = lightMetadata;
+}
+
+float BaseYUV2RGBGLSLShader::GetLuminanceValue() const
+{
+ float lum1 = 400.0f; // default for bad quality HDR-PQ sources (with no metadata)
+ float lum2 = lum1;
+ float lum3 = lum1;
+
+ if (m_hasLightMetadata)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ if (m_lightMetadata.MaxCLL >= lum)
+ {
+ lum1 = static_cast<float>(lum);
+ lum2 = static_cast<float>(m_lightMetadata.MaxCLL);
+ }
+ else
+ {
+ lum1 = static_cast<float>(m_lightMetadata.MaxCLL);
+ lum2 = static_cast<float>(lum);
+ }
+ lum3 = static_cast<float>(m_lightMetadata.MaxFALL);
+ lum1 = (lum1 * 0.5f) + (lum2 * 0.2f) + (lum3 * 0.3f);
+ }
+ else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance &&
+ m_displayMetadata.max_luminance.num > 0)
+ {
+ uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den;
+ lum1 = static_cast<float>(lum);
+ }
+
+ return lum1;
+}
+
+//////////////////////////////////////////////////////////////////////
+// YUV2RGBProgressiveShader - YUV2RGB with no deinterlacing
+// Use for weave deinterlacing / progressive
+//////////////////////////////////////////////////////////////////////
+
+YUV2RGBProgressiveShader::YUV2RGBProgressiveShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod)
+ : BaseYUV2RGBGLSLShader(format, dstPrimaries, srcPrimaries, toneMap, toneMapMethod)
+{
+ PixelShader()->LoadSource("gles_yuv2rgb_basic.frag", m_defines);
+ PixelShader()->InsertSource("gles_tonemap.frag", "void main()");
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// YUV2RGBBobShader - YUV2RGB with Bob deinterlacing
+//////////////////////////////////////////////////////////////////////
+
+YUV2RGBBobShader::YUV2RGBBobShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod)
+ : BaseYUV2RGBGLSLShader(format, dstPrimaries, srcPrimaries, toneMap, toneMapMethod)
+{
+ PixelShader()->LoadSource("gles_yuv2rgb_bob.frag", m_defines);
+ PixelShader()->InsertSource("gles_tonemap.frag", "void main()");
+}
+
+void YUV2RGBBobShader::OnCompiledAndLinked()
+{
+ BaseYUV2RGBGLSLShader::OnCompiledAndLinked();
+ m_hStepX = glGetUniformLocation(ProgramHandle(), "m_stepX");
+ m_hStepY = glGetUniformLocation(ProgramHandle(), "m_stepY");
+ m_hField = glGetUniformLocation(ProgramHandle(), "m_field");
+ VerifyGLState();
+}
+
+bool YUV2RGBBobShader::OnEnabled()
+{
+ if(!BaseYUV2RGBGLSLShader::OnEnabled())
+ return false;
+
+ glUniform1i(m_hField, m_field);
+ glUniform1f(m_hStepX, 1.0f / (float)m_width);
+ glUniform1f(m_hStepY, 1.0f / (float)m_height);
+ VerifyGLState();
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h
new file mode 100644
index 0000000..07b8a3c
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ConversionMatrix.h"
+#include "ShaderFormats.h"
+#include "cores/VideoSettings.h"
+#include "guilib/Shader.h"
+#include "utils/TransformMatrix.h"
+
+extern "C" {
+#include <libavutil/mastering_display_metadata.h>
+#include <libavutil/pixfmt.h>
+}
+
+namespace Shaders {
+namespace GLES
+{
+
+class BaseYUV2RGBGLSLShader : public CGLSLShaderProgram
+{
+ public:
+ BaseYUV2RGBGLSLShader(EShaderFormat format,
+ AVColorPrimaries dst,
+ AVColorPrimaries src,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod);
+ ~BaseYUV2RGBGLSLShader() override;
+ void SetField(int field) { m_field = field; }
+ void SetWidth(int w) { m_width = w; }
+ void SetHeight(int h) { m_height = h; }
+
+ void SetColParams(AVColorSpace colSpace, int bits, bool limited, int textureBits);
+ void SetBlack(float black) { m_black = black; }
+ void SetContrast(float contrast) { m_contrast = contrast; }
+ void SetConvertFullColorRange(bool convertFullRange) { m_convertFullRange = convertFullRange; }
+ void SetDisplayMetadata(bool hasDisplayMetadata, AVMasteringDisplayMetadata displayMetadata,
+ bool hasLightMetadata, AVContentLightMetadata lightMetadata);
+ void SetToneMapParam(float param) { m_toneMappingParam = param; }
+ float GetLuminanceValue() const;
+
+ GLint GetVertexLoc() { return m_hVertex; }
+ GLint GetYcoordLoc() { return m_hYcoord; }
+ GLint GetUcoordLoc() { return m_hUcoord; }
+ GLint GetVcoordLoc() { return m_hVcoord; }
+
+ void SetMatrices(const GLfloat *p, const GLfloat *m) { m_proj = p; m_model = m; }
+ void SetAlpha(GLfloat alpha) { m_alpha = alpha; }
+
+ protected:
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+ void OnDisabled() override;
+ void Free();
+
+ EShaderFormat m_format;
+ int m_width;
+ int m_height;
+ int m_field;
+ bool m_hasDisplayMetadata{false};
+ AVMasteringDisplayMetadata m_displayMetadata;
+ bool m_hasLightMetadata{false};
+ AVContentLightMetadata m_lightMetadata;
+ bool m_toneMapping{false};
+ ETONEMAPMETHOD m_toneMappingMethod{VS_TONEMAPMETHOD_REINHARD};
+ float m_toneMappingParam{1.0};
+
+ bool m_colorConversion{false};
+
+ float m_black;
+ float m_contrast;
+
+ std::string m_defines;
+
+ CConvertMatrix m_convMatrix;
+
+ // shader attribute handles
+ GLint m_hYTex{-1};
+ GLint m_hUTex{-1};
+ GLint m_hVTex{-1};
+ GLint m_hYuvMat{-1};
+ GLint m_hStep{-1};
+ GLint m_hGammaSrc{-1};
+ GLint m_hGammaDstInv{-1};
+ GLint m_hPrimMat{-1};
+ GLint m_hToneP1{-1};
+ GLint m_hCoefsDst{-1};
+ GLint m_hLuminance = -1;
+
+ GLint m_hVertex{-1};
+ GLint m_hYcoord{-1};
+ GLint m_hUcoord{-1};
+ GLint m_hVcoord{-1};
+ GLint m_hProj{-1};
+ GLint m_hModel{-1};
+ GLint m_hAlpha{-1};
+
+ const GLfloat *m_proj{nullptr};
+ const GLfloat *m_model{nullptr};
+ GLfloat m_alpha{1.0f};
+
+ bool m_convertFullRange;
+ };
+
+ class YUV2RGBProgressiveShader : public BaseYUV2RGBGLSLShader
+ {
+ public:
+ YUV2RGBProgressiveShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod);
+ };
+
+ class YUV2RGBBobShader : public BaseYUV2RGBGLSLShader
+ {
+ public:
+ YUV2RGBBobShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod);
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+
+ GLint m_hStepX = -1;
+ GLint m_hStepY = -1;
+ GLint m_hField = -1;
+ };
+
+ } // namespace GLES
+} // end namespace
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h
new file mode 100644
index 0000000..dc2cf1d
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h
@@ -0,0 +1,74 @@
+// Generated with https://git.fruit.je/src?a=blob;f=dither/dither.c;h=913d06c11f8d7cbd24121ecd772396b7c10f4826;hb=HEAD
+
+#pragma once
+
+#include <stdint.h>
+
+static const int dither_size = 64;
+static const int dither_size2 = 4096;
+static const uint16_t dither_matrix[] = {
+ 3, 2603, 743, 2361, 199, 2711, 806, 3181, 2113, 3820, 988, 3485, 1560, 2650, 327, 3063, 1660, 3340, 356, 1055, 2218, 2796, 3278, 528, 2104, 2909, 1465, 4076, 911, 3358, 1586, 3039, 41, 4038, 690, 3493, 155, 3754, 636, 3403, 271, 1355, 2973, 1816, 1287, 3908, 1095, 2549, 4086, 984, 3452, 357, 3560, 1662, 39, 1362, 2954, 1538, 552, 2823, 1871, 319, 2964, 792,
+ 3590, 1799, 4095, 1197, 3302, 2009, 3974, 1602, 475, 1433, 2801, 2059, 551, 4067, 1916, 914, 3887, 1308, 2969, 1817, 3780, 790, 1262, 1828, 3421, 802, 2454, 126, 3006, 2212, 536, 2513, 1029, 2142, 2920, 1641, 3178, 1902, 2848, 966, 2579, 3567, 2252, 142, 2798, 388, 3014, 2005, 216, 2285, 1583, 2994, 1091, 2813, 2002, 3712, 856, 4069, 2292, 1256, 3682, 2405, 1359, 3225,
+ 1046, 2855, 404, 2646, 1698, 545, 1138, 2982, 1985, 3610, 133, 3256, 1794, 971, 3167, 2251, 483, 2613, 668, 3468, 75, 3156, 2398, 3838, 201, 2690, 1060, 3599, 1820, 1168, 3890, 1743, 3564, 1487, 372, 2617, 1078, 462, 2193, 3915, 1659, 567, 1123, 3806, 1897, 3623, 849, 1483, 3513, 2857, 777, 2164, 3993, 584, 3338, 461, 2748, 1806, 206, 3264, 729, 1993, 3925, 492,
+ 2269, 1508, 3497, 927, 3743, 2109, 3469, 248, 2697, 1041, 2404, 1260, 3741, 2162, 52, 3526, 1214, 3722, 2085, 1449, 2557, 1938, 442, 1418, 2987, 1612, 3232, 2139, 654, 2884, 245, 2754, 621, 3060, 3858, 1341, 3698, 2948, 1436, 64, 3194, 2025, 3092, 1506, 595, 2216, 3252, 2632, 484, 1239, 3625, 122, 1864, 2589, 1475, 2360, 1195, 3588, 2536, 1010, 2890, 110, 2628, 1735,
+ 3110, 217, 1939, 2913, 101, 2483, 1274, 3124, 1636, 3791, 810, 3049, 417, 2828, 1416, 2515, 1992, 219, 3088, 844, 3343, 1171, 3632, 2201, 637, 3939, 354, 1330, 3800, 1943, 3284, 1390, 2347, 947, 2047, 200, 2128, 739, 3511, 2707, 1177, 4051, 336, 2501, 3472, 1316, 17, 2087, 3801, 1892, 2466, 1409, 3241, 1031, 3844, 152, 3123, 631, 1485, 3835, 1664, 3396, 1181, 3569,
+ 890, 3790, 2353, 1098, 3229, 820, 3910, 448, 2319, 579, 2618, 1903, 3502, 873, 3988, 585, 3015, 1639, 2261, 4055, 292, 2809, 894, 3168, 1959, 995, 2627, 3128, 2282, 506, 1050, 3986, 125, 3537, 2804, 1533, 3291, 2523, 1860, 539, 2328, 829, 2866, 1965, 941, 2718, 4021, 1444, 877, 3210, 695, 3755, 391, 2980, 839, 3378, 1739, 2217, 3031, 351, 2334, 760, 2468, 413,
+ 2833, 1382, 553, 4013, 1886, 2660, 1512, 2092, 3541, 1391, 3950, 202, 1543, 2705, 1734, 3312, 990, 3765, 607, 1317, 2458, 1813, 3906, 134, 2390, 3415, 1671, 38, 1218, 3668, 2535, 1552, 2657, 1883, 518, 4094, 1028, 304, 3884, 1303, 3557, 1680, 3406, 203, 3717, 1854, 422, 2304, 2944, 249, 2651, 1757, 2297, 1576, 2706, 2014, 524, 3970, 1139, 2063, 3639, 1417, 4053, 1763,
+ 2150, 3450, 2616, 1247, 334, 3355, 650, 2955, 20, 3187, 885, 2178, 3352, 507, 2351, 162, 2735, 1492, 3254, 2030, 3544, 565, 1575, 2921, 1376, 547, 4037, 2017, 3337, 1730, 287, 3247, 660, 3048, 1201, 2383, 1935, 3138, 1594, 2902, 132, 2758, 1358, 2279, 763, 3203, 1574, 3527, 1157, 3938, 958, 3383, 1130, 4012, 234, 3545, 1335, 2558, 27, 3206, 651, 2949, 209, 3170,
+ 977, 50, 1647, 3087, 2189, 1733, 3768, 1331, 2012, 2470, 1484, 2999, 979, 3863, 1288, 3603, 2147, 447, 2876, 6, 1122, 2673, 3368, 837, 3769, 2529, 965, 2699, 575, 2914, 2149, 954, 3693, 1635, 3441, 11, 3725, 780, 2425, 953, 3797, 2171, 677, 3957, 2587, 1108, 2849, 656, 2129, 1650, 2881, 63, 2612, 735, 2348, 951, 3106, 1614, 3490, 1834, 1223, 2597, 1523, 2306,
+ 3851, 1983, 3714, 599, 3524, 179, 2408, 904, 4050, 688, 3427, 369, 2671, 1994, 2931, 645, 1686, 3931, 1221, 2553, 3842, 2071, 305, 2276, 1829, 221, 3227, 1452, 3552, 1096, 3956, 1472, 2438, 335, 2187, 2889, 1428, 2746, 415, 3224, 1851, 1086, 2957, 350, 1764, 3393, 116, 2516, 3662, 398, 2209, 3716, 1310, 3464, 1480, 3762, 381, 2853, 772, 2374, 3943, 487, 3542, 616,
+ 1302, 2963, 925, 2731, 1148, 2869, 1414, 3026, 310, 2740, 1744, 3699, 1573, 76, 1126, 3300, 2493, 878, 3438, 1849, 785, 1478, 3653, 2765, 1085, 3605, 2246, 383, 1878, 2479, 106, 2841, 801, 3901, 1842, 610, 3518, 1104, 3996, 2270, 549, 3492, 2105, 3640, 931, 2224, 3916, 1866, 816, 3193, 1024, 1845, 3161, 502, 2990, 1942, 2475, 1035, 3673, 243, 1977, 3097, 1679, 2121,
+ 3318, 363, 2287, 1590, 3989, 500, 3453, 1796, 3659, 1240, 2331, 834, 3115, 2537, 4070, 2050, 212, 3079, 2267, 371, 2834, 3223, 1270, 527, 3130, 2052, 938, 2757, 3775, 740, 3471, 1656, 3281, 1062, 3102, 1349, 2512, 2090, 173, 1577, 3100, 1412, 40, 1931, 3152, 520, 1253, 2906, 1467, 2366, 4081, 254, 2089, 2522, 1174, 123, 4040, 2205, 1557, 3196, 1076, 2686, 103, 3920,
+ 850, 2585, 3651, 147, 1909, 2562, 989, 2275, 761, 3235, 186, 3852, 1212, 1888, 603, 1399, 3749, 1561, 711, 3969, 2138, 158, 1997, 4079, 1615, 65, 3913, 1778, 1301, 3072, 2083, 394, 2599, 1990, 154, 3818, 480, 3351, 1827, 3665, 853, 2734, 4066, 1128, 2538, 1619, 3257, 192, 3606, 557, 1625, 2793, 867, 3894, 1791, 3366, 1384, 472, 2779, 843, 3802, 1332, 2434, 1453,
+ 2867, 1807, 1198, 2181, 3244, 1311, 3823, 81, 2649, 1381, 2776, 2046, 449, 3574, 2161, 3237, 1067, 2118, 2996, 1700, 1044, 3696, 2654, 759, 2455, 2966, 707, 3157, 256, 2362, 920, 4058, 671, 3601, 2263, 1528, 2976, 922, 2630, 373, 2432, 1971, 580, 3385, 289, 3770, 1960, 2598, 992, 2160, 3069, 1131, 3240, 370, 2745, 899, 3036, 1863, 3575, 2268, 399, 2953, 724, 3270,
+ 314, 3516, 658, 3795, 412, 2997, 809, 3349, 1771, 3999, 702, 3449, 1657, 2621, 280, 2795, 512, 3615, 66, 2531, 3420, 477, 1568, 3357, 1250, 2100, 3508, 1001, 2006, 3727, 1397, 2988, 1812, 1135, 2800, 812, 1922, 3587, 1395, 3935, 1167, 3055, 1554, 2231, 2858, 1203, 773, 3964, 1703, 3433, 5, 3812, 1918, 2393, 1457, 3778, 664, 2569, 53, 1468, 3387, 1727, 4077, 2222,
+ 1037, 1966, 3089, 1367, 2762, 1694, 2355, 1087, 2837, 328, 2373, 1286, 3126, 961, 3928, 1746, 2430, 1230, 3177, 857, 1435, 2888, 2198, 264, 3777, 456, 1539, 2578, 3371, 592, 2655, 36, 2173, 3345, 297, 4010, 2397, 73, 2832, 661, 3310, 168, 3830, 691, 1420, 3547, 2305, 387, 2912, 742, 2643, 1544, 614, 3619, 164, 2172, 3316, 1259, 3978, 2042, 613, 2743, 210, 1413,
+ 3680, 2637, 13, 2316, 709, 4059, 220, 3670, 1569, 3285, 903, 3786, 31, 2293, 1354, 3342, 738, 4033, 2221, 1877, 3879, 728, 3246, 1926, 1141, 2821, 4006, 156, 1193, 1907, 3231, 1507, 3753, 948, 3083, 1667, 718, 3142, 1780, 2278, 1338, 2747, 1716, 3139, 2136, 94, 3085, 1978, 1356, 3723, 1065, 2101, 3008, 1300, 2831, 1899, 455, 3113, 939, 2898, 3608, 1097, 2126, 3147,
+ 532, 1527, 3911, 1769, 3322, 1296, 2656, 2078, 563, 2000, 2586, 1714, 2926, 782, 2677, 181, 2938, 1535, 396, 2840, 197, 2435, 1032, 3626, 2542, 653, 2235, 1800, 2875, 3872, 353, 2456, 644, 2606, 1351, 2132, 3514, 1100, 3857, 324, 3451, 796, 2526, 419, 3747, 1766, 981, 3362, 2400, 187, 2728, 4023, 416, 3499, 881, 3849, 1521, 2431, 1841, 333, 2299, 1567, 3831, 964,
+ 2402, 3411, 1068, 2543, 340, 3109, 907, 3460, 1372, 3942, 259, 3408, 1103, 3631, 1632, 3813, 1049, 2043, 3581, 1344, 3332, 1604, 2983, 21, 1663, 3305, 1272, 3664, 519, 2208, 1048, 3555, 2029, 3949, 465, 2896, 228, 2623, 1579, 2478, 1450, 4045, 1147, 3326, 909, 2573, 4003, 513, 1249, 3532, 1775, 910, 2340, 1717, 2610, 624, 3391, 194, 3912, 2704, 765, 3243, 108, 2919,
+ 1689, 291, 2959, 826, 3779, 1626, 2250, 74, 2694, 847, 3013, 1460, 2401, 382, 2864, 627, 2463, 3140, 797, 2666, 515, 3990, 824, 2156, 3846, 341, 2911, 932, 2723, 1638, 3080, 1387, 149, 1748, 3266, 1202, 3711, 942, 3248, 744, 2970, 24, 2344, 1910, 2977, 232, 2107, 2892, 1850, 3047, 343, 2037, 3402, 85, 3220, 1278, 2200, 2854, 905, 1430, 3746, 1975, 2506, 686,
+ 3997, 2247, 1255, 3484, 2152, 544, 3034, 3870, 1520, 3528, 2180, 606, 4090, 1781, 2123, 3354, 1591, 80, 3767, 1955, 1245, 2363, 1750, 3165, 1089, 2472, 1566, 3455, 83, 4085, 692, 2685, 3475, 2356, 741, 2769, 1693, 2314, 395, 3637, 2070, 1658, 3467, 481, 1407, 3660, 1531, 753, 3819, 1047, 2532, 3902, 1161, 2491, 1621, 4065, 433, 1676, 3573, 2226, 490, 1189, 3091, 1790,
+ 944, 3309, 1920, 135, 1440, 2799, 1859, 1115, 2427, 443, 1847, 2816, 993, 3041, 238, 1199, 3981, 2254, 949, 2900, 3456, 237, 3658, 548, 1961, 3024, 708, 2313, 1976, 1178, 3301, 2084, 556, 1321, 3880, 2015, 90, 4016, 1914, 2680, 970, 3118, 1194, 3930, 2508, 937, 3282, 2439, 57, 2777, 1578, 510, 3127, 769, 2939, 957, 2041, 3238, 28, 3019, 1890, 3389, 263, 3676,
+ 2076, 474, 3067, 2450, 4043, 800, 3556, 250, 3365, 1306, 3841, 107, 3419, 1410, 3570, 2197, 499, 3093, 1831, 425, 1550, 2652, 924, 2414, 4030, 190, 3546, 1007, 3708, 2645, 290, 1607, 3728, 2591, 358, 3205, 1162, 2863, 1346, 633, 3874, 279, 2766, 766, 2082, 3025, 438, 1946, 3629, 808, 3473, 2307, 1343, 3796, 265, 2415, 3729, 1323, 2473, 793, 3836, 1350, 2782, 1070,
+ 2624, 3860, 864, 1741, 437, 3098, 985, 2607, 1934, 2933, 828, 2703, 1889, 466, 2572, 1493, 2794, 1136, 3643, 2056, 3914, 1172, 3412, 1826, 1327, 3146, 1651, 2530, 440, 1486, 3073, 2242, 1033, 1815, 2972, 1515, 3463, 489, 2469, 3347, 1454, 2311, 1726, 3197, 124, 1622, 4091, 1156, 2170, 2946, 1835, 167, 2700, 1968, 3320, 1227, 469, 3121, 1018, 2727, 1761, 428, 1969, 3265,
+ 60, 1524, 2301, 3593, 2683, 1269, 2207, 3891, 593, 1564, 3633, 1176, 2375, 3745, 967, 3904, 752, 3313, 180, 2452, 701, 2820, 59, 2923, 467, 2163, 866, 3941, 1898, 3392, 657, 3991, 2, 3592, 831, 2378, 982, 3798, 2099, 140, 3062, 876, 3737, 1279, 3548, 2376, 725, 3344, 301, 1318, 3961, 1107, 3562, 578, 1517, 2861, 2184, 1643, 3922, 193, 3479, 2309, 4088, 840,
+ 2487, 3498, 1235, 272, 2031, 3707, 33, 1394, 3148, 2120, 225, 3290, 731, 2044, 3208, 8, 2349, 1691, 2715, 1371, 3162, 1631, 2233, 3821, 1079, 3481, 2806, 112, 2929, 1054, 2420, 1446, 2847, 1933, 3180, 223, 2736, 1879, 1211, 3582, 1773, 2441, 424, 2648, 588, 1927, 2788, 1500, 3117, 2447, 602, 2880, 2190, 1856, 4015, 96, 3628, 604, 1913, 2882, 1319, 628, 1466, 3027,
+ 1688, 564, 2732, 3236, 715, 1613, 3306, 2273, 430, 3980, 2548, 1627, 2865, 348, 1722, 2958, 1309, 3690, 860, 4072, 397, 3561, 634, 1438, 2674, 1870, 663, 2377, 1295, 3825, 274, 3487, 1173, 550, 2169, 4060, 756, 3263, 367, 2611, 623, 4032, 2067, 3356, 1064, 3885, 178, 3645, 818, 1809, 3377, 1456, 375, 3137, 859, 2682, 1073, 3400, 2437, 913, 3242, 2604, 3638, 325,
+ 2206, 3898, 1982, 1084, 4018, 2521, 1020, 2856, 1832, 1234, 886, 3480, 1277, 4046, 2215, 1045, 3428, 283, 3021, 2027, 1284, 2642, 1912, 3262, 257, 3917, 1601, 3595, 2065, 716, 3212, 1633, 2687, 3703, 1021, 1674, 3029, 1378, 3896, 1609, 3018, 1365, 43, 1708, 2829, 1553, 2514, 1266, 2300, 3829, 14, 2525, 3720, 1292, 2380, 1718, 3050, 1464, 303, 3994, 2055, 114, 1923, 1142,
+ 3184, 1353, 145, 3001, 2102, 313, 3517, 679, 3685, 2016, 2915, 95, 2459, 641, 3163, 454, 2600, 1767, 2277, 586, 3444, 163, 3764, 962, 2429, 1140, 3191, 320, 2981, 1753, 2564, 845, 2054, 307, 3304, 2232, 79, 2659, 827, 2294, 1026, 3489, 2560, 3804, 361, 3287, 687, 3053, 408, 1375, 2985, 1774, 778, 3272, 262, 3900, 517, 2103, 3151, 726, 1565, 3759, 2502, 3520,
+ 784, 2324, 3740, 1758, 955, 3131, 1546, 2413, 195, 3211, 786, 3757, 1785, 3535, 1434, 2049, 3882, 1124, 3735, 1459, 2554, 2079, 1505, 3040, 751, 2752, 1419, 2329, 972, 3736, 131, 3104, 3973, 1290, 2770, 672, 3448, 1762, 3694, 231, 3222, 521, 1963, 855, 2326, 1160, 3979, 1697, 3509, 2066, 617, 4052, 2290, 1600, 2810, 1158, 2570, 3549, 998, 2288, 2993, 1237, 503, 1777,
+ 2860, 414, 2726, 570, 3649, 2256, 841, 3862, 1322, 2561, 1652, 2280, 1069, 2583, 170, 2886, 673, 2443, 68, 3334, 734, 3953, 364, 2214, 3677, 32, 4063, 573, 3279, 1469, 2407, 1911, 511, 2343, 1504, 3807, 1053, 2381, 1297, 2785, 1653, 2417, 3646, 1445, 3169, 1996, 113, 2771, 999, 2546, 3253, 1188, 153, 3641, 591, 3414, 1900, 46, 1782, 3814, 240, 2196, 3417, 969,
+ 4047, 2114, 1481, 3381, 1952, 87, 2952, 2053, 3360, 589, 4022, 337, 3380, 833, 3960, 1603, 3293, 963, 3046, 1981, 2802, 991, 3174, 1824, 1164, 3132, 1620, 2676, 2081, 378, 3883, 1094, 2960, 3580, 174, 2034, 2878, 379, 3315, 612, 3983, 1238, 160, 2842, 562, 3404, 2399, 798, 3731, 269, 1562, 2907, 1973, 2631, 1403, 2320, 926, 3683, 2764, 1133, 3195, 1443, 2667, 1872,
+ 0, 3099, 1027, 2462, 1191, 3982, 1617, 365, 1118, 3077, 1389, 2713, 1811, 2995, 2127, 482, 2011, 3598, 1655, 470, 1369, 3565, 2332, 649, 2850, 1930, 794, 3572, 1120, 3394, 887, 2609, 1587, 733, 1846, 3228, 836, 3888, 2010, 2688, 945, 2956, 2143, 1677, 3704, 1282, 1855, 2968, 1431, 2308, 3919, 453, 3386, 720, 3972, 345, 3221, 2001, 431, 2426, 667, 3893, 377, 3617,
+ 2039, 746, 3832, 296, 3286, 615, 2357, 3613, 1940, 2262, 37, 3578, 568, 1326, 3721, 1153, 2789, 270, 2541, 4035, 2203, 146, 1534, 3375, 306, 3871, 2186, 214, 2556, 1893, 3071, 56, 3350, 2074, 4089, 1186, 2626, 1491, 19, 1713, 3470, 390, 3803, 722, 2566, 299, 4075, 497, 3437, 1102, 1802, 2580, 1144, 1862, 2852, 1580, 2485, 1271, 4073, 1729, 2870, 1222, 2339, 872,
+ 3297, 2551, 1294, 2897, 1737, 2719, 1313, 3042, 771, 3810, 1490, 2411, 1949, 2664, 130, 3116, 1494, 3787, 1246, 598, 3260, 1848, 3773, 1112, 2480, 946, 2941, 1532, 4009, 611, 1682, 3824, 1226, 2729, 285, 3028, 534, 3531, 2509, 3150, 1265, 2385, 1405, 3329, 1768, 3108, 1190, 1987, 2724, 54, 3577, 842, 3808, 2219, 119, 3751, 700, 2986, 884, 3482, 100, 3215, 1408, 2759,
+ 1593, 478, 3558, 2062, 817, 3734, 207, 1797, 2581, 468, 3158, 815, 3945, 980, 3486, 2259, 770, 1788, 3185, 2370, 1030, 2903, 748, 2708, 1805, 3602, 530, 3218, 1248, 2238, 2894, 486, 2396, 851, 3622, 1666, 2354, 1137, 1947, 712, 4031, 902, 3009, 78, 2317, 803, 3533, 2486, 879, 3143, 2115, 3017, 317, 3209, 1244, 2058, 3407, 253, 2266, 1526, 2517, 767, 3657, 196,
+ 4011, 1861, 2346, 109, 3086, 1011, 2112, 4061, 1229, 3447, 1683, 2767, 282, 2891, 1843, 494, 3372, 2639, 16, 1951, 3705, 312, 2068, 4002, 70, 1455, 2756, 2003, 157, 3454, 1117, 3687, 1366, 3207, 2144, 674, 3869, 182, 3671, 2188, 277, 2684, 1598, 3881, 1061, 2822, 198, 1616, 3760, 1393, 569, 1673, 2712, 1522, 3591, 861, 2575, 1752, 3288, 1025, 3948, 1814, 2134, 3038,
+ 933, 3239, 1213, 3897, 1582, 2423, 3182, 626, 2681, 136, 2327, 1127, 3612, 2179, 1182, 4093, 2095, 935, 3895, 697, 2528, 1597, 3033, 1152, 2359, 3325, 1016, 3799, 1756, 2593, 342, 2008, 2817, 121, 1497, 3280, 1760, 2786, 1285, 3084, 1732, 3597, 576, 1917, 3426, 1357, 3940, 2091, 376, 3335, 2013, 3877, 819, 2440, 401, 3070, 1345, 3866, 541, 2797, 288, 3333, 574, 1529,
+ 2457, 405, 2846, 659, 3395, 346, 1184, 3483, 1595, 3853, 901, 3275, 1755, 680, 3056, 229, 1581, 2975, 1348, 3330, 1233, 3609, 485, 3435, 705, 2048, 322, 2461, 736, 3944, 1545, 3341, 936, 4017, 2614, 402, 2428, 776, 3348, 488, 2584, 1228, 2158, 3061, 318, 2391, 632, 3022, 2453, 1043, 2778, 141, 2961, 1175, 4044, 1919, 25, 2159, 3145, 1624, 2336, 1225, 2629, 3840,
+ 1106, 3501, 2148, 1479, 2641, 1830, 2922, 1998, 504, 2096, 3005, 432, 2488, 3826, 1402, 2448, 3692, 366, 2225, 2772, 175, 2140, 2826, 1334, 2608, 3739, 1571, 3495, 2108, 1232, 2678, 559, 2325, 1905, 1145, 3503, 1360, 3955, 2124, 994, 3855, 151, 3370, 1013, 2737, 1709, 3630, 1462, 783, 4020, 1801, 1307, 3689, 2274, 629, 2670, 3538, 787, 1289, 3585, 917, 3742, 1684, 51,
+ 3002, 1712, 218, 3642, 749, 3954, 26, 3600, 1000, 2721, 1361, 3656, 1210, 58, 3153, 640, 1974, 3213, 898, 1819, 4026, 928, 1779, 3839, 139, 1056, 2989, 525, 3234, 10, 3553, 1648, 3792, 258, 3101, 699, 2838, 49, 1474, 2945, 1772, 2412, 1547, 4062, 822, 3245, 7, 2035, 3135, 298, 3384, 2192, 498, 1838, 3364, 1038, 1556, 3023, 2444, 177, 2716, 501, 2905, 2061,
+ 883, 4084, 2602, 1200, 2386, 1392, 2812, 2248, 1728, 4027, 242, 1875, 2873, 2166, 1644, 3554, 1080, 2524, 3772, 505, 2369, 3160, 434, 2286, 3192, 1882, 2392, 1363, 2720, 1970, 1003, 2918, 895, 2763, 2106, 3730, 1585, 2379, 3589, 418, 3259, 669, 2773, 352, 2291, 1092, 2691, 3724, 1170, 2661, 952, 2862, 1518, 3043, 222, 2518, 3905, 360, 1692, 4019, 1936, 3432, 973, 3258,
+ 2245, 555, 1925, 3074, 444, 3319, 1066, 392, 3204, 813, 2358, 3461, 865, 3976, 458, 2644, 1839, 127, 1514, 2917, 1163, 2028, 3519, 1220, 642, 4068, 278, 3604, 605, 3975, 2503, 406, 3359, 1793, 1264, 471, 3328, 1059, 1921, 2672, 1254, 3794, 1364, 3621, 1789, 3488, 1563, 652, 2337, 1695, 3822, 82, 3465, 1231, 3672, 2045, 940, 2223, 3120, 685, 1320, 2367, 1784, 326,
+ 3766, 1519, 3424, 923, 3706, 1867, 2155, 3783, 1451, 2937, 1608, 543, 2596, 1477, 3336, 960, 3899, 2032, 3425, 863, 3661, 44, 1589, 2893, 2135, 1511, 3064, 1759, 2310, 1293, 1833, 3761, 2227, 102, 4078, 2033, 2702, 239, 3984, 732, 2228, 98, 2992, 713, 2590, 233, 2433, 3992, 384, 2965, 755, 2477, 1881, 681, 2775, 529, 1804, 3373, 1155, 2550, 3793, 148, 3507, 2693,
+ 1057, 2924, 93, 2714, 1588, 204, 2971, 694, 2544, 104, 3892, 1958, 3250, 189, 2130, 3003, 355, 3122, 648, 2582, 1740, 2213, 3932, 331, 3331, 891, 2533, 781, 3176, 185, 3004, 721, 1180, 3125, 2333, 908, 1654, 3096, 1421, 3431, 2825, 1736, 2125, 3339, 1423, 3058, 805, 1932, 3226, 1386, 3571, 1105, 4092, 2167, 1548, 3771, 2899, 69, 3607, 464, 1724, 3045, 1204, 1964,
+ 2389, 706, 3971, 1132, 2449, 3859, 1166, 3369, 1280, 3476, 930, 2422, 1268, 3718, 745, 1705, 2717, 1379, 2271, 3847, 450, 2814, 1022, 2692, 1315, 3647, 89, 3845, 1052, 3536, 1584, 2157, 3652, 1510, 682, 2868, 3624, 538, 2489, 1006, 362, 3876, 1051, 435, 3774, 1146, 3543, 1005, 2237, 191, 2023, 2636, 410, 3200, 183, 2321, 889, 2133, 1488, 2874, 2234, 643, 4041, 389,
+ 3594, 1642, 2094, 3295, 523, 2040, 2675, 359, 2295, 1853, 3051, 316, 2824, 1002, 2500, 4071, 1114, 3529, 211, 1304, 3075, 1441, 3382, 693, 2387, 1665, 2901, 1263, 2445, 1915, 581, 3308, 329, 2481, 3834, 188, 2165, 1217, 3750, 1876, 3283, 1470, 2725, 2281, 1702, 2815, 77, 2594, 3929, 1629, 3324, 906, 2940, 1267, 3523, 1388, 4008, 2662, 762, 3702, 1063, 2749, 1525, 3159,
+ 852, 2998, 261, 2739, 1822, 976, 3550, 1672, 4057, 554, 1374, 3828, 1894, 3202, 4, 2220, 572, 1884, 3198, 2119, 723, 3618, 176, 2036, 3968, 427, 2183, 3401, 368, 4036, 2871, 1324, 2709, 1880, 1072, 3149, 1723, 2811, 23, 2394, 757, 2984, 159, 3966, 582, 2093, 3268, 1463, 618, 2859, 463, 3715, 1725, 2283, 655, 3068, 386, 1675, 3011, 266, 1986, 3410, 12, 2073,
+ 2474, 1216, 3732, 900, 3924, 2364, 47, 2830, 871, 2605, 3255, 2191, 597, 1610, 3443, 1406, 3655, 2555, 875, 3995, 1570, 2471, 1731, 3000, 804, 3230, 975, 1798, 2620, 897, 2211, 48, 3923, 835, 3446, 2257, 583, 3311, 1549, 4054, 2098, 1273, 3405, 1967, 1340, 3663, 862, 2007, 3748, 1219, 1924, 2416, 30, 3861, 1165, 2467, 1929, 3379, 1121, 3952, 2567, 915, 1787, 3909,
+ 509, 3442, 1501, 2563, 457, 1559, 3353, 1352, 3654, 2060, 150, 1125, 3579, 2574, 1039, 2962, 323, 1628, 3010, 88, 2753, 535, 3713, 1040, 2663, 1891, 3811, 213, 3616, 1429, 3439, 1715, 2117, 2932, 380, 1385, 3889, 1110, 2592, 608, 3094, 411, 2547, 807, 3119, 338, 2315, 3037, 224, 3398, 2710, 1017, 3103, 1516, 3292, 236, 3744, 719, 2382, 1555, 537, 3522, 2647, 1305,
+ 2774, 2024, 128, 3190, 1945, 2947, 646, 2510, 421, 1236, 3936, 2698, 1792, 284, 3756, 832, 2322, 3867, 1090, 2241, 3323, 1333, 2137, 3141, 22, 1299, 2803, 2080, 768, 2835, 452, 3066, 639, 1243, 3679, 2635, 2019, 321, 3627, 1312, 1989, 3726, 1489, 3551, 2122, 1605, 4064, 1081, 1770, 2204, 666, 3998, 1858, 540, 2111, 2668, 1427, 2936, 97, 3129, 1895, 2210, 241, 3719,
+ 1009, 3307, 1810, 934, 3634, 1119, 3959, 1719, 3445, 2916, 1599, 533, 3179, 2365, 1447, 2844, 1953, 526, 3430, 1887, 789, 3815, 347, 1513, 4082, 2460, 542, 3172, 1670, 2260, 3987, 1368, 3510, 2240, 1808, 115, 3035, 1618, 2146, 3361, 184, 2335, 662, 2783, 42, 2925, 596, 2571, 3596, 956, 2877, 295, 2552, 3494, 987, 3926, 893, 1840, 3667, 1241, 3856, 1083, 3216, 727,
+ 2342, 409, 4080, 2436, 300, 2741, 2185, 171, 2026, 683, 2230, 3738, 1339, 747, 4005, 138, 3219, 1502, 2565, 255, 3076, 1699, 2368, 3418, 638, 1747, 3500, 1113, 3763, 120, 1023, 2689, 252, 2792, 774, 4025, 1008, 3217, 791, 2665, 1150, 4004, 1837, 997, 3850, 1437, 3413, 1984, 129, 3171, 1606, 3688, 1206, 2244, 1803, 429, 3112, 2418, 811, 2696, 344, 2496, 1749, 2967,
+ 1377, 2839, 1099, 2077, 3271, 838, 1442, 3583, 2679, 1034, 3054, 62, 2069, 3267, 1852, 2519, 1205, 3695, 869, 4024, 2176, 1101, 2974, 918, 2151, 2843, 267, 2406, 1496, 2951, 2174, 1690, 3817, 1143, 3299, 1979, 2495, 476, 3868, 1498, 2979, 493, 3134, 2490, 1937, 445, 2350, 854, 3946, 1169, 2341, 775, 3007, 84, 2805, 3563, 2097, 215, 3294, 1640, 3477, 870, 4014, 71,
+ 3678, 1711, 3504, 670, 1623, 3864, 2883, 546, 1645, 4034, 1396, 3457, 2634, 473, 1015, 3559, 423, 2202, 2887, 1611, 558, 3374, 137, 1896, 3644, 1075, 3937, 825, 3458, 460, 3691, 590, 2075, 3081, 439, 1337, 3440, 1742, 2318, 67, 1954, 3491, 1401, 230, 3648, 2751, 1328, 3012, 1687, 2658, 374, 3314, 1704, 4074, 1448, 620, 1329, 3958, 1999, 560, 2088, 2791, 1257, 2284,
+ 821, 2492, 227, 3057, 2540, 29, 1258, 2239, 3016, 273, 2424, 635, 1251, 3684, 2255, 1681, 3105, 1432, 45, 3506, 2619, 1261, 3886, 2484, 436, 3107, 1701, 2625, 1380, 2352, 1551, 3199, 929, 2410, 1865, 3781, 246, 2942, 1185, 3681, 2588, 912, 2264, 3269, 1208, 714, 3752, 268, 3466, 730, 3837, 1242, 2722, 919, 2372, 3416, 2615, 880, 2851, 1476, 3788, 286, 3289, 1668,
+ 3399, 1314, 3921, 1795, 1036, 3478, 1948, 3697, 921, 3397, 1707, 3878, 2004, 2943, 166, 2733, 814, 3918, 2086, 978, 1957, 3155, 622, 1461, 2750, 2064, 61, 3346, 676, 3032, 205, 1821, 3977, 1, 2819, 892, 2442, 754, 3233, 566, 1537, 3963, 678, 1696, 2895, 1904, 2465, 1458, 2131, 2904, 1776, 2298, 226, 3710, 1988, 308, 1630, 3636, 34, 3095, 1116, 2464, 703, 2638,
+ 385, 2781, 600, 2323, 3175, 764, 2808, 403, 1825, 2695, 1082, 2845, 339, 1499, 4087, 1179, 3376, 459, 2827, 3758, 294, 2345, 1818, 3409, 959, 3784, 1276, 1857, 4042, 1149, 3534, 2520, 1209, 3363, 1422, 3525, 1634, 4083, 2194, 1823, 3082, 293, 2633, 3530, 99, 3927, 531, 3114, 1058, 9, 3620, 874, 3277, 684, 3052, 1215, 3188, 1908, 2265, 758, 3434, 1885, 3967, 968,
+ 3701, 2022, 3459, 1503, 332, 4001, 1473, 2168, 3833, 117, 2338, 779, 3173, 2153, 609, 2576, 1637, 2409, 846, 1542, 3321, 1077, 4056, 169, 2978, 561, 3214, 2253, 400, 2807, 1928, 495, 2991, 689, 2177, 420, 2885, 1088, 172, 2761, 1281, 3422, 1991, 1109, 2388, 943, 3317, 1669, 4029, 2653, 1398, 2497, 1540, 2818, 1836, 3865, 522, 983, 4049, 2601, 1536, 165, 2182, 3164,
+ 1572, 92, 1183, 2927, 1873, 2451, 1093, 3201, 888, 3044, 1439, 3789, 1151, 3512, 1783, 3650, 251, 3274, 1868, 2669, 710, 2872, 1370, 2446, 2038, 1646, 2577, 1012, 3635, 2145, 974, 3805, 2272, 1738, 3933, 2527, 1342, 3303, 1944, 3843, 514, 2296, 788, 3709, 1592, 2780, 2020, 281, 2249, 665, 3462, 315, 3985, 1074, 111, 2534, 2018, 2930, 1411, 441, 3576, 2836, 1347, 508,
+ 2744, 2243, 3951, 696, 3666, 161, 3521, 516, 2494, 1962, 3436, 491, 2476, 15, 2760, 868, 2258, 1291, 3875, 91, 3700, 2141, 393, 3611, 750, 3903, 275, 3296, 1596, 105, 3144, 1541, 260, 3183, 1042, 118, 3686, 594, 2384, 996, 3539, 1710, 3133, 235, 3030, 587, 3785, 1154, 3020, 1941, 1196, 3154, 1720, 2236, 3474, 1325, 3614, 247, 3249, 1844, 2229, 1014, 3776, 1869,
+ 3515, 986, 1956, 2545, 1336, 3078, 2051, 1649, 4048, 309, 1192, 2787, 1721, 3388, 1426, 3965, 2928, 496, 2175, 3111, 1019, 1745, 3065, 1482, 3186, 1252, 2879, 698, 2507, 3947, 896, 2742, 3586, 1400, 2110, 2934, 1706, 2738, 1383, 2950, 35, 2482, 1425, 4039, 1187, 2505, 1509, 3390, 479, 3873, 2395, 848, 2768, 407, 2910, 619, 1754, 2755, 1207, 3816, 717, 3059, 276, 2498,
+ 647, 2908, 311, 3166, 571, 2303, 916, 2935, 1283, 3276, 1874, 3907, 823, 2302, 577, 1995, 1129, 3566, 1415, 601, 2539, 3540, 858, 2595, 18, 2330, 1751, 3505, 1111, 1906, 2403, 625, 2199, 446, 3782, 799, 3367, 349, 4007, 830, 3251, 1071, 2730, 426, 1972, 3568, 72, 2154, 2790, 1404, 144, 3674, 1471, 3854, 1004, 2072, 3934, 882, 2419, 86, 2640, 1685, 3298, 1530,
+ 3848, 1373, 3429, 1661, 3809, 1424, 3584, 55, 2622, 675, 2421, 208, 3090, 1224, 3675, 2504, 143, 2057, 2701, 4028, 1558, 244, 1980, 3962, 1159, 3669, 451, 2021, 2784, 302, 3733, 1298, 3261, 1786, 2568, 1275, 2289, 1495, 2499, 1765, 2116, 3827, 704, 3423, 2371, 795, 3189, 1678, 630, 3136, 1901, 2559, 737, 2195, 3273, 2511, 330, 3327, 1950, 3496, 950, 4000, 1134, 2312,
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/CMakeLists.txt
new file mode 100644
index 0000000..5acdc09
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/CMakeLists.txt
@@ -0,0 +1,3 @@
+set(SOURCES TestConversionMatrix.cpp)
+
+core_add_test_library(videoshaders_test)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp
new file mode 100644
index 0000000..f6bc7f0
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+
+#include "cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h"
+#include "xbmc/utils/MathUtils.h"
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+// clang-format off
+
+namespace
+{
+
+constexpr bool DEBUG_PRINT{false};
+
+void DebugPrint(Matrix3 matrix)
+{
+ if (!DEBUG_PRINT)
+ return;
+
+ std::cout << std::fixed;
+
+ std::cout << "===== Matrix Start =====" << std::endl;
+
+ for (unsigned int i = 0; i < 3; i++)
+ {
+ std::cout << std::setprecision(10)
+ << "{" << matrix[i][0] << ", "
+ << matrix[i][1] << ", "
+ << matrix[i][2] << "}," << std::endl;
+ }
+
+ std::cout << "===== Matrix End =====" << std::endl;
+}
+
+void DebugPrint(Matrix4 matrix)
+{
+ if (!DEBUG_PRINT)
+ return;
+
+ std::cout << std::fixed;
+
+ std::cout << "===== Matrix Start =====" << std::endl;
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ std::cout << std::setprecision(10)
+ << "{" << matrix[i][0] << ", "
+ << matrix[i][1] << ", "
+ << matrix[i][2] << ", "
+ << matrix[i][3] << "}," << std::endl;
+ }
+
+ std::cout << "===== Matrix End =====" << std::endl;
+}
+
+template<uint8_t Order>
+bool CompareMatrices(CMatrix<Order>& matrixA, CMatrix<Order>& matrixB, float threshold)
+{
+ bool result = true;
+ for (unsigned int i = 0; i < Order; i++)
+ {
+ for (unsigned int j = 0; j < Order; j++)
+ {
+ result &= MathUtils::FloatEquals(matrixA[i][j], matrixB[i][j], threshold);
+ }
+ }
+ return result;
+}
+
+std::array<std::array<float, 4>, 4> bt709_8bit =
+{{
+ {1.1643834114, 1.1643834114, 1.1643834114, 0.0000000000},
+ {0.0000000000, -0.2132485956, 2.1124014854, 0.0000000000},
+ {1.7927408218, -0.5329092145, 0.0000000000, 0.0000000000},
+ {-0.9729449749, 0.3014826477, -1.1334021091, 1.0000000000}
+}};
+
+Matrix4 bt709Mat_8bit(bt709_8bit);
+
+std::array<std::array<float, 4>, 4> bt709_10bit =
+{{
+ {1.1643834114, 1.1643834114, 1.1643834114, 0.0000000000},
+ {0.0000000000, -0.2132485956, 2.1124014854, 0.0000000000},
+ {1.7927408218, -0.5329092145, 0.0000000000, 0.0000000000},
+ {-0.9729449749, 0.3014826477, -1.1334021091, 1.0000000000},
+}};
+
+Matrix4 bt709Mat_10bit(bt709_10bit);
+
+std::array<std::array<float, 4>, 4> bt709_10bit_texture =
+{{
+ {74.5922470093, 74.5922470093, 74.5922470093, 0.0000000000},
+ {0.0000000000, -13.6610431671, 135.3237915039, 0.0000000000},
+ {114.8458175659, -34.1390075684, 0.0000000000, 0.0000000000},
+ {-0.9729449749, 0.3014826477, -1.1334021091, 1.0000000000},
+}};
+
+Matrix4 bt709Mat_10bit_texture(bt709_10bit_texture);
+
+std::array<std::array<float, 3>, 3> bt601_to_bt709 =
+{{
+ {1.0440435410, -0.0000000230, 0.0000000056},
+ {-0.0440433398, 0.9999997616, 0.0117934495},
+ {-0.0000000298, -0.0000000075, 0.9882065654},
+}};
+
+Matrix3 bt601_to_bt709_Mat(bt601_to_bt709);
+
+std::array<std::array<float, 3>, 3> bt2020_to_bt709 =
+{{
+ {1.6604905128, -0.1245504916, -0.0181507617},
+ {-0.5876411796, 1.1328998804, -0.1005788445},
+ {-0.0728498399, -0.0083493963, 1.1187294722},
+}};
+
+Matrix3 bt2020_to_bt709_Mat(bt2020_to_bt709);
+
+} // namespace
+
+TEST(TestConvertMatrix, YUV2RGB)
+{
+ CConvertMatrix convMat;
+ convMat.SetSourceColorSpace(AVCOL_SPC_BT709)
+ .SetSourceLimitedRange(true);
+
+ convMat.SetDestinationBlack(0.0f)
+ .SetDestinationContrast(1.0f)
+ .SetDestinationLimitedRange(false);
+
+ Matrix4 yuvMat;
+
+ convMat.SetSourceBitDepth(8)
+ .SetSourceTextureBitDepth(8);
+ yuvMat = convMat.GetYuvMat();
+ DebugPrint(yuvMat);
+
+ EXPECT_TRUE(CompareMatrices(bt709Mat_8bit, yuvMat, 0.0001f));
+
+ convMat.SetSourceBitDepth(10)
+ .SetSourceTextureBitDepth(8);
+ yuvMat = convMat.GetYuvMat();
+ DebugPrint(yuvMat);
+
+ EXPECT_TRUE(CompareMatrices(bt709Mat_10bit, yuvMat, 0.0001f));
+
+ convMat.SetSourceBitDepth(10)
+ .SetSourceTextureBitDepth(10);
+ yuvMat = convMat.GetYuvMat();
+ DebugPrint(yuvMat);
+
+ EXPECT_TRUE(CompareMatrices(bt709Mat_10bit_texture, yuvMat, 0.0001f));
+}
+
+TEST(TestConvertMatrix, ColorSpaceConversion)
+{
+ CConvertMatrix convMat;
+ Matrix3 primMat;
+
+ convMat.SetSourceColorPrimaries(AVCOL_PRI_BT470BG)
+ .SetDestinationColorPrimaries(AVCOL_PRI_BT709);
+ primMat = convMat.GetPrimMat();
+ DebugPrint(primMat);
+
+ EXPECT_TRUE(CompareMatrices(bt601_to_bt709_Mat, primMat, 0.0001f));
+
+ convMat.SetSourceColorPrimaries(AVCOL_PRI_BT2020)
+ .SetDestinationColorPrimaries(AVCOL_PRI_BT709);
+ primMat = convMat.GetPrimMat();
+ DebugPrint(primMat);
+
+ EXPECT_TRUE(CompareMatrices(bt2020_to_bt709_Mat, primMat, 0.0001f));
+}
+
+// clang-format on
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp
new file mode 100644
index 0000000..3ec27f8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinRenderer.h"
+
+#include "RenderCapture.h"
+#include "RenderCaptureDX.h"
+#include "RenderFactory.h"
+#include "RenderFlags.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+#include "windows/RendererDXVA.h"
+#include "windows/RendererShaders.h"
+#include "windows/RendererSoftware.h"
+
+#include <mutex>
+
+struct render_details
+{
+ using map = std::map<RenderMethod, int>;
+ using weights_fn = std::function<void(map&, const VideoPicture&)>;
+ using create_fn = std::function<CRendererBase*(CVideoSettings&)>;
+
+ RenderMethod method;
+ std::string name;
+ create_fn create;
+ weights_fn weights;
+
+ template<class T>
+ constexpr static render_details get(RenderMethod method, const std::string& name)
+ {
+ return { method, name, T::Create, T::GetWeight };
+ }
+};
+
+static std::vector<render_details> RenderMethodDetails =
+{
+ render_details::get<CRendererSoftware>(RENDER_SW, "Software"),
+ render_details::get<CRendererShaders>(RENDER_PS, "Pixel Shaders"),
+ render_details::get<CRendererDXVA>(RENDER_DXVA, "DXVA"),
+};
+
+CBaseRenderer* CWinRenderer::Create(CVideoBuffer*)
+{
+ return new CWinRenderer();
+}
+
+bool CWinRenderer::Register()
+{
+ VIDEOPLAYER::CRendererFactory::RegisterRenderer("default", Create);
+ return true;
+}
+
+CWinRenderer::CWinRenderer()
+{
+ m_format = AV_PIX_FMT_NONE;
+ PreInit();
+}
+
+CWinRenderer::~CWinRenderer()
+{
+ CWinRenderer::UnInit();
+}
+
+CRendererBase* CWinRenderer::SelectRenderer(const VideoPicture& picture)
+{
+ int iRequestedMethod = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_RENDERMETHOD);
+ CLog::LogF(LOGDEBUG, "requested render method: {}", iRequestedMethod);
+
+ std::map<RenderMethod, int> weights;
+ for (auto& details : RenderMethodDetails)
+ details.weights(weights, picture);
+
+ RenderMethod method;
+ switch (iRequestedMethod)
+ {
+ case RENDER_METHOD_SOFTWARE:
+ if (weights[RENDER_SW])
+ {
+ method = RENDER_SW;
+ break;
+ }
+ // fallback to PS
+ case RENDER_METHOD_D3D_PS:
+ if (weights[RENDER_PS])
+ {
+ method = RENDER_PS;
+ break;
+ }
+ //fallback to DXVA
+ case RENDER_METHOD_DXVA:
+ if (weights[RENDER_DXVA])
+ {
+ method = RENDER_DXVA;
+ break;
+ }
+ // fallback to AUTO
+ case RENDER_METHOD_AUTO:
+ default:
+ {
+ const auto it = std::max_element(weights.begin(), weights.end(),
+ [](auto& w1, auto& w2) { return w1.second < w2.second; });
+
+ if (it != weights.end())
+ {
+ method = it->first;
+ break;
+ }
+
+ // there is no elements in weights, so no renderer which supports incoming video buffer
+ CLog::LogF(LOGERROR, "unable to select render method for video buffer");
+ return nullptr;
+ }
+ }
+
+ const auto it = std::find_if(RenderMethodDetails.begin(), RenderMethodDetails.end(),
+ [method](render_details& d) { return d.method == method; });
+
+ if (it != RenderMethodDetails.end())
+ {
+ CLog::LogF(LOGDEBUG, "selected render method: {}", it->name);
+ return it->create(m_videoSettings);
+ }
+
+ // something goes really wrong
+ return nullptr;
+}
+
+CRect CWinRenderer::GetScreenRect() const
+{
+ CRect screenRect(0.f, 0.f,
+ static_cast<float>(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()),
+ static_cast<float>(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()));
+
+ switch (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode())
+ {
+ case RENDER_STEREO_MODE_SPLIT_HORIZONTAL:
+ screenRect.y2 *= 2;
+ break;
+ case RENDER_STEREO_MODE_SPLIT_VERTICAL:
+ screenRect.x2 *= 2;
+ break;
+ default:
+ break;
+ }
+
+ return screenRect;
+}
+
+bool CWinRenderer::Configure(const VideoPicture &picture, float fps, unsigned int orientation)
+{
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_renderOrientation = orientation;
+ m_fps = fps;
+ m_iFlags = GetFlagsChromaPosition(picture.chroma_position)
+ | GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight)
+ | GetFlagsColorPrimaries(picture.color_primaries)
+ | GetFlagsStereoMode(picture.stereoMode);
+ m_format = picture.videoBuffer->GetFormat();
+
+ // calculate the input frame aspect ratio
+ CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight);
+ SetViewMode(m_videoSettings.m_ViewMode);
+ ManageRenderArea();
+
+ m_renderer.reset(SelectRenderer(picture));
+ if (!m_renderer || !m_renderer->Configure(picture, fps, orientation))
+ {
+ m_renderer.reset();
+ return false;
+ }
+
+ m_bConfigured = true;
+ return true;
+}
+
+int CWinRenderer::NextBuffer() const
+{
+ return m_renderer->NextBuffer();
+}
+
+void CWinRenderer::AddVideoPicture(const VideoPicture &picture, int index)
+{
+ m_renderer->AddVideoPicture(picture, index);
+}
+
+void CWinRenderer::Update()
+{
+ if (!m_bConfigured)
+ return;
+
+ ManageRenderArea();
+ m_renderer->ManageTextures();
+}
+
+void CWinRenderer::RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha)
+{
+ if (!m_bConfigured)
+ return;
+
+ if (clear)
+ CServiceBroker::GetWinSystem()->GetGfxContext().Clear(DX::Windowing()->UseLimitedColor() ? 0x101010 : 0);
+ DX::Windowing()->SetAlphaBlendEnable(alpha < 255);
+
+ ManageRenderArea();
+ m_renderer->Render(index, index2, DX::Windowing()->GetBackBuffer(), m_sourceRect, m_destRect,
+ GetScreenRect(), flags);
+ DX::Windowing()->SetAlphaBlendEnable(true);
+}
+
+bool CWinRenderer::RenderCapture(CRenderCapture* capture)
+{
+ if (!m_bConfigured)
+ return false;
+
+ capture->BeginRender();
+ if (capture->GetState() != CAPTURESTATE_FAILED)
+ {
+ const CRect destRect(0, 0, static_cast<float>(capture->GetWidth()), static_cast<float>(capture->GetHeight()));
+
+ auto cap = static_cast<CRenderCaptureDX*>(capture);
+
+ m_renderer->Render(cap->GetTarget(), m_sourceRect, destRect, GetScreenRect());
+ capture->EndRender();
+
+ return true;
+ }
+
+ return false;
+}
+
+void CWinRenderer::SetBufferSize(int numBuffers)
+{
+ if (!m_bConfigured)
+ return;
+
+ m_renderer->SetBufferSize(numBuffers);
+}
+
+void CWinRenderer::PreInit()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_bConfigured = false;
+ UnInit();
+}
+
+void CWinRenderer::UnInit()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ m_renderer.reset();
+ m_bConfigured = false;
+}
+
+bool CWinRenderer::Flush(bool saveBuffers)
+{
+ if (!m_bConfigured)
+ return false;
+
+ return m_renderer->Flush(saveBuffers);
+}
+
+bool CWinRenderer::Supports(ERENDERFEATURE feature) const
+{
+ if(feature == RENDERFEATURE_BRIGHTNESS)
+ return true;
+
+ if(feature == RENDERFEATURE_CONTRAST)
+ return true;
+
+ if (feature == RENDERFEATURE_STRETCH ||
+ feature == RENDERFEATURE_NONLINSTRETCH ||
+ feature == RENDERFEATURE_ZOOM ||
+ feature == RENDERFEATURE_VERTICAL_SHIFT ||
+ feature == RENDERFEATURE_PIXEL_RATIO ||
+ feature == RENDERFEATURE_ROTATION ||
+ feature == RENDERFEATURE_POSTPROCESS ||
+ feature == RENDERFEATURE_TONEMAP)
+ return true;
+
+ return false;
+}
+
+bool CWinRenderer::Supports(ESCALINGMETHOD method) const
+{
+ if (!m_bConfigured)
+ return false;
+
+ return m_renderer->Supports(method);
+}
+
+bool CWinRenderer::WantsDoublePass()
+{
+ if (!m_bConfigured)
+ return false;
+
+ return m_renderer->WantsDoublePass();
+}
+
+bool CWinRenderer::ConfigChanged(const VideoPicture& picture)
+{
+ if (!m_bConfigured)
+ return true;
+
+ return picture.videoBuffer->GetFormat() != m_format;
+}
+
+CRenderInfo CWinRenderer::GetRenderInfo()
+{
+ if (!m_bConfigured)
+ return {};
+
+ return m_renderer->GetRenderInfo();
+}
+
+void CWinRenderer::ReleaseBuffer(int idx)
+{
+ if (!m_bConfigured)
+ return;
+
+ m_renderer->ReleaseBuffer(idx);
+}
+
+bool CWinRenderer::NeedBuffer(int idx)
+{
+ if (!m_bConfigured)
+ return false;
+
+ return m_renderer->NeedBuffer(idx);
+}
+
+DEBUG_INFO_VIDEO CWinRenderer::GetDebugInfo(int idx)
+{
+ if (!m_bConfigured)
+ return {};
+
+ return m_renderer->GetDebugInfo(idx);
+}
+
+CRenderCapture* CWinRenderer::GetRenderCapture()
+{
+ return new CRenderCaptureDX;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.h
new file mode 100644
index 0000000..731f3ff
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BaseRenderer.h"
+#include "windows/RendererBase.h"
+
+struct VideoPicture;
+class CRenderCapture;
+
+class CWinRenderer : public CBaseRenderer
+{
+public:
+ CWinRenderer();
+ ~CWinRenderer();
+
+ static CBaseRenderer* Create(CVideoBuffer *buffer);
+ static bool Register();
+
+ void Update() override;
+ bool RenderCapture(CRenderCapture* capture) override;
+
+ // Player functions
+ bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override;
+ void AddVideoPicture(const VideoPicture &picture, int index) override;
+ void UnInit() override;
+ bool IsConfigured() override { return m_bConfigured; }
+ bool Flush(bool saveBuffers) override;
+ CRenderInfo GetRenderInfo() override;
+ void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override;
+ void SetBufferSize(int numBuffers) override;
+ void ReleaseBuffer(int idx) override;
+ bool NeedBuffer(int idx) override;
+
+ // Feature support
+ bool SupportsMultiPassRendering() override { return false; }
+ bool Supports(ERENDERFEATURE feature) const override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+ bool WantsDoublePass() override;
+ bool ConfigChanged(const VideoPicture& picture) override;
+
+ // Debug info video
+ DEBUG_INFO_VIDEO GetDebugInfo(int idx) override;
+
+ CRenderCapture* GetRenderCapture() override;
+
+protected:
+ void PreInit();
+ int NextBuffer() const;
+ CRendererBase* SelectRenderer(const VideoPicture &picture);
+ CRect GetScreenRect() const;
+
+ bool m_bConfigured = false;
+ std::unique_ptr<CRendererBase> m_renderer;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/windows/CMakeLists.txt
new file mode 100644
index 0000000..3747b80
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES RendererBase.cpp
+ RendererDXVA.cpp
+ RendererHQ.cpp
+ RendererShaders.cpp
+ RendererSoftware.cpp)
+
+set(HEADERS RendererBase.h
+ RendererDXVA.h
+ RendererHQ.h
+ RendererShaders.h
+ RendererSoftware.h)
+
+core_add_library(videorenderers-windows)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp
new file mode 100644
index 0000000..0c422b6
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererBase.h"
+
+#include "DVDCodecs/Video/DVDVideoCodec.h"
+#include "DVDCodecs/Video/DXVA.h"
+#include "ServiceBroker.h"
+#include "VideoRenderers/BaseRenderer.h"
+#include "VideoRenderers/RenderFlags.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+using namespace Microsoft::WRL;
+
+void CRenderBuffer::AppendPicture(const VideoPicture& picture)
+{
+ videoBuffer = picture.videoBuffer;
+ videoBuffer->Acquire();
+
+ pictureFlags = picture.iFlags;
+ primaries = static_cast<AVColorPrimaries>(picture.color_primaries);
+ color_space = static_cast<AVColorSpace>(picture.color_space);
+ color_transfer = static_cast<AVColorTransferCharacteristic>(picture.color_transfer);
+ full_range = picture.color_range == 1;
+ bits = picture.colorBits;
+ stereoMode = picture.stereoMode;
+ pixelFormat = picture.pixelFormat;
+
+ hasDisplayMetadata = picture.hasDisplayMetadata;
+ displayMetadata = picture.displayMetadata;
+ lightMetadata = picture.lightMetadata;
+ hasLightMetadata = picture.hasLightMetadata && picture.lightMetadata.MaxCLL;
+ if (hasDisplayMetadata && displayMetadata.has_luminance && !displayMetadata.max_luminance.num)
+ displayMetadata.has_luminance = 0;
+}
+
+void CRenderBuffer::ReleasePicture()
+{
+ if (videoBuffer)
+ videoBuffer->Release();
+ videoBuffer = nullptr;
+ m_bLoaded = false;
+}
+
+CRenderBuffer::CRenderBuffer(AVPixelFormat av_pix_format, unsigned width, unsigned height)
+ : av_format(av_pix_format) , m_width(width) , m_height(height), m_widthTex(width), m_heightTex(height)
+{
+}
+
+HRESULT CRenderBuffer::GetResource(ID3D11Resource** ppResource, unsigned* index) const
+{
+ if (!ppResource)
+ return E_POINTER;
+ if (!index)
+ return E_POINTER;
+
+ auto dxva = dynamic_cast<DXVA::CVideoBuffer*>(videoBuffer);
+ if (!dxva)
+ return E_NOT_SET;
+
+ ComPtr<ID3D11Resource> pResource;
+ const HRESULT hr = dxva->GetResource(&pResource);
+ if (SUCCEEDED(hr))
+ {
+ *ppResource = pResource.Detach();
+ *index = dxva->GetIdx();
+ }
+
+ return hr;
+}
+
+void CRenderBuffer::QueueCopyFromGPU()
+{
+ if (!videoBuffer)
+ return;
+
+ unsigned index;
+ ComPtr<ID3D11Resource> pResource;
+ const HRESULT hr = GetResource(&pResource, &index);
+
+ if (FAILED(hr))
+ {
+ CLog::LogF(LOGERROR, "unable to open d3d11va resource.");
+ return;
+ }
+
+ if (!m_staging)
+ {
+ // create staging texture
+ ComPtr<ID3D11Texture2D> surface;
+ if (SUCCEEDED(pResource.As(&surface)))
+ {
+ D3D11_TEXTURE2D_DESC tDesc;
+ surface->GetDesc(&tDesc);
+
+ CD3D11_TEXTURE2D_DESC sDesc(tDesc);
+ sDesc.ArraySize = 1;
+ sDesc.Usage = D3D11_USAGE_STAGING;
+ sDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ sDesc.BindFlags = 0;
+ sDesc.MiscFlags = 0;
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ if (SUCCEEDED(pDevice->CreateTexture2D(&sDesc, nullptr, &m_staging)))
+ m_sDesc = sDesc;
+ }
+ }
+
+ if (m_staging)
+ {
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+ // queue copying content from decoder texture to temporary texture.
+ // actual data copying will be performed before rendering
+ pContext->CopySubresourceRegion(m_staging.Get(), D3D11CalcSubresource(0, 0, 1), 0, 0, 0,
+ pResource.Get(), D3D11CalcSubresource(0, index, 1), nullptr);
+ m_bPending = true;
+ }
+}
+
+
+CRendererBase::CRendererBase(CVideoSettings& videoSettings)
+ : m_videoSettings(videoSettings)
+{
+ m_colorManager.reset(new CColorManager());
+}
+
+CRendererBase::~CRendererBase()
+{
+ if (DX::Windowing()->IsHDROutput())
+ {
+ CLog::LogF(LOGDEBUG, "Restoring SDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ if (m_AutoSwitchHDR)
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
+ Flush(false);
+}
+
+CRenderInfo CRendererBase::GetRenderInfo()
+{
+ CRenderInfo info;
+ info.formats =
+ {
+ AV_PIX_FMT_D3D11VA_VLD,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_P010,
+ AV_PIX_FMT_P016,
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_YUV420P16
+ };
+ info.max_buffer_size = NUM_BUFFERS;
+ info.optimal_buffer_size = 4;
+
+ return info;
+}
+
+bool CRendererBase::Configure(const VideoPicture& picture, float fps, unsigned orientation)
+{
+ m_iNumBuffers = 0;
+ m_iBufferIndex = 0;
+
+ m_sourceWidth = picture.iWidth;
+ m_sourceHeight = picture.iHeight;
+ m_fps = fps;
+ m_renderOrientation = orientation;
+
+ m_useDithering = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("videoscreen.dither");
+ m_ditherDepth = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("videoscreen.ditherdepth");
+
+ m_lastHdr10 = {};
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ m_useHLGtoPQ = false;
+ m_AutoSwitchHDR = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ DX::Windowing()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) &&
+ DX::Windowing()->IsHDRDisplay();
+
+ // Auto switch HDR only if supported and "Settings/Player/Use HDR display capabilities" = ON
+ if (m_AutoSwitchHDR)
+ {
+ bool streamIsHDR = (picture.color_primaries == AVCOL_PRI_BT2020) &&
+ (picture.color_transfer == AVCOL_TRC_SMPTE2084 ||
+ picture.color_transfer == AVCOL_TRC_ARIB_STD_B67);
+
+ if (streamIsHDR != DX::Windowing()->IsHDROutput())
+ DX::Windowing()->ToggleHDR();
+ }
+
+ return true;
+}
+
+void CRendererBase::AddVideoPicture(const VideoPicture& picture, int index)
+{
+ if (m_renderBuffers[index])
+ {
+ m_renderBuffers[index]->AppendPicture(picture);
+ m_renderBuffers[index]->frameIdx = m_frameIdx;
+ m_frameIdx += 2;
+ }
+}
+
+void CRendererBase::Render(int index,
+ int index2,
+ CD3DTexture& target,
+ const CRect& sourceRect,
+ const CRect& destRect,
+ const CRect& viewRect,
+ unsigned flags)
+{
+ m_iBufferIndex = index;
+ ManageTextures();
+ Render(target, sourceRect, destRect, viewRect, flags);
+}
+
+void CRendererBase::Render(CD3DTexture& target, const CRect& sourceRect, const CRect& destRect, const CRect& viewRect, unsigned flags)
+{
+ if (m_iNumBuffers == 0)
+ return;
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+ if (!buf->IsLoaded())
+ {
+ if (!buf->UploadBuffer())
+ return;
+ }
+
+ ProcessHDR(buf);
+
+ if (m_viewWidth != static_cast<unsigned>(viewRect.Width()) ||
+ m_viewHeight != static_cast<unsigned>(viewRect.Height()))
+ {
+ m_viewWidth = static_cast<unsigned>(viewRect.Width());
+ m_viewHeight = static_cast<unsigned>(viewRect.Height());
+
+ OnViewSizeChanged();
+ }
+
+ CheckVideoParameters();
+ UpdateVideoFilters();
+
+ CPoint dest[4];
+ CRect source = sourceRect; // can be changed
+ CRect(destRect).GetQuad(dest); // can be changed
+
+ RenderImpl(m_IntermediateTarget, source, dest, flags);
+
+ if (m_toneMapping)
+ {
+ m_outputShader->SetDisplayMetadata(buf->hasDisplayMetadata, buf->displayMetadata, buf->hasLightMetadata, buf->lightMetadata);
+ m_outputShader->SetToneMapParam(m_toneMapMethod, m_videoSettings.m_ToneMapParam);
+ }
+
+ FinalOutput(m_IntermediateTarget, target, source, dest);
+
+ // Restore our view port.
+ DX::Windowing()->RestoreViewPort();
+ DX::Windowing()->ApplyStateBlock();
+}
+
+void CRendererBase::FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& src, const CPoint(&destPoints)[4])
+{
+ m_outputShader->Render(source, src, destPoints, target);
+}
+
+void CRendererBase::ManageTextures()
+{
+ if (m_iNumBuffers < m_iBuffersRequired)
+ {
+ for (int i = m_iNumBuffers; i < m_iBuffersRequired; i++)
+ CreateRenderBuffer(i);
+
+ m_iNumBuffers = m_iBuffersRequired;
+ }
+ else if (m_iNumBuffers > m_iBuffersRequired)
+ {
+ for (int i = m_iNumBuffers - 1; i >= m_iBuffersRequired; i--)
+ DeleteRenderBuffer(i);
+
+ m_iNumBuffers = m_iBuffersRequired;
+ m_iBufferIndex = m_iBufferIndex % m_iNumBuffers;
+ }
+}
+
+int CRendererBase::NextBuffer() const
+{
+ if (m_iNumBuffers)
+ return (m_iBufferIndex + 1) % m_iNumBuffers;
+ return -1;
+}
+
+void CRendererBase::ReleaseBuffer(int idx)
+{
+ if (m_renderBuffers[idx])
+ m_renderBuffers[idx]->ReleasePicture();
+}
+
+bool CRendererBase::Flush(bool saveBuffers)
+{
+ if (!saveBuffers)
+ {
+ for (int i = 0; i < NUM_BUFFERS; i++)
+ DeleteRenderBuffer(i);
+
+ m_iBufferIndex = 0;
+ m_iNumBuffers = 0;
+ }
+
+ return true;
+}
+
+bool CRendererBase::CreateRenderBuffer(int index)
+{
+ m_renderBuffers.insert(std::make_pair(index, CreateBuffer()));
+ return true;
+}
+
+void CRendererBase::DeleteRenderBuffer(int index)
+{
+ if (m_renderBuffers[index])
+ {
+ delete m_renderBuffers[index];
+ m_renderBuffers.erase(index);
+ }
+}
+
+bool CRendererBase::CreateIntermediateTarget(unsigned width, unsigned height, bool dynamic)
+{
+ DXGI_FORMAT format = DX::Windowing()->GetBackBuffer().GetFormat();
+
+ // don't create new one if it exists with requested size and format
+ if (m_IntermediateTarget.Get() && m_IntermediateTarget.GetFormat() == format
+ && m_IntermediateTarget.GetWidth() == width && m_IntermediateTarget.GetHeight() == height)
+ return true;
+
+ if (m_IntermediateTarget.Get())
+ m_IntermediateTarget.Release();
+
+ CLog::LogF(LOGDEBUG, "intermediate target format {}.", format);
+
+ if (!m_IntermediateTarget.Create(width, height, 1, dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT, format))
+ {
+ CLog::LogF(LOGERROR, "intermediate target creation failed.");
+ return false;
+ }
+ return true;
+}
+
+void CRendererBase::OnCMSConfigChanged(AVColorPrimaries srcPrimaries)
+{
+ m_lutSize = 0;
+ m_lutIsLoading = true;
+
+ auto loadLutTask = Concurrency::create_task([this, srcPrimaries] {
+ // load 3DLUT data
+ int lutSize, dataSize;
+ if (!CColorManager::Get3dLutSize(CMS_DATA_FMT_RGBA, &lutSize, &dataSize))
+ return 0;
+
+ const auto lutData = static_cast<uint16_t*>(KODI::MEMORY::AlignedMalloc(dataSize, 16));
+ bool success = m_colorManager->GetVideo3dLut(srcPrimaries, &m_cmsToken, CMS_DATA_FMT_RGBA,
+ lutSize, lutData);
+ if (success)
+ {
+ success = COutputShader::CreateLUTView(lutSize, lutData, false, m_pLUTView.ReleaseAndGetAddressOf());
+ }
+ else
+ CLog::Log(LOGERROR, "CRendererBase::OnCMSConfigChanged: unable to loading the 3dlut data.");
+
+ KODI::MEMORY::AlignedFree(lutData);
+ if (!success)
+ return 0;
+
+ return lutSize;
+ });
+
+ loadLutTask.then([&](const int lutSize) {
+ m_lutSize = lutSize;
+ if (m_outputShader)
+ m_outputShader->SetLUT(m_lutSize, m_pLUTView.Get());
+ m_lutIsLoading = false;
+ });
+}
+
+// this is copy from CBaseRenderer::ReorderDrawPoints()
+void CRendererBase::ReorderDrawPoints(const CRect& destRect, CPoint(&rotatedPoints)[4]) const
+{
+ // 0 - top left, 1 - top right, 2 - bottom right, 3 - bottom left
+ float origMat[4][2] = {{destRect.x1, destRect.y1},
+ {destRect.x2, destRect.y1},
+ {destRect.x2, destRect.y2},
+ {destRect.x1, destRect.y2}};
+
+ const int pointOffset = m_renderOrientation / 90;
+
+ for (int destIdx = 0, srcIdx = pointOffset; destIdx < 4; destIdx++)
+ {
+ rotatedPoints[destIdx].x = origMat[srcIdx][0];
+ rotatedPoints[destIdx].y = origMat[srcIdx][1];
+
+ srcIdx++;
+ srcIdx = srcIdx % 4;
+ }
+}
+
+void CRendererBase::UpdateVideoFilters()
+{
+ if (!m_outputShader)
+ {
+ m_outputShader = std::make_shared<COutputShader>();
+ if (!m_outputShader->Create(m_cmsOn, m_useDithering, m_ditherDepth, m_toneMapping,
+ m_toneMapMethod, m_useHLGtoPQ))
+ {
+ CLog::LogF(LOGDEBUG, "unable to create output shader.");
+ m_outputShader.reset();
+ }
+ else if (m_pLUTView && m_lutSize)
+ {
+ m_outputShader->SetLUT(m_lutSize, m_pLUTView.Get());
+ }
+ }
+}
+
+void CRendererBase::CheckVideoParameters()
+{
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+ ETONEMAPMETHOD method = m_videoSettings.m_ToneMapMethod;
+
+ bool isHDRPQ = (buf->color_transfer == AVCOL_TRC_SMPTE2084 && buf->primaries == AVCOL_PRI_BT2020);
+
+ bool toneMap = (isHDRPQ && m_HdrType == HDR_TYPE::HDR_NONE_SDR && method != VS_TONEMAPMETHOD_OFF);
+
+ bool hlg = (m_HdrType == HDR_TYPE::HDR_HLG);
+
+ if (toneMap != m_toneMapping || m_cmsOn != m_colorManager->IsEnabled() || hlg != m_useHLGtoPQ ||
+ method != m_toneMapMethod)
+ {
+ m_toneMapping = toneMap;
+ m_cmsOn = m_colorManager->IsEnabled();
+ m_useHLGtoPQ = hlg;
+ m_toneMapMethod = method;
+
+ m_outputShader.reset();
+ OnOutputReset();
+ }
+
+ if (m_cmsOn && !m_lutIsLoading)
+ {
+ const AVColorPrimaries color_primaries = static_cast<AVColorPrimaries>(buf->primaries);
+
+ if (!m_colorManager->CheckConfiguration(m_cmsToken, color_primaries))
+ OnCMSConfigChanged(color_primaries);
+ }
+}
+
+DXGI_FORMAT CRendererBase::GetDXGIFormat(const VideoPicture& picture)
+{
+ if (picture.videoBuffer && picture.videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ return GetDXGIFormat(picture.videoBuffer);
+
+ return DXGI_FORMAT_UNKNOWN;
+}
+
+DXGI_FORMAT CRendererBase::GetDXGIFormat(CVideoBuffer* videoBuffer)
+{
+ const auto dxva_buf = dynamic_cast<DXVA::CVideoBuffer*>(videoBuffer);
+ if (dxva_buf)
+ return dxva_buf->format;
+
+ return DXGI_FORMAT_UNKNOWN;
+}
+
+AVPixelFormat CRendererBase::GetAVFormat(DXGI_FORMAT dxgi_format)
+{
+ switch (dxgi_format)
+ {
+ case DXGI_FORMAT_NV12:
+ return AV_PIX_FMT_NV12;
+ case DXGI_FORMAT_P010:
+ return AV_PIX_FMT_P010;
+ case DXGI_FORMAT_P016:
+ return AV_PIX_FMT_P016;
+ default:
+ return AV_PIX_FMT_NONE;
+ }
+}
+
+DXGI_HDR_METADATA_HDR10 CRendererBase::GetDXGIHDR10MetaData(CRenderBuffer* rb)
+{
+ DXGI_HDR_METADATA_HDR10 hdr = {};
+
+ constexpr int FACTOR_1 = 50000;
+ constexpr int FACTOR_2 = 10000;
+
+ if (rb->hasDisplayMetadata && rb->displayMetadata.has_primaries)
+ {
+ if (rb->displayMetadata.display_primaries[0][0].den == FACTOR_1 &&
+ rb->displayMetadata.white_point[0].den == FACTOR_1)
+ {
+ hdr.RedPrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[0][0].num);
+ hdr.RedPrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[0][1].num);
+ hdr.GreenPrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[1][0].num);
+ hdr.GreenPrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[1][1].num);
+ hdr.BluePrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[2][0].num);
+ hdr.BluePrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[2][1].num);
+ hdr.WhitePoint[0] = static_cast<uint16_t>(rb->displayMetadata.white_point[0].num);
+ hdr.WhitePoint[1] = static_cast<uint16_t>(rb->displayMetadata.white_point[1].num);
+ }
+ else
+ {
+ hdr.RedPrimary[0] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[0][0]));
+ hdr.RedPrimary[1] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[0][1]));
+ hdr.GreenPrimary[0] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[1][0]));
+ hdr.GreenPrimary[1] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[1][1]));
+ hdr.BluePrimary[0] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[2][0]));
+ hdr.BluePrimary[1] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.display_primaries[2][1]));
+ hdr.WhitePoint[0] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.white_point[0]));
+ hdr.WhitePoint[1] =
+ static_cast<uint16_t>(FACTOR_1 * av_q2d(rb->displayMetadata.white_point[1]));
+ }
+ }
+
+ if (rb->hasDisplayMetadata && rb->displayMetadata.has_luminance)
+ {
+ if (rb->displayMetadata.max_luminance.den == FACTOR_2 &&
+ rb->displayMetadata.min_luminance.den == FACTOR_2)
+ {
+ hdr.MaxMasteringLuminance = static_cast<uint32_t>(rb->displayMetadata.max_luminance.num);
+ hdr.MinMasteringLuminance = static_cast<uint32_t>(rb->displayMetadata.min_luminance.num);
+ }
+ else
+ {
+ hdr.MaxMasteringLuminance =
+ static_cast<uint32_t>(FACTOR_2 * av_q2d(rb->displayMetadata.max_luminance));
+ hdr.MinMasteringLuminance =
+ static_cast<uint32_t>(FACTOR_2 * av_q2d(rb->displayMetadata.min_luminance));
+ }
+ }
+
+ if (rb->hasLightMetadata)
+ {
+ hdr.MaxContentLightLevel = static_cast<uint16_t>(rb->lightMetadata.MaxCLL);
+ hdr.MaxFrameAverageLightLevel = static_cast<uint16_t>(rb->lightMetadata.MaxFALL);
+ }
+
+ return hdr;
+}
+
+void CRendererBase::ProcessHDR(CRenderBuffer* rb)
+{
+ if (m_AutoSwitchHDR && rb->primaries == AVCOL_PRI_BT2020 &&
+ (rb->color_transfer == AVCOL_TRC_SMPTE2084 || rb->color_transfer == AVCOL_TRC_ARIB_STD_B67) &&
+ !DX::Windowing()->IsHDROutput())
+ {
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR ON
+ }
+
+ if (!DX::Windowing()->IsHDROutput())
+ {
+ if (m_HdrType != HDR_TYPE::HDR_NONE_SDR)
+ {
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ m_lastHdr10 = {};
+ }
+ return;
+ }
+
+ // HDR10
+ if (rb->color_transfer == AVCOL_TRC_SMPTE2084 && rb->primaries == AVCOL_PRI_BT2020)
+ {
+ DXGI_HDR_METADATA_HDR10 hdr10 = GetDXGIHDR10MetaData(rb);
+ if (m_HdrType == HDR_TYPE::HDR_HDR10)
+ {
+ // Sets HDR10 metadata only if it differs from previous
+ if (0 != std::memcmp(&hdr10, &m_lastHdr10, sizeof(hdr10)))
+ {
+ DX::Windowing()->SetHdrMetaData(hdr10);
+ m_lastHdr10 = hdr10;
+ }
+ }
+ else
+ {
+ // Sets HDR10 metadata and enables HDR10 color space (switch to HDR rendering)
+ DX::Windowing()->SetHdrMetaData(hdr10);
+ CLog::LogF(LOGINFO, "Switching to HDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+ m_HdrType = HDR_TYPE::HDR_HDR10;
+ m_lastHdr10 = hdr10;
+ }
+ }
+ // HLG
+ else if (rb->color_transfer == AVCOL_TRC_ARIB_STD_B67 && rb->primaries == AVCOL_PRI_BT2020)
+ {
+ if (m_HdrType != HDR_TYPE::HDR_HLG)
+ {
+ // Windows 10 doesn't support HLG HDR passthrough
+ // It's used HDR10 with reference metadata and shaders to convert HLG transfer to PQ transfer
+ // Values according BT.2100 recommendations
+ DXGI_HDR_METADATA_HDR10 hdr10 = {};
+ hdr10.RedPrimary[0] = 34000; // Display P3 primaries
+ hdr10.RedPrimary[1] = 16000;
+ hdr10.GreenPrimary[0] = 13250;
+ hdr10.GreenPrimary[1] = 34500;
+ hdr10.BluePrimary[0] = 7500;
+ hdr10.BluePrimary[1] = 3000;
+ hdr10.WhitePoint[0] = 15635;
+ hdr10.WhitePoint[1] = 16450;
+ hdr10.MaxMasteringLuminance = 1000 * 10000; // 1000 nits
+ hdr10.MinMasteringLuminance = 50; // 0.005 nits
+ DX::Windowing()->SetHdrMetaData(hdr10);
+ CLog::LogF(LOGINFO, "Switching to HDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+ m_HdrType = HDR_TYPE::HDR_HLG;
+ }
+ }
+ // SDR
+ else
+ {
+ if (m_HdrType != HDR_TYPE::HDR_NONE_SDR)
+ {
+ // Switch to SDR rendering
+ CLog::LogF(LOGINFO, "Switching to SDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ m_lastHdr10 = {};
+ if (m_AutoSwitchHDR)
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
+ }
+}
+
+DEBUG_INFO_VIDEO CRendererBase::GetDebugInfo(int idx)
+{
+ CRenderBuffer* rb = m_renderBuffers[idx];
+
+ const char* px = av_get_pix_fmt_name(rb->pixelFormat);
+ const char* pr = av_color_primaries_name(rb->primaries);
+ const char* tr = av_color_transfer_name(rb->color_transfer);
+
+ const std::string pixel = px ? px : "unknown";
+ const std::string prim = pr ? pr : "unknown";
+ const std::string trans = tr ? tr : "unknown";
+
+ const int max = static_cast<int>(std::exp2(rb->bits));
+ const int range_min = rb->full_range ? 0 : (max * 16) / 256;
+ const int range_max = rb->full_range ? max - 1 : (max * 235) / 256;
+
+ DEBUG_INFO_VIDEO info;
+
+ info.videoSource = StringUtils::Format(
+ "Source: {}x{}{}, fr: {:.3f}, pixel: {} {}-bit, range: {}-{}, matx: {}, trc: {}",
+ m_sourceWidth, m_sourceHeight, (rb->pictureFlags & DVP_FLAG_INTERLACED) ? "i" : "p", m_fps,
+ pixel, rb->bits, range_min, range_max, prim, trans);
+
+ info.metaPrim = "Primaries (meta): ";
+ info.metaLight = "HDR light (meta): ";
+
+ if (rb->hasDisplayMetadata && rb->displayMetadata.has_primaries &&
+ rb->displayMetadata.display_primaries[0][0].num)
+ {
+ double prim[3][2];
+ double wp[2];
+
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 2; j++)
+ prim[i][j] = static_cast<double>(rb->displayMetadata.display_primaries[i][j].num) /
+ static_cast<double>(rb->displayMetadata.display_primaries[i][j].den);
+ }
+
+ for (int j = 0; j < 2; j++)
+ wp[j] = static_cast<double>(rb->displayMetadata.white_point[j].num) /
+ static_cast<double>(rb->displayMetadata.white_point[j].den);
+
+ info.metaPrim += StringUtils::Format(
+ "R({:.3f} {:.3f}), G({:.3f} {:.3f}), B({:.3f} {:.3f}), WP({:.3f} {:.3f})", prim[0][0],
+ prim[0][1], prim[1][0], prim[1][1], prim[2][0], prim[2][1], wp[0], wp[1]);
+ }
+ else
+ {
+ info.metaPrim += "none";
+ }
+
+ if (rb->hasDisplayMetadata && rb->displayMetadata.has_luminance &&
+ rb->displayMetadata.max_luminance.num)
+ {
+ double maxML = static_cast<double>(rb->displayMetadata.max_luminance.num) /
+ static_cast<double>(rb->displayMetadata.max_luminance.den);
+ double minML = static_cast<double>(rb->displayMetadata.min_luminance.num) /
+ static_cast<double>(rb->displayMetadata.min_luminance.den);
+
+ info.metaLight += StringUtils::Format("max ML: {:.0f}, min ML: {:.4f}", maxML, minML);
+
+ if (rb->hasLightMetadata && rb->lightMetadata.MaxCLL)
+ {
+ info.metaLight += StringUtils::Format(", max CLL: {}, max FALL: {}", rb->lightMetadata.MaxCLL,
+ rb->lightMetadata.MaxFALL);
+ }
+ }
+ else
+ {
+ info.metaLight += "none";
+ }
+
+ if (m_outputShader)
+ info.shader = m_outputShader->GetDebugInfo();
+
+ return info;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h
new file mode 100644
index 0000000..d4f401a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+
+#include "VideoRenderers/ColorManager.h"
+#include "VideoRenderers/DebugInfo.h"
+#include "VideoRenderers/RenderInfo.h"
+#include "VideoRenderers/VideoShaders/WinVideoFilter.h"
+#include "cores/VideoSettings.h"
+#include "guilib/D3DResource.h"
+
+#include <vector>
+
+#include <d3d11_4.h>
+#include <dxgi1_5.h>
+extern "C" {
+#include <libavutil/mastering_display_metadata.h>
+#include <libavutil/pixdesc.h>
+}
+
+struct VideoPicture;
+class CVideoBuffer;
+
+namespace win
+{
+ namespace helpers
+ {
+ template<typename T>
+ bool contains(std::vector<T> vector, T item)
+ {
+ return find(vector.begin(), vector.end(), item) != vector.end();
+ }
+ }
+}
+
+enum RenderMethod
+{
+ RENDER_INVALID = 0,
+ RENDER_DXVA = 1,
+ RENDER_PS = 2,
+ RENDER_SW = 3
+};
+
+enum class HDR_TYPE
+{
+ HDR_NONE_SDR = 0,
+ HDR_HDR10 = 1,
+ HDR_HLG = 2
+};
+
+class CRenderBuffer
+{
+public:
+ virtual ~CRenderBuffer() = default;
+
+ unsigned GetWidth() const { return m_widthTex; }
+ unsigned GetHeight() const { return m_heightTex; }
+ bool IsLoaded() { return m_bLoaded; }
+
+ virtual void AppendPicture(const VideoPicture& picture);
+ virtual void ReleasePicture();
+ virtual bool UploadBuffer() { return false; }
+ virtual HRESULT GetResource(ID3D11Resource** ppResource, unsigned* index) const;
+
+ // implementation specified
+ virtual bool GetDataPlanes(uint8_t*(&planes)[3], int(&strides)[3]) { return false; }
+ virtual unsigned GetViewCount() const { return 0; }
+ virtual ID3D11View* GetView(unsigned viewIdx) { return nullptr; }
+
+ AVPixelFormat av_format;
+ CVideoBuffer* videoBuffer = nullptr;
+ unsigned int pictureFlags = 0;
+ AVColorPrimaries primaries = AVCOL_PRI_BT709;
+ AVColorSpace color_space = AVCOL_SPC_BT709;
+ AVColorTransferCharacteristic color_transfer = AVCOL_TRC_BT709;
+ bool full_range = false;
+ int bits = 8;
+ uint8_t texBits = 8;
+ AVPixelFormat pixelFormat = AV_PIX_FMT_NONE; // source pixel format
+ bool hasDisplayMetadata = false;
+ bool hasLightMetadata = false;
+ AVMasteringDisplayMetadata displayMetadata = {};
+ AVContentLightMetadata lightMetadata = {};
+ std::string stereoMode;
+ uint64_t frameIdx = 0;
+
+protected:
+ CRenderBuffer(AVPixelFormat av_pix_format, unsigned width, unsigned height);
+ void QueueCopyFromGPU();
+
+ // video buffer size
+ unsigned int m_width;
+ unsigned int m_height;
+ // real texture size
+ unsigned int m_widthTex;
+ unsigned int m_heightTex;
+ // copy from GPU mem
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> m_staging;
+ D3D11_TEXTURE2D_DESC m_sDesc{};
+ bool m_bPending = false;
+ bool m_bLoaded = false;
+};
+
+class CRendererBase
+{
+public:
+ virtual ~CRendererBase();
+
+ virtual CRenderInfo GetRenderInfo();
+ virtual bool Configure(const VideoPicture &picture, float fps, unsigned int orientation);
+ virtual bool Supports(ESCALINGMETHOD method) const = 0;
+ virtual bool WantsDoublePass() { return false; }
+ virtual bool NeedBuffer(int idx) { return false; }
+
+ void AddVideoPicture(const VideoPicture &picture, int index);
+ void Render(int index, int index2, CD3DTexture& target, const CRect& sourceRect,
+ const CRect& destRect, const CRect& viewRect, unsigned flags);
+ void Render(CD3DTexture& target, const CRect& sourceRect, const CRect& destRect,
+ const CRect& viewRect, unsigned flags = 0);
+
+ void ManageTextures();
+ int NextBuffer() const;
+ void ReleaseBuffer(int idx);
+ bool Flush(bool saveBuffers);
+ void SetBufferSize(int numBuffers) { m_iBuffersRequired = numBuffers; }
+
+ DEBUG_INFO_VIDEO GetDebugInfo(int idx);
+
+ static DXGI_FORMAT GetDXGIFormat(const VideoPicture &picture);
+ static DXGI_FORMAT GetDXGIFormat(CVideoBuffer* videoBuffer);
+ static AVPixelFormat GetAVFormat(DXGI_FORMAT dxgi_format);
+ static DXGI_HDR_METADATA_HDR10 GetDXGIHDR10MetaData(CRenderBuffer* rb);
+
+protected:
+ explicit CRendererBase(CVideoSettings& videoSettings);
+
+ bool CreateIntermediateTarget(unsigned int width, unsigned int height, bool dynamic = false);
+ void OnCMSConfigChanged(AVColorPrimaries srcPrimaries);
+ void ReorderDrawPoints(const CRect& destRect, CPoint(&rotatedPoints)[4]) const;
+ bool CreateRenderBuffer(int index);
+ void DeleteRenderBuffer(int index);
+
+ void ProcessHDR(CRenderBuffer* rb);
+
+ virtual void RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint (&destPoints)[4], uint32_t flags) = 0;
+ virtual void FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& sourceRect, const CPoint(&destPoints)[4]);
+
+ virtual CRenderBuffer* CreateBuffer() = 0;
+ virtual void UpdateVideoFilters();
+ virtual void CheckVideoParameters();
+ virtual void OnViewSizeChanged() {}
+ virtual void OnOutputReset() {}
+
+ bool m_toneMapping = false;
+ bool m_useDithering = false;
+ bool m_cmsOn = false;
+ bool m_lutIsLoading = false;
+ bool m_useHLGtoPQ = false;
+ ETONEMAPMETHOD m_toneMapMethod = VS_TONEMAPMETHOD_OFF;
+
+ int m_iBufferIndex = 0;
+ int m_iNumBuffers = 0;
+ int m_iBuffersRequired = 0;
+ int m_ditherDepth = 0;
+ int m_cmsToken = -1;
+ int m_lutSize = 0;
+ unsigned m_sourceWidth = 0;
+ unsigned m_sourceHeight = 0;
+ unsigned m_viewWidth = 0;
+ unsigned m_viewHeight = 0;
+ unsigned m_renderOrientation = 0;
+ float m_fps = 0.0f;
+ uint64_t m_frameIdx = 0;
+
+ AVPixelFormat m_format = AV_PIX_FMT_NONE;
+ CD3DTexture m_IntermediateTarget;
+ std::shared_ptr<COutputShader> m_outputShader;
+ std::unique_ptr<CColorManager> m_colorManager;
+ Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_pLUTView;
+ CVideoSettings& m_videoSettings;
+ std::map<int, CRenderBuffer*> m_renderBuffers;
+
+ DXGI_HDR_METADATA_HDR10 m_lastHdr10 = {};
+ HDR_TYPE m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ bool m_AutoSwitchHDR = false;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp
new file mode 100644
index 0000000..a6d2c1b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererDXVA.h"
+
+#include "DVDCodecs/Video/DVDVideoCodec.h"
+#include "VideoRenderers/BaseRenderer.h"
+#include "WIN32Util.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+#include "utils/memcpy_sse2.h"
+#include "windowing/GraphicContext.h"
+
+#include <ppl.h>
+
+using namespace Microsoft::WRL;
+
+CRendererBase* CRendererDXVA::Create(CVideoSettings& videoSettings)
+{
+ return new CRendererDXVA(videoSettings);
+}
+
+void CRendererDXVA::GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture)
+{
+ unsigned weight = 0;
+ const AVPixelFormat av_pixel_format = picture.videoBuffer->GetFormat();
+
+ if (av_pixel_format == AV_PIX_FMT_D3D11VA_VLD)
+ weight += 1000;
+ else
+ {
+ // check format for buffer
+ const DXGI_FORMAT dxgi_format = CRenderBufferImpl::GetDXGIFormat(av_pixel_format, GetDXGIFormat(picture));
+ if (dxgi_format == DXGI_FORMAT_UNKNOWN)
+ return;
+
+ CD3D11_TEXTURE2D_DESC texDesc(
+ dxgi_format,
+ FFALIGN(picture.iWidth, 32),
+ FFALIGN(picture.iHeight, 32),
+ 1, 1,
+ D3D11_BIND_DECODER,
+ D3D11_USAGE_DYNAMIC,
+ D3D11_CPU_ACCESS_WRITE
+ );
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ if (FAILED(pDevice->CreateTexture2D(&texDesc, nullptr, nullptr)))
+ {
+ CLog::LogF(LOGWARNING, "Texture format {} is not supported.", dxgi_format);
+ return;
+ }
+
+ if (av_pixel_format == AV_PIX_FMT_NV12 ||
+ av_pixel_format == AV_PIX_FMT_P010 ||
+ av_pixel_format == AV_PIX_FMT_P016)
+ weight += 500; // single copying
+
+ else if (av_pixel_format == AV_PIX_FMT_YUV420P ||
+ av_pixel_format == AV_PIX_FMT_YUV420P10 ||
+ av_pixel_format == AV_PIX_FMT_YUV420P16)
+ weight += 400; // single copying + convert
+ }
+
+ // prefer DXVA method for interlaced HW decoded material
+ if (av_pixel_format == AV_PIX_FMT_D3D11VA_VLD &&
+ picture.iFlags & DVP_FLAG_INTERLACED)
+ weight += 1000;
+
+ if (weight > 0)
+ weights[RENDER_DXVA] = weight;
+}
+
+CRenderInfo CRendererDXVA::GetRenderInfo()
+{
+ auto info = __super::GetRenderInfo();
+
+ const int buffers = NUM_BUFFERS + m_processor->PastRefs();
+ info.optimal_buffer_size = std::min(NUM_BUFFERS, buffers);
+ info.m_deintMethods.push_back(VS_INTERLACEMETHOD_DXVA_AUTO);
+
+ return info;
+}
+
+bool CRendererDXVA::Configure(const VideoPicture& picture, float fps, unsigned orientation)
+{
+ const auto support_type = D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT;
+
+ if (__super::Configure(picture, fps, orientation))
+ {
+ m_format = picture.videoBuffer->GetFormat();
+ const DXGI_FORMAT dxgi_format = CRenderBufferImpl::GetDXGIFormat(m_format, GetDXGIFormat(picture));
+
+ // create processor
+ m_processor = std::make_unique<DXVA::CProcessorHD>();
+ if (m_processor->PreInit() && m_processor->Open(m_sourceWidth, m_sourceHeight) &&
+ m_processor->IsFormatSupported(dxgi_format, support_type))
+ {
+ return true;
+ }
+
+ CLog::LogF(LOGERROR, "unable to create DXVA processor");
+ m_processor.reset();
+ }
+ return false;
+}
+
+bool CRendererDXVA::NeedBuffer(int idx)
+{
+ if (m_renderBuffers[idx]->IsLoaded() && m_renderBuffers[idx]->pictureFlags & DVP_FLAG_INTERLACED)
+ {
+ if (m_renderBuffers[idx]->frameIdx + (m_processor->PastRefs() * 2u) >=
+ m_renderBuffers[m_iBufferIndex]->frameIdx)
+ return true;
+ }
+
+ return false;
+}
+
+void CRendererDXVA::CheckVideoParameters()
+{
+ __super::CheckVideoParameters();
+
+ CreateIntermediateTarget(
+ HasHQScaler() ? m_sourceWidth : m_viewWidth,
+ HasHQScaler() ? m_sourceHeight : m_viewHeight);
+}
+
+void CRendererDXVA::RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags)
+{
+ CRect src = sourceRect;
+ CRect dst = HasHQScaler() ? sourceRect : ApplyTransforms(CRect(destPoints[0], destPoints[2]));
+ const CRect trg(0.0f, 0.0f, static_cast<float>(target.GetWidth()), static_cast<float>(target.GetHeight()));
+
+ CWIN32Util::CropSource(src, dst, trg, m_renderOrientation);
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+ CRenderBuffer* views[8] = {};
+ FillBuffersSet(views);
+
+ m_processor->Render(src, dst, target.Get(), views,
+ flags, buf->frameIdx % UINT32_MAX, m_renderOrientation,
+ m_videoSettings.m_Contrast,
+ m_videoSettings.m_Brightness);
+
+ if (!HasHQScaler())
+ {
+ // change src and dest in case of dxva scale
+ dst.GetQuad(destPoints);
+ sourceRect = dst;
+ }
+}
+
+CRect CRendererDXVA::ApplyTransforms(const CRect& destRect) const
+{
+ CRect result;
+ CPoint rotated[4];
+ ReorderDrawPoints(destRect, rotated);
+
+ switch (m_renderOrientation)
+ {
+ case 90:
+ result = { rotated[3], rotated[1] };
+ break;
+ case 180:
+ result = destRect;
+ break;
+ case 270:
+ result = { rotated[1], rotated[3] };
+ break;
+ default:
+ result = CServiceBroker::GetWinSystem()->GetGfxContext().StereoCorrection(destRect);
+ break;
+ }
+
+ return result;
+}
+
+void CRendererDXVA::FillBuffersSet(CRenderBuffer* (&buffers)[8])
+{
+ int past = 0;
+ int future = 0;
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+ buffers[2] = buf;
+
+ // set future frames
+ while (future < 2)
+ {
+ bool found = false;
+ for (int i = 0; i < m_iNumBuffers; i++)
+ {
+ if (m_renderBuffers[i]->frameIdx == buf->frameIdx + (future * 2 + 2))
+ {
+ // a future frame may not be loaded yet
+ if (m_renderBuffers[i]->IsLoaded() || m_renderBuffers[i]->UploadBuffer())
+ {
+ buffers[1 - future++] = m_renderBuffers[i];
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+
+ // set past frames
+ while (past < 4)
+ {
+ bool found = false;
+ for (int i = 0; i < m_iNumBuffers; i++)
+ {
+ if (m_renderBuffers[i]->frameIdx == buf->frameIdx - (past * 2 + 2))
+ {
+ if (m_renderBuffers[i]->IsLoaded())
+ {
+ buffers[3 + past++] = m_renderBuffers[i];
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+}
+
+bool CRendererDXVA::Supports(ESCALINGMETHOD method) const
+{
+ if (method == VS_SCALINGMETHOD_DXVA_HARDWARE)
+ return true;
+
+ return __super::Supports(method);
+}
+
+CRenderBuffer* CRendererDXVA::CreateBuffer()
+{
+ return new CRenderBufferImpl(m_format, m_sourceWidth, m_sourceHeight);
+}
+
+CRendererDXVA::CRenderBufferImpl::CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height)
+ : CRenderBuffer(av_pix_format, width, height)
+{
+ const auto dxgi_format = GetDXGIFormat(av_pix_format);
+ if (dxgi_format == DXGI_FORMAT_UNKNOWN)
+ return;
+
+ m_widthTex = FFALIGN(width, 32);
+ m_heightTex = FFALIGN(height, 32);
+
+ m_texture.Create(m_widthTex, m_heightTex, 1, D3D11_USAGE_DYNAMIC, dxgi_format);
+}
+
+CRendererDXVA::CRenderBufferImpl::~CRenderBufferImpl()
+{
+ CRenderBufferImpl::ReleasePicture();
+}
+
+bool CRendererDXVA::CRenderBufferImpl::UploadBuffer()
+{
+ if (!videoBuffer)
+ return false;
+
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ m_bLoaded = true;
+ return true;
+ }
+
+ return UploadToTexture();
+}
+
+HRESULT CRendererDXVA::CRenderBufferImpl::GetResource(ID3D11Resource** ppResource, unsigned* index) const
+{
+ if (!ppResource)
+ return E_POINTER;
+ if (!index)
+ return E_POINTER;
+
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ return __super::GetResource(ppResource, index);
+
+ ComPtr<ID3D11Resource> pResource = m_texture.Get();
+ *ppResource = pResource.Detach();
+ *index = 0;
+
+ return S_OK;
+}
+
+DXGI_FORMAT CRendererDXVA::CRenderBufferImpl::GetDXGIFormat(AVPixelFormat format, DXGI_FORMAT default_fmt)
+{
+ switch (format)
+ {
+ case AV_PIX_FMT_NV12:
+ case AV_PIX_FMT_YUV420P:
+ return DXGI_FORMAT_NV12;
+ case AV_PIX_FMT_P010:
+ case AV_PIX_FMT_YUV420P10:
+ return DXGI_FORMAT_P010;
+ case AV_PIX_FMT_P016:
+ case AV_PIX_FMT_YUV420P16:
+ return DXGI_FORMAT_P016;
+ default:
+ return default_fmt;
+ }
+}
+
+bool CRendererDXVA::CRenderBufferImpl::UploadToTexture()
+{
+ D3D11_MAPPED_SUBRESOURCE rect;
+ if (!m_texture.LockRect(0, &rect, D3D11_MAP_WRITE_DISCARD))
+ return false;
+
+ // destination
+ uint8_t* pData = static_cast<uint8_t*>(rect.pData);
+ uint8_t* dst[] = { pData, pData + m_texture.GetHeight() * rect.RowPitch };
+ int dstStride[] = { static_cast<int>(rect.RowPitch), static_cast<int>(rect.RowPitch) };
+
+ // source
+ uint8_t* src[3];
+ int srcStrides[3];
+ videoBuffer->GetPlanes(src);
+ videoBuffer->GetStrides(srcStrides);
+
+ const unsigned width = m_width;
+ const unsigned height = m_height;
+
+ const AVPixelFormat buffer_format = videoBuffer->GetFormat();
+ // copy to texture
+ if (buffer_format == AV_PIX_FMT_NV12 ||
+ buffer_format == AV_PIX_FMT_P010 ||
+ buffer_format == AV_PIX_FMT_P016)
+ {
+ Concurrency::parallel_invoke([&]() {
+ // copy Y
+ copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0]);
+ }, [&]() {
+ // copy UV
+ copy_plane(src[1], srcStrides[1], height >> 1, width, dst[1], dstStride[1]);
+ });
+ // copy cache size of UV line again to fix Intel cache issue
+ copy_plane(src[1], srcStrides[1], 1, 32, dst[1], dstStride[1]);
+ }
+ // convert 8bit
+ else if (buffer_format == AV_PIX_FMT_YUV420P)
+ {
+ Concurrency::parallel_invoke([&]() {
+ // copy Y
+ copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0]);
+ }, [&]() {
+ // convert U+V -> UV
+ convert_yuv420_nv12_chrome(&src[1], &srcStrides[1], height, width, dst[1], dstStride[1]);
+ });
+ // copy cache size of UV line again to fix Intel cache issue
+ // height and width multiplied by two because they will be divided by func
+ convert_yuv420_nv12_chrome(&src[1], &srcStrides[1], 2, 64, dst[1], dstStride[1]);
+ }
+ // convert 10/16bit
+ else if (buffer_format == AV_PIX_FMT_YUV420P10 ||
+ buffer_format == AV_PIX_FMT_YUV420P16)
+ {
+ const uint8_t bpp = buffer_format == AV_PIX_FMT_YUV420P10 ? 10 : 16;
+ Concurrency::parallel_invoke([&]() {
+ // copy Y
+ copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0], bpp);
+ }, [&]() {
+ // convert U+V -> UV
+ convert_yuv420_p01x_chrome(&src[1], &srcStrides[1], height, width, dst[1], dstStride[1], bpp);
+ });
+ // copy cache size of UV line again to fix Intel cache issue
+ // height multiplied by two because it will be divided by func
+ convert_yuv420_p01x_chrome(&src[1], &srcStrides[1], 2, 32, dst[1], dstStride[1], bpp);
+ }
+
+ m_bLoaded = m_texture.UnlockRect(0);
+ return m_bLoaded;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h
new file mode 100644
index 0000000..9412377
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+
+#include "RendererHQ.h"
+#include "VideoRenderers/HwDecRender/DXVAHD.h"
+
+#include <map>
+
+#include <d3d11_4.h>
+#include <libavutil/pixfmt.h>
+
+enum RenderMethod;
+
+class CRendererDXVA : public CRendererHQ
+{
+ class CRenderBufferImpl;
+public:
+ ~CRendererDXVA() = default;
+
+ CRenderInfo GetRenderInfo() override;
+ bool Supports(ESCALINGMETHOD method) const override;
+ bool WantsDoublePass() override { return true; }
+ bool Configure(const VideoPicture& picture, float fps, unsigned orientation) override;
+ bool NeedBuffer(int idx) override;
+
+ static CRendererBase* Create(CVideoSettings& videoSettings);
+ static void GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture);
+
+protected:
+ explicit CRendererDXVA(CVideoSettings& videoSettings) : CRendererHQ(videoSettings) {}
+
+ void CheckVideoParameters() override;
+ void RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags) override;
+ CRenderBuffer* CreateBuffer() override;
+
+private:
+ void FillBuffersSet(CRenderBuffer* (&buffers)[8]);
+ CRect ApplyTransforms(const CRect& destRect) const;
+
+ std::unique_ptr<DXVA::CProcessorHD> m_processor;
+};
+
+class CRendererDXVA::CRenderBufferImpl : public CRenderBuffer
+{
+public:
+ explicit CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height);
+ ~CRenderBufferImpl();
+
+ bool UploadBuffer() override;
+ HRESULT GetResource(ID3D11Resource** ppResource, unsigned* index) const override;
+
+ static DXGI_FORMAT GetDXGIFormat(AVPixelFormat format, DXGI_FORMAT default_fmt = DXGI_FORMAT_UNKNOWN);
+
+private:
+ bool UploadToTexture();
+
+ CD3DTexture m_texture;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.cpp
new file mode 100644
index 0000000..2fd83ff
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererHQ.h"
+
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "guilib/LocalizeStrings.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+bool CRendererHQ::Supports(ESCALINGMETHOD method) const
+{
+ if (method == VS_SCALINGMETHOD_AUTO)
+ return true;
+
+ if (DX::DeviceResources::Get()->GetDeviceFeatureLevel() >= D3D_FEATURE_LEVEL_9_3 && !m_renderOrientation)
+ {
+ if (method == VS_SCALINGMETHOD_CUBIC_MITCHELL ||
+ method == VS_SCALINGMETHOD_LANCZOS2 ||
+ method == VS_SCALINGMETHOD_SPLINE36_FAST ||
+ method == VS_SCALINGMETHOD_LANCZOS3_FAST ||
+ method == VS_SCALINGMETHOD_SPLINE36 ||
+ method == VS_SCALINGMETHOD_LANCZOS3)
+ {
+ // if scaling is below level, avoid hq scaling
+ const float scaleX = fabs((static_cast<float>(m_sourceWidth) - m_viewWidth) / m_sourceWidth) * 100;
+ const float scaleY = fabs((static_cast<float>(m_sourceHeight) - m_viewHeight) / m_sourceHeight) * 100;
+ const int minScale = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_HQSCALERS);
+
+ return scaleX >= minScale || scaleY >= minScale;
+ }
+ }
+ return false;
+}
+
+void CRendererHQ::OnOutputReset()
+{
+ // re-create shader if output shader changed
+ m_scalerShader.reset();
+}
+
+void CRendererHQ::SelectPSVideoFilter()
+{
+ switch (m_scalingMethod)
+ {
+ case VS_SCALINGMETHOD_CUBIC_MITCHELL:
+ case VS_SCALINGMETHOD_LANCZOS2:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ case VS_SCALINGMETHOD_SPLINE36:
+ case VS_SCALINGMETHOD_LANCZOS3:
+ m_bUseHQScaler = true;
+ break;
+ default:
+ m_bUseHQScaler = false;
+ break;
+ }
+
+ if (m_scalingMethod == VS_SCALINGMETHOD_AUTO)
+ {
+ const bool scaleSD = m_sourceHeight < 720 && m_sourceWidth < 1280;
+ const bool scaleUp = m_sourceHeight < m_viewHeight && m_sourceWidth < m_viewWidth;
+ const bool scaleFps = m_fps < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAutoScaleMaxFps + 0.01f;
+
+ if (scaleSD && scaleUp && scaleFps && Supports(VS_SCALINGMETHOD_LANCZOS3_FAST))
+ {
+ m_scalingMethod = VS_SCALINGMETHOD_LANCZOS3_FAST;
+ m_bUseHQScaler = true;
+ }
+ }
+ if (m_renderOrientation)
+ m_bUseHQScaler = false;
+}
+
+bool CRendererHQ::HasHQScaler() const
+{
+ return m_bUseHQScaler && m_scalerShader;
+}
+
+void CRendererHQ::CheckVideoParameters()
+{
+ __super::CheckVideoParameters();
+
+ if (m_scalingMethodGui != m_videoSettings.m_ScalingMethod)
+ {
+ m_scalingMethodGui = m_videoSettings.m_ScalingMethod;
+ m_scalingMethod = m_scalingMethodGui;
+
+ if (!Supports(m_scalingMethod))
+ {
+ CLog::LogF(LOGWARNING, "chosen scaling method {} is not supported by renderer",
+ static_cast<int>(m_scalingMethod));
+ m_scalingMethod = VS_SCALINGMETHOD_AUTO;
+ }
+
+ SelectPSVideoFilter();
+ m_scalerShader.reset();
+ }
+}
+
+void CRendererHQ::UpdateVideoFilters()
+{
+ __super::UpdateVideoFilters();
+
+ if (m_bUseHQScaler && !m_scalerShader)
+ {
+ // firstly try the more efficient two pass convolution shader
+ m_scalerShader = std::make_unique<CConvolutionShaderSeparable>();
+
+ if (!m_scalerShader->Create(m_scalingMethod, m_outputShader))
+ {
+ m_scalerShader.reset();
+ CLog::LogF(LOGINFO, "two pass convolution shader init problem, falling back to one pass.");
+ }
+
+ // fallback on the one pass version
+ if (!m_scalerShader)
+ {
+ m_scalerShader = std::make_unique<CConvolutionShader1Pass>();
+
+ if (!m_scalerShader->Create(m_scalingMethod, m_outputShader))
+ {
+ // we are in a big trouble
+ m_scalerShader.reset();
+ m_bUseHQScaler = false;
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(34400), g_localizeStrings.Get(34401));
+ }
+ }
+ }
+}
+
+void CRendererHQ::FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& sourceRect, const CPoint(&destPoints)[4])
+{
+ if (HasHQScaler())
+ {
+ const CRect destRect = CServiceBroker::GetWinSystem()->GetGfxContext().StereoCorrection(CRect(destPoints[0], destPoints[2]));
+ m_scalerShader->Render(source, target, sourceRect, destRect, false);
+ }
+ else
+ {
+ CD3D11_VIEWPORT viewPort(0.f, 0.f, static_cast<float>(target.GetWidth()), static_cast<float>(target.GetHeight()));
+ // restore view port
+ DX::DeviceResources::Get()->GetD3DContext()->RSSetViewports(1, &viewPort);
+ // restore scissors
+ auto& context = CServiceBroker::GetWinSystem()->GetGfxContext();
+ DX::Windowing()->SetScissors(context.StereoCorrection(context.GetScissors()));
+ // render frame
+ __super::FinalOutput(source, target, sourceRect, destPoints);
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.h
new file mode 100644
index 0000000..0f4270b
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererHQ.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+
+#include "RendererBase.h"
+
+class CRendererHQ : public CRendererBase
+{
+public:
+ bool Supports(ESCALINGMETHOD method) const override;
+
+protected:
+ explicit CRendererHQ(CVideoSettings& videoSettings) : CRendererBase(videoSettings) {}
+ virtual ~CRendererHQ() = default;
+
+ void OnOutputReset() override;
+ void CheckVideoParameters() override;
+ void UpdateVideoFilters() override;
+ void FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& sourceRect, const CPoint(&destPoints)[4]) override;
+
+ void SelectPSVideoFilter();
+ bool HasHQScaler() const;
+
+ ESCALINGMETHOD m_scalingMethod = VS_SCALINGMETHOD_AUTO;
+ ESCALINGMETHOD m_scalingMethodGui = VS_SCALINGMETHOD_AUTO;
+ std::unique_ptr<CConvolutionShader> m_scalerShader = nullptr;
+ bool m_bUseHQScaler = false;
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp
new file mode 100644
index 0000000..32bce25
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererShaders.h"
+
+#include "DVDCodecs/Video/DXVA.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/CPUInfo.h"
+#ifndef _M_ARM
+ #include "utils/gpu_memcpy_sse4.h"
+#endif
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <ppl.h>
+
+using namespace Microsoft::WRL;
+static DXGI_FORMAT plane_formats[][2] =
+{
+ { DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8G8_UNORM }, // NV12
+ { DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16G16_UNORM }, // P010
+ { DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16G16_UNORM } // P016
+};
+
+CRendererBase* CRendererShaders::Create(CVideoSettings& videoSettings)
+{
+ return new CRendererShaders(videoSettings);
+}
+
+void CRendererShaders::GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture)
+{
+ unsigned weight = 0;
+ const AVPixelFormat av_pixel_format = picture.videoBuffer->GetFormat();
+
+ switch (av_pixel_format)
+ {
+ case AV_PIX_FMT_D3D11VA_VLD:
+ if (IsHWPicSupported(picture))
+ weight += 1000; // support natively
+ else
+ weight += 200; // double copying (GPU->CPU->GPU)
+ break;
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_NV12:
+ weight += 500; // single copying
+ break;
+ case AV_PIX_FMT_YUV420P10:
+ case AV_PIX_FMT_YUV420P16:
+ if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R16_UNORM, D3D11_FORMAT_SUPPORT_TEXTURE2D))
+ weight += 500; // single copying
+ else
+ CLog::LogF(LOGWARNING, "Texture format DXGI_FORMAT_R16_UNORM is not supported.");
+ break;
+ case AV_PIX_FMT_P010:
+ case AV_PIX_FMT_P016:
+ if (DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R16_UNORM, D3D11_FORMAT_SUPPORT_TEXTURE2D) &&
+ DX::Windowing()->IsFormatSupport(DXGI_FORMAT_R16G16_UNORM,
+ D3D11_FORMAT_SUPPORT_TEXTURE2D))
+ weight += 500; // single copying
+ else
+ CLog::LogF(LOGWARNING, "Texture format R16_UNORM / R16G16_UNORM is not supported.");
+ break;
+ }
+
+ if (weight > 0)
+ weights[RENDER_PS] = weight;
+}
+
+bool CRendererShaders::Supports(ESCALINGMETHOD method) const
+{
+ if (method == VS_SCALINGMETHOD_LINEAR)
+ return true;
+
+ return __super::Supports(method);
+}
+
+bool CRendererShaders::Configure(const VideoPicture& picture, float fps, unsigned orientation)
+{
+ if (__super::Configure(picture, fps, orientation))
+ {
+ m_format = picture.videoBuffer->GetFormat();
+ if (m_format == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ const DXGI_FORMAT dxgi_format = GetDXGIFormat(picture);
+
+ // if decoded texture isn't supported in shaders
+ // then change format to supported via copying
+ if (!IsHWPicSupported(picture))
+ m_format = GetAVFormat(dxgi_format);
+ }
+
+ CreateIntermediateTarget(m_sourceWidth, m_sourceHeight);
+ return true;
+ }
+ return false;
+}
+
+void CRendererShaders::RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags)
+{
+ if (!m_colorShader)
+ return;
+
+ // reset scissors and viewport
+ CD3D11_VIEWPORT viewPort(0.0f, 0.0f,
+ static_cast<float>(target.GetWidth()),
+ static_cast<float>(target.GetHeight()));
+ DX::DeviceResources::Get()->GetD3DContext()->RSSetViewports(1, &viewPort);
+ DX::Windowing()->ResetScissors();
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+
+ CPoint srcPoints[4];
+ sourceRect.GetQuad(srcPoints);
+
+ m_colorShader->SetParams(m_videoSettings.m_Contrast, m_videoSettings.m_Brightness,
+ DX::Windowing()->UseLimitedColor());
+ m_colorShader->SetColParams(buf->color_space, buf->bits, !buf->full_range, buf->texBits);
+ m_colorShader->Render(sourceRect, srcPoints, buf, target);
+
+ if (!HasHQScaler())
+ ReorderDrawPoints(CRect(destPoints[0], destPoints[2]), destPoints);
+}
+
+void CRendererShaders::CheckVideoParameters()
+{
+ __super::CheckVideoParameters();
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+ const AVColorPrimaries srcPrim = GetSrcPrimaries(buf->primaries, buf->GetWidth(), buf->GetHeight());
+ if (srcPrim != m_srcPrimaries)
+ {
+ // source params is changed, reset shader
+ m_srcPrimaries = srcPrim;
+ m_colorShader.reset();
+ }
+}
+
+void CRendererShaders::UpdateVideoFilters()
+{
+ __super::UpdateVideoFilters();
+
+ if (!m_colorShader)
+ {
+ m_colorShader = std::make_unique<CYUV2RGBShader>();
+
+ AVColorPrimaries dstPrimaries = AVCOL_PRI_BT709;
+
+ if (DX::Windowing()->IsHDROutput() &&
+ (m_srcPrimaries == AVCOL_PRI_BT709 || m_srcPrimaries == AVCOL_PRI_BT2020))
+ dstPrimaries = m_srcPrimaries;
+
+ if (!m_colorShader->Create(m_format, dstPrimaries, m_srcPrimaries))
+ {
+ // we are in a big trouble
+ CLog::LogF(LOGERROR, "unable to create YUV->RGB shader, rendering is not possible");
+ m_colorShader.reset();
+ }
+ }
+}
+
+bool CRendererShaders::IsHWPicSupported(const VideoPicture& picture)
+{
+ // checking support of decoder texture in shaders
+ const DXGI_FORMAT dxgi_format = GetDXGIFormat(picture);
+ if (dxgi_format != DXGI_FORMAT_UNKNOWN)
+ {
+ CD3D11_TEXTURE2D_DESC texDesc(
+ dxgi_format,
+ FFALIGN(picture.iWidth, 32),
+ FFALIGN(picture.iHeight, 32),
+ 1, 1,
+ D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE,
+ D3D11_USAGE_DEFAULT
+ );
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ return SUCCEEDED(pDevice->CreateTexture2D(&texDesc, nullptr, nullptr));
+ }
+ return false;
+}
+
+AVColorPrimaries CRendererShaders::GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned width, unsigned height)
+{
+ AVColorPrimaries ret = srcPrimaries;
+ if (ret == AVCOL_PRI_UNSPECIFIED)
+ {
+ if (width > 1024 || height >= 600)
+ ret = AVCOL_PRI_BT709;
+ else
+ ret = AVCOL_PRI_BT470BG;
+ }
+ return ret;
+}
+
+CRenderBuffer* CRendererShaders::CreateBuffer()
+{
+ return new CRenderBufferImpl(m_format, m_sourceWidth, m_sourceHeight);
+}
+
+CRendererShaders::CRenderBufferImpl::CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height)
+ : CRenderBuffer(av_pix_format, width, height)
+{
+ DXGI_FORMAT view_formats[YuvImage::MAX_PLANES] = {};
+
+ switch (av_format)
+ {
+ case AV_PIX_FMT_D3D11VA_VLD:
+ m_viewCount = 2;
+ break;
+ case AV_PIX_FMT_NV12:
+ {
+ view_formats[0] = DXGI_FORMAT_R8_UNORM;
+ view_formats[1] = DXGI_FORMAT_R8G8_UNORM;
+ // FL 9.x doesn't support DXGI_FORMAT_R8G8_UNORM, so we have to use SNORM and correct values in shader
+ if (!DX::Windowing()->IsFormatSupport(view_formats[1], D3D11_FORMAT_SUPPORT_TEXTURE2D))
+ view_formats[1] = DXGI_FORMAT_R8G8_SNORM;
+ m_viewCount = 2;
+ break;
+ }
+ case AV_PIX_FMT_P010:
+ case AV_PIX_FMT_P016:
+ {
+ view_formats[0] = DXGI_FORMAT_R16_UNORM;
+ view_formats[1] = DXGI_FORMAT_R16G16_UNORM;
+ m_viewCount = 2;
+ break;
+ }
+ case AV_PIX_FMT_YUV420P:
+ {
+ view_formats[0] = view_formats[1] = view_formats[2] = DXGI_FORMAT_R8_UNORM;
+ m_viewCount = 3;
+ break;
+ }
+ case AV_PIX_FMT_YUV420P10:
+ case AV_PIX_FMT_YUV420P16:
+ {
+ view_formats[0] = view_formats[1] = view_formats[2] = DXGI_FORMAT_R16_UNORM;
+ m_viewCount = 3;
+ texBits = av_format == AV_PIX_FMT_YUV420P10 ? 10 : 16;
+ break;
+ }
+ default:
+ // unsupported format
+ return;
+ }
+
+ if (av_format != AV_PIX_FMT_D3D11VA_VLD)
+ {
+ for (size_t i = 0; i < m_viewCount; i++)
+ {
+ const auto w = i ? m_width >> 1 : m_width;
+ const auto h = i ? m_height >> 1 : m_height;
+
+ if (!m_textures[i].Create(w, h, 1, D3D11_USAGE_DYNAMIC, view_formats[i]))
+ break;
+
+ // clear plane
+ D3D11_MAPPED_SUBRESOURCE mapping = {};
+ if (m_textures[i].LockRect(0, &mapping, D3D11_MAP_WRITE_DISCARD))
+ {
+ if (view_formats[i] == DXGI_FORMAT_R8_UNORM ||
+ view_formats[i] == DXGI_FORMAT_R8G8_UNORM ||
+ view_formats[i] == DXGI_FORMAT_R8G8_SNORM)
+ memset(mapping.pData, i ? 0x80 : 0, mapping.RowPitch * h);
+ else
+ wmemset(static_cast<wchar_t*>(mapping.pData), i ? 0x8000 : 0, mapping.RowPitch * h >> 1);
+
+ if (m_textures[i].UnlockRect(0)) {}
+ }
+ }
+ }
+}
+
+CRendererShaders::CRenderBufferImpl::~CRenderBufferImpl()
+{
+ CRenderBufferImpl::ReleasePicture();
+}
+
+void CRendererShaders::CRenderBufferImpl::AppendPicture(const VideoPicture& picture)
+{
+ __super::AppendPicture(picture);
+
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ if (AV_PIX_FMT_D3D11VA_VLD != av_format)
+ QueueCopyFromGPU();
+
+ const auto hw = dynamic_cast<DXVA::CVideoBuffer*>(videoBuffer);
+ m_widthTex = hw->width;
+ m_heightTex = hw->height;
+ }
+}
+
+bool CRendererShaders::CRenderBufferImpl::UploadBuffer()
+{
+ if (!videoBuffer)
+ return false;
+
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ if (AV_PIX_FMT_D3D11VA_VLD == av_format)
+ m_bLoaded = true;
+ else
+ m_bLoaded = UploadFromGPU();
+ }
+ else
+ m_bLoaded = UploadFromBuffer();
+
+ return m_bLoaded;
+}
+
+unsigned CRendererShaders::CRenderBufferImpl::GetViewCount() const
+{
+ return m_viewCount;
+}
+
+ID3D11View* CRendererShaders::CRenderBufferImpl::GetView(unsigned viewIdx)
+{
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD &&
+ AV_PIX_FMT_D3D11VA_VLD == av_format)
+ {
+ if (m_planes[viewIdx])
+ return m_planes[viewIdx].Get();
+
+ unsigned arrayIdx;
+ ComPtr<ID3D11Resource> pResource;
+ if (FAILED(GetResource(&pResource, &arrayIdx)))
+ {
+ CLog::LogF(LOGERROR, "unable to open d3d11va resource.");
+ return nullptr;
+ }
+
+ const auto dxva_format = CRendererBase::GetDXGIFormat(videoBuffer);
+ // impossible but we check
+ if (dxva_format < DXGI_FORMAT_NV12 || dxva_format > DXGI_FORMAT_P016)
+ return nullptr;
+
+ CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(
+ D3D11_SRV_DIMENSION_TEXTURE2DARRAY,
+ plane_formats[dxva_format - DXGI_FORMAT_NV12][viewIdx],
+ 0, 1, arrayIdx, 1
+ );
+
+ ComPtr<ID3D11Device> pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ if (FAILED(pD3DDevice->CreateShaderResourceView(pResource.Get(), &srvDesc, &m_planes[viewIdx])))
+ {
+ CLog::LogF(LOGERROR, "unable to create shader target for decoder texture.");
+ return nullptr;
+ }
+
+ return m_planes[viewIdx].Get();
+ }
+
+ return m_textures[viewIdx].GetShaderResource();
+}
+
+void CRendererShaders::CRenderBufferImpl::ReleasePicture()
+{
+ __super::ReleasePicture();
+
+ m_planes[0] = nullptr;
+ m_planes[1] = nullptr;
+}
+
+bool CRendererShaders::CRenderBufferImpl::UploadFromGPU()
+{
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+ D3D11_MAPPED_SUBRESOURCE mapGPU;
+ D3D11_MAPPED_SUBRESOURCE mappings[2];
+
+ if (FAILED(pContext->Map(m_staging.Get(), 0, D3D11_MAP_READ, 0, &mapGPU)))
+ return false;
+
+ if (!m_textures[PLANE_Y].LockRect(0, &mappings[PLANE_Y], D3D11_MAP_WRITE_DISCARD) ||
+ !m_textures[PLANE_UV].LockRect(0, &mappings[PLANE_UV], D3D11_MAP_WRITE_DISCARD))
+ {
+ pContext->Unmap(m_staging.Get(), 0);
+ return false;
+ }
+
+ void* (*copy_func)(void* d, const void* s, size_t size) =
+#if defined(HAVE_SSE2)
+ ((CServiceBroker::GetCPUInfo()->GetCPUFeatures() & CPU_FEATURE_SSE4) != 0) ? gpu_memcpy :
+#endif
+ memcpy;
+
+ auto* s_y = static_cast<uint8_t*>(mapGPU.pData);
+ auto* s_uv = static_cast<uint8_t*>(mapGPU.pData) + m_sDesc.Height * mapGPU.RowPitch;
+ auto* d_y = static_cast<uint8_t*>(mappings[PLANE_Y].pData);
+ auto* d_uv = static_cast<uint8_t*>(mappings[PLANE_UV].pData);
+
+ if (mappings[PLANE_Y].RowPitch == mapGPU.RowPitch
+ && mappings[PLANE_UV].RowPitch == mapGPU.RowPitch)
+ {
+ Concurrency::parallel_invoke([&]() {
+ // copy Y
+ copy_func(d_y, s_y, mapGPU.RowPitch * m_height);
+ }, [&]() {
+ // copy UV
+ copy_func(d_uv, s_uv, mapGPU.RowPitch * m_height >> 1);
+ });
+ }
+ else
+ {
+ Concurrency::parallel_invoke([&]() {
+ // copy Y
+ for (unsigned y = 0; y < m_height; ++y)
+ {
+ copy_func(d_y, s_y, mappings[PLANE_Y].RowPitch);
+ s_y += mapGPU.RowPitch;
+ d_y += mappings[PLANE_Y].RowPitch;
+ }
+ }, [&]() {
+ // copy UV
+ for (unsigned y = 0; y < m_height >> 1; ++y)
+ {
+ copy_func(d_uv, s_uv, mappings[PLANE_UV].RowPitch);
+ s_uv += mapGPU.RowPitch;
+ d_uv += mappings[PLANE_UV].RowPitch;
+ }
+ });
+ }
+ pContext->Unmap(m_staging.Get(), 0);
+
+ return m_textures[PLANE_Y].UnlockRect(0) &&
+ m_textures[PLANE_UV].UnlockRect(0);
+}
+
+bool CRendererShaders::CRenderBufferImpl::UploadFromBuffer() const
+{
+ uint8_t* bufData[3];
+ int srcLines[3];
+ videoBuffer->GetPlanes(bufData);
+ videoBuffer->GetStrides(srcLines);
+
+ for (unsigned plane = 0; plane < m_viewCount; ++plane)
+ {
+ D3D11_MAPPED_SUBRESOURCE mapping = {};
+ if (!m_textures[plane].LockRect(0, &mapping, D3D11_MAP_WRITE_DISCARD))
+ break;
+
+ auto* dst = static_cast<uint8_t*>(mapping.pData);
+ auto* src = bufData[plane];
+ int srcLine = srcLines[plane];
+ int dstLine = mapping.RowPitch;
+ int height = plane ? m_height >> 1 : m_height;
+
+ if (srcLine == dstLine)
+ {
+ memcpy(dst, src, srcLine * height);
+ }
+ else
+ {
+ uint8_t* s = src;
+ uint8_t* d = dst;
+ for (int i = 0; i < height; ++i)
+ {
+ memcpy(d, s, std::min(srcLine, dstLine));
+ d += dstLine;
+ s += srcLine;
+ }
+ }
+ }
+
+ for (unsigned plane = 0; plane < m_viewCount; ++plane)
+ if (!m_textures[plane].UnlockRect(0)) {}
+
+ return true;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h
new file mode 100644
index 0000000..945cadd
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+
+#include "RendererHQ.h"
+#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
+
+#include <map>
+
+#include <d3d11_4.h>
+#include <libavutil/pixfmt.h>
+
+#define PLANE_Y 0
+#define PLANE_U 1
+#define PLANE_V 2
+#define PLANE_UV 1
+
+enum RenderMethod;
+
+class CRendererShaders : public CRendererHQ
+{
+ class CRenderBufferImpl;
+public:
+ ~CRendererShaders() = default;
+
+ bool Supports(ESCALINGMETHOD method) const override;
+ bool Configure(const VideoPicture& picture, float fps, unsigned orientation) override;
+
+ static CRendererBase* Create(CVideoSettings& videoSettings);
+ static void GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture);
+
+protected:
+ explicit CRendererShaders(CVideoSettings& videoSettings) : CRendererHQ(videoSettings) {}
+ void RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags) override;
+ void CheckVideoParameters() override;
+ void UpdateVideoFilters() override;
+ CRenderBuffer* CreateBuffer() override;
+ static bool IsHWPicSupported(const VideoPicture& picture);
+
+private:
+ static AVColorPrimaries GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height);
+
+ AVColorPrimaries m_srcPrimaries = AVCOL_PRI_BT709;
+ std::unique_ptr<CYUV2RGBShader> m_colorShader;
+};
+
+class CRendererShaders::CRenderBufferImpl : public CRenderBuffer
+{
+public:
+ explicit CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height);
+ ~CRenderBufferImpl();
+
+ void AppendPicture(const VideoPicture& picture) override;
+ bool UploadBuffer() override;
+ unsigned GetViewCount() const override;
+ ID3D11View* GetView(unsigned viewIdx) override;
+ void ReleasePicture() override;
+
+private:
+ bool UploadFromGPU();
+ bool UploadFromBuffer() const;
+
+ unsigned m_viewCount = 0;
+ CD3DTexture m_textures[YuvImage::MAX_PLANES];
+ Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_planes[2];
+};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.cpp
new file mode 100644
index 0000000..26aeb10
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RendererSoftware.h"
+
+#include "DVDCodecs/Video/DVDVideoCodec.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+using namespace Microsoft::WRL;
+
+CRendererBase* CRendererSoftware::Create(CVideoSettings& videoSettings)
+{
+ return new CRendererSoftware(videoSettings);
+}
+
+void CRendererSoftware::GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture)
+{
+ unsigned weight = 0;
+ const AVPixelFormat av_pixel_format = picture.videoBuffer->GetFormat();
+
+ if (av_pixel_format == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ const AVPixelFormat sw_format = GetAVFormat(GetDXGIFormat(picture));
+ if (sws_isSupportedInput(sw_format))
+ // double copying (GPU->CPU->(converting)->GPU)
+ weight += 100;
+ }
+ else if (sws_isSupportedInput(av_pixel_format))
+ weight += 200;
+
+ if (weight > 0)
+ weights[RENDER_SW] = weight;
+}
+
+CRendererSoftware::~CRendererSoftware()
+{
+ if (m_sw_scale_ctx)
+ {
+ sws_freeContext(m_sw_scale_ctx);
+ m_sw_scale_ctx = nullptr;
+ }
+}
+
+bool CRendererSoftware::Configure(const VideoPicture& picture, float fps, unsigned orientation)
+{
+ if (__super::Configure(picture, fps, orientation))
+ {
+ if (!CreateIntermediateTarget(m_sourceWidth, m_sourceHeight, true))
+ return false;
+
+ m_format = picture.videoBuffer->GetFormat();
+ if (m_format == AV_PIX_FMT_D3D11VA_VLD)
+ m_format = GetAVFormat(GetDXGIFormat(picture));
+
+ return true;
+ }
+ return false;
+}
+
+bool CRendererSoftware::Supports(ESCALINGMETHOD method) const
+{
+ return method == VS_SCALINGMETHOD_AUTO
+ || method == VS_SCALINGMETHOD_LINEAR;
+}
+
+void CRendererSoftware::RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags)
+{
+ // if creation failed
+ if (!m_outputShader)
+ return;
+
+ CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
+
+ // 1. convert yuv to rgb
+ m_sw_scale_ctx = sws_getCachedContext(m_sw_scale_ctx,
+ buf->GetWidth(), buf->GetHeight(), buf->av_format,
+ buf->GetWidth(), buf->GetHeight(), AV_PIX_FMT_BGRA,
+ SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
+
+ if (!m_sw_scale_ctx)
+ return;
+
+ sws_setColorspaceDetails(m_sw_scale_ctx,
+ sws_getCoefficients(buf->color_space), buf->full_range,
+ sws_getCoefficients(AVCOL_SPC_BT709), buf->full_range,
+ 0, 1 << 16, 1 << 16);
+
+ uint8_t* src[YuvImage::MAX_PLANES];
+ int srcStride[YuvImage::MAX_PLANES];
+ buf->GetDataPlanes(src, srcStride);
+
+ D3D11_MAPPED_SUBRESOURCE mapping;
+ if (target.LockRect(0, &mapping, D3D11_MAP_WRITE_DISCARD))
+ {
+ uint8_t *dst[] = { static_cast<uint8_t*>(mapping.pData), nullptr, nullptr };
+ int dstStride[] = { static_cast<int>(mapping.RowPitch), 0, 0 };
+
+ sws_scale(m_sw_scale_ctx, src, srcStride, 0, std::min(target.GetHeight(), buf->GetHeight()), dst, dstStride);
+
+ if (!target.UnlockRect(0))
+ CLog::LogF(LOGERROR, "failed to unlock swtarget texture.");
+ }
+ else
+ CLog::LogF(LOGERROR, "failed to lock swtarget texture into memory.");
+
+ // rotate initial rect
+ ReorderDrawPoints(CRect(destPoints[0], destPoints[2]), destPoints);
+}
+
+void CRendererSoftware::FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& src, const CPoint(&destPoints)[4])
+{
+ m_outputShader->Render(source, src, destPoints, target,
+ DX::Windowing()->UseLimitedColor(),
+ m_videoSettings.m_Contrast * 0.01f,
+ m_videoSettings.m_Brightness * 0.01f);
+}
+
+CRenderBuffer* CRendererSoftware::CreateBuffer()
+{
+ return new CRenderBufferImpl(m_format, m_sourceWidth, m_sourceHeight);
+}
+
+CRendererSoftware::CRenderBufferImpl::CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height)
+ : CRenderBuffer(av_pix_format, width, height)
+{
+}
+
+CRendererSoftware::CRenderBufferImpl::~CRenderBufferImpl()
+{
+ CRenderBufferImpl::ReleasePicture();
+}
+
+void CRendererSoftware::CRenderBufferImpl::AppendPicture(const VideoPicture& picture)
+{
+ __super::AppendPicture(picture);
+
+ if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD)
+ {
+ QueueCopyFromGPU();
+ m_widthTex = m_sDesc.Width;
+ m_heightTex = m_sDesc.Height;
+ }
+}
+
+bool CRendererSoftware::CRenderBufferImpl::GetDataPlanes(uint8_t*(&planes)[3], int(&strides)[3])
+{
+ if (!videoBuffer)
+ return false;
+
+ switch (videoBuffer->GetFormat())
+ {
+ case AV_PIX_FMT_D3D11VA_VLD:
+ planes[0] = reinterpret_cast<uint8_t*>(m_msr.pData);
+ planes[1] = reinterpret_cast<uint8_t*>(m_msr.pData) + m_msr.RowPitch * m_sDesc.Height;
+ strides[0] = strides[1] = m_msr.RowPitch;
+ break;
+ default:
+ videoBuffer->GetPlanes(planes);
+ videoBuffer->GetStrides(strides);
+ }
+
+ return true;
+}
+
+void CRendererSoftware::CRenderBufferImpl::ReleasePicture()
+{
+ if (m_staging && m_msr.pData != nullptr)
+ {
+ DX::DeviceResources::Get()->GetImmediateContext()->Unmap(m_staging.Get(), 0);
+ m_msr = {};
+ }
+ __super::ReleasePicture();
+}
+
+bool CRendererSoftware::CRenderBufferImpl::UploadBuffer()
+{
+ if (!videoBuffer)
+ return false;
+
+ if (videoBuffer->GetFormat() != AV_PIX_FMT_D3D11VA_VLD)
+ {
+ m_bLoaded = true;
+ return true;
+ }
+
+ if (!m_staging)
+ return false;
+
+ if (m_msr.pData == nullptr)
+ {
+ // map will finish copying data from GPU to CPU
+ m_bLoaded = SUCCEEDED(DX::DeviceResources::Get()->GetImmediateContext()->Map(
+ m_staging.Get(), 0, D3D11_MAP_READ, 0, &m_msr));
+ }
+
+ return m_bLoaded;
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.h
new file mode 100644
index 0000000..25abeeb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererSoftware.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+
+#include "RendererBase.h"
+
+#include <map>
+extern "C" {
+#include <libavutil/pixfmt.h>
+#include <libswscale/swscale.h>
+}
+
+class CRendererSoftware : public CRendererBase
+{
+ class CRenderBufferImpl;
+public:
+ ~CRendererSoftware();
+
+ bool Configure(const VideoPicture& picture, float fps, unsigned orientation) override;
+ bool Supports(ESCALINGMETHOD method) const override;
+
+ static CRendererBase* Create(CVideoSettings& videoSettings);
+ static void GetWeight(std::map<RenderMethod, int>& weights, const VideoPicture& picture);
+
+protected:
+ explicit CRendererSoftware(CVideoSettings& videoSettings) : CRendererBase(videoSettings) {}
+ CRenderBuffer* CreateBuffer() override;
+ void RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags) override;
+ void FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& src, const CPoint(&destPoints)[4]) override;
+
+private:
+ SwsContext* m_sw_scale_ctx = nullptr;
+};
+
+class CRendererSoftware::CRenderBufferImpl : public CRenderBuffer
+{
+public:
+ explicit CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height);
+ ~CRenderBufferImpl();
+
+ void AppendPicture(const VideoPicture& picture) override;
+ bool GetDataPlanes(uint8_t*(&planes)[3], int(&strides)[3]) override;
+
+ void ReleasePicture() override;
+ bool UploadBuffer() override;
+
+private:
+ D3D11_MAPPED_SUBRESOURCE m_msr{};
+};
diff --git a/xbmc/cores/VideoPlayer/test/edl/CMakeLists.txt b/xbmc/cores/VideoPlayer/test/edl/CMakeLists.txt
new file mode 100644
index 0000000..4f88a1e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/CMakeLists.txt
@@ -0,0 +1,3 @@
+set(SOURCES TestEdl.cpp)
+
+core_add_test_library(edl_test)
diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp
new file mode 100644
index 0000000..7098b91
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2022- Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/Edl.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "test/TestUtils.h"
+
+#include <cmath>
+
+#include <gtest/gtest.h>
+
+using namespace EDL;
+
+
+class TestEdl : public ::testing::Test
+{
+protected:
+ TestEdl() = default;
+};
+
+TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/mplayertimebased.edl
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebased.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ // expect kodi to be able to parse the file correctly
+ EXPECT_EQ(found, true);
+ // the file has 5 interest points: 2 scenemarkers, 1 mute, 1 cut and 1 commbreak
+ // edit list should contain any edit that is not a scene marker nor a cut, so 2.
+ // scenemarkers should be 4 (two "raw" scene markers and 2 from the commbreak - start and end of commbreaks)
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.HasSceneMarker(), true);
+ EXPECT_EQ(edl.GetEditList().size(), 2);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 4);
+
+ // cuts
+ // file has only 1 cut starting at 5.3 secs and ending at 7.1 secs
+ // so total cut time should be 1.8 seconds (1800 msec)
+ EXPECT_EQ(edl.HasCuts(), true);
+ EXPECT_EQ(edl.GetTotalCutTime(), 1.8 * 1000);
+ EXPECT_EQ(edl.GetCutMarkers().size(), 1);
+ EXPECT_EQ(edl.GetCutMarkers().at(0), 5.3 * 1000); // 5.3 secs
+ // When removing or restoring cuts, EDL adds (or removes) 1 msec to jump over the start or end boundary of the edit
+ EXPECT_EQ(edl.GetTimeWithoutCuts(edl.GetRawEditList().at(0).start),
+ edl.GetRawEditList().at(0).start + 1);
+ EXPECT_EQ(edl.GetTimeAfterRestoringCuts(edl.GetRawEditList().at(0).start),
+ edl.GetRawEditList().at(0).start);
+ EXPECT_EQ(edl.GetTimeAfterRestoringCuts(edl.GetRawEditList().at(0).start + 2),
+ edl.GetRawEditList().at(0).end + 2);
+
+ // the first edit (note editlist does not contain cuts) is a mute section starting at 15 seconds
+ // of the real file this should correspond to 13.2 secs on the kodi VideoPlayer timeline (start - cuttime)
+ // raw edit list contains cuts so mute should be index 1
+ const auto mute = edl.GetEditList().at(0);
+ const auto muteRaw = edl.GetRawEditList().at(1);
+ EXPECT_EQ(mute.action, Action::MUTE);
+ EXPECT_EQ(muteRaw.action, Action::MUTE);
+ EXPECT_EQ(edl.GetTimeWithoutCuts(mute.start), mute.start - edl.GetTotalCutTime());
+ EXPECT_EQ(edl.GetTimeAfterRestoringCuts(mute.start - edl.GetTotalCutTime()), mute.start);
+ EXPECT_EQ(muteRaw.start - edl.GetTotalCutTime(), mute.start);
+ EXPECT_EQ(edl.InEdit(muteRaw.start, nullptr), true);
+ EXPECT_EQ(edl.InEdit(mute.start, nullptr), false);
+
+ // scene markers
+ // one of the scenemarkers (the first) have start and end times defined, kodi should assume the marker at the END position (255.3 secs)
+ EXPECT_EQ(edl.GetSceneMarkers().at(0), edl.GetTimeWithoutCuts(255.3 * 1000));
+ // one of them only has start defined, at 720.1 secs
+ EXPECT_EQ(edl.GetSceneMarkers().at(1), edl.GetTimeWithoutCuts(720.1 * 1000));
+
+ // commbreaks
+ // the second edit on the file is a commbreak
+ const auto commbreak = edl.GetEditList().at(1);
+ EXPECT_EQ(commbreak.action, Action::COMM_BREAK);
+ // We should have a scenemarker at the commbreak start and another on commbreak end
+ int time;
+ // lets cycle to the next scenemarker if starting from 1 msec before the start (or end) of the commbreak
+ EXPECT_EQ(edl.GetNextSceneMarker(true, commbreak.start - 1, &time), true);
+ EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start);
+ EXPECT_EQ(edl.GetNextSceneMarker(true, commbreak.end - 1, &time), true);
+ EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end);
+ // same if we cycle backwards
+ EXPECT_EQ(edl.GetNextSceneMarker(false, commbreak.start + 1, &time), true);
+ EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start);
+ EXPECT_EQ(edl.GetNextSceneMarker(false, commbreak.end + 1, &time), true);
+ EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end);
+ // We should be in an edit if we are in the middle of a commbreak...
+ // lets check and confirm the edits match (after restoring cuts)
+ Edit thisEdit;
+ const int middleOfCommbreak = commbreak.start + (commbreak.end - commbreak.start) / 2;
+ EXPECT_EQ(edl.InEdit(edl.GetTimeWithoutCuts(middleOfCommbreak), &thisEdit), true);
+ EXPECT_EQ(thisEdit.action, Action::COMM_BREAK);
+ EXPECT_EQ(thisEdit.start, edl.GetTimeAfterRestoringCuts(commbreak.start));
+ EXPECT_EQ(thisEdit.end, edl.GetTimeAfterRestoringCuts(commbreak.end));
+}
+
+TEST_F(TestEdl, TestParsingMplayerTimeBasedInterleavedCutsEDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/mplayertimebasedinterleavedcuts.edl
+ // this is an edl file with commbreaks interleaved with cuts
+ CFileItem mediaItem;
+ mediaItem.SetPath(XBMC_REF_FILE_PATH(
+ "xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedinterleavedcuts.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ // expect kodi to be able to parse the file correctly
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.GetEditList().size(), 2);
+ EXPECT_EQ(edl.HasCuts(), true);
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.GetCutMarkers().size(), 2);
+ // lets check the total cut time matches the sum of the two cut durations defined in the file
+ EXPECT_EQ(edl.GetTotalCutTime(), ((7.1 - 5.3) + (19 - 18)) * 1000);
+ // the first edit is after the first cut, so lets check the start time was adjusted exactly by the cut duration
+ EXPECT_EQ(edl.GetEditList().at(0).start, edl.GetRawEditList().at(1).start - ((7.1 - 5.3) * 1000));
+ EXPECT_EQ(edl.GetEditList().at(0).start,
+ edl.GetTimeWithoutCuts(edl.GetRawEditList().at(1).start));
+ EXPECT_EQ(edl.GetEditList().at(1).start,
+ edl.GetRawEditList().at(3).start - edl.GetTotalCutTime());
+ EXPECT_EQ(edl.GetEditList().at(1).start,
+ edl.GetTimeWithoutCuts(edl.GetRawEditList().at(3).start));
+}
+
+TEST_F(TestEdl, TestParsingMplayerFrameBasedEDL)
+{
+ CEdl edl;
+ // suppose we're playing a file with 60 fps
+ const float fps = 60;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/mplayerframebased.edl
+ // this is an edl file with frame based edit points
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/mplayerframebased.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, fps);
+ // expect kodi to be able to parse the file correctly
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.HasCuts(), true);
+ EXPECT_EQ(edl.HasSceneMarker(), true);
+ EXPECT_EQ(edl.GetEditList().size(), 2);
+ // check edit times are correctly calculated provided the fps
+ EXPECT_EQ(edl.GetEditList().at(0).start,
+ static_cast<int64_t>((360 / fps) * 1000) - edl.GetTotalCutTime());
+ EXPECT_EQ(edl.GetSceneMarkers().at(0),
+ static_cast<int64_t>((6127 / fps) * 1000) - edl.GetTotalCutTime());
+}
+
+TEST_F(TestEdl, TestParsingMplayerTimeBasedMixedEDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/mplayertimebasedmixed.edl
+ // this file has edit points with seconds and others with seconds timestrings
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedmixed.mkv"));
+ bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ // expect kodi to be able to parse the file correctly
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.HasCuts(), true);
+ EXPECT_EQ(edl.HasSceneMarker(), true);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 4);
+ bool sceneFound = false;
+ // check we have correctly parsed the scene with 12:00.1 start point
+ for (const auto& scene : edl.GetSceneMarkers())
+ {
+ if (scene == (12 * 60 + 0.1) * 1000 - edl.GetTotalCutTime())
+ {
+ sceneFound = true;
+ break;
+ }
+ }
+ EXPECT_EQ(sceneFound, true);
+ // check that the first ordered edit starts at 15 secs
+ EXPECT_EQ(edl.GetEditList().front().start, (15 * 1000) - edl.GetTotalCutTime());
+}
+
+TEST_F(TestEdl, TestParsingVideoRedoEDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/videoredo.Vprj
+ // this is an edl file in VideoReDo format
+ CFileItem mediaItem;
+ mediaItem.SetPath(XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/videoredo.mkv"));
+ bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.HasEdits(), true);
+ // videoredo only supports cuts or scenemarkers, hence the editlist should be empty. Raw editlist should contain the cuts.
+ EXPECT_EQ(edl.GetEditList().size(), 0);
+ EXPECT_GT(edl.GetRawEditList().size(), 0);
+ EXPECT_EQ(edl.HasCuts(), true);
+ EXPECT_EQ(edl.HasSceneMarker(), true);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 4);
+ EXPECT_EQ(edl.GetCutMarkers().size(), 3);
+ // in videoredo time processing is ms * 10000
+ // first cut in the file is at 4235230000 - let's confirm this corresponds to second 423.523
+ EXPECT_EQ(edl.GetCutMarkers().front(), 423.523 * 1000);
+}
+
+TEST_F(TestEdl, TestSnapStreamEDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/snapstream.mkv.chapters.xml
+ // this is an edl file in SnapStream BeyondTV format
+ CFileItem mediaItem;
+ mediaItem.SetPath(XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/snapstream.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ // this format only supports commbreak types
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.GetCutMarkers().empty(), true);
+ EXPECT_EQ(edl.GetEditList().size(), 3);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak
+ // snapstream beyond tv uses ms * 10000
+ // check if first commbreak (4235230000 - 5936600000) is 423.523 sec - 593.660 sec
+ EXPECT_EQ(edl.GetEditList().front().start, std::lround(423.523 * 1000));
+ EXPECT_EQ(edl.GetEditList().front().end, std::lround(593.660 * 1000));
+}
+
+TEST_F(TestEdl, TestComSkipVersion1EDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/comskipversion1.txt
+ // this is an edl file in ComSkip (version 1) format
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.mkv"));
+ bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ // fps was not supplied, kodi will not be able to process the file
+ EXPECT_EQ(found, false);
+ // parse the file again this time supplying 60 fps
+ const float fps = 60;
+ found = edl.ReadEditDecisionLists(mediaItem, fps);
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.GetCutMarkers().empty(), true);
+ EXPECT_EQ(edl.GetEditList().size(), 3);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak
+ EXPECT_EQ(edl.GetEditList().front().start, std::lround(12693 / fps * 1000));
+ EXPECT_EQ(edl.GetEditList().front().end, std::lround(17792 / fps * 1000));
+}
+
+TEST_F(TestEdl, TestComSkipVersion2EDL)
+{
+ CEdl edl;
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/comskipversion2.txt
+ // this is an edl file in ComSkip (version 2) format where fps is obtained from the file
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ // fps is obtained from the file as it always takes precedence (note we supplied 0 above),
+ // the EDL file has the value of 2500 for fps. kodi converts this to 25 fps by dividing by a factor of 100
+ const float fpsInEdlFile = 2500 / 100;
+ // this format only supports commbreak types
+ EXPECT_EQ(edl.HasEdits(), true);
+ EXPECT_EQ(edl.GetCutMarkers().empty(), true);
+ EXPECT_EQ(edl.GetEditList().size(), 3);
+ EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak
+ EXPECT_EQ(edl.GetEditList().front().start, std::lround(12693 / fpsInEdlFile * 1000));
+ EXPECT_EQ(edl.GetEditList().front().end, std::lround(17792 / fpsInEdlFile * 1000));
+}
+
+TEST_F(TestEdl, TestRuntimeSetEDL)
+{
+ // this is a simple test for SetLastEditTime, SetLastEditActionType and corresponding getters
+ CEdl edl;
+ edl.SetLastEditTime(1000);
+ edl.SetLastEditActionType(Action::COMM_BREAK);
+ EXPECT_EQ(edl.GetLastEditTime(), 1000);
+ EXPECT_EQ(edl.GetLastEditActionType(), Action::COMM_BREAK);
+}
+
+TEST_F(TestEdl, TestCommBreakAdvancedSettings)
+{
+ CEdl edl;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // the test goal is to test m_iEdlCommBreakAutowait and m_iEdlCommBreakAutowind
+ // lets check first the behaviour with default values (both disabled)
+ // Keep all EDL advanced settings to default
+ advancedSettings->m_iEdlCommBreakAutowait = 0; // disabled (default)
+ advancedSettings->m_iEdlCommBreakAutowind = 0; // disabled (default)
+ advancedSettings->m_bEdlMergeShortCommBreaks = false; // disabled (default)
+ advancedSettings->m_iEdlMinCommBreakLength = 3 * 30; // 3*30 secs (default)
+ advancedSettings->m_iEdlMaxCommBreakLength = 8 * 30 + 10; // 8*30+10 secs (default value)
+ advancedSettings->m_iEdlMaxStartGap = 5 * 60; // 5 minutes (default)
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowait, 0);
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowind, 0);
+ EXPECT_EQ(advancedSettings->m_bEdlMergeShortCommBreaks, false);
+ EXPECT_EQ(advancedSettings->m_iEdlMinCommBreakLength, 3 * 30);
+ EXPECT_EQ(advancedSettings->m_iEdlMinCommBreakLength, 3 * 30);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxCommBreakLength, 8 * 30 + 10);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxStartGap, 5 * 60);
+
+ // create a dummy "media" fileitem whose corresponding edl file is testdata/edlautowindautowait.txt
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.mkv"));
+ bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ // confirm the start and end times of all the commbreaks match
+ EXPECT_EQ(edl.GetEditList().size(), 5);
+ EXPECT_EQ(edl.GetEditList().at(0).start, 10 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(0).end, 22 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(1).start, 30 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(1).end, 32 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(2).start, 37 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(2).end, 50 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(3).start, 52 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(3).end, 60 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(4).start, 62 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(4).end, std::lround(65.1 * 1000));
+ // now lets change autowait and autowind and check the edits are correcly adjusted
+ edl.Clear();
+ advancedSettings->m_iEdlCommBreakAutowait = 3; // secs
+ advancedSettings->m_iEdlCommBreakAutowind = 3; // secs
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowait, 3);
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowind, 3);
+ found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(edl.GetEditList().size(), 5);
+ // the second edit has a duration smaller than the autowait
+ // this moves the start time to the end of the edit
+ EXPECT_EQ(edl.GetEditList().at(1).start, 32 * 1000);
+ EXPECT_EQ(edl.GetEditList().at(1).end, edl.GetEditList().at(1).start);
+ // the others should be adjusted + 3 secs at the start and -3 secs at the end
+ // due to the provided values for autowait and autowind.
+ EXPECT_EQ(edl.GetEditList().at(0).start, (10 + 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(0).end, (22 - 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(2).start, (37 + 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(2).end, (50 - 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(3).start, (52 + 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(3).end, (60 - 3) * 1000);
+ // since we adjust the start to second 65 and the autowind is 3 seconds kodi should
+ // shift the end time not by 3 seconds but by the "excess" time (in this case 0.1 sec)
+ // this means start and end will be exactly the same. The commbreak would be removed if
+ // mergeshortcommbreaks was active and advancedsetting m_iEdlMinCommBreakLength
+ // was set to a reasonable threshold.
+ EXPECT_EQ(edl.GetEditList().at(4).start, (62 + 3) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(4).end, (65.1 - 0.1) * 1000);
+ EXPECT_EQ(edl.GetEditList().at(4).start, edl.GetEditList().at(4).end);
+}
+
+TEST_F(TestEdl, TestCommBreakAdvancedSettingsRemoveSmallCommbreaks)
+{
+ // this is a variation of TestCommBreakAdvancedSettings
+ // should make sure the number of commbreaks in the file is now 3 instead of 5
+ // since two of them have duration smaller than 1 sec
+ CEdl edl;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // set EDL advanced settings specific for the test case
+ advancedSettings->m_iEdlCommBreakAutowait = 3; // secs
+ advancedSettings->m_iEdlCommBreakAutowind = 3; // secs
+ advancedSettings->m_bEdlMergeShortCommBreaks = true;
+ advancedSettings->m_iEdlMinCommBreakLength = 1; // sec
+ advancedSettings->m_iEdlMaxCommBreakLength = 0; // deactivate
+ advancedSettings->m_iEdlMaxStartGap = 0; // deactivate
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowait, 3);
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowind, 3);
+ EXPECT_EQ(advancedSettings->m_bEdlMergeShortCommBreaks, true);
+ EXPECT_EQ(advancedSettings->m_iEdlMinCommBreakLength, 1);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxCommBreakLength, 0);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxStartGap, 0);
+
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ EXPECT_EQ(edl.GetEditList().size(), 3);
+}
+
+TEST_F(TestEdl, TestMergeSmallCommbreaks)
+{
+ CEdl edl;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // set EDL advanced settings specific for the test case
+ advancedSettings->m_bEdlMergeShortCommBreaks = true;
+ EXPECT_EQ(advancedSettings->m_bEdlMergeShortCommBreaks, true);
+ // keep any other EDL advanced settings to default
+ advancedSettings->m_iEdlCommBreakAutowait = 0; // disabled (default)
+ advancedSettings->m_iEdlCommBreakAutowind = 0; // disabled (default)
+ advancedSettings->m_iEdlMinCommBreakLength = 3 * 30; // 3*30 secs (default)
+ advancedSettings->m_iEdlMaxCommBreakLength = 8 * 30 + 10; // 8*30+10 secs (default value)
+ advancedSettings->m_iEdlMaxStartGap = 5 * 60; // 5 minutes (default)
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowait, 0);
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowind, 0);
+ EXPECT_EQ(advancedSettings->m_iEdlMinCommBreakLength, 3 * 30);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxCommBreakLength, 8 * 30 + 10);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxStartGap, 5 * 60);
+
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ // kodi should merge all commbreaks into a single one starting at the first point (0)
+ // and ending at the last edit time
+ EXPECT_EQ(edl.GetEditList().size(), 1);
+ EXPECT_EQ(edl.GetEditList().at(0).start, 0);
+ EXPECT_EQ(edl.GetEditList().at(0).end, std::lround(65.1 * 1000));
+}
+
+TEST_F(TestEdl, TestMergeSmallCommbreaksAdvanced)
+{
+ CEdl edl;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // set EDL advanced settings specific for the test case
+ advancedSettings->m_bEdlMergeShortCommBreaks = true;
+ advancedSettings->m_iEdlMaxCommBreakLength = 30; // 30 secs
+ advancedSettings->m_iEdlMinCommBreakLength = 1; // 1 sec
+ advancedSettings->m_iEdlMaxStartGap = 2; // 2 secs
+ EXPECT_EQ(advancedSettings->m_bEdlMergeShortCommBreaks, true);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxCommBreakLength, 30);
+ EXPECT_EQ(advancedSettings->m_iEdlMinCommBreakLength, 1);
+ EXPECT_EQ(advancedSettings->m_iEdlMaxStartGap, 2);
+ // keep any other EDL advanced settings to default
+ advancedSettings->m_iEdlCommBreakAutowait = 0; // disabled (default)
+ advancedSettings->m_iEdlCommBreakAutowind = 0; // disabled (default)
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowait, 0);
+ EXPECT_EQ(advancedSettings->m_iEdlCommBreakAutowind, 0);
+
+ CFileItem mediaItem;
+ mediaItem.SetPath(
+ XBMC_REF_FILE_PATH("xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.mkv"));
+ const bool found = edl.ReadEditDecisionLists(mediaItem, 0);
+ EXPECT_EQ(found, true);
+ // kodi should merge all commbreaks into two
+ EXPECT_EQ(edl.GetEditList().size(), 2);
+ // second edit of the original file + third one
+ EXPECT_EQ(edl.GetEditList().at(0).end - edl.GetEditList().at(0).start, (32 - 10) * 1000);
+ // 4th, 5th and 6th commbreaks joined
+ EXPECT_EQ(edl.GetEditList().at(1).end - edl.GetEditList().at(1).start,
+ std::lround((65.1 - 37) * 1000));
+}
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.txt b/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.txt
new file mode 100644
index 0000000..3716d5e
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.txt
@@ -0,0 +1,5 @@
+FILE PROCESSING COMPLETE
+------------------------
+12693 17792
+28578 34549
+43114 48222
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.txt b/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.txt
new file mode 100644
index 0000000..a54c789
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.txt
@@ -0,0 +1,5 @@
+FILE PROCESSING COMPLETE 678900 FRAMES AT 2500
+------------------------
+12693 17792
+28578 34549
+43114 48222
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.edl b/xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.edl
new file mode 100644
index 0000000..8506acb
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.edl
@@ -0,0 +1,7 @@
+## first commbreak should be automatically removed
+0 1 3
+10 22 3
+30 32 3
+37 50 3
+52 60 3
+62 65.1 3
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/mplayerframebased.edl b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayerframebased.edl
new file mode 100644
index 0000000..d8aca36
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayerframebased.edl
@@ -0,0 +1,5 @@
+#127 #170 0
+#360 #400 1
+#10080 #19728 3
+#1 #6127 2
+#17282 2
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebased.edl b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebased.edl
new file mode 100644
index 0000000..5bb4ad5
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebased.edl
@@ -0,0 +1,6 @@
+5.3 7.1 0
+15 16.7 1
+## This is a comment kodi should happily ignore when parsing
+420 822 3
+1 255.3 2
+720.1 2
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedinterleavedcuts.edl b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedinterleavedcuts.edl
new file mode 100644
index 0000000..5a4f036
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedinterleavedcuts.edl
@@ -0,0 +1,4 @@
+5.3 7.1 0
+15 16.7 3
+18 19 0
+25 36.7 3
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedmixed.edl b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedmixed.edl
new file mode 100644
index 0000000..383eb9a
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/mplayertimebasedmixed.edl
@@ -0,0 +1,5 @@
+5.3 7.1 0
+15 16.7 1
+7:00 13:42 3
+1 4:15.3 2
+12:00.1 2
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/snapstream.mkv.chapters.xml b/xbmc/cores/VideoPlayer/test/edl/testdata/snapstream.mkv.chapters.xml
new file mode 100644
index 0000000..cae6f37
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/snapstream.mkv.chapters.xml
@@ -0,0 +1,14 @@
+<cutlist>
+ <Region>
+ <start comment="0:07:03.0000523">4235230000</start>
+ <end comment="0:09:53.0000660">5936600000</end>
+ </Region>
+ <Region>
+ <start comment="0:15:53.0000553">9535530000</start>
+ <end comment="0:19:12.0000786">11527860000</end>
+ </Region>
+ <Region>
+ <start comment="0:23:58.0000571">14385710000</start>
+ <end comment="0:26:49.0000009">16090090000</end>
+ </Region>
+</cutlist>
diff --git a/xbmc/cores/VideoPlayer/test/edl/testdata/videoredo.Vprj b/xbmc/cores/VideoPlayer/test/edl/testdata/videoredo.Vprj
new file mode 100644
index 0000000..72236b8
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/test/edl/testdata/videoredo.Vprj
@@ -0,0 +1,13 @@
+<Version>2
+<Filename>C:\Path\To\Video.mpg
+<InputPIDList>
+<VideoStreamPID>224</VideoStreamPID>
+<AudioStreamPID>192</AudioStreamPID>
+</InputPIDList>
+<SceneMarker 1415932542>4235230000
+<SceneMarker 1415932543>4284610000
+<SceneMarker 1415932544>4585580000
+<SceneMarker 1415932545>5035360000
+<Cut>4235230000:5936600000
+<Cut>9535530000:11527860000
+<Cut>14385710000:16090090000
diff --git a/xbmc/cores/VideoSettings.cpp b/xbmc/cores/VideoSettings.cpp
new file mode 100644
index 0000000..ec474b3
--- /dev/null
+++ b/xbmc/cores/VideoSettings.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoSettings.h"
+
+#include "threads/CriticalSection.h"
+
+#include <mutex>
+
+CVideoSettings::CVideoSettings()
+{
+ m_InterlaceMethod = VS_INTERLACEMETHOD_AUTO;
+ m_ScalingMethod = VS_SCALINGMETHOD_LINEAR;
+ m_ViewMode = ViewModeNormal;
+ m_CustomZoomAmount = 1.0f;
+ m_CustomPixelRatio = 1.0f;
+ m_CustomVerticalShift = 0.0f;
+ m_CustomNonLinStretch = false;
+ m_AudioStream = -1;
+ m_SubtitleStream = -1;
+ m_SubtitleDelay = 0.0f;
+ m_subtitleVerticalPosition = 0;
+ m_subtitleVerticalPositionSave = false;
+ m_SubtitleOn = true;
+ m_Brightness = 50.0f;
+ m_Contrast = 50.0f;
+ m_Gamma = 20.0f;
+ m_Sharpness = 0.0f;
+ m_NoiseReduction = 0;
+ m_PostProcess = false;
+ m_VolumeAmplification = 0;
+ m_AudioDelay = 0.0f;
+ m_ResumeTime = 0;
+ m_StereoMode = 0;
+ m_StereoInvert = false;
+ m_VideoStream = -1;
+ m_ToneMapMethod = VS_TONEMAPMETHOD_REINHARD;
+ m_ToneMapParam = 1.0f;
+ m_Orientation = 0;
+ m_CenterMixLevel = 0;
+}
+
+bool CVideoSettings::operator!=(const CVideoSettings &right) const
+{
+ if (m_InterlaceMethod != right.m_InterlaceMethod) return true;
+ if (m_ScalingMethod != right.m_ScalingMethod) return true;
+ if (m_ViewMode != right.m_ViewMode) return true;
+ if (m_CustomZoomAmount != right.m_CustomZoomAmount) return true;
+ if (m_CustomPixelRatio != right.m_CustomPixelRatio) return true;
+ if (m_CustomVerticalShift != right.m_CustomVerticalShift) return true;
+ if (m_CustomNonLinStretch != right.m_CustomNonLinStretch) return true;
+ if (m_AudioStream != right.m_AudioStream) return true;
+ if (m_SubtitleStream != right.m_SubtitleStream) return true;
+ if (m_SubtitleDelay != right.m_SubtitleDelay) return true;
+ if (m_subtitleVerticalPosition != right.m_subtitleVerticalPosition)
+ return true;
+ if (m_subtitleVerticalPositionSave != right.m_subtitleVerticalPositionSave)
+ return true;
+ if (m_SubtitleOn != right.m_SubtitleOn) return true;
+ if (m_Brightness != right.m_Brightness) return true;
+ if (m_Contrast != right.m_Contrast) return true;
+ if (m_Gamma != right.m_Gamma) return true;
+ if (m_Sharpness != right.m_Sharpness) return true;
+ if (m_NoiseReduction != right.m_NoiseReduction) return true;
+ if (m_PostProcess != right.m_PostProcess) return true;
+ if (m_VolumeAmplification != right.m_VolumeAmplification) return true;
+ if (m_AudioDelay != right.m_AudioDelay) return true;
+ if (m_ResumeTime != right.m_ResumeTime) return true;
+ if (m_StereoMode != right.m_StereoMode) return true;
+ if (m_StereoInvert != right.m_StereoInvert) return true;
+ if (m_VideoStream != right.m_VideoStream) return true;
+ if (m_ToneMapMethod != right.m_ToneMapMethod) return true;
+ if (m_ToneMapParam != right.m_ToneMapParam) return true;
+ if (m_Orientation != right.m_Orientation) return true;
+ if (m_CenterMixLevel != right.m_CenterMixLevel) return true;
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// CVideoSettingsLocked
+//------------------------------------------------------------------------------
+CVideoSettingsLocked::CVideoSettingsLocked(CVideoSettings &vs, CCriticalSection &critSection) :
+ m_videoSettings(vs), m_critSection(critSection)
+{
+}
+
+void CVideoSettingsLocked::SetSubtitleStream(int stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_SubtitleStream = stream;
+}
+
+void CVideoSettingsLocked::SetSubtitleVisible(bool visible)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_SubtitleOn = visible;
+}
+
+void CVideoSettingsLocked::SetAudioStream(int stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_AudioStream = stream;
+}
+
+void CVideoSettingsLocked::SetVideoStream(int stream)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_VideoStream = stream;
+}
+
+void CVideoSettingsLocked::SetAudioDelay(float delay)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_AudioDelay = delay;
+}
+
+void CVideoSettingsLocked::SetSubtitleDelay(float delay)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_SubtitleDelay = delay;
+}
+
+void CVideoSettingsLocked::SetSubtitleVerticalPosition(int value, bool save)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_subtitleVerticalPosition = value;
+ m_videoSettings.m_subtitleVerticalPositionSave = save;
+}
+
+void CVideoSettingsLocked::SetViewMode(int mode, float zoom, float par, float shift, bool stretch)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_ViewMode = mode;
+ m_videoSettings.m_CustomZoomAmount = zoom;
+ m_videoSettings.m_CustomPixelRatio = par;
+ m_videoSettings.m_CustomVerticalShift = shift;
+ m_videoSettings.m_CustomNonLinStretch = stretch;
+}
+
+void CVideoSettingsLocked::SetVolumeAmplification(float amp)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_videoSettings.m_VolumeAmplification = amp;
+}
diff --git a/xbmc/cores/VideoSettings.h b/xbmc/cores/VideoSettings.h
new file mode 100644
index 0000000..a7135da
--- /dev/null
+++ b/xbmc/cores/VideoSettings.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Map.h"
+
+#include <string_view>
+
+#include <fmt/format.h>
+
+// VideoSettings.h: interface for the CVideoSettings class.
+//
+//////////////////////////////////////////////////////////////////////
+
+enum EINTERLACEMETHOD
+{
+ VS_INTERLACEMETHOD_NONE=0,
+ VS_INTERLACEMETHOD_AUTO=1,
+ VS_INTERLACEMETHOD_RENDER_BLEND=2,
+ VS_INTERLACEMETHOD_RENDER_WEAVE=4,
+ VS_INTERLACEMETHOD_RENDER_BOB=6,
+ VS_INTERLACEMETHOD_DEINTERLACE=7,
+ VS_INTERLACEMETHOD_VDPAU_BOB=8,
+ VS_INTERLACEMETHOD_VDPAU_INVERSE_TELECINE=11,
+ VS_INTERLACEMETHOD_VDPAU_TEMPORAL=12,
+ VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF=13,
+ VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL=14,
+ VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF=15,
+ VS_INTERLACEMETHOD_DEINTERLACE_HALF=16,
+ VS_INTERLACEMETHOD_VAAPI_BOB = 22,
+ VS_INTERLACEMETHOD_VAAPI_MADI = 23,
+ VS_INTERLACEMETHOD_VAAPI_MACI = 24,
+ VS_INTERLACEMETHOD_DXVA_AUTO = 32,
+ VS_INTERLACEMETHOD_MAX // do not use and keep as last enum value.
+};
+
+template<>
+struct fmt::formatter<EINTERLACEMETHOD> : fmt::formatter<std::string_view>
+{
+ template<typename FormatContext>
+ constexpr auto format(const EINTERLACEMETHOD& interlaceMethod, FormatContext& ctx)
+ {
+ const auto it = interlaceMethodMap.find(interlaceMethod);
+ if (it == interlaceMethodMap.cend())
+ throw std::range_error("no interlace method string found");
+
+ return fmt::formatter<string_view>::format(it->second, ctx);
+ }
+
+private:
+ static constexpr auto interlaceMethodMap = make_map<EINTERLACEMETHOD, std::string_view>({
+ {VS_INTERLACEMETHOD_NONE, "none"},
+ {VS_INTERLACEMETHOD_AUTO, "auto"},
+ {VS_INTERLACEMETHOD_RENDER_BLEND, "render blend"},
+ {VS_INTERLACEMETHOD_RENDER_WEAVE, "render weave"},
+ {VS_INTERLACEMETHOD_RENDER_BOB, "render bob"},
+ {VS_INTERLACEMETHOD_DEINTERLACE, "deinterlace"},
+ {VS_INTERLACEMETHOD_VDPAU_BOB, "vdpau bob"},
+ {VS_INTERLACEMETHOD_VDPAU_INVERSE_TELECINE, "vdpau inverse telecine"},
+ {VS_INTERLACEMETHOD_VDPAU_TEMPORAL, "vdpau temporal"},
+ {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF, "vdpau temporal half"},
+ {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL, "vdpau temporal spatial"},
+ {VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF, "vdpau temporal spatial half"},
+ {VS_INTERLACEMETHOD_DEINTERLACE_HALF, "deinterlace half"},
+ {VS_INTERLACEMETHOD_VAAPI_BOB, "vaapi bob"},
+ {VS_INTERLACEMETHOD_VAAPI_MADI, "vaapi madi"},
+ {VS_INTERLACEMETHOD_VAAPI_MACI, "vaapi maci"},
+ {VS_INTERLACEMETHOD_DXVA_AUTO, "dxva auto"},
+ });
+};
+
+enum ESCALINGMETHOD
+{
+ VS_SCALINGMETHOD_NEAREST=0,
+ VS_SCALINGMETHOD_LINEAR,
+ VS_SCALINGMETHOD_CUBIC_B_SPLINE,
+ VS_SCALINGMETHOD_CUBIC_MITCHELL,
+ VS_SCALINGMETHOD_CUBIC_CATMULL,
+ VS_SCALINGMETHOD_CUBIC_0_075,
+ VS_SCALINGMETHOD_CUBIC_0_1,
+ VS_SCALINGMETHOD_LANCZOS2,
+ VS_SCALINGMETHOD_LANCZOS3_FAST,
+ VS_SCALINGMETHOD_LANCZOS3,
+ VS_SCALINGMETHOD_SINC8,
+ VS_SCALINGMETHOD_BICUBIC_SOFTWARE,
+ VS_SCALINGMETHOD_LANCZOS_SOFTWARE,
+ VS_SCALINGMETHOD_SINC_SOFTWARE,
+ VS_SCALINGMETHOD_VDPAU_HARDWARE,
+ VS_SCALINGMETHOD_DXVA_HARDWARE,
+ VS_SCALINGMETHOD_AUTO,
+ VS_SCALINGMETHOD_SPLINE36_FAST,
+ VS_SCALINGMETHOD_SPLINE36,
+ VS_SCALINGMETHOD_MAX // do not use and keep as last enum value.
+};
+
+template<>
+struct fmt::formatter<ESCALINGMETHOD> : fmt::formatter<std::string_view>
+{
+public:
+ template<typename FormatContext>
+ constexpr auto format(const ESCALINGMETHOD& scalingMethod, FormatContext& ctx)
+ {
+ const auto it = scalingMethodMap.find(scalingMethod);
+ if (it == scalingMethodMap.cend())
+ throw std::range_error("no scaling method string found");
+
+ return fmt::formatter<string_view>::format(it->second, ctx);
+ }
+
+private:
+ static constexpr auto scalingMethodMap = make_map<ESCALINGMETHOD, std::string_view>({
+ {VS_SCALINGMETHOD_NEAREST, "nearest neighbour"},
+ {VS_SCALINGMETHOD_LINEAR, "linear"},
+ {VS_SCALINGMETHOD_CUBIC_B_SPLINE, "cubic b spline"},
+ {VS_SCALINGMETHOD_CUBIC_MITCHELL, "cubic mitchell"},
+ {VS_SCALINGMETHOD_CUBIC_CATMULL, "cubic catmull"},
+ {VS_SCALINGMETHOD_CUBIC_0_075, "cubic 0/075"},
+ {VS_SCALINGMETHOD_CUBIC_0_1, "cubic 0/1"},
+ {VS_SCALINGMETHOD_LANCZOS2, "lanczos2"},
+ {VS_SCALINGMETHOD_LANCZOS3_FAST, "lanczos3 fast"},
+ {VS_SCALINGMETHOD_LANCZOS3, "lanczos3"},
+ {VS_SCALINGMETHOD_SINC8, "sinc8"},
+ {VS_SCALINGMETHOD_BICUBIC_SOFTWARE, "bicubic software"},
+ {VS_SCALINGMETHOD_LANCZOS_SOFTWARE, "lanczos software"},
+ {VS_SCALINGMETHOD_SINC_SOFTWARE, "sinc software"},
+ {VS_SCALINGMETHOD_VDPAU_HARDWARE, "vdpau"},
+ {VS_SCALINGMETHOD_DXVA_HARDWARE, "dxva"},
+ {VS_SCALINGMETHOD_AUTO, "auto"},
+ {VS_SCALINGMETHOD_SPLINE36_FAST, "spline32 fast"},
+ {VS_SCALINGMETHOD_SPLINE36, "spline32"},
+ });
+
+ static_assert(VS_SCALINGMETHOD_MAX == scalingMethodMap.size(),
+ "scalingMethodMap doesn't match the size of ESCALINGMETHOD, did you forget to "
+ "add/remove a mapping?");
+};
+
+enum ETONEMAPMETHOD
+{
+ VS_TONEMAPMETHOD_OFF = 0,
+ VS_TONEMAPMETHOD_REINHARD = 1,
+ VS_TONEMAPMETHOD_ACES = 2,
+ VS_TONEMAPMETHOD_HABLE = 3,
+ VS_TONEMAPMETHOD_MAX
+};
+
+template<>
+struct fmt::formatter<ETONEMAPMETHOD> : fmt::formatter<std::string_view>
+{
+public:
+ template<typename FormatContext>
+ constexpr auto format(const ETONEMAPMETHOD& tonemapMethod, FormatContext& ctx)
+ {
+ const auto it = tonemapMethodMap.find(tonemapMethod);
+ if (it == tonemapMethodMap.cend())
+ throw std::range_error("no tonemap method string found");
+
+ return fmt::formatter<string_view>::format(it->second, ctx);
+ }
+
+private:
+ static constexpr auto tonemapMethodMap = make_map<ETONEMAPMETHOD, std::string_view>({
+ {VS_TONEMAPMETHOD_OFF, "off"},
+ {VS_TONEMAPMETHOD_REINHARD, "reinhard"},
+ {VS_TONEMAPMETHOD_ACES, "aces"},
+ {VS_TONEMAPMETHOD_HABLE, "hable"},
+ });
+
+ static_assert(VS_TONEMAPMETHOD_MAX == tonemapMethodMap.size(),
+ "tonemapMethodMap doesn't match the size of ETONEMAPMETHOD, did you forget to "
+ "add/remove a mapping?");
+};
+
+enum ViewMode
+{
+ ViewModeNormal = 0,
+ ViewModeZoom,
+ ViewModeStretch4x3,
+ ViewModeWideZoom,
+ ViewModeStretch16x9,
+ ViewModeOriginal,
+ ViewModeCustom,
+ ViewModeStretch16x9Nonlin,
+ ViewModeZoom120Width,
+ ViewModeZoom110Width
+};
+
+class CVideoSettings
+{
+public:
+ CVideoSettings();
+ ~CVideoSettings() = default;
+
+ bool operator!=(const CVideoSettings &right) const;
+
+ EINTERLACEMETHOD m_InterlaceMethod;
+ ESCALINGMETHOD m_ScalingMethod;
+ int m_ViewMode; // current view mode
+ float m_CustomZoomAmount; // custom setting zoom amount
+ float m_CustomPixelRatio; // custom setting pixel ratio
+ float m_CustomVerticalShift; // custom setting vertical shift
+ bool m_CustomNonLinStretch;
+ int m_AudioStream;
+ float m_VolumeAmplification;
+ int m_SubtitleStream;
+ float m_SubtitleDelay;
+ int m_subtitleVerticalPosition{0};
+ bool m_subtitleVerticalPositionSave{false};
+ bool m_SubtitleOn;
+ float m_Brightness;
+ float m_Contrast;
+ float m_Gamma;
+ float m_NoiseReduction;
+ bool m_PostProcess;
+ float m_Sharpness;
+ float m_AudioDelay;
+ int m_ResumeTime;
+ int m_StereoMode;
+ bool m_StereoInvert;
+ int m_VideoStream;
+ ETONEMAPMETHOD m_ToneMapMethod;
+ float m_ToneMapParam;
+ int m_Orientation;
+ int m_CenterMixLevel; // relative to metadata or default
+};
+
+class CCriticalSection;
+class CVideoSettingsLocked
+{
+public:
+ CVideoSettingsLocked(CVideoSettings &vs, CCriticalSection &critSection);
+ virtual ~CVideoSettingsLocked() = default;
+
+ CVideoSettingsLocked(CVideoSettingsLocked const &) = delete;
+ void operator=(CVideoSettingsLocked const &x) = delete;
+
+ void SetSubtitleStream(int stream);
+ void SetSubtitleVisible(bool visible);
+ void SetAudioStream(int stream);
+ void SetVideoStream(int stream);
+ void SetAudioDelay(float delay);
+ void SetSubtitleDelay(float delay);
+
+ /*!
+ * \brief Set the subtitle vertical position,
+ * it depends on current screen resolution
+ * \param value The subtitle position in pixels
+ * \param save If true, the value will be saved to resolution info
+ */
+ void SetSubtitleVerticalPosition(int value, bool save);
+
+ void SetViewMode(int mode, float zoom, float par, float shift, bool stretch);
+ void SetVolumeAmplification(float amp);
+
+protected:
+ CVideoSettings &m_videoSettings;
+ CCriticalSection &m_critSection;
+};
diff --git a/xbmc/cores/paplayer/AudioDecoder.cpp b/xbmc/cores/paplayer/AudioDecoder.cpp
new file mode 100644
index 0000000..03dc907
--- /dev/null
+++ b/xbmc/cores/paplayer/AudioDecoder.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AudioDecoder.h"
+
+#include "CodecFactory.h"
+#include "FileItem.h"
+#include "ICodec.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include <cmath>
+#include <mutex>
+
+CAudioDecoder::CAudioDecoder()
+{
+ m_codec = NULL;
+ m_rawBuffer = nullptr;
+
+ m_eof = false;
+
+ m_status = STATUS_NO_FILE;
+ m_canPlay = false;
+
+ // output buffer (for transferring data from the Pcm Buffer to the rest of the audio chain)
+ memset(&m_outputBuffer, 0, OUTPUT_SAMPLES * sizeof(float));
+ memset(&m_pcmInputBuffer, 0, INPUT_SIZE * sizeof(unsigned char));
+ memset(&m_inputBuffer, 0, INPUT_SAMPLES * sizeof(float));
+
+ m_rawBufferSize = 0;
+}
+
+CAudioDecoder::~CAudioDecoder()
+{
+ Destroy();
+}
+
+void CAudioDecoder::Destroy()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_status = STATUS_NO_FILE;
+
+ m_pcmBuffer.Destroy();
+
+ if ( m_codec )
+ delete m_codec;
+ m_codec = NULL;
+
+ m_canPlay = false;
+}
+
+bool CAudioDecoder::Create(const CFileItem &file, int64_t seekOffset)
+{
+ Destroy();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // reset our playback timing variables
+ m_eof = false;
+
+ // get correct cache size
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ unsigned int filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_INTERNET);
+ if ( file.IsHD() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHE_HARDDISK);
+ else if ( file.IsOnDVD() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_DVDROM);
+ else if ( file.IsOnLAN() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_LAN);
+
+ // create our codec
+ m_codec=CodecFactory::CreateCodecDemux(file, filecache * 1024);
+
+ if (!m_codec || !m_codec->Init(file, filecache * 1024))
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder: Unable to Init Codec while loading file {}",
+ file.GetDynPath());
+ Destroy();
+ return false;
+ }
+ unsigned int blockSize = (m_codec->m_bitsPerSample >> 3) * m_codec->m_format.m_channelLayout.Count();
+
+ if (blockSize == 0)
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder: Codec provided invalid parameters ({}-bit, {} channels)",
+ m_codec->m_bitsPerSample, GetFormat().m_channelLayout.Count());
+ return false;
+ }
+
+ /* allocate the pcmBuffer for 2 seconds of audio */
+ m_pcmBuffer.Create(2 * blockSize * m_codec->m_format.m_sampleRate);
+
+ if (file.HasMusicInfoTag())
+ {
+ // set total time from the given tag
+ if (file.GetMusicInfoTag()->GetDuration())
+ m_codec->SetTotalTime(file.GetMusicInfoTag()->GetDuration());
+
+ // update ReplayGain from the given tag if it's better then original (cuesheet)
+ ReplayGain rgInfo = m_codec->m_tag.GetReplayGain();
+ bool anySet = false;
+ if (!rgInfo.Get(ReplayGain::ALBUM).Valid()
+ && file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::ALBUM).Valid())
+ {
+ rgInfo.Set(ReplayGain::ALBUM, file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::ALBUM));
+ anySet = true;
+ }
+ if (!rgInfo.Get(ReplayGain::TRACK).Valid()
+ && file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::TRACK).Valid())
+ {
+ rgInfo.Set(ReplayGain::TRACK, file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::TRACK));
+ anySet = true;
+ }
+ if (anySet)
+ m_codec->m_tag.SetReplayGain(rgInfo);
+ }
+
+ if (seekOffset)
+ m_codec->Seek(seekOffset);
+
+ m_status = STATUS_QUEUING;
+
+ m_rawBufferSize = 0;
+
+ return true;
+}
+
+AEAudioFormat CAudioDecoder::GetFormat()
+{
+ AEAudioFormat format;
+ if (!m_codec)
+ return format;
+ return m_codec->m_format;
+}
+
+unsigned int CAudioDecoder::GetChannels()
+{
+ return GetFormat().m_channelLayout.Count();
+}
+
+int64_t CAudioDecoder::Seek(int64_t time)
+{
+ m_pcmBuffer.Clear();
+ m_rawBufferSize = 0;
+ if (!m_codec)
+ return 0;
+ if (time < 0) time = 0;
+ if (time > m_codec->m_TotalTime) time = m_codec->m_TotalTime;
+ return m_codec->Seek(time);
+}
+
+void CAudioDecoder::SetTotalTime(int64_t time)
+{
+ if (m_codec)
+ m_codec->m_TotalTime = time;
+}
+
+int64_t CAudioDecoder::TotalTime()
+{
+ if (m_codec)
+ return m_codec->m_TotalTime;
+ return 0;
+}
+
+unsigned int CAudioDecoder::GetDataSize(bool checkPktSize)
+{
+ if (m_status == STATUS_QUEUING || m_status == STATUS_NO_FILE)
+ return 0;
+
+ if (m_codec->m_format.m_dataFormat != AE_FMT_RAW)
+ {
+ // check for end of file and end of buffer
+ if (m_status == STATUS_ENDING)
+ {
+ if (m_pcmBuffer.getMaxReadSize() == 0)
+ m_status = STATUS_ENDED;
+ else if (checkPktSize && m_pcmBuffer.getMaxReadSize() < PACKET_SIZE)
+ m_status = STATUS_ENDED;
+ }
+ return std::min(m_pcmBuffer.getMaxReadSize() / (m_codec->m_bitsPerSample >> 3), (unsigned int)OUTPUT_SAMPLES);
+ }
+ else
+ {
+ if (m_status == STATUS_ENDING)
+ m_status = STATUS_ENDED;
+ return m_rawBufferSize;
+ }
+}
+
+void *CAudioDecoder::GetData(unsigned int samples)
+{
+ unsigned int size = samples * (m_codec->m_bitsPerSample >> 3);
+ if (size > sizeof(m_outputBuffer))
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder::GetData - More data was requested then we have space to buffer!");
+ return NULL;
+ }
+
+ if (size > m_pcmBuffer.getMaxReadSize())
+ {
+ CLog::Log(
+ LOGWARNING,
+ "CAudioDecoder::GetData() more bytes/samples ({}) requested than we have to give ({})!",
+ size, m_pcmBuffer.getMaxReadSize());
+ size = m_pcmBuffer.getMaxReadSize();
+ }
+
+ if (m_pcmBuffer.ReadData((char *)m_outputBuffer, size))
+ {
+ if (m_status == STATUS_ENDING && m_pcmBuffer.getMaxReadSize() == 0)
+ m_status = STATUS_ENDED;
+
+ return m_outputBuffer;
+ }
+
+ CLog::Log(LOGERROR, "CAudioDecoder::GetData() ReadBinary failed with {} samples", samples);
+ return NULL;
+}
+
+uint8_t *CAudioDecoder::GetRawData(int &size)
+{
+ if (m_status == STATUS_ENDING)
+ m_status = STATUS_ENDED;
+
+ if (m_rawBufferSize)
+ {
+ size = m_rawBufferSize;
+ m_rawBufferSize = 0;
+ return m_rawBuffer;
+ }
+ return nullptr;
+}
+
+int CAudioDecoder::ReadSamples(int numsamples)
+{
+ if (m_status == STATUS_NO_FILE || m_status == STATUS_ENDING || m_status == STATUS_ENDED)
+ return RET_SLEEP; // nothing loaded yet
+
+ // start playing once we're fully queued and we're ready to go
+ if (m_status == STATUS_QUEUED && m_canPlay)
+ m_status = STATUS_PLAYING;
+
+ // grab a lock to ensure the codec is created at this point.
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_codec->m_format.m_dataFormat != AE_FMT_RAW)
+ {
+ // Read in more data
+ int maxsize = std::min<int>(INPUT_SAMPLES, m_pcmBuffer.getMaxWriteSize() / (m_codec->m_bitsPerSample >> 3));
+ numsamples = std::min<int>(numsamples, maxsize);
+ numsamples -= (numsamples % GetFormat().m_channelLayout.Count()); // make sure it's divisible by our number of channels
+ if (numsamples)
+ {
+ size_t readSize = 0;
+ int result = m_codec->ReadPCM(
+ m_pcmInputBuffer, static_cast<size_t>(numsamples * (m_codec->m_bitsPerSample >> 3)),
+ &readSize);
+
+ if (result != READ_ERROR && readSize)
+ {
+ // move it into our buffer
+ m_pcmBuffer.WriteData((char *)m_pcmInputBuffer, readSize);
+
+ // update status
+ if (m_status == STATUS_QUEUING && m_pcmBuffer.getMaxReadSize() > m_pcmBuffer.getSize() * 0.9)
+ {
+ CLog::Log(LOGINFO, "AudioDecoder: File is queued");
+ m_status = STATUS_QUEUED;
+ }
+
+ if (result == READ_EOF) // EOF reached
+ {
+ // setup ending if we're within set time of the end (currently just EOF)
+ m_eof = true;
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+
+ return RET_SUCCESS;
+ }
+ if (result == READ_ERROR)
+ {
+ // error decoding, lets finish up and get out
+ CLog::Log(LOGERROR, "CAudioDecoder: Error while decoding {}", result);
+ return RET_ERROR;
+ }
+ if (result == READ_EOF)
+ {
+ m_eof = true;
+ // setup ending if we're within set time of the end (currently just EOF)
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+ }
+ }
+ else
+ {
+ if (m_rawBufferSize == 0)
+ {
+ int result = m_codec->ReadRaw(&m_rawBuffer, &m_rawBufferSize);
+ if (result == READ_SUCCESS && m_rawBufferSize)
+ {
+ //! @todo trash this useless ringbuffer
+ if (m_status == STATUS_QUEUING)
+ {
+ m_status = STATUS_QUEUED;
+ }
+ return RET_SUCCESS;
+ }
+ else if (result == READ_ERROR)
+ {
+ // error decoding, lets finish up and get out
+ CLog::Log(LOGERROR, "CAudioDecoder: Error while decoding {}", result);
+ return RET_ERROR;
+ }
+ else if (result == READ_EOF)
+ {
+ m_eof = true;
+ // setup ending if we're within set time of the end (currently just EOF)
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+ }
+ }
+ return RET_SLEEP; // nothing to do
+}
+
+bool CAudioDecoder::CanSeek()
+{
+ if (m_codec)
+ return m_codec->CanSeek();
+ else
+ return false;
+}
+
+float CAudioDecoder::GetReplayGain(float &peakVal)
+{
+#define REPLAY_GAIN_DEFAULT_LEVEL 89.0f
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+
+ const auto& replayGainSettings = appVolume->GetReplayGainSettings();
+ if (replayGainSettings.iType == ReplayGain::NONE)
+ return 1.0f;
+
+ // Compute amount of gain
+ float replaydB = (float)replayGainSettings.iNoGainPreAmp;
+ float peak = 1.0f;
+ const ReplayGain& rgInfo = m_codec->m_tag.GetReplayGain();
+ if (replayGainSettings.iType == ReplayGain::ALBUM)
+ {
+ if (rgInfo.Get(ReplayGain::ALBUM).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::ALBUM).Gain();
+ if (rgInfo.Get(ReplayGain::ALBUM).HasPeak())
+ peak = rgInfo.Get(ReplayGain::ALBUM).Peak();
+ }
+ else if (rgInfo.Get(ReplayGain::TRACK).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::TRACK).Gain();
+ if (rgInfo.Get(ReplayGain::TRACK).HasPeak())
+ peak = rgInfo.Get(ReplayGain::TRACK).Peak();
+ }
+ }
+ else if (replayGainSettings.iType == ReplayGain::TRACK)
+ {
+ if (rgInfo.Get(ReplayGain::TRACK).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::TRACK).Gain();
+ if (rgInfo.Get(ReplayGain::TRACK).HasPeak())
+ peak = rgInfo.Get(ReplayGain::TRACK).Peak();
+ }
+ else if (rgInfo.Get(ReplayGain::ALBUM).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::ALBUM).Gain();
+ if (rgInfo.Get(ReplayGain::ALBUM).HasPeak())
+ peak = rgInfo.Get(ReplayGain::ALBUM).Peak();
+ }
+ }
+ // convert to a gain type
+ float replaygain = std::pow(10.0f, (replaydB - REPLAY_GAIN_DEFAULT_LEVEL) * 0.05f);
+
+ CLog::Log(LOGDEBUG,
+ "AudioDecoder::GetReplayGain - Final Replaygain applied: {:f}, Track/Album Gain {:f}, "
+ "Peak {:f}",
+ replaygain, replaydB, peak);
+
+ peakVal = peak;
+ return replaygain;
+}
+
diff --git a/xbmc/cores/paplayer/AudioDecoder.h b/xbmc/cores/paplayer/AudioDecoder.h
new file mode 100644
index 0000000..9d0eac6
--- /dev/null
+++ b/xbmc/cores/paplayer/AudioDecoder.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "utils/RingBuffer.h"
+
+struct AEAudioFormat;
+class CFileItem;
+class ICodec;
+
+#define PACKET_SIZE 3840 // audio packet size - we keep 1 in reserve for gapless playback
+ // using a multiple of 1, 2, 3, 4, 5, 6 to guarantee track alignment
+ // note that 7 or higher channels won't work too well.
+
+#define INPUT_SIZE PACKET_SIZE * 3 // input data size we read from the codecs at a time
+ // * 3 to allow 24 bit audio
+
+#define OUTPUT_SAMPLES PACKET_SIZE // max number of output samples
+#define INPUT_SAMPLES PACKET_SIZE // number of input samples (distributed over channels)
+
+#define STATUS_NO_FILE 0
+#define STATUS_QUEUING 1
+#define STATUS_QUEUED 2
+#define STATUS_PLAYING 3
+#define STATUS_ENDING 4
+#define STATUS_ENDED 5
+
+// return codes from decoders
+#define RET_ERROR -1
+#define RET_SUCCESS 0
+#define RET_SLEEP 1
+
+class CAudioDecoder
+{
+public:
+ CAudioDecoder();
+ ~CAudioDecoder();
+
+ bool Create(const CFileItem &file, int64_t seekOffset);
+ void Destroy();
+
+ int ReadSamples(int numsamples);
+
+ bool CanSeek();
+ int64_t Seek(int64_t time);
+ int64_t TotalTime();
+ void SetTotalTime(int64_t time);
+ void Start() { m_canPlay = true;}; // cause a pre-buffered stream to start.
+ int GetStatus() { return m_status; }
+ void SetStatus(int status) { m_status = status; }
+
+ AEAudioFormat GetFormat();
+ unsigned int GetChannels();
+ // Data management
+ unsigned int GetDataSize(bool checkPktSize);
+ void *GetData(unsigned int samples);
+ uint8_t* GetRawData(int &size);
+ ICodec *GetCodec() const { return m_codec; }
+ float GetReplayGain(float &peakVal);
+
+private:
+ // pcm buffer
+ CRingBuffer m_pcmBuffer;
+
+ // output buffer (for transferring data from the Pcm Buffer to the rest of the audio chain)
+ float m_outputBuffer[OUTPUT_SAMPLES];
+
+ // input buffer (for transferring data from the Codecs to our Pcm Ringbuffer
+ uint8_t m_pcmInputBuffer[INPUT_SIZE];
+ float m_inputBuffer[INPUT_SAMPLES];
+
+ uint8_t *m_rawBuffer;
+ int m_rawBufferSize;
+
+ // status
+ bool m_eof;
+ int m_status;
+ bool m_canPlay;
+
+ // the codec we're using
+ ICodec* m_codec;
+
+ CCriticalSection m_critSection;
+};
diff --git a/xbmc/cores/paplayer/CMakeLists.txt b/xbmc/cores/paplayer/CMakeLists.txt
new file mode 100644
index 0000000..8da2e7c
--- /dev/null
+++ b/xbmc/cores/paplayer/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES AudioDecoder.cpp
+ CodecFactory.cpp
+ PAPlayer.cpp
+ VideoPlayerCodec.cpp)
+
+set(HEADERS AudioDecoder.h
+ CachingCodec.h
+ CodecFactory.h
+ ICodec.h
+ PAPlayer.h
+ VideoPlayerCodec.h)
+
+core_add_library(paplayer)
diff --git a/xbmc/cores/paplayer/CachingCodec.h b/xbmc/cores/paplayer/CachingCodec.h
new file mode 100644
index 0000000..55ac36b
--- /dev/null
+++ b/xbmc/cores/paplayer/CachingCodec.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ICodec.h"
+
+class CachingCodec : public ICodec
+{
+public:
+ virtual ~CachingCodec() {}
+ virtual int GetCacheLevel() const { return -1; }
+};
diff --git a/xbmc/cores/paplayer/CodecFactory.cpp b/xbmc/cores/paplayer/CodecFactory.cpp
new file mode 100644
index 0000000..70684d8
--- /dev/null
+++ b/xbmc/cores/paplayer/CodecFactory.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CodecFactory.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "VideoPlayerCodec.h"
+#include "addons/AudioDecoder.h"
+#include "addons/ExtsMimeSupportList.h"
+#include "addons/addoninfo/AddonType.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI::ADDONS;
+
+ICodec* CodecFactory::CreateCodec(const CURL& urlFile)
+{
+ std::string fileType = urlFile.GetFileType();
+ StringUtils::ToLower(fileType);
+
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetExtensionSupportedAddonInfos(
+ "." + fileType, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ // Check asked and given extension is supported by only for here allowed audiodecoder addons.
+ if (addonInfo.first == ADDON::AddonType::AUDIODECODER)
+ {
+ std::unique_ptr<CAudioDecoder> result = std::make_unique<CAudioDecoder>(addonInfo.second);
+ if (!result->CreateDecoder())
+ continue;
+
+ return result.release();
+ }
+ }
+
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ return dvdcodec;
+}
+
+ICodec* CodecFactory::CreateCodecDemux(const CFileItem& file, unsigned int filecache)
+{
+ CURL urlFile(file.GetDynPath());
+ std::string content = file.GetMimeType();
+ StringUtils::ToLower(content);
+ if (!content.empty())
+ {
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetMimetypeSupportedAddonInfos(
+ content, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ // Check asked and given mime type is supported by only for here allowed audiodecoder addons.
+ if (addonInfo.first == ADDON::AddonType::AUDIODECODER)
+ {
+ std::unique_ptr<CAudioDecoder> result = std::make_unique<CAudioDecoder>(addonInfo.second);
+ if (!result->CreateDecoder() && result->SupportsFile(file.GetPath()))
+ continue;
+
+ return result.release();
+ }
+ }
+ }
+
+ if( content == "audio/mpeg" ||
+ content == "audio/mpeg3" ||
+ content == "audio/mp3" ||
+ content == "audio/aac" ||
+ content == "audio/aacp" ||
+ content == "audio/x-ms-wma" ||
+ content == "audio/x-ape" ||
+ content == "audio/ape" ||
+ content == "application/ogg" ||
+ content == "audio/ogg" ||
+ content == "audio/x-xbmc-pcm" ||
+ content == "audio/flac" ||
+ content == "audio/x-flac" ||
+ content == "application/x-flac"
+ )
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType(content);
+ return dvdcodec;
+ }
+ else if (urlFile.IsProtocol("shout"))
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType("audio/mp3");
+ return dvdcodec; // if we got this far with internet radio - content-type was wrong. gamble on mp3.
+ }
+ else if (urlFile.IsFileType("wav") ||
+ content == "audio/wav" ||
+ content == "audio/x-wav")
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType("audio/x-spdif-compressed");
+ if (dvdcodec->Init(file, filecache))
+ {
+ return dvdcodec;
+ }
+
+ dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType(content);
+ return dvdcodec;
+ }
+ else
+ return CreateCodec(urlFile);
+}
+
diff --git a/xbmc/cores/paplayer/CodecFactory.h b/xbmc/cores/paplayer/CodecFactory.h
new file mode 100644
index 0000000..01a2614
--- /dev/null
+++ b/xbmc/cores/paplayer/CodecFactory.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ICodec.h"
+
+class CFileItem;
+
+class CodecFactory
+{
+public:
+ CodecFactory() = default;
+ virtual ~CodecFactory() = default;
+ static ICodec* CreateCodec(const CURL& urlFile);
+ static ICodec* CreateCodecDemux(const CFileItem& file, unsigned int filecache);
+};
+
diff --git a/xbmc/cores/paplayer/ICodec.h b/xbmc/cores/paplayer/ICodec.h
new file mode 100644
index 0000000..1b45280
--- /dev/null
+++ b/xbmc/cores/paplayer/ICodec.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "filesystem/File.h"
+#include "music/tags/MusicInfoTag.h"
+
+#include <string>
+
+#define READ_EOF -1
+#define READ_SUCCESS 0
+#define READ_ERROR 1
+
+class CFileItem;
+
+class ICodec
+{
+public:
+ ICodec()
+ {
+ m_TotalTime = 0;
+ m_bitRate = 0;
+ m_bitsPerSample = 0;
+ m_bitsPerCodedSample = 0;
+ };
+ virtual ~ICodec() = default;
+
+ // Virtual functions that all codecs should implement. Note that these may need
+ // enhancing and or refactoring at a later date. It works currently well for MP3 and
+ // APE codecs.
+
+ // Init(filename)
+ // This routine should handle any initialization necessary. At a minimum it needs to:
+ // 1. Load any dlls and make sure any buffers etc. are allocated.
+ // 2. Load the file (or at least attempt to load it)
+ // 3. Fill in the m_TotalTime, m_SampleRate, m_BitsPerSample and m_Channels parameters.
+ virtual bool Init(const CFileItem &file, unsigned int filecache)=0;
+
+ virtual bool CanSeek() {return true;}
+
+ // Seek()
+ // Should seek to the appropriate time (in ms) in the file, and return the
+ // time to which we managed to seek (in the case where seeking is problematic)
+ // This is used in FFwd/Rewd so can be called very often.
+ virtual bool Seek(int64_t iSeekTime)=0;
+
+ // ReadPCM()
+ // Decodes audio into pBuffer up to size bytes. The actual amount of returned data
+ // is given in actualsize. Returns READ_SUCCESS on success. Returns READ_EOF when
+ // the data has been exhausted, and READ_ERROR on error.
+ virtual int ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize) = 0;
+
+ virtual int ReadRaw(uint8_t **pBuffer, int *bufferSize) { return READ_ERROR; }
+
+ // CanInit()
+ // Should return true if the codec can be initialized
+ // eg. check if a dll needed for the codec exists
+ virtual bool CanInit()=0;
+
+ // set the total time - useful when info comes from a preset tag
+ virtual void SetTotalTime(int64_t totaltime) {}
+
+ virtual bool IsCaching() const {return false;}
+ virtual int GetCacheLevel() const {return -1;}
+
+ int64_t m_TotalTime; // time in ms
+ int m_bitRate;
+ int m_bitsPerSample;
+ int m_bitsPerCodedSample;
+ std::string m_CodecName;
+ MUSIC_INFO::CMusicInfoTag m_tag;
+ XFILE::CFile m_file;
+ AEAudioFormat m_format;
+};
+
diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp
new file mode 100644
index 0000000..05bf906
--- /dev/null
+++ b/xbmc/cores/paplayer/PAPlayer.cpp
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PAPlayer.h"
+
+#include "FileItem.h"
+#include "ICodec.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "cores/AudioEngine/Utils/AEStreamData.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/DataCacheCore.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "messaging/ApplicationMessenger.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/JobManager.h"
+#include "utils/log.h"
+#include "video/Bookmark.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+#define TIME_TO_CACHE_NEXT_FILE 5000 /* 5 seconds before end of song, start caching the next song */
+#define FAST_XFADE_TIME 80 /* 80 milliseconds */
+#define MAX_SKIP_XFADE_TIME 2000 /* max 2 seconds crossfade on track skip */
+
+// PAP: Psycho-acoustic Audio Player
+// Supporting all open audio codec standards.
+// First one being nullsoft's nsv audio decoder format
+
+PAPlayer::PAPlayer(IPlayerCallback& callback) :
+ IPlayer(callback),
+ CThread("PAPlayer"),
+ m_signalSpeedChange(false),
+ m_playbackSpeed(1 ),
+ m_isPlaying(false),
+ m_isPaused(false),
+ m_isFinished(false),
+ m_defaultCrossfadeMS (0),
+ m_upcomingCrossfadeMS(0),
+ m_audioCallback(NULL ),
+ m_jobCounter(0),
+ m_newForcedPlayerTime(-1),
+ m_newForcedTotalTime (-1)
+{
+ memset(&m_playerGUIData, 0, sizeof(m_playerGUIData));
+ m_processInfo.reset(CProcessInfo::CreateInstance());
+ m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore());
+}
+
+PAPlayer::~PAPlayer()
+{
+ CloseFile();
+}
+
+void PAPlayer::SoftStart(bool wait/* = false */)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_fadeOutTriggered)
+ continue;
+
+ si->m_stream->Resume();
+ si->m_stream->FadeVolume(0.0f, 1.0f, FAST_XFADE_TIME);
+ }
+
+ if (wait)
+ {
+ /* wait for them to fade in */
+ lock.unlock();
+ CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME));
+ lock.lock();
+
+ /* be sure they have faded in */
+ while(wait)
+ {
+ wait = false;
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream->IsFading())
+ {
+ lock.unlock();
+ wait = true;
+ CThread::Sleep(1ms);
+ lock.lock();
+ break;
+ }
+ }
+ }
+ }
+}
+
+void PAPlayer::SoftStop(bool wait/* = false */, bool close/* = true */)
+{
+ /* fade all the streams out fast for a nice soft stop */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream)
+ si->m_stream->FadeVolume(1.0f, 0.0f, FAST_XFADE_TIME);
+
+ if (close)
+ {
+ si->m_prepareTriggered = true;
+ si->m_playNextTriggered = true;
+ si->m_fadeOutTriggered = true;
+ }
+ }
+
+ /* if we are going to wait for them to finish fading */
+ if(wait)
+ {
+ // fail safe timer, do not wait longer than 1000ms
+ XbmcThreads::EndTime<> timer(1000ms);
+
+ /* wait for them to fade out */
+ lock.unlock();
+ CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME));
+ lock.lock();
+
+ /* be sure they have faded out */
+ while(wait && !CServiceBroker::GetActiveAE()->IsSuspended() && !timer.IsTimePast())
+ {
+ wait = false;
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream && si->m_stream->IsFading())
+ {
+ lock.unlock();
+ wait = true;
+ CThread::Sleep(1ms);
+ lock.lock();
+ break;
+ }
+ }
+ }
+
+ /* if we are not closing the streams, pause them */
+ if (!close)
+ {
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ si->m_stream->Pause();
+ }
+ }
+ }
+}
+
+void PAPlayer::CloseAllStreams(bool fade/* = true */)
+{
+ if (!fade)
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ while (!m_streams.empty())
+ {
+ StreamInfo* si = m_streams.front();
+ m_streams.pop_front();
+
+ if (si->m_stream)
+ {
+ CloseFileCB(*si);
+ si->m_stream.reset();
+ }
+
+ si->m_decoder.Destroy();
+ delete si;
+ }
+
+ while (!m_finishing.empty())
+ {
+ StreamInfo* si = m_finishing.front();
+ m_finishing.pop_front();
+
+ if (si->m_stream)
+ {
+ CloseFileCB(*si);
+ si->m_stream.reset();
+ }
+
+ si->m_decoder.Destroy();
+ delete si;
+ }
+ m_currentStream = nullptr;
+ }
+ else
+ {
+ SoftStop(false, true);
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_currentStream = NULL;
+ }
+}
+
+bool PAPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
+{
+ m_defaultCrossfadeMS = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE) * 1000;
+ m_fullScreen = options.fullscreen;
+
+ if (m_streams.size() > 1 || !m_defaultCrossfadeMS || m_isPaused)
+ {
+ CloseAllStreams(!m_isPaused);
+ StopThread();
+ m_isPaused = false; // Make sure to reset the pause state
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter++;
+ }
+ CServiceBroker::GetJobManager()->Submit([=]() { QueueNextFileEx(file, false); }, this,
+ CJob::PRIORITY_NORMAL);
+
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (m_streams.size() == 2)
+ {
+ //do a short crossfade on trackskip, set to max 2 seconds for these prev/next transitions
+ m_upcomingCrossfadeMS = std::min(m_defaultCrossfadeMS, (unsigned int)MAX_SKIP_XFADE_TIME);
+
+ //start transition to next track
+ StreamInfo* si = m_streams.front();
+ si->m_playNextAtFrame = si->m_framesSent; //start next track at current frame
+ si->m_prepareTriggered = true; //next track is ready to go
+ }
+ lock.unlock();
+
+ if (!IsRunning())
+ Create();
+
+ /* trigger playback start */
+ m_isPlaying = true;
+ m_startEvent.Set();
+
+ // OnPlayBackStarted to be made only once. Callback processing may be slower than player process
+ // so clear signal flag first otherwise async stream processing could also make callback
+ m_signalStarted = false;
+ m_callback.OnPlayBackStarted(file);
+
+ return true;
+}
+
+void PAPlayer::UpdateCrossfadeTime(const CFileItem& file)
+{
+ // we explicitly disable crossfading for audio cds
+ if (file.IsCDDA())
+ m_upcomingCrossfadeMS = 0;
+ else
+ m_upcomingCrossfadeMS = m_defaultCrossfadeMS = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE) * 1000;
+
+ if (m_upcomingCrossfadeMS)
+ {
+ if (!m_currentStream || (file.HasMusicInfoTag() &&
+ !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS) &&
+ m_currentStream->m_fileItem->HasMusicInfoTag() &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetAlbum() != "") &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetAlbum() ==
+ file.GetMusicInfoTag()->GetAlbum()) &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetDiscNumber() ==
+ file.GetMusicInfoTag()->GetDiscNumber()) &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetTrackNumber() ==
+ file.GetMusicInfoTag()->GetTrackNumber() - 1)))
+ {
+ //do not crossfade when playing consecutive albumtracks
+ m_upcomingCrossfadeMS = 0;
+ }
+ }
+}
+
+bool PAPlayer::QueueNextFile(const CFileItem &file)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter++;
+ }
+ CServiceBroker::GetJobManager()->Submit([this, file]() { QueueNextFileEx(file, true); }, this,
+ CJob::PRIORITY_NORMAL);
+
+ return true;
+}
+
+bool PAPlayer::QueueNextFileEx(const CFileItem &file, bool fadeIn)
+{
+ if (m_currentStream)
+ {
+ // check if we advance a track of a CUE sheet
+ // if this is the case we don't need to open a new stream
+ std::string newURL = file.GetDynURL().GetFileName();
+ std::string oldURL = m_currentStream->m_fileItem->GetDynURL().GetFileName();
+ if (newURL.compare(oldURL) == 0 && file.GetStartOffset() &&
+ file.GetStartOffset() == m_currentStream->m_fileItem->GetEndOffset() && m_currentStream &&
+ m_currentStream->m_prepareTriggered)
+ {
+ m_currentStream->m_nextFileItem.reset(new CFileItem(file));
+ m_upcomingCrossfadeMS = 0;
+ return true;
+ }
+ m_currentStream->m_nextFileItem.reset();
+ }
+
+ StreamInfo *si = new StreamInfo();
+ si->m_fileItem = std::make_unique<CFileItem>(file);
+
+ // Start stream at zero offset
+ si->m_startOffset = 0;
+ //File item start offset defines where in song to resume
+ double starttime = CUtil::ConvertMilliSecsToSecs(si->m_fileItem->GetStartOffset());
+
+ // Music from cuesheet => "item_start" and offset match
+ // Start offset defines where this song starts in file of multiple songs
+ if (si->m_fileItem->HasProperty("item_start") &&
+ (si->m_fileItem->GetProperty("item_start").asInteger() == si->m_fileItem->GetStartOffset()))
+ {
+ // Start stream at offset from cuesheet
+ si->m_startOffset = si->m_fileItem->GetStartOffset();
+ starttime = 0; // No resume point
+ }
+
+ if (!si->m_decoder.Create(file, si->m_startOffset))
+ {
+ CLog::Log(LOGWARNING, "PAPlayer::QueueNextFileEx - Failed to create the decoder");
+
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+
+ delete si;
+ return false;
+ }
+
+ /* decode until there is data-available */
+ si->m_decoder.Start();
+ while (si->m_decoder.GetDataSize(true) == 0)
+ {
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR)
+ {
+ CLog::Log(LOGINFO, "PAPlayer::QueueNextFileEx - Error reading samples");
+
+ si->m_decoder.Destroy();
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+ delete si;
+ return false;
+ }
+
+ /* yield our time so that the main PAP thread doesn't stall */
+ CThread::Sleep(1ms);
+ }
+
+ // set m_upcomingCrossfadeMS depending on type of file and user settings
+ UpdateCrossfadeTime(*si->m_fileItem);
+
+ /* init the streaminfo struct */
+ si->m_audioFormat = si->m_decoder.GetFormat();
+ // si->m_startOffset already initialized
+ si->m_endOffset = file.GetEndOffset();
+ si->m_bytesPerSample = CAEUtil::DataFormatToBits(si->m_audioFormat.m_dataFormat) >> 3;
+ si->m_bytesPerFrame = si->m_bytesPerSample * si->m_audioFormat.m_channelLayout.Count();
+ si->m_started = false;
+ si->m_finishing = false;
+ si->m_framesSent = 0;
+ si->m_seekNextAtFrame = 0;
+ si->m_seekFrame = -1;
+ si->m_stream = NULL;
+ si->m_volume = (fadeIn && m_upcomingCrossfadeMS) ? 0.0f : 1.0f;
+ si->m_fadeOutTriggered = false;
+ si->m_isSlaved = false;
+
+ si->m_decoderTotal = si->m_decoder.TotalTime();
+ int64_t streamTotalTime = si->m_decoderTotal;
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+
+ // Seek to a resume point
+ if (si->m_fileItem->HasProperty("StartPercent") &&
+ (si->m_fileItem->GetProperty("StartPercent").asDouble() > 0) &&
+ (si->m_fileItem->GetProperty("StartPercent").asDouble() <= 100))
+ {
+ si->m_seekFrame =
+ si->m_audioFormat.m_sampleRate *
+ CUtil::ConvertMilliSecsToSecs(static_cast<int>(+(static_cast<double>(
+ streamTotalTime * (si->m_fileItem->GetProperty("StartPercent").asDouble() / 100.0)))));
+ }
+ else if (starttime > 0)
+ si->m_seekFrame = si->m_audioFormat.m_sampleRate * starttime;
+ else if (si->m_fileItem->HasProperty("audiobook_bookmark"))
+ si->m_seekFrame = si->m_audioFormat.m_sampleRate *
+ CUtil::ConvertMilliSecsToSecs(
+ si->m_fileItem->GetProperty("audiobook_bookmark").asInteger());
+
+ si->m_prepareNextAtFrame = 0;
+ // cd drives don't really like it to be crossfaded or prepared
+ if (!file.IsCDDA())
+ {
+ if (streamTotalTime >= TIME_TO_CACHE_NEXT_FILE + m_defaultCrossfadeMS)
+ si->m_prepareNextAtFrame = (int)((streamTotalTime - TIME_TO_CACHE_NEXT_FILE - m_defaultCrossfadeMS) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ }
+
+ if (m_currentStream && ((m_currentStream->m_audioFormat.m_dataFormat == AE_FMT_RAW) || (si->m_audioFormat.m_dataFormat == AE_FMT_RAW)))
+ {
+ m_currentStream->m_prepareTriggered = false;
+ m_currentStream->m_waitOnDrain = true;
+ m_currentStream->m_prepareNextAtFrame = 0;
+ si->m_decoder.Destroy();
+ delete si;
+ return false;
+ }
+
+ si->m_prepareTriggered = false;
+ si->m_playNextAtFrame = 0;
+ si->m_playNextTriggered = false;
+ si->m_waitOnDrain = false;
+
+ if (!PrepareStream(si))
+ {
+ CLog::Log(LOGINFO, "PAPlayer::QueueNextFileEx - Error preparing stream");
+
+ si->m_decoder.Destroy();
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+ delete si;
+ return false;
+ }
+
+ /* add the stream to the list */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_streams.push_back(si);
+ //update the current stream to start playing the next track at the correct frame.
+ UpdateStreamInfoPlayNextAtFrame(m_currentStream, m_upcomingCrossfadeMS);
+
+ return true;
+}
+
+void PAPlayer::UpdateStreamInfoPlayNextAtFrame(StreamInfo *si, unsigned int crossFadingTime)
+{
+ // if no crossfading or cue sheet, wait for eof
+ if (si && (crossFadingTime || si->m_endOffset))
+ {
+ int64_t streamTotalTime = si->m_decoder.TotalTime();
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+ if (streamTotalTime < crossFadingTime)
+ si->m_playNextAtFrame = (int)((streamTotalTime / 2) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ else
+ si->m_playNextAtFrame = (int)((streamTotalTime - crossFadingTime) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ }
+}
+
+inline bool PAPlayer::PrepareStream(StreamInfo *si)
+{
+ /* if we have a stream we are already prepared */
+ if (si->m_stream)
+ return true;
+
+ /* get a paused stream */
+ AEAudioFormat format = si->m_audioFormat;
+ si->m_stream = CServiceBroker::GetActiveAE()->MakeStream(
+ format,
+ AESTREAM_PAUSED
+ );
+
+ if (!si->m_stream)
+ {
+ CLog::Log(LOGDEBUG, "PAPlayer::PrepareStream - Failed to get IAEStream");
+ return false;
+ }
+
+ si->m_stream->SetVolume(si->m_volume);
+ float peak = 1.0;
+ float gain = si->m_decoder.GetReplayGain(peak);
+ if (peak * gain <= 1.0f)
+ // No clipping protection needed
+ si->m_stream->SetReplayGain(gain);
+ else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING))
+ // Normalise volume reducing replaygain to avoid needing clipping protection, plays file at lower level
+ si->m_stream->SetReplayGain(1.0f / fabs(peak));
+ else
+ // Clipping protection (when enabled in AE) by audio limiting, applied just where needed
+ si->m_stream->SetAmplification(gain);
+
+ /* if its not the first stream and crossfade is not enabled */
+ if (m_currentStream && m_currentStream != si && !m_upcomingCrossfadeMS)
+ {
+ /* slave the stream for gapless */
+ si->m_isSlaved = true;
+ m_currentStream->m_stream->RegisterSlave(si->m_stream.get());
+ }
+
+ /* fill the stream's buffer */
+ while(si->m_stream->IsBuffering())
+ {
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR)
+ {
+ CLog::Log(LOGINFO, "PAPlayer::PrepareStream - Stream Finished");
+ break;
+ }
+
+ if (!QueueData(si))
+ break;
+
+ /* yield our time so that the main PAP thread doesn't stall */
+ CThread::Sleep(1ms);
+ }
+
+ CLog::Log(LOGINFO, "PAPlayer::PrepareStream - Ready");
+
+ return true;
+}
+
+bool PAPlayer::CloseFile(bool reopen)
+{
+ if (reopen)
+ CServiceBroker::GetActiveAE()->KeepConfiguration(3000);
+
+ if (!m_isPaused)
+ SoftStop(true, true);
+ CloseAllStreams(false);
+
+ /* wait for the thread to terminate */
+ StopThread(true);//true - wait for end of thread
+
+ // wait for any pending jobs to complete
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ while (m_jobCounter > 0)
+ {
+ lock.unlock();
+ m_jobEvent.Wait(100ms);
+ lock.lock();
+ }
+ }
+
+ return true;
+}
+
+void PAPlayer::Process()
+{
+ if (!m_startEvent.Wait(100ms))
+ {
+ CLog::Log(LOGDEBUG, "PAPlayer::Process - Failed to receive start event");
+ return;
+ }
+
+ CLog::Log(LOGDEBUG, "PAPlayer::Process - Playback started");
+ while(m_isPlaying && !m_bStop)
+ {
+ /* this needs to happen outside of any locks to prevent deadlocks */
+ if (m_signalSpeedChange)
+ {
+ m_callback.OnPlayBackSpeedChanged(m_playbackSpeed);
+ m_signalSpeedChange = false;
+ }
+
+ double freeBufferTime = 0.0;
+ ProcessStreams(freeBufferTime);
+
+ // if none of our streams wants at least 10ms of data, we sleep
+ if (freeBufferTime < 0.01)
+ {
+ CThread::Sleep(10ms);
+ }
+
+ if (m_newForcedPlayerTime != -1)
+ {
+ if (SetTimeInternal(m_newForcedPlayerTime))
+ {
+ m_newForcedPlayerTime = -1;
+ }
+ }
+
+ if (m_newForcedTotalTime != -1)
+ {
+ if (SetTotalTimeInternal(m_newForcedTotalTime))
+ {
+ m_newForcedTotalTime = -1;
+ }
+ }
+
+ GetTimeInternal(); //update for GUI
+ }
+ m_isPlaying = false;
+}
+
+inline void PAPlayer::ProcessStreams(double &freeBufferTime)
+{
+ std::unique_lock<CCriticalSection> sharedLock(m_streamsLock);
+ if (m_isFinished && m_streams.empty() && m_finishing.empty())
+ {
+ m_isPlaying = false;
+ freeBufferTime = 1.0;
+ return;
+ }
+
+ /* destroy any drained streams */
+ for (auto itt = m_finishing.begin(); itt != m_finishing.end();)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream->IsDrained())
+ {
+ itt = m_finishing.erase(itt);
+ CloseFileCB(*si);
+ delete si;
+ CLog::Log(LOGDEBUG, "PAPlayer::ProcessStreams - Stream Freed");
+ }
+ else
+ ++itt;
+ }
+
+ sharedLock.unlock();
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (!m_currentStream && !si->m_started)
+ {
+ m_currentStream = si;
+ UpdateGUIData(si); //update for GUI
+ }
+ /* if the stream is finishing */
+ if ((si->m_playNextTriggered && si->m_stream && !si->m_stream->IsFading()) || !ProcessStream(si, freeBufferTime))
+ {
+ if (!si->m_prepareTriggered)
+ {
+ if (si->m_waitOnDrain)
+ {
+ si->m_stream->Drain(true);
+ si->m_waitOnDrain = false;
+ }
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ /* remove the stream */
+ itt = m_streams.erase(itt);
+ /* if its the current stream */
+ if (si == m_currentStream)
+ {
+ /* if it was the last stream */
+ if (itt == m_streams.end())
+ {
+ /* if it didn't trigger the next queue item */
+ if (!si->m_prepareTriggered)
+ {
+ if (si->m_waitOnDrain)
+ {
+ si->m_stream->Drain(true);
+ si->m_waitOnDrain = false;
+ }
+ m_callback.OnQueueNextItem();
+ si->m_prepareTriggered = true;
+ }
+ m_currentStream = NULL;
+ }
+ else
+ {
+ m_currentStream = *itt;
+ UpdateGUIData(*itt); //update for GUI
+ }
+ }
+
+ /* unregister the audio callback */
+ si->m_stream->UnRegisterAudioCallback();
+ si->m_decoder.Destroy();
+ si->m_stream->Drain(false);
+ m_finishing.push_back(si);
+ return;
+ }
+
+ if (!si->m_started)
+ continue;
+
+ // is it time to prepare the next stream?
+ if (si->m_prepareNextAtFrame > 0 && !si->m_prepareTriggered && si->m_framesSent >= si->m_prepareNextAtFrame)
+ {
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ // it is time to start playing the next stream?
+ if (si->m_playNextAtFrame > 0 && !si->m_playNextTriggered && !si->m_nextFileItem && si->m_framesSent >= si->m_playNextAtFrame)
+ {
+ if (!si->m_prepareTriggered)
+ {
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ if (!m_isFinished)
+ {
+ if (m_upcomingCrossfadeMS)
+ {
+ si->m_stream->FadeVolume(1.0f, 0.0f, m_upcomingCrossfadeMS);
+ si->m_fadeOutTriggered = true;
+ }
+ m_currentStream = NULL;
+
+ /* unregister the audio callback */
+ si->m_stream->UnRegisterAudioCallback();
+ }
+
+ si->m_playNextTriggered = true;
+ }
+ }
+}
+
+inline bool PAPlayer::ProcessStream(StreamInfo *si, double &freeBufferTime)
+{
+ /* if playback needs to start on this stream, do it */
+ if (si == m_currentStream && !si->m_started)
+ {
+ si->m_started = true;
+ si->m_stream->RegisterAudioCallback(m_audioCallback);
+ if (!si->m_isSlaved)
+ si->m_stream->Resume();
+ si->m_stream->FadeVolume(0.0f, 1.0f, m_upcomingCrossfadeMS);
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(*si->m_fileItem);
+ m_signalStarted = true;
+ if (m_fullScreen)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
+ m_fullScreen = false;
+ }
+ m_callback.OnAVStarted(*si->m_fileItem);
+ }
+
+ /* if we have not started yet and the stream has been primed */
+ unsigned int space = si->m_stream->GetSpace();
+ if (!si->m_started && !space)
+ return true;
+
+ if (!m_playbackSpeed)
+ return true;
+
+ /* see if it is time yet to FF/RW or a direct seek */
+ if (!si->m_playNextTriggered && ((m_playbackSpeed != 1 && si->m_framesSent >= si->m_seekNextAtFrame) || si->m_seekFrame > -1))
+ {
+ int64_t time = (int64_t)0;
+ /* if its a direct seek */
+ if (si->m_seekFrame > -1)
+ {
+ time = (int64_t)((float)si->m_seekFrame / (float)si->m_audioFormat.m_sampleRate * 1000.0f);
+ si->m_framesSent = (int)(si->m_seekFrame - ((float)si->m_startOffset * (float)si->m_audioFormat.m_sampleRate) / 1000.0f);
+ si->m_seekFrame = -1;
+ m_playerGUIData.m_time = time; //update for GUI
+ si->m_seekNextAtFrame = 0;
+ CDataCacheCore::GetInstance().SetPlayTimes(0, time, 0, m_playerGUIData.m_totalTime);
+ }
+ /* if its FF/RW */
+ else
+ {
+ si->m_framesSent += si->m_audioFormat.m_sampleRate * (m_playbackSpeed - 1);
+ si->m_seekNextAtFrame = si->m_framesSent + si->m_audioFormat.m_sampleRate / 2;
+ time = (int64_t)(((float)si->m_framesSent / (float)si->m_audioFormat.m_sampleRate * 1000.0f) + (float)si->m_startOffset);
+ }
+
+ /* if we are seeking back before the start of the track start normal playback */
+ if (time < si->m_startOffset || si->m_framesSent < 0)
+ {
+ time = si->m_startOffset;
+ si->m_framesSent = 0;
+ si->m_seekNextAtFrame = 0;
+ SetSpeed(1);
+ }
+
+ si->m_decoder.Seek(time);
+ }
+
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR ||
+ ((si->m_endOffset) && (si->m_framesSent / si->m_audioFormat.m_sampleRate >= (si->m_endOffset - si->m_startOffset) / 1000)))
+ {
+ if (si == m_currentStream && si->m_nextFileItem)
+ {
+ CloseFileCB(*si);
+
+ // update current stream with info of next track
+ si->m_startOffset = si->m_nextFileItem->GetStartOffset();
+ if (si->m_nextFileItem->GetEndOffset())
+ si->m_endOffset = si->m_nextFileItem->GetEndOffset();
+ else
+ si->m_endOffset = 0;
+ si->m_framesSent = 0;
+
+ *si->m_fileItem = *si->m_nextFileItem;
+ si->m_nextFileItem.reset();
+
+ int64_t streamTotalTime = si->m_decoder.TotalTime() - si->m_startOffset;
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+
+ // calculate time when to prepare next stream
+ si->m_prepareNextAtFrame = 0;
+ if (streamTotalTime >= TIME_TO_CACHE_NEXT_FILE + m_defaultCrossfadeMS)
+ si->m_prepareNextAtFrame = (int)((streamTotalTime - TIME_TO_CACHE_NEXT_FILE - m_defaultCrossfadeMS) * si->m_audioFormat.m_sampleRate / 1000.0f);
+
+ si->m_prepareTriggered = false;
+ si->m_playNextAtFrame = 0;
+ si->m_playNextTriggered = false;
+ si->m_seekNextAtFrame = 0;
+
+ //update the current stream to start playing the next track at the correct frame.
+ UpdateStreamInfoPlayNextAtFrame(m_currentStream, m_upcomingCrossfadeMS);
+
+ UpdateGUIData(si);
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(*si->m_fileItem);
+ m_signalStarted = true;
+ m_callback.OnAVStarted(*si->m_fileItem);
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "PAPlayer::ProcessStream - Stream Finished");
+ return false;
+ }
+ }
+
+ if (!QueueData(si))
+ return false;
+
+ /* update free buffer time if we are running */
+ if (si->m_started)
+ {
+ if (si->m_stream->IsBuffering())
+ freeBufferTime = 1.0;
+ else
+ {
+ double free_space;
+ if (si->m_audioFormat.m_dataFormat != AE_FMT_RAW)
+ free_space = (double)(si->m_stream->GetSpace() / si->m_bytesPerSample) / si->m_audioFormat.m_sampleRate;
+ else
+ {
+ if (si->m_audioFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD &&
+ !m_processInfo->WantsRawPassthrough())
+ {
+ free_space = static_cast<double>(si->m_stream->GetSpace()) *
+ si->m_audioFormat.m_streamInfo.GetDuration() / 1000 / 2;
+ }
+ else
+ {
+ free_space = static_cast<double>(si->m_stream->GetSpace()) *
+ si->m_audioFormat.m_streamInfo.GetDuration() / 1000;
+ }
+ }
+
+ freeBufferTime = std::max(freeBufferTime , free_space);
+ }
+ }
+
+ return true;
+}
+
+bool PAPlayer::QueueData(StreamInfo *si)
+{
+ unsigned int space = si->m_stream->GetSpace();
+
+ if (si->m_audioFormat.m_dataFormat != AE_FMT_RAW)
+ {
+ unsigned int samples = std::min(si->m_decoder.GetDataSize(false), space / si->m_bytesPerSample);
+ if (!samples)
+ return true;
+
+ // we want complete frames
+ samples -= samples % si->m_audioFormat.m_channelLayout.Count();
+
+ uint8_t* data = (uint8_t*)si->m_decoder.GetData(samples);
+ if (!data)
+ {
+ CLog::Log(LOGERROR, "PAPlayer::QueueData - Failed to get data from the decoder");
+ return false;
+ }
+
+ unsigned int frames = samples/si->m_audioFormat.m_channelLayout.Count();
+ unsigned int added = si->m_stream->AddData(&data, 0, frames, nullptr);
+ si->m_framesSent += added;
+ }
+ else
+ {
+ if (!space)
+ return true;
+
+ int size;
+ uint8_t *data = si->m_decoder.GetRawData(size);
+ if (data && size)
+ {
+ int added = si->m_stream->AddData(&data, 0, size, nullptr);
+ if (added != size)
+ {
+ CLog::Log(LOGERROR, "PAPlayer::QueueData - unknown error");
+ return false;
+ }
+ if (si->m_audioFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD &&
+ !m_processInfo->WantsRawPassthrough())
+ {
+ si->m_framesSent += si->m_audioFormat.m_streamInfo.GetDuration() / 1000 / 2 *
+ si->m_audioFormat.m_streamInfo.m_sampleRate;
+ }
+ else
+ {
+ si->m_framesSent += si->m_audioFormat.m_streamInfo.GetDuration() / 1000 *
+ si->m_audioFormat.m_streamInfo.m_sampleRate;
+ }
+ }
+ }
+
+ const ICodec* codec = si->m_decoder.GetCodec();
+ m_playerGUIData.m_cacheLevel = codec ? codec->GetCacheLevel() : 0; //update for GUI
+
+ return true;
+}
+
+void PAPlayer::OnExit()
+{
+ //@todo signal OnPlayBackError if there was an error on last stream
+ if (m_isFinished && !m_bStop)
+ m_callback.OnPlayBackEnded();
+ else
+ m_callback.OnPlayBackStopped();
+}
+
+void PAPlayer::OnNothingToQueueNotify()
+{
+ m_isFinished = true;
+}
+
+bool PAPlayer::IsPlaying() const
+{
+ return m_isPlaying;
+}
+
+void PAPlayer::Pause()
+{
+ if (m_isPaused)
+ {
+ SetSpeed(1);
+ }
+ else
+ {
+ SetSpeed(0);
+ }
+}
+
+void PAPlayer::SetVolume(float volume)
+{
+
+}
+
+void PAPlayer::SetDynamicRangeCompression(long drc)
+{
+
+}
+
+void PAPlayer::SetSpeed(float speed)
+{
+ m_playbackSpeed = static_cast<int>(speed);
+ CDataCacheCore::GetInstance().SetSpeed(1.0, speed);
+ if (m_playbackSpeed != 0 && m_isPaused)
+ {
+ m_isPaused = false;
+ SoftStart();
+ m_callback.OnPlayBackResumed();
+ }
+ else if (m_playbackSpeed == 0 && !m_isPaused)
+ {
+ m_isPaused = true;
+ SoftStop(true, false);
+ m_callback.OnPlayBackPaused();
+ }
+ m_signalSpeedChange = true;
+}
+
+int64_t PAPlayer::GetTimeInternal()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return 0;
+
+ double time = ((double)m_currentStream->m_framesSent / (double)m_currentStream->m_audioFormat.m_sampleRate);
+ if (m_currentStream->m_stream)
+ time -= m_currentStream->m_stream->GetDelay();
+ time = time * 1000.0;
+
+ m_playerGUIData.m_time = (int64_t)time; //update for GUI
+ CDataCacheCore::GetInstance().SetPlayTimes(0, time, 0, m_playerGUIData.m_totalTime);
+
+ return (int64_t)time;
+}
+
+bool PAPlayer::SetTotalTimeInternal(int64_t time)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ {
+ return false;
+ }
+
+ m_currentStream->m_decoder.SetTotalTime(time);
+ UpdateGUIData(m_currentStream);
+
+ return true;
+}
+
+bool PAPlayer::SetTimeInternal(int64_t time)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return false;
+
+ m_currentStream->m_framesSent = time / 1000 * m_currentStream->m_audioFormat.m_sampleRate;
+
+ if (m_currentStream->m_stream)
+ m_currentStream->m_framesSent += m_currentStream->m_stream->GetDelay() * m_currentStream->m_audioFormat.m_sampleRate;
+
+ return true;
+}
+
+void PAPlayer::SetTime(int64_t time)
+{
+ m_newForcedPlayerTime = time;
+}
+
+int64_t PAPlayer::GetTotalTime64()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return 0;
+
+ int64_t total = m_currentStream->m_decoder.TotalTime();
+ if (m_currentStream->m_endOffset)
+ total = m_currentStream->m_endOffset;
+ total -= m_currentStream->m_startOffset;
+ return total;
+}
+
+void PAPlayer::SetTotalTime(int64_t time)
+{
+ m_newForcedTotalTime = time;
+}
+
+int PAPlayer::GetCacheLevel() const
+{
+ return m_playerGUIData.m_cacheLevel;
+}
+
+void PAPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const
+{
+ info.bitrate = m_playerGUIData.m_audioBitrate;
+ info.channels = m_playerGUIData.m_channelCount;
+ info.codecName = m_playerGUIData.m_codec;
+ info.samplerate = m_playerGUIData.m_sampleRate;
+ info.bitspersample = m_playerGUIData.m_bitsPerSample;
+}
+
+bool PAPlayer::CanSeek() const
+{
+ return m_playerGUIData.m_canSeek;
+}
+
+void PAPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
+{
+ if (!CanSeek()) return;
+
+ long long seek;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings->m_musicUseTimeSeeking && m_playerGUIData.m_totalTime > 2 * advancedSettings->m_musicTimeSeekForwardBig)
+ {
+ if (bLargeStep)
+ seek = bPlus ? advancedSettings->m_musicTimeSeekForwardBig : advancedSettings->m_musicTimeSeekBackwardBig;
+ else
+ seek = bPlus ? advancedSettings->m_musicTimeSeekForward : advancedSettings->m_musicTimeSeekBackward;
+ seek *= 1000;
+ seek += m_playerGUIData.m_time;
+ }
+ else
+ {
+ float percent;
+ if (bLargeStep)
+ percent = bPlus ? static_cast<float>(advancedSettings->m_musicPercentSeekForwardBig) : static_cast<float>(advancedSettings->m_musicPercentSeekBackwardBig);
+ else
+ percent = bPlus ? static_cast<float>(advancedSettings->m_musicPercentSeekForward) : static_cast<float>(advancedSettings->m_musicPercentSeekBackward);
+ seek = static_cast<long long>(GetTotalTime64() * (GetPercentage() + percent) / 100);
+ }
+
+ SeekTime(seek);
+}
+
+void PAPlayer::SeekTime(int64_t iTime /*=0*/)
+{
+ if (!CanSeek()) return;
+
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return;
+
+ int64_t seekOffset = iTime - GetTimeInternal();
+
+ if (m_playbackSpeed != 1)
+ SetSpeed(1);
+
+ m_currentStream->m_seekFrame = (int)((float)m_currentStream->m_audioFormat.m_sampleRate * ((float)iTime + (float)m_currentStream->m_startOffset) / 1000.0f);
+ m_callback.OnPlayBackSeek(iTime, seekOffset);
+}
+
+void PAPlayer::SeekPercentage(float fPercent /*=0*/)
+{
+ if (fPercent < 0.0f ) fPercent = 0.0f;
+ if (fPercent > 100.0f) fPercent = 100.0f;
+ SeekTime((int64_t)(fPercent * 0.01f * (float)GetTotalTime64()));
+}
+
+float PAPlayer::GetPercentage()
+{
+ if (m_playerGUIData.m_totalTime > 0)
+ return m_playerGUIData.m_time * 100.0f / m_playerGUIData.m_totalTime;
+
+ return 0.0f;
+}
+
+void PAPlayer::UpdateGUIData(StreamInfo *si)
+{
+ /* Store data need by external threads in member
+ * structure to prevent locking conflicts when
+ * data required by GUI and main application
+ */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+
+ m_playerGUIData.m_sampleRate = si->m_audioFormat.m_sampleRate;
+ m_playerGUIData.m_channelCount = si->m_audioFormat.m_channelLayout.Count();
+ m_playerGUIData.m_canSeek = si->m_decoder.CanSeek();
+
+ const ICodec* codec = si->m_decoder.GetCodec();
+
+ m_playerGUIData.m_audioBitrate = codec ? codec->m_bitRate : 0;
+ strncpy(m_playerGUIData.m_codec,codec ? codec->m_CodecName.c_str() : "",20);
+ m_playerGUIData.m_cacheLevel = codec ? codec->GetCacheLevel() : 0;
+ m_playerGUIData.m_bitsPerSample = (codec && codec->m_bitsPerCodedSample) ? codec->m_bitsPerCodedSample : si->m_bytesPerSample << 3;
+
+ int64_t total = si->m_decoder.TotalTime();
+ if (si->m_endOffset)
+ total = m_currentStream->m_endOffset;
+ total -= m_currentStream->m_startOffset;
+ m_playerGUIData.m_totalTime = total;
+
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+}
+
+void PAPlayer::OnJobComplete(unsigned int jobID, bool success, CJob *job)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter--;
+ m_jobEvent.Set();
+}
+
+void PAPlayer::CloseFileCB(StreamInfo &si)
+{
+ IPlayerCallback *cb = &m_callback;
+ CFileItem fileItem(*si.m_fileItem);
+ CBookmark bookmark;
+ double total = si.m_decoderTotal;
+ if (si.m_endOffset)
+ total = si.m_endOffset;
+ total -= si.m_startOffset;
+ bookmark.totalTimeInSeconds = total / 1000;
+ bookmark.timeInSeconds = (static_cast<double>(si.m_framesSent) /
+ static_cast<double>(si.m_audioFormat.m_sampleRate));
+ bookmark.timeInSeconds -= si.m_stream->GetDelay();
+ bookmark.player = m_name;
+ bookmark.playerState = GetPlayerState();
+ CServiceBroker::GetJobManager()->Submit([=]() { cb->OnPlayerCloseFile(fileItem, bookmark); },
+ CJob::PRIORITY_NORMAL);
+}
+
+void PAPlayer::AdvancePlaylistOnError(CFileItem &fileItem)
+{
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(fileItem);
+ m_signalStarted = true;
+ m_callback.OnAVStarted(fileItem);
+}
diff --git a/xbmc/cores/paplayer/PAPlayer.h b/xbmc/cores/paplayer/PAPlayer.h
new file mode 100644
index 0000000..fe2489d
--- /dev/null
+++ b/xbmc/cores/paplayer/PAPlayer.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "AudioDecoder.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/IAudioCallback.h"
+#include "cores/IPlayer.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+#include "utils/Job.h"
+
+#include <atomic>
+#include <list>
+#include <vector>
+
+class IAEStream;
+class CFileItem;
+class CProcessInfo;
+
+class PAPlayer : public IPlayer, public CThread, public IJobCallback
+{
+friend class CQueueNextFileJob;
+public:
+ explicit PAPlayer(IPlayerCallback& callback);
+ ~PAPlayer() override;
+
+ bool OpenFile(const CFileItem& file, const CPlayerOptions &options) override;
+ bool QueueNextFile(const CFileItem &file) override;
+ void OnNothingToQueueNotify() override;
+ bool CloseFile(bool reopen = false) override;
+ bool IsPlaying() const override;
+ void Pause() override;
+ bool HasVideo() const override { return false; }
+ bool HasAudio() const override { return true; }
+ bool CanSeek() const override;
+ void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) override;
+ void SeekPercentage(float fPercent = 0.0f) override;
+ void SetVolume(float volume) override;
+ void SetDynamicRangeCompression(long drc) override;
+ void SetSpeed(float speed = 0) override;
+ int GetCacheLevel() const override;
+ void SetTotalTime(int64_t time) override;
+ void GetAudioStreamInfo(int index, AudioStreamInfo& info) const override;
+ void SetTime(int64_t time) override;
+ void SeekTime(int64_t iTime = 0) override;
+ void GetAudioCapabilities(std::vector<int>& audioCaps) const override {}
+
+ int GetAudioStreamCount() const override { return 1; }
+ int GetAudioStream() override { return 0; }
+
+ // implementation of IJobCallback
+ void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
+
+ struct
+ {
+ char m_codec[21];
+ int64_t m_time;
+ int64_t m_totalTime;
+ int m_channelCount;
+ int m_bitsPerSample;
+ int m_sampleRate;
+ int m_audioBitrate;
+ int m_cacheLevel;
+ bool m_canSeek;
+ } m_playerGUIData;
+
+protected:
+ // implementation of CThread
+ void OnStartup() override {}
+ void Process() override;
+ void OnExit() override;
+ float GetPercentage();
+
+private:
+ struct StreamInfo
+ {
+ std::unique_ptr<CFileItem> m_fileItem;
+ std::unique_ptr<CFileItem> m_nextFileItem;
+ CAudioDecoder m_decoder; /* the stream decoder */
+ int64_t m_startOffset; /* the stream start offset */
+ int64_t m_endOffset; /* the stream end offset */
+ int64_t m_decoderTotal = 0;
+ AEAudioFormat m_audioFormat;
+ unsigned int m_bytesPerSample; /* number of bytes per audio sample */
+ unsigned int m_bytesPerFrame; /* number of bytes per audio frame */
+
+ bool m_started; /* if playback of this stream has been started */
+ bool m_finishing; /* if this stream is finishing */
+ int m_framesSent; /* number of frames sent to the stream */
+ int m_prepareNextAtFrame; /* when to prepare the next stream */
+ bool m_prepareTriggered; /* if the next stream has been prepared */
+ int m_playNextAtFrame; /* when to start playing the next stream */
+ bool m_playNextTriggered; /* if this stream has started the next one */
+ bool m_fadeOutTriggered; /* if the stream has been told to fade out */
+ int m_seekNextAtFrame; /* the FF/RR sample to seek at */
+ int m_seekFrame; /* the exact position to seek too, -1 for none */
+
+ IAE::StreamPtr m_stream; /* the playback stream */
+ float m_volume; /* the initial volume level to set the stream to on creation */
+
+ bool m_isSlaved; /* true if the stream has been slaved to another */
+ bool m_waitOnDrain; /* wait for stream being drained in AE */
+ };
+
+ typedef std::list<StreamInfo*> StreamList;
+
+ bool m_signalSpeedChange; /* true if OnPlaybackSpeedChange needs to be called */
+ bool m_signalStarted = true;
+ std::atomic_int m_playbackSpeed; /* the playback speed (1 = normal) */
+ bool m_isPlaying;
+ bool m_isPaused;
+ bool m_isFinished; /* if there are no more songs in the queue */
+ bool m_fullScreen;
+ unsigned int m_defaultCrossfadeMS; /* how long the default crossfade is in ms */
+ unsigned int m_upcomingCrossfadeMS; /* how long the upcoming crossfade is in ms */
+ CEvent m_startEvent; /* event for playback start */
+ StreamInfo* m_currentStream = nullptr;
+ IAudioCallback* m_audioCallback; /* the viz audio callback */
+
+ CCriticalSection m_streamsLock; /* lock for the stream list */
+ StreamList m_streams; /* playing streams */
+ StreamList m_finishing; /* finishing streams */
+ int m_jobCounter;
+ CEvent m_jobEvent;
+ int64_t m_newForcedPlayerTime;
+ int64_t m_newForcedTotalTime;
+ std::unique_ptr<CProcessInfo> m_processInfo;
+
+ bool QueueNextFileEx(const CFileItem &file, bool fadeIn);
+ void SoftStart(bool wait = false);
+ void SoftStop(bool wait = false, bool close = true);
+ void CloseAllStreams(bool fade = true);
+ void ProcessStreams(double &freeBufferTime);
+ bool PrepareStream(StreamInfo *si);
+ bool ProcessStream(StreamInfo *si, double &freeBufferTime);
+ bool QueueData(StreamInfo *si);
+ int64_t GetTotalTime64();
+ void UpdateCrossfadeTime(const CFileItem& file);
+ void UpdateStreamInfoPlayNextAtFrame(StreamInfo *si, unsigned int crossFadingTime);
+ void UpdateGUIData(StreamInfo *si);
+ int64_t GetTimeInternal();
+ bool SetTimeInternal(int64_t time);
+ bool SetTotalTimeInternal(int64_t time);
+ void CloseFileCB(StreamInfo &si);
+ void AdvancePlaylistOnError(CFileItem &fileItem);
+};
+
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.cpp b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
new file mode 100644
index 0000000..a77a27d
--- /dev/null
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoPlayerCodec.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h"
+#include "cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h"
+#include "cores/VideoPlayer/DVDStreamInfo.h"
+#include "music/tags/TagLoaderTagLib.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+VideoPlayerCodec::VideoPlayerCodec() : m_processInfo(CProcessInfo::CreateInstance())
+{
+ m_CodecName = "VideoPlayer";
+}
+
+VideoPlayerCodec::~VideoPlayerCodec()
+{
+ DeInit();
+}
+
+AEAudioFormat VideoPlayerCodec::GetFormat()
+{
+ AEAudioFormat format;
+ if (m_pAudioCodec)
+ {
+ format = m_pAudioCodec->GetFormat();
+ }
+ return format;
+}
+
+void VideoPlayerCodec::SetContentType(const std::string &strContent)
+{
+ m_strContentType = strContent;
+ StringUtils::ToLower(m_strContentType);
+}
+
+void VideoPlayerCodec::SetPassthroughStreamType(CAEStreamInfo::DataType streamType)
+{
+ m_srcFormat.m_streamInfo.m_type = streamType;
+}
+
+bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
+{
+ // take precaution if Init()ialized earlier
+ if (m_bInited)
+ {
+ // keep things as is if Init() was done with known strFile
+ if (m_strFileName == file.GetDynPath())
+ return true;
+
+ // got differing filename, so cleanup before starting over
+ DeInit();
+ }
+
+ m_nDecodedLen = 0;
+
+ CFileItem fileitem(file);
+ fileitem.SetMimeType(m_strContentType);
+ fileitem.SetMimeTypeForInternetFile();
+ m_pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, fileitem);
+ if (!m_pInputStream)
+ {
+ CLog::Log(LOGERROR, "{}: Error creating input stream for {}", __FUNCTION__, file.GetDynPath());
+ return false;
+ }
+
+ //! @todo
+ //! convey CFileItem::ContentLookup() into Open()
+ if (!m_pInputStream->Open())
+ {
+ CLog::Log(LOGERROR, "{}: Error opening file {}", __FUNCTION__, file.GetDynPath());
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ m_pDemuxer = NULL;
+
+ try
+ {
+ m_pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(m_pInputStream);
+ if (!m_pDemuxer)
+ {
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ CLog::Log(LOGERROR, "{}: Error creating demuxer", __FUNCTION__);
+ return false;
+ }
+ }
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{}: Exception thrown when opening demuxer", __FUNCTION__);
+ if (m_pDemuxer)
+ {
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ }
+ return false;
+ }
+
+ CDemuxStream* pStream = NULL;
+ m_nAudioStream = -1;
+ int64_t demuxerId = -1;
+ for (auto stream : m_pDemuxer->GetStreams())
+ {
+ if (stream && stream->type == STREAM_AUDIO)
+ {
+ m_nAudioStream = stream->uniqueId;
+ demuxerId = stream->demuxerId;
+ pStream = stream;
+ break;
+ }
+ }
+
+ if (m_nAudioStream == -1)
+ {
+ CLog::Log(LOGERROR, "{}: Could not find audio stream", __FUNCTION__);
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ CDVDStreamInfo hint(*pStream, true);
+
+ CAEStreamInfo::DataType ptStreamTye =
+ GetPassthroughStreamType(hint.codec, hint.samplerate, hint.profile);
+ m_pAudioCodec = CDVDFactoryCodec::CreateAudioCodec(hint, *m_processInfo, true, true, ptStreamTye);
+ if (!m_pAudioCodec)
+ {
+ CLog::Log(LOGERROR, "{}: Could not create audio codec", __FUNCTION__);
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ // Extract ReplayGain info
+ // tagLoaderTagLib.Load will try to determine tag type by file extension, so set fallback by contentType
+ std::string strFallbackFileExtension = "";
+ if (m_strContentType == "audio/aacp" ||
+ m_strContentType == "audio/aac")
+ strFallbackFileExtension = "m4a";
+ else if (m_strContentType == "audio/x-ms-wma")
+ strFallbackFileExtension = "wma";
+ else if (m_strContentType == "audio/x-ape" ||
+ m_strContentType == "audio/ape")
+ strFallbackFileExtension = "ape";
+ CTagLoaderTagLib tagLoaderTagLib;
+ tagLoaderTagLib.Load(file.GetDynPath(), m_tag, strFallbackFileExtension);
+
+ // we have to decode initial data in order to get channels/samplerate
+ // for sanity - we read no more than 10 packets
+ int nErrors = 0;
+ for (int nPacket = 0;
+ nPacket < 10 && (m_channels == 0 || m_format.m_sampleRate == 0 || m_format.m_frameSize == 0);
+ nPacket++)
+ {
+ uint8_t dummy[256];
+ size_t nSize = 256;
+ if (ReadPCM(dummy, nSize, &nSize) == READ_ERROR)
+ ++nErrors;
+
+ m_srcFormat = m_pAudioCodec->GetFormat();
+ m_format = m_srcFormat;
+ m_channels = m_srcFormat.m_channelLayout.Count();
+ m_bitsPerSample = CAEUtil::DataFormatToBits(m_srcFormat.m_dataFormat);
+ m_bitsPerCodedSample = static_cast<CDemuxStreamAudio*>(pStream)->iBitsPerSample;
+ }
+ if (nErrors >= 10)
+ {
+ CLog::Log(LOGDEBUG, "{}: Could not decode data", __FUNCTION__);
+ return false;
+ }
+
+ // test if seeking is supported
+ m_bCanSeek = false;
+ if (m_pInputStream->Seek(0, SEEK_POSSIBLE))
+ {
+ if (Seek(1))
+ {
+ // rewind stream to beginning
+ Seek(0);
+ m_bCanSeek = true;
+ }
+ else
+ {
+ m_pInputStream->Seek(0, SEEK_SET);
+ if (!m_pDemuxer->Reset())
+ return false;
+ }
+ }
+
+ if (m_channels == 0) // no data - just guess and hope for the best
+ {
+ m_srcFormat.m_channelLayout = CAEChannelInfo(AE_CH_LAYOUT_2_0);
+ m_channels = m_srcFormat.m_channelLayout.Count();
+ }
+
+ if (m_srcFormat.m_sampleRate == 0)
+ m_srcFormat.m_sampleRate = 44100;
+
+ m_TotalTime = m_pDemuxer->GetStreamLength();
+ m_bitRate = m_pAudioCodec->GetBitRate();
+ if (!m_bitRate && m_TotalTime)
+ {
+ m_bitRate = (int)(((m_pInputStream->GetLength()*1000) / m_TotalTime) * 8);
+ }
+ m_CodecName = m_pDemuxer->GetStreamCodecName(demuxerId, m_nAudioStream);
+
+ m_needConvert = false;
+ if (NeedConvert(m_srcFormat.m_dataFormat))
+ {
+ m_needConvert = true;
+ // if we don't know the framesize yet, we will fail when converting
+ if (m_srcFormat.m_frameSize == 0)
+ return false;
+
+ m_pResampler = ActiveAE::CAEResampleFactory::Create();
+
+ SampleConfig dstConfig, srcConfig;
+ dstConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_srcFormat.m_channelLayout);
+ dstConfig.channels = m_channels;
+ dstConfig.sample_rate = m_srcFormat.m_sampleRate;
+ dstConfig.fmt = CAEUtil::GetAVSampleFormat(AE_FMT_FLOAT);
+ dstConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(AE_FMT_FLOAT);
+ dstConfig.dither_bits = CAEUtil::DataFormatToDitherBits(AE_FMT_FLOAT);
+
+ srcConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_srcFormat.m_channelLayout);
+ srcConfig.channels = m_channels;
+ srcConfig.sample_rate = m_srcFormat.m_sampleRate;
+ srcConfig.fmt = CAEUtil::GetAVSampleFormat(m_srcFormat.m_dataFormat);
+ srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_srcFormat.m_dataFormat);
+ srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_srcFormat.m_dataFormat);
+
+ m_pResampler->Init(dstConfig, srcConfig,
+ false,
+ false,
+ M_SQRT1_2,
+ NULL,
+ AE_QUALITY_UNKNOWN,
+ false);
+
+ m_planes = AE_IS_PLANAR(m_srcFormat.m_dataFormat) ? m_channels : 1;
+ m_format = m_srcFormat;
+ m_format.m_dataFormat = AE_FMT_FLOAT;
+ m_bitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat);
+ }
+
+ m_strFileName = file.GetDynPath();
+ m_bInited = true;
+
+ return true;
+}
+
+void VideoPlayerCodec::DeInit()
+{
+ if (m_pDemuxer != NULL)
+ {
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ }
+
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+
+ m_pAudioCodec.reset();
+
+ delete m_pResampler;
+ m_pResampler = NULL;
+
+ // cleanup format information
+ m_TotalTime = 0;
+ m_bitsPerSample = 0;
+ m_bitRate = 0;
+ m_channels = 0;
+ m_format.m_dataFormat = AE_FMT_INVALID;
+
+ m_nDecodedLen = 0;
+
+ m_strFileName = "";
+ m_bInited = false;
+}
+
+bool VideoPlayerCodec::Seek(int64_t iSeekTime)
+{
+ // default to announce backwards seek if !m_pPacket to not make FFmpeg
+ // skip mpeg audio frames at playback start
+ bool seekback = true;
+
+ bool ret = m_pDemuxer->SeekTime((int)iSeekTime, seekback);
+ m_pAudioCodec->Reset();
+
+ m_nDecodedLen = 0;
+
+ return ret;
+}
+
+int VideoPlayerCodec::ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize)
+{
+ if (m_nDecodedLen > 0)
+ {
+ size_t nLen = (size < m_nDecodedLen) ? size : m_nDecodedLen;
+ *actualsize = nLen;
+ if (m_needConvert)
+ {
+ int samples = *actualsize / (m_bitsPerSample>>3);
+ int frames = samples / m_channels;
+ m_pResampler->Resample(&pBuffer, frames, m_audioFrame.data, frames, 1.0);
+ for (int i=0; i<m_planes; i++)
+ {
+ m_audioFrame.data[i] += frames*m_srcFormat.m_frameSize/m_planes;
+ }
+ }
+ else
+ {
+ memcpy(pBuffer, m_audioFrame.data[0], *actualsize);
+ m_audioFrame.data[0] += (*actualsize);
+ }
+ m_nDecodedLen -= nLen;
+ return READ_SUCCESS;
+ }
+
+ m_nDecodedLen = 0;
+ m_pAudioCodec->GetData(m_audioFrame);
+ int bytes = m_audioFrame.nb_frames * m_audioFrame.framesize;
+
+ if (!bytes)
+ {
+ DemuxPacket* pPacket = nullptr;
+ do
+ {
+ if (pPacket)
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ pPacket = m_pDemuxer->Read();
+ } while (pPacket && pPacket->iStreamId != m_nAudioStream);
+
+ if (!pPacket)
+ {
+ return READ_EOF;
+ }
+
+ pPacket->pts = DVD_NOPTS_VALUE;
+ pPacket->dts = DVD_NOPTS_VALUE;
+
+ int ret = m_pAudioCodec->AddData(*pPacket);
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ if (ret < 0)
+ {
+ return READ_ERROR;
+ }
+
+ m_pAudioCodec->GetData(m_audioFrame);
+ bytes = m_audioFrame.nb_frames * m_audioFrame.framesize;
+ }
+
+ m_nDecodedLen = bytes;
+ // scale decoded bytes to destination format
+ if (m_needConvert)
+ m_nDecodedLen *= (m_bitsPerSample>>3) / (m_srcFormat.m_frameSize / m_channels);
+
+ *actualsize = (m_nDecodedLen <= size) ? m_nDecodedLen : size;
+ if (*actualsize > 0)
+ {
+ if (m_needConvert)
+ {
+ int samples = *actualsize / (m_bitsPerSample>>3);
+ int frames = samples / m_channels;
+ m_pResampler->Resample(&pBuffer, frames, m_audioFrame.data, frames, 1.0);
+ for (int i=0; i<m_planes; i++)
+ {
+ m_audioFrame.data[i] += frames*m_srcFormat.m_frameSize/m_planes;
+ }
+ }
+ else
+ {
+ memcpy(pBuffer, m_audioFrame.data[0], *actualsize);
+ m_audioFrame.data[0] += *actualsize;
+ }
+ m_nDecodedLen -= *actualsize;
+ }
+
+ return READ_SUCCESS;
+}
+
+int VideoPlayerCodec::ReadRaw(uint8_t **pBuffer, int *bufferSize)
+{
+ DemuxPacket* pPacket;
+
+ m_nDecodedLen = 0;
+ DVDAudioFrame audioframe;
+
+ m_pAudioCodec->GetData(audioframe);
+ if (audioframe.nb_frames)
+ {
+ return READ_SUCCESS;
+ }
+
+ do
+ {
+ pPacket = m_pDemuxer->Read();
+ } while (pPacket && pPacket->iStreamId != m_nAudioStream);
+
+ if (!pPacket)
+ {
+ return READ_EOF;
+ }
+ pPacket->pts = DVD_NOPTS_VALUE;
+ pPacket->dts = DVD_NOPTS_VALUE;
+ int ret = m_pAudioCodec->AddData(*pPacket);
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ if (ret < 0)
+ {
+ return READ_ERROR;
+ }
+
+ m_pAudioCodec->GetData(audioframe);
+ if (audioframe.nb_frames)
+ {
+ *bufferSize = audioframe.nb_frames;
+ *pBuffer = audioframe.data[0];
+ }
+ else
+ {
+ *bufferSize = 0;
+ }
+
+ return READ_SUCCESS;
+}
+
+bool VideoPlayerCodec::CanInit()
+{
+ return true;
+}
+
+bool VideoPlayerCodec::CanSeek()
+{
+ return m_bCanSeek;
+}
+
+bool VideoPlayerCodec::NeedConvert(AEDataFormat fmt)
+{
+ if (fmt == AE_FMT_RAW)
+ return false;
+
+ switch(fmt)
+ {
+ case AE_FMT_U8:
+ case AE_FMT_S16NE:
+ case AE_FMT_S32NE:
+ case AE_FMT_FLOAT:
+ case AE_FMT_DOUBLE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+CAEStreamInfo::DataType VideoPlayerCodec::GetPassthroughStreamType(AVCodecID codecId,
+ int samplerate,
+ int profile)
+{
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_sampleRate = samplerate;
+ format.m_streamInfo.m_type = CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+ switch (codecId)
+ {
+ case AV_CODEC_ID_AC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_EAC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_DTS:
+ if (profile == FF_PROFILE_DTS_HD_HRA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD;
+ else if (profile == FF_PROFILE_DTS_HD_MA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_MA;
+ else
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_TRUEHD:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_TRUEHD;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ default:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_NULL;
+ }
+
+ bool supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+
+ if (!supports && codecId == AV_CODEC_ID_DTS &&
+ format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_DTSHD_CORE &&
+ CServiceBroker::GetActiveAE()->UsesDtsCoreFallback())
+ {
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+ }
+
+ if (supports)
+ return format.m_streamInfo.m_type;
+ else
+ return CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+}
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.h b/xbmc/cores/paplayer/VideoPlayerCodec.h
new file mode 100644
index 0000000..08a9c2f
--- /dev/null
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ICodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
+#include "cores/VideoPlayer/DVDInputStreams/DVDInputStream.h"
+
+namespace ActiveAE
+{
+ class IAEResample;
+};
+
+class VideoPlayerCodec : public ICodec
+{
+public:
+ VideoPlayerCodec();
+ ~VideoPlayerCodec() override;
+
+ bool Init(const CFileItem &file, unsigned int filecache) override;
+ bool Seek(int64_t iSeekTime) override;
+ int ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize) override;
+ int ReadRaw(uint8_t **pBuffer, int *bufferSize) override;
+ bool CanInit() override;
+ bool CanSeek() override;
+
+ void DeInit();
+ AEAudioFormat GetFormat();
+ void SetContentType(const std::string &strContent);
+
+ bool NeedConvert(AEDataFormat fmt);
+ void SetPassthroughStreamType(CAEStreamInfo::DataType streamType);
+
+private:
+ CAEStreamInfo::DataType GetPassthroughStreamType(AVCodecID codecId, int samplerate, int profile);
+
+ CDVDDemux* m_pDemuxer{nullptr};
+ std::shared_ptr<CDVDInputStream> m_pInputStream;
+ std::unique_ptr<CDVDAudioCodec> m_pAudioCodec;
+
+ std::string m_strContentType;
+ std::string m_strFileName;
+ int m_nAudioStream{-1};
+ size_t m_nDecodedLen{0};
+
+ bool m_bInited{false};
+ bool m_bCanSeek{false};
+
+ ActiveAE::IAEResample* m_pResampler{nullptr};
+ DVDAudioFrame m_audioFrame{};
+ int m_planes{0};
+ bool m_needConvert{false};
+ AEAudioFormat m_srcFormat{};
+ int m_channels{0};
+
+ std::unique_ptr<CProcessInfo> m_processInfo;
+};
+
diff --git a/xbmc/cores/playercorefactory/CMakeLists.txt b/xbmc/cores/playercorefactory/CMakeLists.txt
new file mode 100644
index 0000000..9834ce2
--- /dev/null
+++ b/xbmc/cores/playercorefactory/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES PlayerCoreConfig.cpp
+ PlayerCoreFactory.cpp
+ PlayerSelectionRule.cpp)
+
+set(HEADERS PlayerCoreConfig.h
+ PlayerCoreFactory.h
+ PlayerSelectionRule.h)
+
+core_add_library(playercorefactory)
diff --git a/xbmc/cores/playercorefactory/PlayerCoreConfig.cpp b/xbmc/cores/playercorefactory/PlayerCoreConfig.cpp
new file mode 100644
index 0000000..545062e
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerCoreConfig.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlayerCoreConfig.h"
+
+#include "cores/ExternalPlayer/ExternalPlayer.h"
+#include "cores/IPlayer.h"
+#include "cores/RetroPlayer/RetroPlayer.h"
+#include "cores/VideoPlayer/VideoPlayer.h"
+#include "cores/paplayer/PAPlayer.h"
+#ifdef HAS_UPNP
+#include "network/upnp/UPnPPlayer.h"
+#endif
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <utility>
+
+CPlayerCoreConfig::CPlayerCoreConfig(std::string name,
+ std::string type,
+ const TiXmlElement* pConfig,
+ const std::string& id /* = "" */)
+ : m_name(std::move(name)), m_id(id), m_type(std::move(type))
+{
+ if (pConfig)
+ {
+ m_config.reset(static_cast<TiXmlElement*>(pConfig->Clone()));
+ const char* sAudio = pConfig->Attribute("audio");
+ const char* sVideo = pConfig->Attribute("video");
+ m_bPlaysAudio = sAudio && StringUtils::CompareNoCase(sAudio, "true") == 0;
+ m_bPlaysVideo = sVideo && StringUtils::CompareNoCase(sVideo, "true") == 0;
+ }
+
+ CLog::Log(LOGDEBUG, "CPlayerCoreConfig::<ctor>: created player {}", m_name);
+}
+
+std::shared_ptr<IPlayer> CPlayerCoreConfig::CreatePlayer(IPlayerCallback& callback) const
+{
+ std::shared_ptr<IPlayer> player;
+
+ if (m_type.compare("video") == 0)
+ {
+ player = std::make_shared<CVideoPlayer>(callback);
+ }
+ else if (m_type.compare("music") == 0)
+ {
+ player = std::make_shared<PAPlayer>(callback);
+ }
+ else if (m_type.compare("game") == 0)
+ {
+ player = std::make_shared<KODI::RETRO::CRetroPlayer>(callback);
+ }
+ else if (m_type.compare("external") == 0)
+ {
+ player = std::make_shared<CExternalPlayer>(callback);
+ }
+
+#if defined(HAS_UPNP)
+ else if (m_type.compare("remote") == 0)
+ {
+ player = std::make_shared<UPNP::CUPnPPlayer>(callback, m_id.c_str());
+ }
+#endif
+ else
+ return nullptr;
+
+ if (!player)
+ return nullptr;
+
+ player->m_name = m_name;
+ player->m_type = m_type;
+
+ if (player->Initialize(m_config.get()))
+ return player;
+
+ return nullptr;
+}
diff --git a/xbmc/cores/playercorefactory/PlayerCoreConfig.h b/xbmc/cores/playercorefactory/PlayerCoreConfig.h
new file mode 100644
index 0000000..09c2f4a
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerCoreConfig.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+class IPlayer;
+class IPlayerCallback;
+class TiXmlElement;
+
+class CPlayerCoreConfig
+{
+public:
+ CPlayerCoreConfig(std::string name,
+ std::string type,
+ const TiXmlElement* pConfig,
+ const std::string& id = "");
+
+ ~CPlayerCoreConfig() = default;
+
+ const std::string& GetName() const
+ {
+ return m_name;
+ }
+
+ const std::string& GetId() const
+ {
+ return m_id;
+ }
+
+ bool PlaysAudio() const
+ {
+ return m_bPlaysAudio;
+ }
+
+ bool PlaysVideo() const
+ {
+ return m_bPlaysVideo;
+ }
+
+ std::shared_ptr<IPlayer> CreatePlayer(IPlayerCallback& callback) const;
+
+ std::string m_name;
+ std::string m_id; // uuid for upnp
+ std::string m_type;
+ bool m_bPlaysAudio{false};
+ bool m_bPlaysVideo{false};
+ std::unique_ptr<TiXmlElement> m_config;
+};
diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp
new file mode 100644
index 0000000..3042774
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlayerCoreFactory.h"
+
+#include "FileItem.h"
+#include "PlayerCoreConfig.h"
+#include "PlayerSelectionRule.h"
+#include "URL.h"
+#include "cores/IPlayerCallback.h"
+#include "cores/VideoPlayer/Interface/InputStreamConstants.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "guilib/LocalizeStrings.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <sstream>
+
+#define PLAYERCOREFACTORY_XML "playercorefactory.xml"
+
+CPlayerCoreFactory::CPlayerCoreFactory(const CProfileManager &profileManager) :
+ m_profileManager(profileManager)
+{
+ m_settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ if (m_settings->IsLoaded())
+ OnSettingsLoaded();
+
+ m_settings->GetSettingsManager()->RegisterSettingsHandler(this);
+}
+
+CPlayerCoreFactory::~CPlayerCoreFactory()
+{
+ m_settings->GetSettingsManager()->UnregisterSettingsHandler(this);
+}
+
+void CPlayerCoreFactory::OnSettingsLoaded()
+{
+ LoadConfiguration("special://xbmc/system/" PLAYERCOREFACTORY_XML, true);
+ LoadConfiguration(m_profileManager.GetUserDataItem(PLAYERCOREFACTORY_XML), false);
+}
+
+std::shared_ptr<IPlayer> CPlayerCoreFactory::CreatePlayer(const std::string& nameId,
+ IPlayerCallback& callback) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ size_t idx = GetPlayerIndex(nameId);
+
+ if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
+ return nullptr;
+
+ return m_vecPlayerConfigs[idx]->CreatePlayer(callback);
+}
+
+void CPlayerCoreFactory::GetPlayers(std::vector<std::string>&players) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ players.clear();
+ for (auto& conf : m_vecPlayerConfigs)
+ {
+ if (conf->m_bPlaysAudio || conf->m_bPlaysVideo)
+ players.emplace_back(conf->m_name);
+ }
+}
+
+void CPlayerCoreFactory::GetPlayers(std::vector<std::string>&players, const bool audio, const bool video) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: for video={}, audio={}", video, audio);
+
+ for (auto& conf : m_vecPlayerConfigs)
+ {
+ if (audio == conf->m_bPlaysAudio && video == conf->m_bPlaysVideo)
+ {
+ if (std::find(players.begin(), players.end(), conf->m_name) != players.end())
+ continue;
+
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding player: {}", conf->m_name);
+ players.emplace_back(conf->m_name);
+ }
+ }
+}
+
+void CPlayerCoreFactory::GetPlayers(const CFileItem& item, std::vector<std::string>&players) const
+{
+ CURL url(item.GetDynPath());
+
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers({})", CURL::GetRedacted(item.GetDynPath()));
+
+ enum class ForcedPlayer
+ {
+ NONE,
+ VIDEO_DEFAULT,
+ AUDIO_DEFAULT
+ };
+
+ ForcedPlayer defaultInputstreamPlayerOverride = ForcedPlayer::NONE;
+
+ // If we are using an inpustream add-on
+ if (!item.GetProperty(STREAM_PROPERTY_INPUTSTREAM).empty())
+ {
+ if (!item.GetProperty(STREAM_PROPERTY_INPUTSTREAM_PLAYER).empty())
+ {
+ const std::string inputstreamPlayerOverride =
+ item.GetProperty(STREAM_PROPERTY_INPUTSTREAM_PLAYER).asString();
+
+ if (inputstreamPlayerOverride == "videodefaultplayer")
+ defaultInputstreamPlayerOverride = ForcedPlayer::VIDEO_DEFAULT;
+ else if ((inputstreamPlayerOverride == "audiodefaultplayer"))
+ defaultInputstreamPlayerOverride = ForcedPlayer::AUDIO_DEFAULT;
+ }
+ }
+
+ std::vector<std::string>validPlayers;
+ GetPlayers(validPlayers);
+
+ // Process rules
+ for (auto& rule : m_vecCoreSelectionRules)
+ rule->GetPlayers(item, validPlayers, players);
+
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: matched {0} rules with players", players.size());
+
+ // Process defaults
+
+ // Set video default player. Check whether it's video first (overrule audio and
+ // game check). Also push these players in case it is NOT audio or game either.
+ //
+ // If an inputstream add-on is used, first check if we have an override to use
+ // "videodefaultplayer"
+ if (defaultInputstreamPlayerOverride == ForcedPlayer::VIDEO_DEFAULT ||
+ (defaultInputstreamPlayerOverride == ForcedPlayer::NONE &&
+ (item.IsVideo() || (!item.IsAudio() && !item.IsGame()))))
+ {
+ int idx = GetPlayerIndex("videodefaultplayer");
+ if (idx > -1)
+ {
+ std::string eVideoDefault = GetPlayerName(idx);
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding videodefaultplayer ({})",
+ eVideoDefault);
+ players.push_back(eVideoDefault);
+ }
+ GetPlayers(players, false, true); // Video-only players
+ GetPlayers(players, true, true); // Audio & video players
+ }
+
+ // Set audio default player
+ // Pushback all audio players in case we don't know the type
+ if (defaultInputstreamPlayerOverride == ForcedPlayer::AUDIO_DEFAULT ||
+ (defaultInputstreamPlayerOverride == ForcedPlayer::NONE && item.IsAudio()))
+ {
+ int idx = GetPlayerIndex("audiodefaultplayer");
+ if (idx > -1)
+ {
+ std::string eAudioDefault = GetPlayerName(idx);
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding audiodefaultplayer ({})",
+ eAudioDefault);
+ players.push_back(eAudioDefault);
+ }
+ GetPlayers(players, true, false); // Audio-only players
+ GetPlayers(players, true, true); // Audio & video players
+ }
+
+ if (item.IsGame())
+ {
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding retroplayer");
+ players.emplace_back("RetroPlayer");
+ }
+
+ CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: added {0} players", players.size());
+}
+
+int CPlayerCoreFactory::GetPlayerIndex(const std::string& strCoreName) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!strCoreName.empty())
+ {
+ // Dereference "*default*player" aliases
+ std::string strRealCoreName;
+ if (StringUtils::EqualsNoCase(strCoreName, "audiodefaultplayer"))
+ strRealCoreName = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioDefaultPlayer;
+ else if (StringUtils::EqualsNoCase(strCoreName, "videodefaultplayer"))
+ strRealCoreName = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoDefaultPlayer;
+ else
+ strRealCoreName = strCoreName;
+
+ for(size_t i = 0; i < m_vecPlayerConfigs.size(); i++)
+ {
+ if (StringUtils::EqualsNoCase(m_vecPlayerConfigs[i]->GetName(), strRealCoreName))
+ return i;
+ }
+ CLog::Log(LOGWARNING, "CPlayerCoreFactory::GetPlayer({}): no such player: {}", strCoreName,
+ strRealCoreName);
+ }
+ return -1;
+}
+
+std::string CPlayerCoreFactory::GetPlayerName(size_t idx) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
+ return "";
+
+ return m_vecPlayerConfigs[idx]->m_name;
+}
+
+void CPlayerCoreFactory::GetPlayers(std::vector<std::string>&players, std::string &type) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (auto& config : m_vecPlayerConfigs)
+ {
+ if (config->m_type != type)
+ continue;
+ players.emplace_back(config->m_name);
+ }
+}
+
+void CPlayerCoreFactory::GetRemotePlayers(std::vector<std::string>&players) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (auto& config : m_vecPlayerConfigs)
+ {
+ if (config->m_type != "remote")
+ continue;
+ players.emplace_back(config->m_name);
+ }
+}
+
+std::string CPlayerCoreFactory::GetPlayerType(const std::string& player) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ size_t idx = GetPlayerIndex(player);
+
+ if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
+ return "";
+
+ return m_vecPlayerConfigs[idx]->m_type;
+}
+
+bool CPlayerCoreFactory::PlaysAudio(const std::string& player) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ size_t idx = GetPlayerIndex(player);
+
+ if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
+ return false;
+
+ return m_vecPlayerConfigs[idx]->m_bPlaysAudio;
+}
+
+bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ size_t idx = GetPlayerIndex(player);
+
+ if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
+ return false;
+
+ return m_vecPlayerConfigs[idx]->m_bPlaysVideo;
+}
+
+std::string CPlayerCoreFactory::GetDefaultPlayer(const CFileItem& item) const
+{
+ std::vector<std::string>players;
+ GetPlayers(item, players);
+
+ //If we have any players return the first one
+ if (!players.empty())
+ return players.at(0);
+
+ return "";
+}
+
+std::string CPlayerCoreFactory::SelectPlayerDialog(const std::vector<std::string>&players, float posX, float posY) const
+{
+ CContextButtons choices;
+ if (players.size())
+ {
+ //Add default player
+ std::string strCaption = players[0];
+ strCaption += " (";
+ strCaption += g_localizeStrings.Get(13278);
+ strCaption += ")";
+ choices.Add(0, strCaption);
+
+ //Add all other players
+ for (unsigned int i = 1; i < players.size(); i++)
+ choices.Add(i, players[i]);
+
+ int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices);
+ if (choice >= 0)
+ return players[choice];
+ }
+ return "";
+}
+
+std::string CPlayerCoreFactory::SelectPlayerDialog(float posX, float posY) const
+{
+ std::vector<std::string>players;
+ GetPlayers(players);
+ return SelectPlayerDialog(players, posX, posY);
+}
+
+bool CPlayerCoreFactory::LoadConfiguration(const std::string &file, bool clear)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ CLog::Log(LOGINFO, "Loading player core factory settings from {}.", file);
+ if (!CFileUtils::Exists(file))
+ { // tell the user it doesn't exist
+ CLog::Log(LOGINFO, "{} does not exist. Skipping.", file);
+ return false;
+ }
+
+ CXBMCTinyXML playerCoreFactoryXML;
+ if (!playerCoreFactoryXML.LoadFile(file))
+ {
+ CLog::Log(LOGERROR, "Error loading {}, Line {} ({})", file, playerCoreFactoryXML.ErrorRow(),
+ playerCoreFactoryXML.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement *pConfig = playerCoreFactoryXML.RootElement();
+ if (pConfig == NULL)
+ {
+ CLog::Log(LOGERROR, "Error loading {}, Bad structure", file);
+ return false;
+ }
+
+ if (clear)
+ {
+ m_vecPlayerConfigs.clear();
+ m_vecCoreSelectionRules.clear();
+
+ // Builtin players
+ auto VideoPlayer = std::make_unique<CPlayerCoreConfig>("VideoPlayer", "video", nullptr);
+ VideoPlayer->m_bPlaysAudio = true;
+ VideoPlayer->m_bPlaysVideo = true;
+ m_vecPlayerConfigs.emplace_back(std::move(VideoPlayer));
+
+ auto paplayer = std::make_unique<CPlayerCoreConfig>("PAPlayer", "music", nullptr);
+ paplayer->m_bPlaysAudio = true;
+ m_vecPlayerConfigs.emplace_back(std::move(paplayer));
+
+ auto retroPlayer = std::make_unique<CPlayerCoreConfig>("RetroPlayer", "game", nullptr);
+ m_vecPlayerConfigs.emplace_back(std::move(retroPlayer));
+ }
+
+ if (StringUtils::CompareNoCase(pConfig->Value(), "playercorefactory") != 0)
+ {
+ CLog::Log(LOGERROR, "Error loading configuration, no <playercorefactory> node");
+ return false;
+ }
+
+ TiXmlElement *pPlayers = pConfig->FirstChildElement("players");
+ if (pPlayers)
+ {
+ TiXmlElement* pPlayer = pPlayers->FirstChildElement("player");
+ while (pPlayer)
+ {
+ std::string name = XMLUtils::GetAttribute(pPlayer, "name");
+ std::string type = XMLUtils::GetAttribute(pPlayer, "type");
+ if (type.empty()) type = name;
+ StringUtils::ToLower(type);
+
+ std::string internaltype;
+ if (type == "videoplayer")
+ internaltype = "video";
+ else if (type == "paplayer")
+ internaltype = "music";
+ else if (type == "externalplayer")
+ internaltype = "external";
+
+ int count = 0;
+ std::string playername = name;
+ while (GetPlayerIndex(playername) >= 0)
+ {
+ count++;
+ std::stringstream itoa;
+ itoa << count;
+ playername = name + itoa.str();
+ }
+
+ if (!internaltype.empty())
+ {
+ m_vecPlayerConfigs.emplace_back(
+ std::make_unique<CPlayerCoreConfig>(playername, internaltype, pPlayer));
+ }
+
+ pPlayer = pPlayer->NextSiblingElement("player");
+ }
+ }
+
+ TiXmlElement *pRule = pConfig->FirstChildElement("rules");
+ while (pRule)
+ {
+ const char* szAction = pRule->Attribute("action");
+ if (szAction)
+ {
+ if (StringUtils::CompareNoCase(szAction, "append") == 0)
+ {
+ m_vecCoreSelectionRules.emplace_back(std::make_unique<CPlayerSelectionRule>(pRule));
+ }
+ else if (StringUtils::CompareNoCase(szAction, "prepend") == 0)
+ {
+ m_vecCoreSelectionRules.emplace_front(std::make_unique<CPlayerSelectionRule>(pRule));
+ }
+ else
+ {
+ m_vecCoreSelectionRules.clear();
+ m_vecCoreSelectionRules.emplace_back(std::make_unique<CPlayerSelectionRule>(pRule));
+ }
+ }
+ else
+ {
+ m_vecCoreSelectionRules.emplace_back(std::make_unique<CPlayerSelectionRule>(pRule));
+ }
+
+ pRule = pRule->NextSiblingElement("rules");
+ }
+
+ // succeeded - tell the user it worked
+ CLog::Log(LOGINFO, "Loaded playercorefactory configuration");
+
+ return true;
+}
+
+void CPlayerCoreFactory::OnPlayerDiscovered(const std::string& id, const std::string& name)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (auto& playerConfig : m_vecPlayerConfigs)
+ {
+ if (playerConfig->GetId() == id)
+ {
+ playerConfig->m_name = name;
+ playerConfig->m_type = "remote";
+ return;
+ }
+ }
+
+ int count = 0;
+ std::string playername = name;
+ while (GetPlayerIndex(playername) >= 0)
+ {
+ count++;
+ std::stringstream itoa;
+ itoa << count;
+ playername = name + itoa.str();
+ }
+
+ auto player = std::make_unique<CPlayerCoreConfig>(playername, "remote", nullptr, id);
+ player->m_bPlaysAudio = true;
+ player->m_bPlaysVideo = true;
+ m_vecPlayerConfigs.emplace_back(std::move(player));
+}
+
+void CPlayerCoreFactory::OnPlayerRemoved(const std::string& id)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (auto& playerConfig : m_vecPlayerConfigs)
+ {
+ if (playerConfig->GetId() == id)
+ playerConfig->m_type = "";
+ }
+}
diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.h b/xbmc/cores/playercorefactory/PlayerCoreFactory.h
new file mode 100644
index 0000000..e2963e8
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "settings/lib/ISettingsHandler.h"
+#include "threads/CriticalSection.h"
+
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+// forward references
+
+class TiXmlElement;
+class CFileItem;
+class CPlayerCoreConfig;
+class CPlayerSelectionRule;
+class CProfileManager;
+class CSettings;
+class IPlayer;
+class IPlayerCallback;
+
+class CPlayerCoreFactory : public ISettingsHandler
+{
+public:
+ CPlayerCoreFactory(const CProfileManager &profileManager);
+ CPlayerCoreFactory(const CPlayerCoreFactory&) = delete;
+ CPlayerCoreFactory& operator=(CPlayerCoreFactory const&) = delete;
+ ~CPlayerCoreFactory() override;
+
+ void OnSettingsLoaded() override;
+
+ std::shared_ptr<IPlayer> CreatePlayer(const std::string& nameId, IPlayerCallback& callback) const;
+ void GetPlayers(const CFileItem& item, std::vector<std::string>&players) const; //Players supporting the specified file
+ void GetPlayers(std::vector<std::string>&players, bool audio, bool video) const; //All audio players and/or video players
+ void GetPlayers(std::vector<std::string>&players) const; //All players
+ void GetPlayers(std::vector<std::string>&players, std::string &type) const;
+ void GetRemotePlayers(std::vector<std::string>&players) const; //All remote players we can attach to
+ std::string GetPlayerType(const std::string &player) const;
+ bool PlaysAudio(const std::string &player) const;
+ bool PlaysVideo(const std::string &player) const;
+
+ std::string GetDefaultPlayer(const CFileItem& item) const;
+ std::string SelectPlayerDialog(const std::vector<std::string>&players, float posX = 0, float posY = 0) const;
+ std::string SelectPlayerDialog(float posX, float posY) const;
+ void OnPlayerDiscovered(const std::string& id, const std::string& name);
+ void OnPlayerRemoved(const std::string& id);
+
+private:
+ // Construction parameters
+ std::shared_ptr<CSettings> m_settings;
+ const CProfileManager &m_profileManager;
+
+ int GetPlayerIndex(const std::string& strCoreName) const;
+ std::string GetPlayerName(size_t idx) const;
+
+ bool LoadConfiguration(const std::string &file, bool clear);
+
+ std::vector<std::unique_ptr<CPlayerCoreConfig>> m_vecPlayerConfigs;
+ std::list<std::unique_ptr<CPlayerSelectionRule>> m_vecCoreSelectionRules;
+ mutable CCriticalSection m_section;
+};
diff --git a/xbmc/cores/playercorefactory/PlayerSelectionRule.cpp b/xbmc/cores/playercorefactory/PlayerSelectionRule.cpp
new file mode 100644
index 0000000..cad7399
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerSelectionRule.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlayerSelectionRule.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/RegExp.h"
+#include "utils/StreamDetails.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <algorithm>
+
+CPlayerSelectionRule::CPlayerSelectionRule(TiXmlElement* pRule)
+{
+ Initialize(pRule);
+}
+
+void CPlayerSelectionRule::Initialize(TiXmlElement* pRule)
+{
+ m_name = XMLUtils::GetAttribute(pRule, "name");
+ if (m_name.empty())
+ m_name = "un-named";
+
+ CLog::Log(LOGDEBUG, "CPlayerSelectionRule::Initialize: creating rule: {}", m_name);
+
+ m_tInternetStream = GetTristate(pRule->Attribute("internetstream"));
+ m_tRemote = GetTristate(pRule->Attribute("remote"));
+ m_tAudio = GetTristate(pRule->Attribute("audio"));
+ m_tVideo = GetTristate(pRule->Attribute("video"));
+ m_tGame = GetTristate(pRule->Attribute("game"));
+
+ m_tBD = GetTristate(pRule->Attribute("bd"));
+ m_tDVD = GetTristate(pRule->Attribute("dvd"));
+ m_tDVDFile = GetTristate(pRule->Attribute("dvdfile"));
+ m_tDiscImage = GetTristate(pRule->Attribute("discimage"));
+ if (m_tDiscImage < 0)
+ {
+ m_tDiscImage = GetTristate(pRule->Attribute("dvdimage"));
+ if (m_tDiscImage >= 0)
+ CLog::Log(LOGWARNING, "\"dvdimage\" tag is deprecated. use \"discimage\"");
+ }
+
+ m_protocols = XMLUtils::GetAttribute(pRule, "protocols");
+ m_fileTypes = XMLUtils::GetAttribute(pRule, "filetypes");
+ m_mimeTypes = XMLUtils::GetAttribute(pRule, "mimetypes");
+ m_fileName = XMLUtils::GetAttribute(pRule, "filename");
+
+ m_audioCodec = XMLUtils::GetAttribute(pRule, "audiocodec");
+ m_audioChannels = XMLUtils::GetAttribute(pRule, "audiochannels");
+ m_videoCodec = XMLUtils::GetAttribute(pRule, "videocodec");
+ m_videoResolution = XMLUtils::GetAttribute(pRule, "videoresolution");
+ m_videoAspect = XMLUtils::GetAttribute(pRule, "videoaspect");
+
+ m_bStreamDetails = m_audioCodec.length() > 0 || m_audioChannels.length() > 0 ||
+ m_videoCodec.length() > 0 || m_videoResolution.length() > 0 || m_videoAspect.length() > 0;
+
+ if (m_bStreamDetails && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS))
+ {
+ CLog::Log(LOGWARNING,
+ "CPlayerSelectionRule::Initialize: rule: {} needs media flagging, which is disabled",
+ m_name);
+ }
+
+ m_playerName = XMLUtils::GetAttribute(pRule, "player");
+
+ TiXmlElement* pSubRule = pRule->FirstChildElement("rule");
+ while (pSubRule)
+ {
+ vecSubRules.emplace_back(std::make_unique<CPlayerSelectionRule>(pSubRule));
+ pSubRule = pSubRule->NextSiblingElement("rule");
+ }
+}
+
+int CPlayerSelectionRule::GetTristate(const char* szValue)
+{
+ if (szValue)
+ {
+ if (StringUtils::CompareNoCase(szValue, "true") == 0)
+ return 1;
+ if (StringUtils::CompareNoCase(szValue, "false") == 0)
+ return 0;
+ }
+ return -1;
+}
+
+bool CPlayerSelectionRule::CompileRegExp(const std::string& str, CRegExp& regExp)
+{
+ return !str.empty() && regExp.RegComp(str.c_str());
+}
+
+bool CPlayerSelectionRule::MatchesRegExp(const std::string& str, CRegExp& regExp)
+{
+ return regExp.RegFind(str, 0) == 0;
+}
+
+void CPlayerSelectionRule::GetPlayers(const CFileItem& item, std::vector<std::string>&validPlayers, std::vector<std::string>&players)
+{
+ CLog::Log(LOGDEBUG, "CPlayerSelectionRule::GetPlayers: considering rule: {}", m_name);
+
+ if (m_bStreamDetails && !item.HasVideoInfoTag())
+ return;
+ if (m_tAudio >= 0 && (m_tAudio > 0) != item.IsAudio())
+ return;
+ if (m_tVideo >= 0 && (m_tVideo > 0) != item.IsVideo())
+ return;
+ if (m_tGame >= 0 && (m_tGame > 0) != item.IsGame())
+ return;
+ if (m_tInternetStream >= 0 && (m_tInternetStream > 0) != item.IsInternetStream())
+ return;
+ if (m_tRemote >= 0 && (m_tRemote > 0) != item.IsRemote())
+ return;
+
+ if (m_tBD >= 0 && (m_tBD > 0) != (item.IsBDFile() && item.IsOnDVD()))
+ return;
+ if (m_tDVD >= 0 && (m_tDVD > 0) != item.IsDVD())
+ return;
+ if (m_tDVDFile >= 0 && (m_tDVDFile > 0) != item.IsDVDFile())
+ return;
+ if (m_tDiscImage >= 0 && (m_tDiscImage > 0) != item.IsDiscImage())
+ return;
+
+ CRegExp regExp(false, CRegExp::autoUtf8);
+
+ if (m_bStreamDetails)
+ {
+ if (!item.GetVideoInfoTag()->HasStreamDetails())
+ {
+ CLog::Log(LOGDEBUG,
+ "CPlayerSelectionRule::GetPlayers: cannot check rule: {}, no StreamDetails",
+ m_name);
+ return;
+ }
+
+ CStreamDetails streamDetails = item.GetVideoInfoTag()->m_streamDetails;
+
+ if (CompileRegExp(m_audioCodec, regExp) && !MatchesRegExp(streamDetails.GetAudioCodec(), regExp))
+ return;
+
+ std::stringstream itoa;
+ itoa << streamDetails.GetAudioChannels();
+ std::string audioChannelsstr = itoa.str();
+
+ if (CompileRegExp(m_audioChannels, regExp) && !MatchesRegExp(audioChannelsstr, regExp))
+ return;
+
+ if (CompileRegExp(m_videoCodec, regExp) && !MatchesRegExp(streamDetails.GetVideoCodec(), regExp))
+ return;
+
+ if (CompileRegExp(m_videoResolution, regExp) &&
+ !MatchesRegExp(CStreamDetails::VideoDimsToResolutionDescription(streamDetails.GetVideoWidth(), streamDetails.GetVideoHeight()), regExp))
+ return;
+
+ if (CompileRegExp(m_videoAspect, regExp) &&
+ !MatchesRegExp(CStreamDetails::VideoAspectToAspectDescription(streamDetails.GetVideoAspect()), regExp))
+ return;
+ }
+
+ CURL url(item.GetDynPath());
+
+ if (CompileRegExp(m_fileTypes, regExp) && !MatchesRegExp(url.GetFileType(), regExp))
+ return;
+
+ if (CompileRegExp(m_protocols, regExp) && !MatchesRegExp(url.GetProtocol(), regExp))
+ return;
+
+ if (CompileRegExp(m_mimeTypes, regExp) && !MatchesRegExp(item.GetMimeType(), regExp))
+ return;
+
+ if (CompileRegExp(m_fileName, regExp) && !MatchesRegExp(item.GetDynPath(), regExp))
+ return;
+
+ CLog::Log(LOGDEBUG, "CPlayerSelectionRule::GetPlayers: matches rule: {}", m_name);
+
+ for (const auto& rule : vecSubRules)
+ rule->GetPlayers(item, validPlayers, players);
+
+ if (std::find(validPlayers.begin(), validPlayers.end(), m_playerName) != validPlayers.end())
+ {
+ CLog::Log(LOGDEBUG, "CPlayerSelectionRule::GetPlayers: adding player: {} for rule: {}",
+ m_playerName, m_name);
+ players.push_back(m_playerName);
+ }
+}
+
+
diff --git a/xbmc/cores/playercorefactory/PlayerSelectionRule.h b/xbmc/cores/playercorefactory/PlayerSelectionRule.h
new file mode 100644
index 0000000..b818d8c
--- /dev/null
+++ b/xbmc/cores/playercorefactory/PlayerSelectionRule.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PlayerCoreFactory.h"
+
+#include <string>
+#include <vector>
+
+class CFileItem;
+class CRegExp;
+class TiXmlElement;
+
+class CPlayerSelectionRule
+{
+public:
+ explicit CPlayerSelectionRule(TiXmlElement* rule);
+ virtual ~CPlayerSelectionRule() = default;
+
+ void GetPlayers(const CFileItem& item, std::vector<std::string>&validPlayers, std::vector<std::string>&players);
+
+private:
+ static int GetTristate(const char* szValue);
+ static bool CompileRegExp(const std::string& str, CRegExp& regExp);
+ static bool MatchesRegExp(const std::string& str, CRegExp& regExp);
+ void Initialize(TiXmlElement* pRule);
+
+ std::string m_name;
+
+ int m_tAudio;
+ int m_tVideo;
+ int m_tGame;
+ int m_tInternetStream;
+ int m_tRemote;
+
+ int m_tBD;
+ int m_tDVD;
+ int m_tDVDFile;
+ int m_tDiscImage;
+
+ std::string m_protocols;
+ std::string m_fileTypes;
+ std::string m_mimeTypes;
+ std::string m_fileName;
+
+ bool m_bStreamDetails;
+ std::string m_audioCodec;
+ std::string m_audioChannels;
+ std::string m_videoCodec;
+ std::string m_videoResolution;
+ std::string m_videoAspect;
+
+ std::string m_playerName;
+
+ std::vector<std::unique_ptr<CPlayerSelectionRule>> vecSubRules;
+};