22
33#include " NAVConfig.hpp"
44
5+ #include < algorithm>
56#include < numeric>
7+ #include < set>
68#include < sstream>
79#include < stdexcept>
810#include < string>
@@ -25,6 +27,36 @@ extern "C"
2527
2628#define NAV_FFCALL (name ) f->func_##name
2729
30+ static std::vector<AVHWDeviceType> devicePreference = {
31+ #ifdef _WIN32
32+ AV_HWDEVICE_TYPE_D3D11VA,
33+ #endif
34+ #if _NAV_FFMPEG_VERSION >= 5
35+ AV_HWDEVICE_TYPE_VULKAN,
36+ #endif
37+ AV_HWDEVICE_TYPE_VAAPI
38+ // TODO: VideoToolbox HW acceleration.
39+ // Apple backend must be done first.
40+ };
41+
42+ static std::set<AVPixelFormat> supportedHWAccelPixFmt = {
43+ #if _NAV_FFMPEG_VERSION >= 5
44+ AV_PIX_FMT_VULKAN,
45+ #endif
46+ AV_PIX_FMT_D3D11,
47+ AV_PIX_FMT_VAAPI
48+ };
49+
50+ static ptrdiff_t getHWDevicePriority (AVHWDeviceType type)
51+ {
52+ auto it = std::find (devicePreference.begin (), devicePreference.end (), type);
53+
54+ if (it != devicePreference.end ())
55+ return std::distance (devicePreference.begin (), it);
56+ else
57+ return ptrdiff_t ((devicePreference.size ()) + (size_t ) type);
58+ }
59+
2860static int inputRead (void *nav_input, uint8_t *buf, int buf_size)
2961{
3062 struct nav_input *input = (struct nav_input *) nav_input;
@@ -68,7 +100,7 @@ static int64_t inputSeek(void *nav_input, int64_t offset, int origin)
68100 return realoff;
69101}
70102
71- static std::runtime_error throwFromAVError (decltype (&av_strerror) func_av_strerror, int code)
103+ [[noreturn]] static std::runtime_error throwFromAVError (decltype (&av_strerror) func_av_strerror, int code)
72104{
73105 constexpr size_t BUFSIZE = 256 ;
74106 char temp[BUFSIZE];
@@ -379,15 +411,16 @@ struct CallOnLeave
379411namespace nav ::_NAV_FFMPEG_NAMESPACE
380412{
381413
382- FFmpegFrame::FFmpegFrame (FFmpegBackend *f, nav_streaminfo_t *sinfo, AVFrame *frame, double pts, size_t si)
414+ FFmpegFrame::FFmpegFrame (FFmpegBackend *f, nav_streaminfo_t *sinfo, AVFrame *frame, const AVCodecContext *cctx, double pts, size_t si)
383415: f(f) // must be first
384416, acquireData()
385417, pts(pts)
386418, frame(NAV_FFCALL(av_frame_clone)(frame))
387419, streamInfo(sinfo)
388420, index(si)
421+ , codecContext(cctx)
389422{
390- if (frame == nullptr )
423+ if (this -> frame == nullptr )
391424 throw std::runtime_error (" Cannot clone AVFrame" );
392425}
393426
@@ -410,13 +443,37 @@ const uint8_t *const *FFmpegFrame::acquire(ptrdiff_t **strides, size_t *nplanes)
410443{
411444 if (acquireData.source == nullptr )
412445 {
446+ AVFrame *targetFrame = frame;
447+
448+ if (codecContext->hw_device_ctx )
449+ {
450+ // Hardware accelerated. Download the frame to CPU.
451+ AVFrame *swFrame = NAV_FFCALL (av_frame_alloc)();
452+ if (swFrame == nullptr )
453+ {
454+ nav::error::set (" Cannot allocate CPU frame." );
455+ return nullptr ;
456+ }
457+
458+ if (int r = NAV_FFCALL (av_hwframe_transfer_data)(swFrame, frame, 0 ); r < 0 )
459+ {
460+ NAV_FFCALL (av_frame_free)(&swFrame);
461+ throwFromAVError (NAV_FFCALL (av_strerror), r);
462+ }
463+
464+ targetFrame = swFrame;
465+ }
466+
413467 // AVFrame is already in CPU
414- for (size_t i = 0 ; frame->data [i]; i++)
468+ acquireData.planes .clear ();
469+ acquireData.strides .clear ();
470+ for (size_t i = 0 ; targetFrame->data [i]; i++)
415471 {
416- acquireData.planes .push_back (frame->data [i]);
417- acquireData.strides .push_back (frame->linesize [i]);
418- acquireData.source = frame->data [0 ]; // Just for marking.
472+ acquireData.planes .push_back (targetFrame->data [i]);
473+ acquireData.strides .push_back (targetFrame->linesize [i]);
419474 }
475+
476+ acquireData.source = (uint8_t *) targetFrame;
420477 }
421478
422479 if (nplanes)
@@ -427,23 +484,54 @@ const uint8_t *const *FFmpegFrame::acquire(ptrdiff_t **strides, size_t *nplanes)
427484
428485void FFmpegFrame::release () noexcept
429486{
487+ if (codecContext->hw_device_ctx && acquireData.source )
488+ {
489+ AVFrame *swFrame = (AVFrame *) acquireData.source ;
490+ NAV_FFCALL (av_frame_free)(&swFrame);
491+ acquireData.source = nullptr ;
492+ }
430493}
431494
432495nav_hwacceltype FFmpegFrame::getHWAccelType () const noexcept
433496{
434- return NAV_HWACCELTYPE_NONE;
497+ switch ((AVPixelFormat) frame->format )
498+ {
499+ case AV_PIX_FMT_D3D11:
500+ return NAV_HWACCELTYPE_D3D11;
501+ #if _NAV_FFMPEG_VERSION >= 5
502+ case AV_PIX_FMT_VULKAN:
503+ return NAV_HWACCELTYPE_VULKAN;
504+ #endif
505+ case AV_PIX_FMT_VAAPI:
506+ return NAV_HWACCELTYPE_VAAPI;
507+ default :
508+ return NAV_HWACCELTYPE_NONE;
509+ }
435510}
436511
437512void *FFmpegFrame::getHWAccelHandle ()
438513{
439- nav::error::set (" Not yet implemented" );
514+ switch ((AVPixelFormat) frame->format )
515+ {
516+ case AV_PIX_FMT_D3D11:
517+ return frame->data [0 ];
518+ #if _NAV_FFMPEG_VERSION >= 5
519+ case AV_PIX_FMT_VULKAN:
520+ nav::error::set (" NYI" );
521+ return nullptr ; // TODO: this should be AVVKFrame
522+ #endif
523+ case AV_PIX_FMT_VAAPI:
524+ return frame->data [3 ];
525+ }
526+ nav::error::set (" Not hardware accelerated or unknown hardware acceleration" );
440527 return nullptr ;
441528}
442529
443530FFmpegFrame::~FFmpegFrame ()
444531{
445532 if (frame)
446533 {
534+ release ();
447535 NAV_FFCALL (av_frame_unref)(frame);
448536 NAV_FFCALL (av_frame_free)(&frame);
449537 }
@@ -502,7 +590,56 @@ FFmpegState::FFmpegState(FFmpegBackend *backend, UniqueAVFormatContext &fmtctx,
502590 }
503591
504592 if (good)
593+ {
505594 good = NAV_FFCALL (avcodec_parameters_to_context)(codecContext, stream->codecpar ) >= 0 ;
595+ codecContext->get_format = pickPixelFormat;
596+ }
597+
598+ if (good && !settings.disable_hwaccel && stream->codecpar ->codec_type == AVMEDIA_TYPE_VIDEO)
599+ {
600+ // Try enable hardware acceleration
601+ std::vector<AVHWDeviceType> devices = getHWAccels ();
602+
603+ for (AVHWDeviceType hwacceltype: devices)
604+ {
605+ for (int j = 0 ; j < std::numeric_limits<int >::max (); j++)
606+ {
607+ const AVCodecHWConfig *hwconfig = NAV_FFCALL (avcodec_get_hw_config)(codec, j);
608+ if (hwconfig == nullptr )
609+ break ;
610+
611+ if (hwconfig->device_type != hwacceltype)
612+ continue ;
613+
614+ if (supportedHWAccelPixFmt.find (hwconfig->pix_fmt ) == supportedHWAccelPixFmt.end ())
615+ // Not a supported hardware pixel format
616+ continue ;
617+
618+ if (hwconfig->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
619+ {
620+ if (NAV_FFCALL (av_hwdevice_ctx_create)(
621+ &codecContext->hw_device_ctx ,
622+ hwacceltype,
623+ nullptr ,
624+ nullptr ,
625+ 0
626+ ) == 0 )
627+ {
628+ // Got one
629+ codecContext->pix_fmt = hwconfig->pix_fmt ;
630+ // FIXME: Query the hardware constraints.
631+ // But for now, assume NV12.
632+ codecContext->sw_pix_fmt = AV_PIX_FMT_NV12;
633+ break ;
634+ }
635+ }
636+ }
637+
638+ if (codecContext->hw_device_ctx )
639+ // Got HW accel device
640+ break ;
641+ }
642+ }
506643
507644 if (good)
508645 {
@@ -569,23 +706,31 @@ FFmpegState::FFmpegState(FFmpegBackend *backend, UniqueAVFormatContext &fmtctx,
569706 }
570707 else
571708 {
572- AVPixelFormat originalFormat = (AVPixelFormat) stream->codecpar ->format ;
709+ AVPixelFormat originalFormat = codecContext->hw_device_ctx
710+ ? codecContext->sw_pix_fmt
711+ : ((AVPixelFormat) stream->codecpar ->format );
573712 AVPixelFormat rescaleFormat = originalFormat;
574713 std::tie (sinfo.video .format , rescaleFormat) = getBestPixelFormat (originalFormat);
575714
576715 // Need to rescale
577716 if (rescaleFormat != originalFormat)
578717 {
579- rescaler = NAV_FFCALL (sws_getContext)(
580- stream->codecpar ->width ,
581- stream->codecpar ->height ,
582- originalFormat,
583- stream->codecpar ->width ,
584- stream->codecpar ->height ,
585- rescaleFormat,
586- SWS_BICUBIC, nullptr , nullptr , nullptr
587- );
588- good = rescaler != nullptr ;
718+ if (codecContext->hw_device_ctx )
719+ // This is not what we've agreed beforehand
720+ good = false ;
721+ else
722+ {
723+ rescaler = NAV_FFCALL (sws_getContext)(
724+ stream->codecpar ->width ,
725+ stream->codecpar ->height ,
726+ originalFormat,
727+ stream->codecpar ->width ,
728+ stream->codecpar ->height ,
729+ rescaleFormat,
730+ SWS_BICUBIC, nullptr , nullptr , nullptr
731+ );
732+ good = rescaler != nullptr ;
733+ }
589734 }
590735
591736 if (good)
@@ -677,6 +822,12 @@ bool FFmpegState::setStreamEnabled(size_t index, bool enabled)
677822 return false ;
678823 }
679824
825+ if (prepared)
826+ {
827+ nav::error::set (" Decoder already initialized" );
828+ return false ;
829+ }
830+
680831 formatContext->streams [index]->discard = enabled ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
681832 return true ;
682833}
@@ -722,7 +873,23 @@ double FFmpegState::setPosition(double off)
722873
723874bool FFmpegState::prepare ()
724875{
725- prepared = true ;
876+ if (!prepared)
877+ {
878+ // Free inactive stream resources.
879+ for (size_t i = 0 ; i < getStreamCount (); i++)
880+ {
881+ if (formatContext->streams [i]->discard == AVDISCARD_ALL)
882+ {
883+ NAV_FFCALL (avcodec_free_context)(&decoders[i]);
884+ NAV_FFCALL (swr_free)(&resamplers[i]);
885+ NAV_FFCALL (sws_freeContext)(rescalers[i]);
886+ // Leave the streaminfo intact though, don't modify it.
887+ }
888+ }
889+
890+ prepared = true ;
891+ }
892+
726893 return true ;
727894}
728895
@@ -832,7 +999,7 @@ nav_frame_t *FFmpegState::decode(AVFrame *frame, size_t index)
832999 SwrContext *resampler = resamplers[index];
8331000 if (resampler == nullptr )
8341001 // Skipping conversion
835- return new FFmpegFrame (f, streamInfo, tempFrame.get (), position, index);
1002+ return new FFmpegFrame (f, streamInfo, tempFrame.get (), decoders[index], position, index);
8361003
8371004#pragma GCC diagnostic push
8381005#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@@ -871,7 +1038,7 @@ nav_frame_t *FFmpegState::decode(AVFrame *frame, size_t index)
8711038 SwsContext *rescaler = rescalers[index];
8721039 if (rescaler == nullptr )
8731040 // Skipping conversion
874- return new FFmpegFrame (f, streamInfo, tempFrame.get (), position, index);
1041+ return new FFmpegFrame (f, streamInfo, tempFrame.get (), decoders[index], position, index);
8751042
8761043 size_t needSize = streamInfo->video .size ();
8771044 uint8_t *bufferSetup[AV_NUM_DATA_POINTERS] = {nullptr };
@@ -946,6 +1113,33 @@ bool FFmpegState::canDecode(size_t index)
9461113 return streamInfo[index].type != NAV_STREAMTYPE_UNKNOWN && formatContext->streams [index]->discard == AVDISCARD_ALL;
9471114}
9481115
1116+ std::vector<AVHWDeviceType> FFmpegState::getHWAccels ()
1117+ {
1118+ std::vector<AVHWDeviceType> devices;
1119+
1120+ AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
1121+ while ((type = NAV_FFCALL (av_hwdevice_iterate_types)(type)) != AV_HWDEVICE_TYPE_NONE)
1122+ devices.push_back (type);
1123+
1124+ std::sort (devices.begin (), devices.end (), [](AVHWDeviceType a, AVHWDeviceType b)
1125+ {
1126+ return getHWDevicePriority (a) < getHWDevicePriority (b);
1127+ });
1128+
1129+ return devices;
1130+ }
1131+
1132+ AVPixelFormat FFmpegState::pickPixelFormat (AVCodecContext *s, const AVPixelFormat *fmt) noexcept
1133+ {
1134+ for (int i = 0 ; fmt[i] != AV_PIX_FMT_NONE; i++)
1135+ {
1136+ if (supportedHWAccelPixFmt.find (fmt[i]) != supportedHWAccelPixFmt.end ())
1137+ return fmt[i];
1138+ }
1139+
1140+ return AV_PIX_FMT_NONE;
1141+ }
1142+
9491143#undef NAV_FFCALL
9501144#define NAV_FFCALL (n ) this ->func_##n
9511145
0 commit comments