moonlight-qt/app/streaming/video/ffmpeg-renderers/mmal.cpp

225 lines
6.8 KiB
C++
Raw Normal View History

2019-04-16 08:20:21 +00:00
#include "mmal.h"
#include "streaming/streamutils.h"
#include "streaming/session.h"
#include <Limelight.h>
#include <SDL_syswm.h>
2019-04-16 08:20:21 +00:00
MmalRenderer::MmalRenderer()
: m_Renderer(nullptr),
m_InputPort(nullptr),
m_BackgroundRenderer(nullptr)
2019-04-16 08:20:21 +00:00
{
}
MmalRenderer::~MmalRenderer()
{
if (m_InputPort != nullptr) {
mmal_port_disable(m_InputPort);
}
if (m_Renderer != nullptr) {
mmal_component_destroy(m_Renderer);
}
if (m_BackgroundRenderer != nullptr) {
SDL_DestroyRenderer(m_BackgroundRenderer);
}
2019-04-16 08:20:21 +00:00
}
2020-02-23 08:22:44 +00:00
bool MmalRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary** options)
2019-04-16 08:20:21 +00:00
{
// FFmpeg defaults this to 10 which is too large to fit in the default 64 MB VRAM split.
// Reducing to 2 seems to work fine for our bitstreams (max of 1 buffered frame needed).
av_dict_set_int(options, "extra_buffers", 2, 0);
2019-04-16 08:20:21 +00:00
2020-02-23 08:22:44 +00:00
// MMAL seems to dislike certain initial width and height values, but it seems okay
// with getting zero for the width and height. We'll zero them all the time to be safe.
context->width = 0;
context->height = 0;
2019-04-16 08:20:21 +00:00
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using MMAL renderer");
return true;
}
bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
{
MMAL_STATUS_T status;
// Clear the background if possible
setupBackground(params);
2019-04-16 08:20:21 +00:00
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &m_Renderer);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_component_create() failed: %x (%s)",
status, mmal_status_to_string(status));
return false;
}
m_InputPort = m_Renderer->input[0];
m_InputPort->format->encoding = MMAL_ENCODING_OPAQUE;
m_InputPort->format->es->video.width = params->width;
m_InputPort->format->es->video.height = params->height;
m_InputPort->format->es->video.crop.x = 0;
m_InputPort->format->es->video.crop.y = 0;
m_InputPort->format->es->video.crop.width = params->width;
m_InputPort->format->es->video.crop.height = params->height;
status = mmal_port_format_commit(m_InputPort);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_port_format_commit() failed: %x (%s)",
status, mmal_status_to_string(status));
return false;
}
status = mmal_component_enable(m_Renderer);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_component_enable() failed: %x (%s)",
status, mmal_status_to_string(status));
return false;
}
{
MMAL_DISPLAYREGION_T dr = {};
2019-04-16 08:20:21 +00:00
dr.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
dr.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
dr.set |= MMAL_DISPLAY_SET_FULLSCREEN;
dr.fullscreen = MMAL_FALSE;
2019-04-16 08:20:21 +00:00
dr.set |= MMAL_DISPLAY_SET_MODE;
dr.mode = MMAL_DISPLAY_MODE_LETTERBOX;
2019-04-16 08:20:21 +00:00
dr.set |= MMAL_DISPLAY_SET_NOASPECT;
dr.noaspect = MMAL_TRUE;
dr.set |= MMAL_DISPLAY_SET_SRC_RECT;
dr.src_rect.x = 0;
dr.src_rect.y = 0;
dr.src_rect.width = params->width;
dr.src_rect.height = params->height;
2019-04-16 08:20:21 +00:00
{
SDL_Rect src, dst;
src.x = src.y = 0;
src.w = params->width;
src.h = params->height;
dst.x = dst.y = 0;
SDL_GetWindowSize(params->window, &dst.w, &dst.h);
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
dr.set |= MMAL_DISPLAY_SET_DEST_RECT;
dr.dest_rect.x = dst.x;
dr.dest_rect.y = dst.y;
dr.dest_rect.width = dst.w;
dr.dest_rect.height = dst.h;
}
status = mmal_port_parameter_set(m_InputPort, &dr.hdr);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_port_parameter_set() failed: %x (%s)",
status, mmal_status_to_string(status));
return false;
}
2019-04-16 08:20:21 +00:00
}
status = mmal_port_enable(m_InputPort, InputPortCallback);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_port_enable() failed: %x (%s)",
status, mmal_status_to_string(status));
return false;
}
return true;
}
void MmalRenderer::setupBackground(PDECODER_PARAMETERS params)
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if (!SDL_GetWindowWMInfo(params->window, &info)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetWindowWMInfo() failed: %s",
SDL_GetError());
return;
}
// On X11, we can safely create a renderer and draw a black background.
// Due to conflicts with Qt, it's unsafe to do this for KMSDRM.
if (info.subsystem == SDL_SYSWM_X11) {
m_BackgroundRenderer = SDL_CreateRenderer(params->window, -1, SDL_RENDERER_SOFTWARE);
if (m_BackgroundRenderer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_CreateRenderer() failed: %s",
SDL_GetError());
return;
}
SDL_SetRenderDrawColor(m_BackgroundRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(m_BackgroundRenderer);
SDL_RenderPresent(m_BackgroundRenderer);
}
}
2019-04-16 08:20:21 +00:00
void MmalRenderer::InputPortCallback(MMAL_PORT_T*, MMAL_BUFFER_HEADER_T* buffer)
{
mmal_buffer_header_release(buffer);
}
enum AVPixelFormat MmalRenderer::getPreferredPixelFormat(int videoFormat)
{
// Opaque MMAL buffers
SDL_assert(videoFormat == VIDEO_FORMAT_H264);
return AV_PIX_FMT_MMAL;
}
int MmalRenderer::getRendererAttributes()
{
// This renderer can only draw in full-screen and maxes out at 1080p
return RENDERER_ATTRIBUTE_FULLSCREEN_ONLY | RENDERER_ATTRIBUTE_1080P_MAX;
}
2019-04-25 04:31:52 +00:00
bool MmalRenderer::needsTestFrame()
{
// We won't be able to decode if the GPU memory is 64 MB or lower,
// so we must test before allowing the decoder to be used.
return true;
}
2019-04-16 08:20:21 +00:00
void MmalRenderer::renderFrame(AVFrame* frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
2019-04-16 08:20:21 +00:00
MMAL_BUFFER_HEADER_T* buffer = (MMAL_BUFFER_HEADER_T*)frame->data[3];
MMAL_STATUS_T status;
status = mmal_port_send_buffer(m_InputPort, buffer);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"mmal_port_send_buffer() failed: %x (%s)",
status, mmal_status_to_string(status));
}
else {
// Prevent the buffer from being freed during av_frame_free()
// until rendering is complete. The reference is dropped in
// InputPortCallback().
mmal_buffer_header_acquire(buffer);
}
}