Skip to content

Commit c292633

Browse files
committed
FFmpeg hardware acceleration support.
1 parent 2277ce7 commit c292633

File tree

4 files changed

+236
-26
lines changed

4 files changed

+236
-26
lines changed

include/nav/types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ typedef struct nav_settings
101101
*/
102102
uint32_t max_threads;
103103
/* If true, this hints backends to prefer CPU decoding. */
104-
bool disable_hwaccel;
104+
nav_bool disable_hwaccel;
105105
} nav_settings;
106106

107107
#endif /* _NAV_TYPES_H_ */

src/ffmpeg_common/FFmpegBackend.cpp

Lines changed: 217 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
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+
2860
static 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
379411
namespace 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

428485
void 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

432495
nav_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

437512
void *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

443530
FFmpegFrame::~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

723874
bool 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

Comments
 (0)