|
|
@ -2,7 +2,6 @@
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <vector>
|
|
|
|
#include <vector>
|
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/assert.h"
|
|
|
@ -17,10 +16,47 @@ 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 {
|
|
|
|
|
|
|
|
constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
|
|
|
|
|
|
|
|
"i915",
|
|
|
|
|
|
|
|
"amdgpu",
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
|
|
|
|
|
|
|
|
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
|
|
|
|
|
|
|
|
if (*p == AV_PIX_FMT_VAAPI) {
|
|
|
|
|
|
|
|
return AV_PIX_FMT_VAAPI;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
|
|
|
|
|
|
|
|
return *pix_fmts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void AVFrameDeleter(AVFrame* ptr) {
|
|
|
|
void AVFrameDeleter(AVFrame* ptr) {
|
|
|
|
av_frame_unref(ptr);
|
|
|
|
av_frame_free(&ptr);
|
|
|
|
av_free(ptr);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
|
|
|
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
|
|
@ -32,19 +68,31 @@ Codec::~Codec() {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Free libav memory
|
|
|
|
// Free libav memory
|
|
|
|
AVFrame* av_frame{nullptr};
|
|
|
|
|
|
|
|
avcodec_send_packet(av_codec_ctx, nullptr);
|
|
|
|
avcodec_send_packet(av_codec_ctx, nullptr);
|
|
|
|
av_frame = av_frame_alloc();
|
|
|
|
AVFrame* av_frame = av_frame_alloc();
|
|
|
|
avcodec_receive_frame(av_codec_ctx, av_frame);
|
|
|
|
avcodec_receive_frame(av_codec_ctx, av_frame);
|
|
|
|
avcodec_flush_buffers(av_codec_ctx);
|
|
|
|
avcodec_flush_buffers(av_codec_ctx);
|
|
|
|
|
|
|
|
av_frame_free(&av_frame);
|
|
|
|
av_frame_unref(av_frame);
|
|
|
|
|
|
|
|
av_free(av_frame);
|
|
|
|
|
|
|
|
avcodec_close(av_codec_ctx);
|
|
|
|
avcodec_close(av_codec_ctx);
|
|
|
|
|
|
|
|
av_buffer_unref(&av_hw_device);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Codec::InitializeHwdec() {
|
|
|
|
|
|
|
|
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
|
|
|
|
|
|
|
|
#if defined(LIBVA_FOUND)
|
|
|
|
|
|
|
|
if (CreateVaapiHwdevice(&av_hw_device)) {
|
|
|
|
|
|
|
|
const auto hw_device_ctx = av_buffer_ref(av_hw_device);
|
|
|
|
|
|
|
|
ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
|
|
|
|
|
|
|
|
av_codec_ctx->hw_device_ctx = hw_device_ctx;
|
|
|
|
|
|
|
|
av_codec_ctx->get_format = GetHwFormat;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// TODO more GPU accelerated decoders
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Codec::Initialize() {
|
|
|
|
void Codec::Initialize() {
|
|
|
|
AVCodecID codec{AV_CODEC_ID_NONE};
|
|
|
|
AVCodecID codec;
|
|
|
|
switch (current_codec) {
|
|
|
|
switch (current_codec) {
|
|
|
|
case NvdecCommon::VideoCodec::H264:
|
|
|
|
case NvdecCommon::VideoCodec::H264:
|
|
|
|
codec = AV_CODEC_ID_H264;
|
|
|
|
codec = AV_CODEC_ID_H264;
|
|
|
@ -53,22 +101,24 @@ void Codec::Initialize() {
|
|
|
|
codec = AV_CODEC_ID_VP9;
|
|
|
|
codec = AV_CODEC_ID_VP9;
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
|
|
|
|
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
|
|
|
// TODO(ameerj): libavcodec gpu hw acceleration
|
|
|
|
if (!av_codec_ctx->hw_device_ctx) {
|
|
|
|
|
|
|
|
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
|
|
|
|
|
|
|
|
}
|
|
|
|
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
|
|
|
|
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
|
|
|
|
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);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
initialized = true;
|
|
|
|
initialized = true;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
|
|
|
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
|
|
@ -80,36 +130,64 @@ void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
|
|
|
|
|
|
|
|
|
|
|
void Codec::Decode() {
|
|
|
|
void Codec::Decode() {
|
|
|
|
const bool is_first_frame = !initialized;
|
|
|
|
const bool is_first_frame = !initialized;
|
|
|
|
if (!initialized) {
|
|
|
|
if (is_first_frame) {
|
|
|
|
Initialize();
|
|
|
|
Initialize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool vp9_hidden_frame = false;
|
|
|
|
bool vp9_hidden_frame = false;
|
|
|
|
AVPacket packet{};
|
|
|
|
|
|
|
|
av_init_packet(&packet);
|
|
|
|
|
|
|
|
std::vector<u8> frame_data;
|
|
|
|
std::vector<u8> frame_data;
|
|
|
|
|
|
|
|
|
|
|
|
if (current_codec == NvdecCommon::VideoCodec::H264) {
|
|
|
|
if (current_codec == NvdecCommon::VideoCodec::H264) {
|
|
|
|
frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
|
|
|
|
frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
|
|
|
|
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
|
|
|
|
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
|
|
|
|
frame_data = vp9_decoder->ComposeFrameHeader(state);
|
|
|
|
frame_data = vp9_decoder->ComposeFrameHeader(state);
|
|
|
|
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
|
|
|
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AVPacket packet{};
|
|
|
|
|
|
|
|
av_init_packet(&packet);
|
|
|
|
packet.data = frame_data.data();
|
|
|
|
packet.data = frame_data.data();
|
|
|
|
packet.size = static_cast<s32>(frame_data.size());
|
|
|
|
packet.size = static_cast<s32>(frame_data.size());
|
|
|
|
|
|
|
|
if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
|
|
|
|
avcodec_send_packet(av_codec_ctx, &packet);
|
|
|
|
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
|
|
|
|
|
|
|
|
return;
|
|
|
|
if (!vp9_hidden_frame) {
|
|
|
|
}
|
|
|
|
// Only receive/store visible frames
|
|
|
|
// Only receive/store visible frames
|
|
|
|
AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
|
|
|
|
if (vp9_hidden_frame) {
|
|
|
|
avcodec_receive_frame(av_codec_ctx, frame.get());
|
|
|
|
return;
|
|
|
|
av_frames.push(std::move(frame));
|
|
|
|
}
|
|
|
|
// Limit queue to 10 frames. Workaround for ZLA decode and queue spam
|
|
|
|
AVFrame* hw_frame = av_frame_alloc();
|
|
|
|
if (av_frames.size() > 10) {
|
|
|
|
AVFrame* sw_frame = hw_frame;
|
|
|
|
av_frames.pop();
|
|
|
|
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
|
|
|
|
}
|
|
|
|
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
|
|
|
|
|
|
|
|
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
|
|
|
|
|
|
|
|
av_frame_free(&hw_frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hw_frame->width || !hw_frame->height) {
|
|
|
|
|
|
|
|
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
|
|
|
|
|
|
|
|
av_frame_free(&hw_frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(LIBVA_FOUND)
|
|
|
|
|
|
|
|
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
|
|
|
|
|
|
|
|
if (hw_frame->format == AV_PIX_FMT_VAAPI) {
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
// because Intel drivers crash unless using AV_PIX_FMT_NV12
|
|
|
|
|
|
|
|
sw_frame->format = AV_PIX_FMT_NV12;
|
|
|
|
|
|
|
|
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
|
|
|
|
|
|
|
|
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
|
|
|
|
|
|
|
|
av_frame_free(&hw_frame);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) {
|
|
|
|
|
|
|
|
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
|
|
|
|
|
|
|
|
av_frame_free(&sw_frame);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter});
|
|
|
|
|
|
|
|
if (av_frames.size() > 10) {
|
|
|
|
|
|
|
|
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
|
|
|
|
|
|
|
|
av_frames.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -119,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() {
|
|
|
|
if (av_frames.empty()) {
|
|
|
|
if (av_frames.empty()) {
|
|
|
|
return AVFramePtr{nullptr, AVFrameDeleter};
|
|
|
|
return AVFramePtr{nullptr, AVFrameDeleter};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AVFramePtr frame = std::move(av_frames.front());
|
|
|
|
AVFramePtr frame = std::move(av_frames.front());
|
|
|
|
av_frames.pop();
|
|
|
|
av_frames.pop();
|
|
|
|
return frame;
|
|
|
|
return frame;
|
|
|
@ -144,6 +221,5 @@ std::string_view Codec::GetCurrentCodecName() const {
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
return "Unknown";
|
|
|
|
return "Unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Tegra
|
|
|
|
} // namespace Tegra
|
|
|
|