Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/about/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,7 @@ encoder
nvenc For NVIDIA graphics cards
quicksync For Intel graphics cards
amdvce For AMD graphics cards
mf Media Foundation (slower than encoders above)
software Encoding occurs on the CPU
========= ===========

Expand Down
2 changes: 1 addition & 1 deletion src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ namespace platf {
* implementations must take ownership of 'frame'
*/
virtual int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) {
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
return -1;
};
Expand Down
6 changes: 3 additions & 3 deletions src/platform/linux/cuda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ namespace cuda {
}

int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) override {
this->hwframe.reset(frame);
this->frame = frame;

Expand Down Expand Up @@ -193,8 +193,8 @@ namespace cuda {
}

int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) {
if (cuda_t::set_frame(frame, hw_frames_ctx, target_format)) {
return -1;
}

Expand Down
2 changes: 1 addition & 1 deletion src/platform/linux/vaapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ namespace va {
}

int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) override {
this->hwframe.reset(frame);
this->frame = frame;

Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/nv12_zero_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace platf {
}

int
nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) {
this->frame = frame;

av_frame.reset(frame);
Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/nv12_zero_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace platf {
int
convert(img_t &img);
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx);
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format);
void
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
};
Expand Down
33 changes: 31 additions & 2 deletions src/platform/windows/display_vram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,16 @@ namespace platf::dxgi {
ID3D11ShaderResourceView *emptyShaderResourceView = nullptr;
device_ctx->PSSetShaderResources(0, 1, &emptyShaderResourceView);

// If this requires conversion to a swframe, transfer data back from the GPU
if (swframe) {
auto status = av_hwframe_transfer_data(this->frame, hwframe.get(), 0);
if (status < 0) {
char string[AV_ERROR_MAX_STRING_SIZE];
BOOST_LOG(error) << "Failed to transfer image data from hardware frame: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status);
return -1;
}
}

return 0;
}

Expand Down Expand Up @@ -425,9 +435,25 @@ namespace platf::dxgi {
}

