@ -17,6 +17,8 @@ extern "C" {
// for querying VAAPI driver information
// for querying VAAPI driver information
# include <libavutil/hwcontext_vaapi.h>
# include <libavutil/hwcontext_vaapi.h>
# endif
# endif
# include <libavutil/hwcontext.h>
}
}
namespace FFmpeg {
namespace FFmpeg {
@ -27,282 +29,287 @@ constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12;
constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P ;
constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P ;
constexpr std : : array PreferredGpuDecoders = {
constexpr std : : array PreferredGpuDecoders = {
# if defined (_WIN32)
# if defined (_WIN32)
AV_HWDEVICE_TYPE_CUDA ,
AV_HWDEVICE_TYPE_CUDA ,
AV_HWDEVICE_TYPE_D3D11VA ,
AV_HWDEVICE_TYPE_D3D11VA ,
AV_HWDEVICE_TYPE_DXVA2 ,
AV_HWDEVICE_TYPE_DXVA2 ,
AV_HWDEVICE_TYPE_D3D12VA ,
# elif defined(__FreeBSD__)
# elif defined(__FreeBSD__)
AV_HWDEVICE_TYPE_VDPAU ,
AV_HWDEVICE_TYPE_VDPAU ,
# elif defined(__APPLE__)
AV_HWDEVICE_TYPE_VIDEOTOOLBOX ,
# elif defined(ANDROID)
AV_HWDEVICE_TYPE_MEDIACODEC ,
# elif defined(__unix__)
# elif defined(__unix__)
AV_HWDEVICE_TYPE_CUDA ,
AV_HWDEVICE_TYPE_VAAPI ,
AV_HWDEVICE_TYPE_VDPAU ,
AV_HWDEVICE_TYPE_CUDA ,
AV_HWDEVICE_TYPE_VAAPI ,
AV_HWDEVICE_TYPE_VDPAU ,
# endif
# endif
AV_HWDEVICE_TYPE_VULKAN ,
AV_HWDEVICE_TYPE_VULKAN ,
} ;
} ;
AVPixelFormat GetGpuFormat ( AVCodecContext * codec_context , const AVPixelFormat * pix_fmts ) {
AVPixelFormat GetGpuFormat ( AVCodecContext * codec_context , const AVPixelFormat * pix_fmts ) {
const auto desc = av_pix_fmt_desc_get ( codec_context - > pix_fmt ) ;
if ( desc & & ! ( desc - > flags & AV_PIX_FMT_FLAG_HWACCEL ) ) {
for ( int i = 0 ; ; i + + ) {
const AVCodecHWConfig * config = avcodec_get_hw_config ( codec_context - > codec , i ) ;
if ( ! config ) {
break ;
}
for ( const auto type : PreferredGpuDecoders ) {
if ( config - > methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX & & config - > device_type = = type ) {
codec_context - > pix_fmt = config - > pix_fmt ;
}
}
}
}
for ( const AVPixelFormat * p = pix_fmts ; * p ! = AV_PIX_FMT_NONE ; + + p ) {
if ( * p = = codec_context - > pix_fmt ) {
return codec_context - > pix_fmt ;
}
}
LOG_INFO ( HW_GPU , " Could not find supported GPU pixel format, falling back to CPU decoder " ) ;
av_buffer_unref ( & codec_context - > hw_device_ctx ) ;
codec_context - > pix_fmt = PreferredCpuFormat ;
return codec_context - > pix_fmt ;
const auto desc = av_pix_fmt_desc_get ( codec_context - > pix_fmt ) ;
if ( desc & & ! ( desc - > flags & AV_PIX_FMT_FLAG_HWACCEL ) ) {
for ( int i = 0 ; ; i + + ) {
const AVCodecHWConfig * config = avcodec_get_hw_config ( codec_context - > codec , i ) ;
if ( ! config ) {
break ;
}
for ( const auto type : PreferredGpuDecoders ) {
if ( config - > methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX & & config - > device_type = = type ) {
codec_context - > pix_fmt = config - > pix_fmt ;
}
}
}
}
for ( const AVPixelFormat * p = pix_fmts ; * p ! = AV_PIX_FMT_NONE ; + + p ) {
if ( * p = = codec_context - > pix_fmt ) {
return codec_context - > pix_fmt ;
}
}
LOG_INFO ( HW_GPU , " Could not find supported GPU pixel format, falling back to CPU decoder " ) ;
av_buffer_unref ( & codec_context - > hw_device_ctx ) ;
codec_context - > pix_fmt = PreferredCpuFormat ;
return codec_context - > pix_fmt ;
}
}
std : : string AVError ( int errnum ) {
std : : string AVError ( int errnum ) {
char errbuf [ AV_ERROR_MAX_STRING_SIZE ] = { } ;
av_make_error_string ( errbuf , sizeof ( errbuf ) - 1 , errnum ) ;
return errbuf ;
char errbuf [ AV_ERROR_MAX_STRING_SIZE ] = { } ;
av_make_error_string ( errbuf , sizeof ( errbuf ) - 1 , errnum ) ;
return errbuf ;
}
}
}
}
Packet : : Packet ( std : : span < const u8 > data ) {
Packet : : Packet ( std : : span < const u8 > data ) {
m_packet = av_packet_alloc ( ) ;
m_packet - > data = const_cast < u8 * > ( data . data ( ) ) ;
m_packet - > size = static_cast < s32 > ( data . size ( ) ) ;
m_packet = av_packet_alloc ( ) ;
m_packet - > data = const_cast < u8 * > ( data . data ( ) ) ;
m_packet - > size = static_cast < s32 > ( data . size ( ) ) ;
}
}
Packet : : ~ Packet ( ) {
Packet : : ~ Packet ( ) {
av_packet_free ( & m_packet ) ;
av_packet_free ( & m_packet ) ;
}
}
Frame : : Frame ( ) {
Frame : : Frame ( ) {
m_frame = av_frame_alloc ( ) ;
m_frame = av_frame_alloc ( ) ;
}
}
Frame : : ~ Frame ( ) {
Frame : : ~ Frame ( ) {
av_frame_free ( & m_frame ) ;
av_frame_free ( & m_frame ) ;
}
}
Decoder : : Decoder ( Tegra : : Host1x : : NvdecCommon : : VideoCodec codec ) {
Decoder : : Decoder ( Tegra : : Host1x : : NvdecCommon : : VideoCodec codec ) {
const AVCodecID av_codec = [ & ] {
switch ( codec ) {
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : H264 :
return AV_CODEC_ID_H264 ;
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : VP8 :
return AV_CODEC_ID_VP8 ;
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : VP9 :
return AV_CODEC_ID_VP9 ;
default :
UNIMPLEMENTED_MSG ( " Unknown codec {} " , codec ) ;
return AV_CODEC_ID_NONE ;
}
} ( ) ;
m_codec = avcodec_find_decoder ( av_codec ) ;
const AVCodecID av_codec = [ & ] {
switch ( codec ) {
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : H264 :
return AV_CODEC_ID_H264 ;
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : VP8 :
return AV_CODEC_ID_VP8 ;
case Tegra : : Host1x : : NvdecCommon : : VideoCodec : : VP9 :
return AV_CODEC_ID_VP9 ;
default :
UNIMPLEMENTED_MSG ( " Unknown codec {} " , codec ) ;
return AV_CODEC_ID_NONE ;
}
} ( ) ;
m_codec = avcodec_find_decoder ( av_codec ) ;
}
}
bool Decoder : : SupportsDecodingOnDevice ( AVPixelFormat * out_pix_fmt , AVHWDeviceType type ) const {
bool Decoder : : SupportsDecodingOnDevice ( AVPixelFormat * out_pix_fmt , AVHWDeviceType type ) const {
for ( int i = 0 ; ; i + + ) {
const AVCodecHWConfig * config = avcodec_get_hw_config ( m_codec , i ) ;
if ( ! config ) {
LOG_DEBUG ( HW_GPU , " {} decoder does not support device type {} " , m_codec - > name , av_hwdevice_get_type_name ( type ) ) ;
break ;
}
if ( config - > methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX & & config - > device_type = = type ) {
LOG_INFO ( HW_GPU , " Using {} GPU decoder " , av_hwdevice_get_type_name ( type ) ) ;
* out_pix_fmt = config - > pix_fmt ;
return true ;
}
}
return false ;
for ( int i = 0 ; ; i + + ) {
const AVCodecHWConfig * config = avcodec_get_hw_config ( m_codec , i ) ;
if ( ! config ) {
LOG_DEBUG ( HW_GPU , " {} decoder does not support device type {} " , m_codec - > name , av_hwdevice_get_type_name ( type ) ) ;
break ;
}
if ( config - > methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX & & config - > device_type = = type ) {
LOG_INFO ( HW_GPU , " Using {} GPU decoder " , av_hwdevice_get_type_name ( type ) ) ;
* out_pix_fmt = config - > pix_fmt ;
return true ;
}
}
return false ;
}
}
std : : vector < AVHWDeviceType > HardwareContext : : GetSupportedDeviceTypes ( ) {
std : : vector < AVHWDeviceType > HardwareContext : : GetSupportedDeviceTypes ( ) {
std : : vector < AVHWDeviceType > types ;
AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE ;
std : : vector < AVHWDeviceType > types ;
AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE ;
while ( true ) {
current_device_type = av_hwdevice_iterate_types ( current_device_type ) ;
if ( current_device_type = = AV_HWDEVICE_TYPE_NONE ) {
return types ;
}
while ( true ) {
current_device_type = av_hwdevice_iterate_types ( current_device_type ) ;
if ( current_device_type = = AV_HWDEVICE_TYPE_NONE ) {
return types ;
}
types . push_back ( current_device_type ) ;
}
types . push_back ( current_device_type ) ;
}
}
}
HardwareContext : : ~ HardwareContext ( ) {
HardwareContext : : ~ HardwareContext ( ) {
av_buffer_unref ( & m_gpu_decoder ) ;
av_buffer_unref ( & m_gpu_decoder ) ;
}
}
bool HardwareContext : : InitializeForDecoder ( DecoderContext & decoder_context , const Decoder & decoder ) {
bool HardwareContext : : InitializeForDecoder ( DecoderContext & decoder_context , const Decoder & decoder ) {
const auto supported_types = GetSupportedDeviceTypes ( ) ;
for ( const auto type : PreferredGpuDecoders ) {
AVPixelFormat hw_pix_fmt ;
if ( std : : ranges : : find ( supported_types , type ) = = supported_types . end ( ) ) {
LOG_DEBUG ( HW_GPU , " {} explicitly unsupported " , av_hwdevice_get_type_name ( type ) ) ;
continue ;
}
if ( ! this - > InitializeWithType ( type ) ) {
continue ;
}
if ( decoder . SupportsDecodingOnDevice ( & hw_pix_fmt , type ) ) {
decoder_context . InitializeHardwareDecoder ( * this , hw_pix_fmt ) ;
return true ;
}
}
return false ;
const auto supported_types = GetSupportedDeviceTypes ( ) ;
for ( const auto type : PreferredGpuDecoders ) {
AVPixelFormat hw_pix_fmt ;
if ( std : : ranges : : find ( supported_types , type ) = = supported_types . end ( ) ) {
LOG_DEBUG ( HW_GPU , " {} explicitly unsupported " , av_hwdevice_get_type_name ( type ) ) ;
continue ;
}
if ( ! this - > InitializeWithType ( type ) ) {
continue ;
}
if ( decoder . SupportsDecodingOnDevice ( & hw_pix_fmt , type ) ) {
decoder_context . InitializeHardwareDecoder ( * this , hw_pix_fmt ) ;
return true ;
}
}
return false ;
}
}
bool HardwareContext : : InitializeWithType ( AVHWDeviceType type ) {
bool HardwareContext : : InitializeWithType ( AVHWDeviceType type ) {
av_buffer_unref ( & m_gpu_decoder ) ;
av_buffer_unref ( & m_gpu_decoder ) ;
if ( const int ret = av_hwdevice_ctx_create ( & m_gpu_decoder , type , nullptr , nullptr , 0 ) ; ret < 0 ) {
LOG_DEBUG ( HW_GPU , " av_hwdevice_ctx_create({}) failed: {} " , av_hwdevice_get_type_name ( type ) , AVError ( ret ) ) ;
return false ;
}
if ( const int ret = av_hwdevice_ctx_create ( & m_gpu_decoder , type , nullptr , nullptr , 0 ) ; ret < 0 ) {
LOG_DEBUG ( HW_GPU , " av_hwdevice_ctx_create({}) failed: {} " , av_hwdevice_get_type_name ( type ) , AVError ( ret ) ) ;
return false ;
}
# ifdef LIBVA_FOUND
# ifdef LIBVA_FOUND
if ( type = = AV_HWDEVICE_TYPE_VAAPI ) {
// We need to determine if this is an impersonated VAAPI driver.
auto * hwctx = reinterpret_cast < AVHWDeviceContext * > ( m_gpu_decoder - > data ) ;
auto * vactx = static_cast < AVVAAPIDeviceContext * > ( hwctx - > hwctx ) ;
const char * vendor_name = vaQueryVendorString ( vactx - > display ) ;
if ( strstr ( vendor_name , " VDPAU backend " ) ) {
// VDPAU impersonated VAAPI impls are super buggy, we need to skip them.
LOG_DEBUG ( HW_GPU , " Skipping VDPAU impersonated VAAPI driver " ) ;
return false ;
} else {
// According to some user testing, certain VAAPI drivers (Intel?) could be buggy.
// Log the driver name just in case.
LOG_DEBUG ( HW_GPU , " Using VAAPI driver: {} " , vendor_name ) ;
}
}
if ( type = = AV_HWDEVICE_TYPE_VAAPI ) {
// We need to determine if this is an impersonated VAAPI driver.
auto * hwctx = reinterpret_cast < AVHWDeviceContext * > ( m_gpu_decoder - > data ) ;
auto * vactx = static_cast < AVVAAPIDeviceContext * > ( hwctx - > hwctx ) ;
const char * vendor_name = vaQueryVendorString ( vactx - > display ) ;
if ( strstr ( vendor_name , " VDPAU backend " ) ) {
// VDPAU impersonated VAAPI impls are super buggy, we need to skip them.
LOG_DEBUG ( HW_GPU , " Skipping VDPAU impersonated VAAPI driver " ) ;
return false ;
} else {
// According to some user testing, certain VAAPI drivers (Intel?) could be buggy.
// Log the driver name just in case.
LOG_DEBUG ( HW_GPU , " Using VAAPI driver: {} " , vendor_name ) ;
}
}
# endif
# endif
return true ;
return true ;
}
}
DecoderContext : : DecoderContext ( const Decoder & decoder ) : m_decoder { decoder } {
DecoderContext : : DecoderContext ( const Decoder & decoder ) : m_decoder { decoder } {
m_codec_context = avcodec_alloc_context3 ( m_decoder . GetCodec ( ) ) ;
av_opt_set ( m_codec_context - > priv_data , " tune " , " zerolatency " , 0 ) ;
m_codec_context - > thread_count = 0 ;
m_codec_context - > thread_type & = ~ FF_THREAD_FRAME ;
m_codec_context = avcodec_alloc_context3 ( m_decoder . GetCodec ( ) ) ;
av_opt_set ( m_codec_context - > priv_data , " tune " , " zerolatency " , 0 ) ;
m_codec_context - > thread_count = 0 ;
m_codec_context - > thread_type & = ~ FF_THREAD_FRAME ;
}
}
DecoderContext : : ~ DecoderContext ( ) {
DecoderContext : : ~ DecoderContext ( ) {
av_buffer_unref ( & m_codec_context - > hw_device_ctx ) ;
avcodec_free_context ( & m_codec_context ) ;
av_buffer_unref ( & m_codec_context - > hw_device_ctx ) ;
avcodec_free_context ( & m_codec_context ) ;
}
}
void DecoderContext : : InitializeHardwareDecoder ( const HardwareContext & context , AVPixelFormat hw_pix_fmt ) {
void DecoderContext : : InitializeHardwareDecoder ( const HardwareContext & context , AVPixelFormat hw_pix_fmt ) {
m_codec_context - > hw_device_ctx = av_buffer_ref ( context . GetBufferRef ( ) ) ;
m_codec_context - > get_format = GetGpuFormat ;
m_codec_context - > pix_fmt = hw_pix_fmt ;
m_codec_context - > hw_device_ctx = av_buffer_ref ( context . GetBufferRef ( ) ) ;
m_codec_context - > get_format = GetGpuFormat ;
m_codec_context - > pix_fmt = hw_pix_fmt ;
}
}
bool DecoderContext : : OpenContext ( const Decoder & decoder ) {
bool DecoderContext : : OpenContext ( const Decoder & decoder ) {
if ( const int ret = avcodec_open2 ( m_codec_context , decoder . GetCodec ( ) , nullptr ) ; ret < 0 ) {
LOG_ERROR ( HW_GPU , " avcodec_open2 error: {} " , AVError ( ret ) ) ;
return false ;
}
if ( const int ret = avcodec_open2 ( m_codec_context , decoder . GetCodec ( ) , nullptr ) ; ret < 0 ) {
LOG_ERROR ( HW_GPU , " avcodec_open2 error: {} " , AVError ( ret ) ) ;
return false ;
}
if ( ! m_codec_context - > hw_device_ctx ) {
LOG_INFO ( HW_GPU , " Using FFmpeg CPU decoder " ) ;
}
if ( ! m_codec_context - > hw_device_ctx ) {
LOG_INFO ( HW_GPU , " Using FFmpeg CPU decoder " ) ;
}
return true ;
return true ;
}
}
bool DecoderContext : : SendPacket ( const Packet & packet ) {
bool DecoderContext : : SendPacket ( const Packet & packet ) {
if ( const int ret = avcodec_send_packet ( m_codec_context , packet . GetPacket ( ) ) ; ret < 0 & & ret ! = AVERROR_EOF & & ret ! = AVERROR ( EAGAIN ) ) {
LOG_ERROR ( HW_GPU , " avcodec_send_packet error: {} " , AVError ( ret ) ) ;
return false ;
}
if ( const int ret = avcodec_send_packet ( m_codec_context , packet . GetPacket ( ) ) ; ret < 0 & & ret ! = AVERROR_EOF & & ret ! = AVERROR ( EAGAIN ) ) {
LOG_ERROR ( HW_GPU , " avcodec_send_packet error: {} " , AVError ( ret ) ) ;
return false ;
}
return true ;
return true ;
}
}
std : : shared_ptr < Frame > DecoderContext : : ReceiveFrame ( ) {
std : : shared_ptr < Frame > DecoderContext : : ReceiveFrame ( ) {
auto ReceiveImpl = [ & ] ( AVFrame * frame ) - > int {
auto ReceiveImpl = [ & ] ( AVFrame * frame ) - > int {
const int ret = avcodec_receive_frame ( m_codec_context , frame ) ;
const int ret = avcodec_receive_frame ( m_codec_context , frame ) ;
if ( ret < 0 & & ret ! = AVERROR_EOF & & ret ! = AVERROR ( EAGAIN ) ) {
LOG_ERROR ( HW_GPU , " avcodec_receive_frame error: {} " , AVError ( ret ) ) ;
}
return ret ;
} ;
std : : shared_ptr < Frame > intermediate_frame = std : : make_shared < Frame > ( ) ;
if ( ReceiveImpl ( intermediate_frame - > GetFrame ( ) ) < 0 ) {
return { } ;
}
m_final_frame = std : : make_shared < Frame > ( ) ;
if ( m_codec_context - > hw_device_ctx ) {
m_final_frame - > SetFormat ( PreferredGpuFormat ) ;
if ( const int ret = av_hwframe_transfer_data ( m_final_frame - > GetFrame ( ) , intermediate_frame - > GetFrame ( ) , 0 ) ; ret < 0 ) {
LOG_ERROR ( HW_GPU , " av_hwframe_transfer_data error: {} " , AVError ( ret ) ) ;
return { } ;
}
} else {
m_final_frame = std : : move ( intermediate_frame ) ;
}
return std : : move ( m_final_frame ) ;
if ( ret < 0 & & ret ! = AVERROR_EOF & & ret ! = AVERROR ( EAGAIN ) ) {
LOG_ERROR ( HW_GPU , " avcodec_receive_frame error: {} " , AVError ( ret ) ) ;
}
return ret ;
} ;
std : : shared_ptr < Frame > intermediate_frame = std : : make_shared < Frame > ( ) ;
if ( ReceiveImpl ( intermediate_frame - > GetFrame ( ) ) < 0 ) {
return { } ;
}
m_final_frame = std : : make_shared < Frame > ( ) ;
if ( m_codec_context - > hw_device_ctx ) {
m_final_frame - > SetFormat ( PreferredGpuFormat ) ;
if ( const int ret = av_hwframe_transfer_data ( m_final_frame - > GetFrame ( ) , intermediate_frame - > GetFrame ( ) , 0 ) ; ret < 0 ) {
LOG_ERROR ( HW_GPU , " av_hwframe_transfer_data error: {} " , AVError ( ret ) ) ;
return { } ;
}
} else {
m_final_frame = std : : move ( intermediate_frame ) ;
}
return std : : move ( m_final_frame ) ;
}
}
void DecodeApi : : Reset ( ) {
void DecodeApi : : Reset ( ) {
m_hardware_context . reset ( ) ;
m_decoder_context . reset ( ) ;
m_decoder . reset ( ) ;
m_hardware_context . reset ( ) ;
m_decoder_context . reset ( ) ;
m_decoder . reset ( ) ;
}
}
bool DecodeApi : : Initialize ( Tegra : : Host1x : : NvdecCommon : : VideoCodec codec ) {
bool DecodeApi : : Initialize ( Tegra : : Host1x : : NvdecCommon : : VideoCodec codec ) {
this - > Reset ( ) ;
m_decoder . emplace ( codec ) ;
m_decoder_context . emplace ( * m_decoder ) ;
// Enable GPU decoding if requested.
if ( Settings : : values . nvdec_emulation . GetValue ( ) = = Settings : : NvdecEmulation : : Gpu ) {
m_hardware_context . emplace ( ) ;
m_hardware_context - > InitializeForDecoder ( * m_decoder_context , * m_decoder ) ;
}
// Open the decoder context.
if ( ! m_decoder_context - > OpenContext ( * m_decoder ) ) {
this - > Reset ( ) ;
return false ;
}
return true ;
this - > Reset ( ) ;
m_decoder . emplace ( codec ) ;
m_decoder_context . emplace ( * m_decoder ) ;
// Enable GPU decoding if requested.
if ( Settings : : values . nvdec_emulation . GetValue ( ) = = Settings : : NvdecEmulation : : Gpu ) {
m_hardware_context . emplace ( ) ;
m_hardware_context - > InitializeForDecoder ( * m_decoder_context , * m_decoder ) ;
}
// Open the decoder context.
if ( ! m_decoder_context - > OpenContext ( * m_decoder ) ) {
this - > Reset ( ) ;
return false ;
}
return true ;
}
}
bool DecodeApi : : SendPacket ( std : : span < const u8 > packet_data ) {
bool DecodeApi : : SendPacket ( std : : span < const u8 > packet_data ) {
FFmpeg : : Packet packet ( packet_data ) ;
return m_decoder_context - > SendPacket ( packet ) ;
FFmpeg : : Packet packet ( packet_data ) ;
return m_decoder_context - > SendPacket ( packet ) ;
}
}
std : : shared_ptr < Frame > DecodeApi : : ReceiveFrame ( ) {
std : : shared_ptr < Frame > DecodeApi : : ReceiveFrame ( ) {
// Receive raw frame from decoder.
return m_decoder_context - > ReceiveFrame ( ) ;
// Receive raw frame from decoder.
return m_decoder_context - > ReceiveFrame ( ) ;
}
}
}
}