@ -16,44 +16,17 @@ extern "C" {
}
}
namespace Tegra {
namespace Tegra {
# if defined(LIBVA_FOUND)
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
namespace {
namespace {
constexpr std : : array < const char * , 2 > VAAPI_DRIVERS = {
AVPixelFormat GetGpuFormat ( AVCodecContext * av_codec_ctx , const AVPixelFormat * pix_fmts ) {
" i915 " ,
" amdgpu " ,
} ;
AVPixelFormat GetHwFormat ( AVCodecContext * , const AVPixelFormat * pix_fmts ) {
for ( const AVPixelFormat * p = pix_fmts ; * p ! = AV_PIX_FMT_NONE ; + + p ) {
for ( const AVPixelFormat * p = pix_fmts ; * p ! = AV_PIX_FMT_NONE ; + + p ) {
if ( * p = = AV_PIX_FMT_VAAPI ) {
if ( * p = = av_codec_ctx - > pix_fmt ) {
return AV_PIX_FMT_VAAPI ;
return av_codec_ctx - > pix_fmt ;
}
}
}
}
LOG_INFO ( Service_NVDRV , " Could not find compatible GPU AV format, falling back to CPU " ) ;
LOG_INFO ( Service_NVDRV , " Could not find compatible GPU AV format, falling back to CPU " ) ;
return * pix_fmts ;
return AV_PIX_FMT_NONE ;
}
bool CreateVaapiHwdevice ( AVBufferRef * * av_hw_device ) {
AVDictionary * hwdevice_options = nullptr ;
av_dict_set ( & hwdevice_options , " connection_type " , " drm " , 0 ) ;
for ( const auto & driver : VAAPI_DRIVERS ) {
av_dict_set ( & hwdevice_options , " kernel_driver " , driver , 0 ) ;
const int hwdevice_error = av_hwdevice_ctx_create ( av_hw_device , AV_HWDEVICE_TYPE_VAAPI ,
nullptr , hwdevice_options , 0 ) ;
if ( hwdevice_error > = 0 ) {
LOG_INFO ( Service_NVDRV , " Using VA-API with {} " , driver ) ;
av_dict_free ( & hwdevice_options ) ;
return true ;
}
LOG_DEBUG ( Service_NVDRV , " VA-API av_hwdevice_ctx_create failed {} " , hwdevice_error ) ;
}
LOG_DEBUG ( Service_NVDRV , " VA-API av_hwdevice_ctx_create failed for all drivers " ) ;
av_dict_free ( & hwdevice_options ) ;
return false ;
}
}
} // namespace
} // namespace
# endif
void AVFrameDeleter ( AVFrame * ptr ) {
void AVFrameDeleter ( AVFrame * ptr ) {
av_frame_free ( & ptr ) ;
av_frame_free ( & ptr ) ;
@ -69,26 +42,75 @@ Codec::~Codec() {
}
}
// Free libav memory
// Free libav memory
avcodec_send_packet ( av_codec_ctx , nullptr ) ;
avcodec_send_packet ( av_codec_ctx , nullptr ) ;
AVFrame * av_frame = av_frame_alloc ( ) ;
AVFrame Ptr av_frame { av_frame_alloc ( ) , AVFrameDeleter } ;
avcodec_receive_frame ( av_codec_ctx , av_frame );
avcodec_receive_frame ( av_codec_ctx , av_frame .get ( ) );
avcodec_flush_buffers ( av_codec_ctx ) ;
avcodec_flush_buffers ( av_codec_ctx ) ;
av_frame_free ( & av_frame ) ;
avcodec_close ( av_codec_ctx ) ;
avcodec_close ( av_codec_ctx ) ;
av_buffer_unref ( & av_ hw_device ) ;
av_buffer_unref ( & av_ gpu_decoder ) ;
}
}
void Codec : : InitializeHwdec ( ) {
bool Codec : : CreateGpuAvDevice ( ) {
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
# if defined(LIBVA_FOUND)
# if defined(LIBVA_FOUND)
if ( CreateVaapiHwdevice ( & av_hw_device ) ) {
static constexpr std : : array < const char * , 3 > VAAPI_DRIVERS = {
const auto hw_device_ctx = av_buffer_ref ( av_hw_device ) ;
" i915 " ,
ASSERT_MSG ( hw_device_ctx , " av_buffer_ref failed " ) ;
" iHD " ,
av_codec_ctx - > hw_device_ctx = hw_device_ctx ;
" amdgpu " ,
av_codec_ctx - > get_format = GetHwFormat ;
} ;
AVDictionary * hwdevice_options = nullptr ;
av_dict_set ( & hwdevice_options , " connection_type " , " drm " , 0 ) ;
for ( const auto & driver : VAAPI_DRIVERS ) {
av_dict_set ( & hwdevice_options , " kernel_driver " , driver , 0 ) ;
const int hwdevice_error = av_hwdevice_ctx_create ( & av_gpu_decoder , AV_HWDEVICE_TYPE_VAAPI ,
nullptr , hwdevice_options , 0 ) ;
if ( hwdevice_error > = 0 ) {
LOG_INFO ( Service_NVDRV , " Using VA-API with {} " , driver ) ;
av_dict_free ( & hwdevice_options ) ;
av_codec_ctx - > pix_fmt = AV_PIX_FMT_VAAPI ;
return true ;
}
LOG_DEBUG ( Service_NVDRV , " VA-API av_hwdevice_ctx_create failed {} " , hwdevice_error ) ;
}
LOG_DEBUG ( Service_NVDRV , " VA-API av_hwdevice_ctx_create failed for all drivers " ) ;
av_dict_free ( & hwdevice_options ) ;
# endif
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX ;
static constexpr std : : array GPU_DECODER_TYPES {
AV_HWDEVICE_TYPE_CUDA ,
# ifdef _WIN32
AV_HWDEVICE_TYPE_D3D11VA ,
# else
AV_HWDEVICE_TYPE_VDPAU ,
# endif
} ;
for ( const auto & type : GPU_DECODER_TYPES ) {
av_hwdevice_ctx_create ( & av_gpu_decoder , type , nullptr , nullptr , 0 ) ;
for ( int i = 0 ; ; i + + ) {
const AVCodecHWConfig * config = avcodec_get_hw_config ( av_codec , i ) ;
if ( ! config ) {
LOG_DEBUG ( Service_NVDRV , " {} decoder does not support device type {}. " ,
av_codec - > name , av_hwdevice_get_type_name ( type ) ) ;
break ;
}
if ( config - > methods & HW_CONFIG_METHOD & & config - > device_type = = type ) {
av_codec_ctx - > pix_fmt = config - > pix_fmt ;
LOG_INFO ( Service_NVDRV , " Using {} GPU decoder " , av_hwdevice_get_type_name ( type ) ) ;
return true ;
}
}
}
return false ;
}
void Codec : : InitializeGpuDecoder ( ) {
if ( ! CreateGpuAvDevice ( ) ) {
av_buffer_unref ( & av_gpu_decoder ) ;
return ;
return ;
}
}
# endif
auto * hw_device_ctx = av_buffer_ref ( av_gpu_decoder ) ;
// TODO more GPU accelerated decoders
ASSERT_MSG ( hw_device_ctx , " av_buffer_ref failed " ) ;
av_codec_ctx - > hw_device_ctx = hw_device_ctx ;
av_codec_ctx - > get_format = GetGpuFormat ;
using_gpu_decode = true ;
}
}
void Codec : : Initialize ( ) {
void Codec : : Initialize ( ) {
@ -107,7 +129,8 @@ void Codec::Initialize() {
av_codec = avcodec_find_decoder ( codec ) ;
av_codec = avcodec_find_decoder ( codec ) ;
av_codec_ctx = avcodec_alloc_context3 ( av_codec ) ;
av_codec_ctx = avcodec_alloc_context3 ( av_codec ) ;
av_opt_set ( av_codec_ctx - > priv_data , " tune " , " zerolatency " , 0 ) ;
av_opt_set ( av_codec_ctx - > priv_data , " tune " , " zerolatency " , 0 ) ;
InitializeHwdec ( ) ;
InitializeGpuDecoder ( ) ;
if ( ! av_codec_ctx - > hw_device_ctx ) {
if ( ! av_codec_ctx - > hw_device_ctx ) {
LOG_INFO ( Service_NVDRV , " Using FFmpeg software decoding " ) ;
LOG_INFO ( Service_NVDRV , " Using FFmpeg software decoding " ) ;
}
}
@ -115,7 +138,7 @@ void Codec::Initialize() {
if ( av_error < 0 ) {
if ( av_error < 0 ) {
LOG_ERROR ( Service_NVDRV , " avcodec_open2() Failed. " ) ;
LOG_ERROR ( Service_NVDRV , " avcodec_open2() Failed. " ) ;
avcodec_close ( av_codec_ctx ) ;
avcodec_close ( av_codec_ctx ) ;
av_buffer_unref ( & av_ hw_device ) ;
av_buffer_unref ( & av_ gpu_decoder ) ;
return ;
return ;
}
}
initialized = true ;
initialized = true ;
@ -153,38 +176,33 @@ void Codec::Decode() {
if ( vp9_hidden_frame ) {
if ( vp9_hidden_frame ) {
return ;
return ;
}
}
AVFrame * hw_frame = av_frame_alloc ( ) ;
AVFrame Ptr initial_frame { av_frame_alloc ( ) , AVFrameDeleter } ;
AVFrame * sw_frame = hw_frame ;
AVFrame Ptr final_frame { nullptr , AVFrameDeleter } ;
ASSERT_MSG ( hw_frame, " av_frame_alloc hw _frame failed" ) ;
ASSERT_MSG ( initial_frame, " av_frame_alloc initial _frame failed" ) ;
if ( const int ret = avcodec_receive_frame ( av_codec_ctx , hw_frame ) ; ret ) {
if ( const int ret = avcodec_receive_frame ( av_codec_ctx , initial_frame. get ( ) ) ; ret ) {
LOG_DEBUG ( Service_NVDRV , " avcodec_receive_frame error {} " , ret ) ;
LOG_DEBUG ( Service_NVDRV , " avcodec_receive_frame error {} " , ret ) ;
av_frame_free ( & hw_frame ) ;
return ;
return ;
}
}
if ( ! hw_frame - > width | | ! hw_frame - > height ) {
if ( initial_frame - > width = = 0 | | initial_frame - > height = = 0 ) {
LOG_WARNING ( Service_NVDRV , " Zero width or height in frame " ) ;
LOG_WARNING ( Service_NVDRV , " Zero width or height in frame " ) ;
av_frame_free ( & hw_frame ) ;
return ;
return ;
}
}
# if defined(LIBVA_FOUND)
if ( using_gpu_decode ) {
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
final_frame = AVFramePtr { av_frame_alloc ( ) , AVFrameDeleter } ;
if ( hw_frame - > format = = AV_PIX_FMT_VAAPI ) {
ASSERT_MSG ( final_frame , " av_frame_alloc final_frame failed " ) ;
sw_frame = av_frame_alloc ( ) ;
ASSERT_MSG ( sw_frame , " av_frame_alloc sw_frame failed " ) ;
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
// because Intel drivers crash unless using AV_PIX_FMT_NV12
// because Intel drivers crash unless using AV_PIX_FMT_NV12
sw_frame - > format = AV_PIX_FMT_NV12 ;
final_frame - > format = AV_PIX_FMT_NV12 ;
const int transfer_data_ret = av_hwframe_transfer_data ( sw_frame , hw_frame , 0 ) ;
const int ret = av_hwframe_transfer_data ( final_frame . get ( ) , initial_frame . get ( ) , 0 ) ;
ASSERT_MSG ( ! transfer_data_ret , " av_hwframe_transfer_data error {} " , transfer_data_ret ) ;
ASSERT_MSG ( ! ret , " av_hwframe_transfer_data error {} " , ret ) ;
av_frame_free ( & hw_frame ) ;
} else {
final_frame = std : : move ( initial_frame ) ;
}
}
# endif
if ( final_frame - > format ! = AV_PIX_FMT_YUV420P & & final_frame - > format ! = AV_PIX_FMT_NV12 ) {
if ( sw_frame - > format ! = AV_PIX_FMT_YUV420P & & sw_frame - > format ! = AV_PIX_FMT_NV12 ) {
UNIMPLEMENTED_MSG ( " Unexpected video format: {} " , final_frame - > format ) ;
UNIMPLEMENTED_MSG ( " Unexpected video format from host graphics: {} " , sw_frame - > format ) ;
av_frame_free ( & sw_frame ) ;
return ;
return ;
}
}
av_frames . push ( AVFramePtr{ sw_frame , AVFrameDeleter } ) ;
av_frames . push ( std: : move ( final_frame ) ) ;
if ( av_frames . size ( ) > 10 ) {
if ( av_frames . size ( ) > 10 ) {
LOG_TRACE ( Service_NVDRV , " av_frames.push overflow dropped frame " ) ;
LOG_TRACE ( Service_NVDRV , " av_frames.push overflow dropped frame " ) ;
av_frames . pop ( ) ;
av_frames . pop ( ) ;