int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) override {
this->hwframe.reset(frame);
this->frame = frame;

// If we have to do conversion to a different swframe pixel format, set up our context to do so
if (frame->format != target_format) {
swframe.reset(av_frame_alloc());
swframe->format = target_format;
swframe->width = frame->width;
swframe->height = frame->height;

// Allocate buffers for the swframe data
av_frame_get_buffer(swframe.get(), 0);

// Expose the swframe to the encoder rather than the hwframe
this->frame = swframe.get();
}
else {
this->frame = frame;
}

// Populate this frame with a hardware buffer if one isn't there already
if (!frame->buf[0]) {
Expand Down Expand Up @@ -724,6 +750,9 @@ namespace platf::dxgi {
public:
frame_t hwframe;

// Used when target_format differs from hwframe.format
frame_t swframe;

::video::color_t *color_p;

buf_t info_scene;
Expand Down
95 changes: 77 additions & 18 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ namespace video {
}

int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx, int target_format) {
this->frame = frame;

// If it's a hwframe, allocate buffers for hardware
Expand Down Expand Up @@ -321,6 +321,12 @@ namespace video {
AVHWDeviceType base_dev_type, derived_dev_type;
AVPixelFormat dev_pix_fmt;

// Encoder requires frames to be read back into the sw_pix_fmt
// prior to being passed to the encoder. This can provide GPU
// accelerated RGB->YUV color conversion for encoders that can't
// otherwise accept GPU surfaces as input.
bool sw_frame_readback;

AVPixelFormat static_pix_fmt, dynamic_pix_fmt;

struct {
Expand Down Expand Up @@ -445,10 +451,10 @@ namespace video {
"nvenc"sv,
#ifdef _WIN32
AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_D3D11,
AV_PIX_FMT_D3D11, false,
#else
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_CUDA,
AV_PIX_FMT_CUDA, false,
#endif
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
{
Expand Down Expand Up @@ -504,6 +510,7 @@ namespace video {
AV_HWDEVICE_TYPE_D3D11VA,
AV_HWDEVICE_TYPE_QSV,
AV_PIX_FMT_QSV,
false,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
{
Expand Down Expand Up @@ -557,7 +564,7 @@ namespace video {
static encoder_t amdvce {
"amdvce"sv,
AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_D3D11,
AV_PIX_FMT_D3D11, false,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
{
// Common options
Expand Down Expand Up @@ -599,12 +606,51 @@ namespace video {
PARALLEL_ENCODING,
dxgi_make_hwdevice_ctx
};

static encoder_t mfenc {
"mf"sv,
AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_D3D11, true,
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
{
// Common options
{
{ "hw_encoding"s, 1 },
{ "scenario"s, "display_remoting"s },
{ "low_latency", 1 },

// Other rate controls exist but they are inconsistently implemented
// and can cause codec init to totally fail if unsupported.
{ "rate_control"s, "cbr"s },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::nullopt,
"hevc_mf"s,
},
{
// Common options
{
{ "hw_encoding"s, 1 },
{ "scenario"s, "display_remoting"s },
{ "low_latency", 1 },
{ "rate_control"s, "cbr"s },
},
{}, // SDR-specific options
{}, // HDR-specific options
std::nullopt,
"h264_mf"s,
},
PARALLEL_ENCODING,
dxgi_make_hwdevice_ctx
};

#endif

static encoder_t software {
"software"sv,
AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_NONE,
AV_PIX_FMT_NONE, false,
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
{
// x265's Info SEI is so long that it causes the IDR picture data to be
Expand Down Expand Up @@ -642,7 +688,7 @@ namespace video {
static encoder_t vaapi {
"vaapi"sv,
AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_VAAPI,
AV_PIX_FMT_VAAPI, false,
AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10,
{
// Common options
Expand Down Expand Up @@ -678,7 +724,7 @@ namespace video {
static encoder_t videotoolbox {
"videotoolbox"sv,
AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE,
AV_PIX_FMT_VIDEOTOOLBOX,
AV_PIX_FMT_VIDEOTOOLBOX, false,
AV_PIX_FMT_NV12, AV_PIX_FMT_NV12,
{
// Common options
Expand Down Expand Up @@ -717,6 +763,7 @@ namespace video {
#ifdef _WIN32
&quicksync,
&amdvce,
&mfenc, // Should be lowest priority hw encoder
#endif
#ifdef __linux__
&vaapi,
Expand Down Expand Up @@ -1230,16 +1277,6 @@ namespace video {
return std::nullopt;
}

if (auto status = avcodec_open2(ctx.get(), codec, &options)) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error)
<< "Could not open codec ["sv
<< video_format.name << "]: "sv
<< av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, status);

return std::nullopt;
}

frame_t frame { av_frame_alloc() };
frame->format = ctx->pix_fmt;
frame->width = ctx->width;
Expand Down Expand Up @@ -1291,12 +1328,34 @@ namespace video {
device = std::move(hwdevice);
}

if (device->set_frame(frame.release(), ctx->hw_frames_ctx)) {
// If this encoder doesn't accept hardware frames, use the swformat
if (hardware && encoder.sw_frame_readback) {
ctx->pix_fmt = sw_fmt;
}

if (device->set_frame(frame.release(), ctx->hw_frames_ctx, ctx->pix_fmt)) {
return std::nullopt;
}

device->set_colorspace(sws_color_space, ctx->color_range);

// FFmpeg doesn't allow a hw_frames_ctx to be attached to the codec context
// if we're going to be using a software pixel format, so we will remove it
// now that set_frame() has had a chance to attach it to the AVFrame.
if (hardware && encoder.sw_frame_readback) {
av_buffer_unref(&ctx->hw_frames_ctx);
}

if (auto status = avcodec_open2(ctx.get(), codec, &options)) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error)
<< "Could not open codec ["sv
<< video_format.name << "]: "sv
<< av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, status);

return std::nullopt;
}

session_t session {
std::move(ctx),
std::move(device),
Expand Down
1 change: 1 addition & 0 deletions src_assets/common/assets/web/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ <h1 class="my-4">Configuration</h1>
<option value="amdvce" v-if="platform === 'windows'">AMD AMF/VCE</option>
<option value="vaapi" v-if="platform === 'linux'">VA-API</option>
<option value="videotoolbox" v-if="platform === 'macos'">VideoToolbox</option>
<option value="mf" v-if="platform === 'windows'">Media Foundation</option>
<option value="software">Software</option>
</select>
<div class="form-text">
Expand Down