2018-06-28 08:44:43 +00:00
|
|
|
#include <Limelight.h>
|
2018-07-18 03:00:16 +00:00
|
|
|
#include "ffmpeg.h"
|
2018-07-08 04:52:20 +00:00
|
|
|
|
2018-07-13 09:28:10 +00:00
|
|
|
#ifdef _WIN32
|
2018-07-18 03:00:16 +00:00
|
|
|
#include "ffmpeg-renderers/dxva2.h"
|
2018-07-13 09:28:10 +00:00
|
|
|
#endif
|
|
|
|
|
2018-07-15 18:53:55 +00:00
|
|
|
#ifdef __APPLE__
|
2018-07-18 03:00:16 +00:00
|
|
|
#include "ffmpeg-renderers/vt.h"
|
2018-07-15 18:53:55 +00:00
|
|
|
#endif
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
bool FFmpegVideoDecoder::chooseDecoder(
|
|
|
|
StreamingPreferences::VideoDecoderSelection vds,
|
|
|
|
SDL_Window* window,
|
|
|
|
int videoFormat,
|
|
|
|
int width, int height,
|
|
|
|
AVCodec*& chosenDecoder,
|
|
|
|
const AVCodecHWConfig*& chosenHwConfig,
|
|
|
|
IFFmpegRenderer*& newRenderer)
|
2018-07-08 04:52:20 +00:00
|
|
|
{
|
2018-07-13 09:28:10 +00:00
|
|
|
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
|
|
|
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
|
|
|
}
|
|
|
|
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
|
|
|
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Q_ASSERT(false);
|
|
|
|
chosenDecoder = nullptr;
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 09:28:10 +00:00
|
|
|
if (!chosenDecoder) {
|
2018-07-08 04:52:20 +00:00
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Unable to find decoder for format: %x",
|
|
|
|
videoFormat);
|
2018-07-13 09:28:10 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0;; i++) {
|
|
|
|
const AVCodecHWConfig *config = avcodec_get_hw_config(chosenDecoder, i);
|
|
|
|
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
|
|
|
// No matching hardware acceleration support.
|
|
|
|
// This is not an error.
|
|
|
|
chosenHwConfig = nullptr;
|
|
|
|
newRenderer = new SdlRenderer();
|
|
|
|
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
|
|
|
newRenderer->initialize(window, videoFormat, width, height)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delete newRenderer;
|
|
|
|
newRenderer = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for acceleration types we support
|
|
|
|
switch (config->device_type) {
|
|
|
|
#ifdef _WIN32
|
|
|
|
case AV_HWDEVICE_TYPE_DXVA2:
|
|
|
|
newRenderer = new DXVA2Renderer();
|
|
|
|
break;
|
2018-07-15 18:53:55 +00:00
|
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
|
|
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
2018-07-16 01:11:57 +00:00
|
|
|
newRenderer = VTRendererFactory::createRenderer();
|
2018-07-15 18:53:55 +00:00
|
|
|
break;
|
2018-07-13 09:28:10 +00:00
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newRenderer->initialize(window, videoFormat, width, height)) {
|
|
|
|
chosenHwConfig = config;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Failed to initialize
|
|
|
|
delete newRenderer;
|
|
|
|
newRenderer = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
2018-07-13 09:28:10 +00:00
|
|
|
{
|
2018-07-18 03:00:16 +00:00
|
|
|
return m_HwDecodeCfg != nullptr;
|
2018-07-13 09:28:10 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
FFmpegVideoDecoder::FFmpegVideoDecoder()
|
|
|
|
: m_VideoDecoderCtx(nullptr),
|
|
|
|
m_DecodeBuffer(1024 * 1024, 0),
|
|
|
|
m_HwDecodeCfg(nullptr),
|
|
|
|
m_Renderer(nullptr)
|
2018-07-13 09:28:10 +00:00
|
|
|
{
|
2018-07-18 03:00:16 +00:00
|
|
|
av_init_packet(&m_Pkt);
|
2018-07-20 20:10:54 +00:00
|
|
|
SDL_AtomicSet(&m_QueuedFrames, 0);
|
2018-07-13 09:28:10 +00:00
|
|
|
|
2018-07-16 09:07:32 +00:00
|
|
|
// Use linear filtering when renderer scaling is required
|
|
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
2018-07-18 03:00:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
|
|
|
{
|
2018-07-20 20:10:54 +00:00
|
|
|
// Drop any frames still queued to ensure
|
|
|
|
// they are properly freed.
|
|
|
|
SDL_Event event;
|
|
|
|
|
|
|
|
while (SDL_AtomicGet(&m_QueuedFrames) > 0) {
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Waiting for %d frames to return",
|
|
|
|
SDL_AtomicGet(&m_QueuedFrames));
|
|
|
|
|
|
|
|
if (SDL_PeepEvents(&event,
|
|
|
|
1,
|
|
|
|
SDL_GETEVENT,
|
|
|
|
SDL_USEREVENT,
|
|
|
|
SDL_USEREVENT) == 1) {
|
|
|
|
dropFrame(&event.user);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SDL_Delay(100);
|
|
|
|
SDL_PumpEvents();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
avcodec_close(m_VideoDecoderCtx);
|
|
|
|
av_free(m_VideoDecoderCtx);
|
|
|
|
m_VideoDecoderCtx = nullptr;
|
|
|
|
|
|
|
|
m_HwDecodeCfg = nullptr;
|
2018-07-16 09:07:32 +00:00
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
delete m_Renderer;
|
|
|
|
m_Renderer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FFmpegVideoDecoder::initialize(
|
|
|
|
StreamingPreferences::VideoDecoderSelection vds,
|
|
|
|
SDL_Window* window,
|
|
|
|
int videoFormat,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
int)
|
|
|
|
{
|
|
|
|
AVCodec* decoder;
|
2018-07-13 09:28:10 +00:00
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
if (!chooseDecoder(vds, window, videoFormat, width, height,
|
|
|
|
decoder, m_HwDecodeCfg, m_Renderer)) {
|
2018-07-13 09:28:10 +00:00
|
|
|
// Error logged in chooseDecoder()
|
2018-07-18 03:00:16 +00:00
|
|
|
return false;
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
m_VideoDecoderCtx = avcodec_alloc_context3(decoder);
|
|
|
|
if (!m_VideoDecoderCtx) {
|
2018-07-08 04:52:20 +00:00
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Unable to allocate video decoder context");
|
2018-07-18 03:00:16 +00:00
|
|
|
return false;
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 09:28:10 +00:00
|
|
|
// Always request low delay decoding
|
2018-07-18 03:00:16 +00:00
|
|
|
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
2018-07-13 09:28:10 +00:00
|
|
|
|
|
|
|
// Enable slice multi-threading for software decoding
|
2018-07-18 03:00:16 +00:00
|
|
|
if (!m_HwDecodeCfg) {
|
|
|
|
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
|
|
|
m_VideoDecoderCtx->thread_count = qMin(MAX_SLICES, SDL_GetCPUCount());
|
2018-07-13 09:28:10 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// No threading for HW decode
|
2018-07-18 03:00:16 +00:00
|
|
|
m_VideoDecoderCtx->thread_count = 1;
|
2018-07-13 09:28:10 +00:00
|
|
|
}
|
2018-07-08 04:52:20 +00:00
|
|
|
|
|
|
|
// Setup decoding parameters
|
2018-07-18 03:00:16 +00:00
|
|
|
m_VideoDecoderCtx->width = width;
|
|
|
|
m_VideoDecoderCtx->height = height;
|
|
|
|
m_VideoDecoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // FIXME: HDR
|
2018-07-08 04:52:20 +00:00
|
|
|
|
2018-07-13 09:28:10 +00:00
|
|
|
// Allow the renderer to attach data to this decoder
|
2018-07-18 03:00:16 +00:00
|
|
|
if (!m_Renderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
|
|
|
return false;
|
2018-07-13 09:28:10 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
int err = avcodec_open2(m_VideoDecoderCtx, decoder, nullptr);
|
2018-07-08 04:52:20 +00:00
|
|
|
if (err < 0) {
|
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Unable to open decoder for format: %x",
|
|
|
|
videoFormat);
|
2018-07-18 03:00:16 +00:00
|
|
|
return false;
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
return true;
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
2018-07-08 04:52:20 +00:00
|
|
|
{
|
|
|
|
PLENTRY entry = du->bufferList;
|
|
|
|
int err;
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
if (du->fullLength + AV_INPUT_BUFFER_PADDING_SIZE > m_DecodeBuffer.length()) {
|
|
|
|
m_DecodeBuffer = QByteArray(du->fullLength + AV_INPUT_BUFFER_PADDING_SIZE, 0);
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int offset = 0;
|
|
|
|
while (entry != nullptr) {
|
2018-07-18 03:00:16 +00:00
|
|
|
memcpy(&m_DecodeBuffer.data()[offset],
|
2018-07-08 04:52:20 +00:00
|
|
|
entry->data,
|
|
|
|
entry->length);
|
|
|
|
offset += entry->length;
|
|
|
|
entry = entry->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_assert(offset == du->fullLength);
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
m_Pkt.data = reinterpret_cast<uint8_t*>(m_DecodeBuffer.data());
|
|
|
|
m_Pkt.size = du->fullLength;
|
2018-07-08 04:52:20 +00:00
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
2018-07-08 04:52:20 +00:00
|
|
|
if (err < 0) {
|
|
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Decoding failed: %d", err);
|
|
|
|
char errorstring[512];
|
|
|
|
av_strerror(err, errorstring, sizeof(errorstring));
|
|
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Decoding failed: %s", errorstring);
|
|
|
|
return DR_NEED_IDR;
|
|
|
|
}
|
|
|
|
|
|
|
|
AVFrame* frame = av_frame_alloc();
|
|
|
|
if (!frame) {
|
|
|
|
// Failed to allocate a frame but we did submit,
|
|
|
|
// so we can return DR_OK
|
|
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Failed to allocate frame");
|
|
|
|
return DR_OK;
|
|
|
|
}
|
|
|
|
|
2018-07-18 03:00:16 +00:00
|
|
|
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
2018-07-08 04:52:20 +00:00
|
|
|
if (err == 0) {
|
2018-07-18 03:05:59 +00:00
|
|
|
// Queue the frame for rendering from the main thread
|
2018-07-20 20:10:54 +00:00
|
|
|
SDL_AtomicIncRef(&m_QueuedFrames);
|
2018-07-18 03:05:59 +00:00
|
|
|
queueFrame(frame);
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
av_frame_free(&frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
return DR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called on main thread
|
2018-07-18 03:00:16 +00:00
|
|
|
void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
2018-07-08 04:52:20 +00:00
|
|
|
{
|
|
|
|
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
2018-07-18 03:00:16 +00:00
|
|
|
m_Renderer->renderFrame(frame);
|
2018-07-20 20:10:54 +00:00
|
|
|
SDL_AtomicDecRef(&m_QueuedFrames);
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called on main thread
|
2018-07-18 03:00:16 +00:00
|
|
|
void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
|
2018-07-08 04:52:20 +00:00
|
|
|
{
|
|
|
|
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
|
|
|
av_frame_free(&frame);
|
2018-07-20 20:10:54 +00:00
|
|
|
SDL_AtomicDecRef(&m_QueuedFrames);
|
2018-07-08 04:52:20 +00:00
|
|
|
}
|