mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 04:44:05 +00:00
- removed `needs_mainloop` attribute and deduce if mainloop is needed from the presence of _run callback (the information is now redundant only displays needing/running mainloop should announce _run callback) - run the custom mainloop (currently only Syphon!) only when display doesn't run its mainloop. This allows running Syphon and GL/SDL display because it connects to the display mainloop.
793 lines
30 KiB
C++
793 lines
30 KiB
C++
/**
|
|
* @file video_display/sdl.cpp
|
|
* @author Lukas Hejtmanek <xhejtman@ics.muni.cz>
|
|
* @author Milos Liska <xliska@fi.muni.cz>
|
|
* @author Martin Pulec <pulec@cesnet.cz>
|
|
*/
|
|
/*
|
|
* Copyright (c) 2010-2023 CESNET, z. s. p. o.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, is permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of CESNET nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
|
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "config_unix.h"
|
|
#include "config_win32.h"
|
|
#include "host.h"
|
|
|
|
#include "debug.h"
|
|
#include "keyboard_control.h"
|
|
#include "lib_common.h"
|
|
#include "messaging.h"
|
|
#include "module.h"
|
|
#include "video_display.h"
|
|
#include "tv.h"
|
|
#include "audio/types.h"
|
|
#include "audio/utils.h"
|
|
#include "utils/ring_buffer.h"
|
|
#include "video.h"
|
|
|
|
#ifdef HAVE_MACOSX
|
|
#include "utils/autorelease_pool.h"
|
|
extern "C" void NSApplicationLoad();
|
|
#elif defined HAVE_LINUX
|
|
#include "x11_common.h"
|
|
#endif /* HAVE_MACOSX */
|
|
|
|
#include <math.h>
|
|
|
|
#include <SDL/SDL.h>
|
|
#include <SDL/SDL_syswm.h>
|
|
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <queue>
|
|
|
|
#define MAGIC_SDL 0x155734ae
|
|
#define FOURCC_UYVY 0x59565955
|
|
#define FOURCC_YUYV 0x32595559
|
|
|
|
#define MAX_BUFFER_SIZE 1
|
|
|
|
using namespace std;
|
|
|
|
#define SDL_USER_NEWFRAME SDL_USEREVENT
|
|
|
|
struct state_sdl {
|
|
uint32_t magic;
|
|
|
|
struct timeval tv;
|
|
int frames;
|
|
|
|
SDL_Overlay * yuv_image;
|
|
SDL_Surface * sdl_screen;
|
|
SDL_Rect dst_rect;
|
|
|
|
bool deinterlace;
|
|
bool fs;
|
|
bool nodecorate;
|
|
bool fixed_size;
|
|
int fixed_w, fixed_h;
|
|
|
|
int screen_w, screen_h;
|
|
|
|
struct ring_buffer *audio_buffer;
|
|
struct audio_frame audio_frame;
|
|
bool play_audio;
|
|
|
|
int buffered_frames_count;
|
|
queue<struct video_frame *> free_frame_queue;
|
|
struct video_desc current_desc; /// with those desc new data frames will be generated (getf)
|
|
struct video_desc current_display_desc;
|
|
mutex lock;
|
|
condition_variable frame_consumed_cv;
|
|
|
|
#ifdef HAVE_MACOSX
|
|
void *autorelease_pool;
|
|
#endif
|
|
uint32_t sdl_flags_win, sdl_flags_fs;
|
|
|
|
struct module mod;
|
|
|
|
state_sdl(struct module *parent) : magic(MAGIC_SDL), frames(0), yuv_image(nullptr), sdl_screen(nullptr), dst_rect(),
|
|
deinterlace(false), fs(false), nodecorate(false), fixed_size(false),
|
|
fixed_w(0), fixed_h(0),
|
|
screen_w(0), screen_h(0), audio_buffer(nullptr), audio_frame(), play_audio(false), buffered_frames_count(0),
|
|
current_desc(), current_display_desc(),
|
|
#ifdef HAVE_MACOSX
|
|
autorelease_pool(nullptr),
|
|
#endif
|
|
sdl_flags_win(0), sdl_flags_fs(0)
|
|
{
|
|
gettimeofday(&tv, NULL);
|
|
module_init_default(&mod);
|
|
mod.cls = MODULE_CLASS_DATA;
|
|
module_register(&mod, parent);
|
|
}
|
|
|
|
~state_sdl() {
|
|
module_done(&mod);
|
|
}
|
|
};
|
|
|
|
static void loadSplashscreen(struct state_sdl *s);
|
|
static void show_help(void);
|
|
static int display_sdl_putf(void *state, struct video_frame *frame, long long nonblock);
|
|
static int display_sdl_reconfigure(void *state, struct video_desc desc);
|
|
static int display_sdl_reconfigure_real(void *state, struct video_desc desc);
|
|
|
|
static void cleanup_screen(struct state_sdl *s);
|
|
static void configure_audio(struct state_sdl *s);
|
|
static void sdl_audio_callback(void *userdata, Uint8 *stream, int len);
|
|
static void compute_dst_rect(struct state_sdl *s, int vid_w, int vid_h, int window_w, int window_h, codec_t codec);
|
|
static bool update_size(struct state_sdl *s, int win_w, int win_h);
|
|
|
|
static void loadSplashscreen(struct state_sdl *s) {
|
|
struct video_frame *frame = get_splashscreen();
|
|
display_sdl_reconfigure(s, video_desc_from_frame(frame));
|
|
display_sdl_putf(s, frame, PUTF_BLOCKING);
|
|
}
|
|
|
|
static void compute_dst_rect(struct state_sdl *s, int vid_w, int vid_h, int window_w, int window_h, codec_t codec)
|
|
{
|
|
s->dst_rect.x = 0;
|
|
s->dst_rect.y = 0;
|
|
s->dst_rect.w = vid_w;
|
|
s->dst_rect.h = vid_h;
|
|
|
|
if (codec_is_a_rgb(codec)) {
|
|
if (window_w > vid_w) {
|
|
s->dst_rect.x = ((int) window_w - vid_w) / 2;
|
|
} else if (window_w < vid_w) {
|
|
s->dst_rect.w = window_w;
|
|
}
|
|
if (window_h > vid_h) {
|
|
s->dst_rect.y = ((int) window_h - vid_h) / 2;
|
|
} else if (window_h < vid_h) {
|
|
s->dst_rect.h = window_h;
|
|
}
|
|
} else if (!codec_is_a_rgb(codec) && (vid_w != window_w || vid_h != window_h)) {
|
|
double frame_aspect = (double) vid_w / vid_h;
|
|
double screen_aspect = (double) window_w / window_h;
|
|
if(screen_aspect > frame_aspect) {
|
|
s->dst_rect.h = window_h;
|
|
s->dst_rect.w = window_h * frame_aspect;
|
|
s->dst_rect.x = ((int) window_w - s->dst_rect.w) / 2;
|
|
} else {
|
|
s->dst_rect.w = window_w;
|
|
s->dst_rect.h = window_w / frame_aspect;
|
|
s->dst_rect.y = ((int) window_h - s->dst_rect.h) / 2;
|
|
}
|
|
}
|
|
|
|
fprintf(stdout, "Setting SDL rect %dx%d - %d,%d.\n", s->dst_rect.w,
|
|
s->dst_rect.h, s->dst_rect.x, s->dst_rect.y);
|
|
}
|
|
|
|
static bool update_size(struct state_sdl *s, int win_w, int win_h)
|
|
{
|
|
unsigned int x_res_x, x_res_y;
|
|
|
|
if (s->fs) {
|
|
x_res_x = s->screen_w;
|
|
x_res_y = s->screen_h;
|
|
} else {
|
|
x_res_x = win_w;
|
|
x_res_y = win_h;
|
|
}
|
|
SDL_Surface *sdl_screen_old = s->sdl_screen;
|
|
s->sdl_screen = SDL_SetVideoMode(x_res_x, x_res_y, 0, s->fs ? s->sdl_flags_fs : s->sdl_flags_win);
|
|
fprintf(stdout, "Setting video mode %dx%d.\n", x_res_x, x_res_y);
|
|
compute_dst_rect(s, s->current_display_desc.width, s->current_display_desc.height, x_res_x, x_res_y, s->current_display_desc.color_spec);
|
|
if (s->sdl_screen == NULL) {
|
|
fprintf(stderr, "Error setting video mode %dx%d!\n", x_res_x,
|
|
x_res_y);
|
|
s->sdl_screen = sdl_screen_old;
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static void display_frame(struct state_sdl *s, struct video_frame *frame)
|
|
{
|
|
struct timeval tv;
|
|
|
|
if (s->deinterlace) {
|
|
vc_deinterlace((unsigned char *) frame->tiles[0].data,
|
|
vc_get_linesize(frame->tiles[0].width,
|
|
frame->color_spec), frame->tiles[0].height);
|
|
}
|
|
|
|
if (!video_desc_eq(video_desc_from_frame(frame), s->current_display_desc)) {
|
|
if (!display_sdl_reconfigure_real(s, video_desc_from_frame(frame))) {
|
|
goto free_frame;
|
|
}
|
|
}
|
|
|
|
if (codec_is_a_rgb(frame->color_spec) && (s->sdl_screen->format->BitsPerPixel == 32 || s->sdl_screen->format->BitsPerPixel == 24)) {
|
|
codec_t dst_codec = s->sdl_screen->format->BitsPerPixel == 32 ? RGBA : RGB;
|
|
decoder_t decoder = get_decoder_from_to(frame->color_spec, dst_codec);
|
|
assert(decoder != nullptr);
|
|
SDL_LockSurface(s->sdl_screen);
|
|
size_t linesize = vc_get_linesize(frame->tiles[0].width, frame->color_spec);
|
|
size_t dst_linesize = vc_get_linesize(frame->tiles[0].width, dst_codec);
|
|
for (size_t i = 0; i < min<size_t>(frame->tiles[0].height, s->sdl_screen->h); ++i) {
|
|
decoder((unsigned char *) s->sdl_screen->pixels +
|
|
s->sdl_screen->pitch * (s->dst_rect.y + i) +
|
|
s->dst_rect.x *
|
|
s->sdl_screen->format->BytesPerPixel,
|
|
(unsigned char *) frame->tiles[0].data + i * linesize,
|
|
min<long>(dst_linesize,s->sdl_screen->pitch),
|
|
s->sdl_screen->format->Rshift,
|
|
s->sdl_screen->format->Gshift,
|
|
s->sdl_screen->format->Bshift);
|
|
}
|
|
|
|
SDL_UnlockSurface(s->sdl_screen);
|
|
SDL_Flip(s->sdl_screen);
|
|
} else if (codec_is_a_rgb(frame->color_spec)) {
|
|
int bpp = get_bpp(frame->color_spec);
|
|
size_t src_linesize = vc_get_linesize(frame->tiles[0].width, frame->color_spec);
|
|
SDL_LockSurface(s->sdl_screen);
|
|
for (size_t i = 0; i < min<size_t>(frame->tiles[0].height, s->sdl_screen->h); ++i) {
|
|
unsigned char *src = (unsigned char *) frame->tiles[0].data + i * src_linesize;
|
|
for (size_t j = 0; j < frame->tiles[0].width; j++) {
|
|
uint32_t rgba =
|
|
(src[0] >> s->sdl_screen->format->Rloss) << s->sdl_screen->format->Rshift |
|
|
(src[1] >> s->sdl_screen->format->Gloss) << s->sdl_screen->format->Gshift |
|
|
(src[2] >> s->sdl_screen->format->Bloss) << s->sdl_screen->format->Bshift;
|
|
memcpy((unsigned char *) s->sdl_screen->pixels +
|
|
s->sdl_screen->pitch * (s->dst_rect.y + i) +
|
|
(s->dst_rect.x + j) *
|
|
s->sdl_screen->format->BytesPerPixel,
|
|
&rgba,
|
|
s->sdl_screen->format->BytesPerPixel);
|
|
src += bpp;
|
|
}
|
|
}
|
|
SDL_UnlockSurface(s->sdl_screen);
|
|
SDL_Flip(s->sdl_screen);
|
|
} else {
|
|
SDL_LockYUVOverlay(s->yuv_image);
|
|
memcpy(*s->yuv_image->pixels, frame->tiles[0].data, frame->tiles[0].data_len);
|
|
SDL_UnlockYUVOverlay(s->yuv_image);
|
|
SDL_DisplayYUVOverlay(s->yuv_image, &s->dst_rect);
|
|
}
|
|
|
|
free_frame:
|
|
s->lock.lock();
|
|
s->free_frame_queue.push(frame);
|
|
s->lock.unlock();
|
|
|
|
s->frames++;
|
|
gettimeofday(&tv, NULL);
|
|
double seconds = tv_diff(tv, s->tv);
|
|
if (seconds > 5) {
|
|
double fps = s->frames / seconds;
|
|
log_msg(LOG_LEVEL_INFO, "[SDL] %d frames in %g seconds = %g FPS\n",
|
|
s->frames, seconds, fps);
|
|
s->tv = tv;
|
|
s->frames = 0;
|
|
}
|
|
}
|
|
|
|
static void display_sdl_run(void *arg)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)arg;
|
|
bool should_exit_sdl = false;
|
|
|
|
while (!should_exit_sdl) {
|
|
struct message *msg;
|
|
while ((msg = check_message(&s->mod))) {
|
|
auto msg_univ = reinterpret_cast<struct msg_universal *>(msg);
|
|
struct response *r;
|
|
if (strncasecmp(msg_univ->text, "win-title ", strlen("win_title ")) == 0) {
|
|
const char *title = msg_univ->text + strlen("win_title");
|
|
SDL_WM_SetCaption(title, title);
|
|
r = new_response(RESPONSE_OK, NULL);
|
|
} else {
|
|
log_msg(LOG_LEVEL_ERROR, "[SDL] Unknown command received: %s\n", msg_univ->text);
|
|
r = new_response(RESPONSE_BAD_REQUEST, NULL);
|
|
}
|
|
free_message(msg, r);
|
|
}
|
|
|
|
SDL_Event sdl_event;
|
|
if (SDL_WaitEvent(&sdl_event)) {
|
|
switch (sdl_event.type) {
|
|
case SDL_USER_NEWFRAME:
|
|
{
|
|
std::unique_lock<std::mutex> lk(s->lock);
|
|
s->buffered_frames_count -= 1;
|
|
lk.unlock();
|
|
s->frame_consumed_cv.notify_one();
|
|
if (sdl_event.user.data1 != NULL) {
|
|
display_frame(s, (struct video_frame *) sdl_event.user.data1);
|
|
} else { // poison pill received
|
|
should_exit_sdl = true;
|
|
}
|
|
break;
|
|
}
|
|
case SDL_VIDEORESIZE:
|
|
update_size(s, sdl_event.resize.w, sdl_event.resize.h);
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
switch (sdl_event.key.keysym.sym) {
|
|
case SDLK_d:
|
|
s->deinterlace = s->deinterlace ? FALSE : TRUE;
|
|
log_msg(LOG_LEVEL_INFO, "Deinterlacing: %s\n",
|
|
s->deinterlace ? "ON" : "OFF");
|
|
break;
|
|
case SDLK_f:
|
|
s->fs = !s->fs;
|
|
update_size(s, s->current_display_desc.width, s->current_display_desc.height);
|
|
break;
|
|
case SDLK_q:
|
|
exit_uv(0);
|
|
break;
|
|
default:
|
|
log_msg(LOG_LEVEL_DEBUG, "[SDL] Unhandled key: %s\n",
|
|
SDL_GetKeyName(sdl_event.key.keysym.sym));
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_QUIT:
|
|
exit_uv(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void show_help(void)
|
|
{
|
|
printf("SDL options:\n");
|
|
printf("\t-d sdl[:fs|:d|:nodecorate|:fixed_size[=WxH]]* | help\n");
|
|
printf("\tfs - fullscreen\n");
|
|
printf("\td - deinterlace\n");
|
|
printf("\tnodecorate - disable WM decoration\n");
|
|
printf("\tfixed_size[=WxH] - use fixed sized window\n");
|
|
//printf("\t<f> - read frame content from the filename\n");
|
|
}
|
|
|
|
static void cleanup_screen(struct state_sdl *s)
|
|
{
|
|
if (!codec_is_a_rgb(s->current_display_desc.color_spec)) {
|
|
if (s->yuv_image != NULL) {
|
|
SDL_FreeYUVOverlay(s->yuv_image);
|
|
s->yuv_image = NULL;
|
|
}
|
|
}
|
|
if (s->sdl_screen != NULL) {
|
|
SDL_FreeSurface(s->sdl_screen);
|
|
s->sdl_screen = NULL;
|
|
}
|
|
}
|
|
|
|
static int display_sdl_reconfigure(void *state, struct video_desc desc)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
|
|
s->current_desc = desc;
|
|
return 1;
|
|
}
|
|
|
|
static int display_sdl_reconfigure_real(void *state, struct video_desc desc)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
|
|
fprintf(stdout, "Reconfigure to size %dx%d\n", desc.width,
|
|
desc.height);
|
|
|
|
uint32_t flags_common = SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_RESIZABLE;
|
|
s->sdl_flags_fs = flags_common | SDL_FULLSCREEN;
|
|
s->sdl_flags_win = flags_common | (s->nodecorate ? SDL_NOFRAME : 0);
|
|
s->current_display_desc = desc;
|
|
|
|
if (!s->fixed_size ||
|
|
!s->sdl_screen /* first run */) {
|
|
if (!update_size(s, desc.width, desc.height)) {
|
|
memset(&s->current_display_desc, 0, sizeof s->current_display_desc);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
SDL_FillRect(s->sdl_screen, NULL, 0x000000);
|
|
SDL_Flip(s->sdl_screen);
|
|
compute_dst_rect(s, s->current_display_desc.width, s->current_display_desc.height, s->sdl_screen->w, s->sdl_screen->h, s->current_display_desc.color_spec);
|
|
}
|
|
|
|
const char *window_title = get_commandline_param("window-title");
|
|
if (window_title) {
|
|
SDL_WM_SetCaption(window_title, window_title);
|
|
} else {
|
|
SDL_WM_SetCaption("Ultragrid - SDL Display", "Ultragrid");
|
|
}
|
|
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
if (!codec_is_a_rgb(desc.color_spec)) {
|
|
s->yuv_image =
|
|
SDL_CreateYUVOverlay(desc.width, desc.height, desc.color_spec == UYVY ?
|
|
FOURCC_UYVY : FOURCC_YUYV, s->sdl_screen);
|
|
if (s->yuv_image == NULL) {
|
|
printf("SDL_overlay initialization failed.\n");
|
|
memset(&s->current_display_desc, 0, sizeof s->current_display_desc);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void *display_sdl_init(struct module *parent, const char *fmt, unsigned int flags)
|
|
{
|
|
struct state_sdl *s = new state_sdl(parent);
|
|
int ret;
|
|
const SDL_VideoInfo *video_info;
|
|
|
|
if (fmt != NULL) {
|
|
if (strcmp(fmt, "help") == 0) {
|
|
show_help();
|
|
delete s;
|
|
return INIT_NOERR;
|
|
}
|
|
|
|
char *tmp = strdup(fmt);
|
|
char *ptr = tmp;
|
|
char *tok;
|
|
char *save_ptr = NULL;
|
|
|
|
while((tok = strtok_r(ptr, ":", &save_ptr)))
|
|
{
|
|
if (strcmp(tok, "fs") == 0) {
|
|
s->fs = 1;
|
|
} else if (strcmp(tok, "d") == 0) {
|
|
s->deinterlace = 1;
|
|
} else if (strcmp(tok, "nodecorate") == 0) {
|
|
s->nodecorate = true;
|
|
} else if (strncmp(tok, "fixed_size", strlen("fixed_size")) == 0) {
|
|
s->fixed_size = true;
|
|
if (strncmp(tok, "fixed_size=", strlen("fixed_size=")) == 0) {
|
|
char *size = tok + strlen("fixed_size=");
|
|
if (strchr(size, 'x')) {
|
|
s->fixed_w = atoi(size);
|
|
s->fixed_h = atoi(strchr(size, 'x') + 1);
|
|
}
|
|
}
|
|
}
|
|
ptr = NULL;
|
|
}
|
|
|
|
free (tmp);
|
|
}
|
|
|
|
#ifdef HAVE_MACOSX
|
|
/* Startup function to call when running Cocoa code from a Carbon application.
|
|
* Whatever the fuck that means.
|
|
* Avoids uncaught exception (1002) when creating CGSWindow */
|
|
NSApplicationLoad();
|
|
s->autorelease_pool = autorelease_pool_allocate();
|
|
#endif
|
|
|
|
ret = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
|
|
|
|
if (ret < 0) {
|
|
printf("Unable to initialize SDL.\n");
|
|
delete s;
|
|
return NULL;
|
|
}
|
|
|
|
video_info = SDL_GetVideoInfo();
|
|
s->screen_w = video_info->current_w;
|
|
s->screen_h = video_info->current_h;
|
|
|
|
SDL_SysWMinfo info;
|
|
memset(&info, 0, sizeof(SDL_SysWMinfo));
|
|
ret = SDL_GetWMInfo(&info);
|
|
#ifdef HAVE_LINUX
|
|
if (ret == 1) {
|
|
x11_set_display(info.info.x11.display);
|
|
} else if (ret == 0) {
|
|
fprintf(stderr, "[SDL] Warning: SDL_GetWMInfo unimplemented\n");
|
|
} else if (ret == -1) {
|
|
fprintf(stderr, "[SDL] Warning: SDL_GetWMInfo failure: %s\n", SDL_GetError());
|
|
} else abort();
|
|
#endif
|
|
|
|
if (s->fixed_size && s->fixed_w && s->fixed_h) {
|
|
struct video_desc desc;
|
|
desc.width = s->fixed_w;
|
|
desc.height = s->fixed_h;
|
|
desc.color_spec = RGBA;
|
|
desc.interlacing = PROGRESSIVE;
|
|
desc.fps = 1;
|
|
desc.tile_count = 1;
|
|
|
|
display_sdl_reconfigure_real(s, desc);
|
|
}
|
|
loadSplashscreen(s);
|
|
|
|
if (flags) {
|
|
if (flags & DISPLAY_FLAG_AUDIO_EMBEDDED) {
|
|
s->play_audio = TRUE;
|
|
configure_audio(s);
|
|
} else {
|
|
if (flags & DISPLAY_FLAG_AUDIO_ANY) {
|
|
log_msg(LOG_LEVEL_ERROR, "[SDL] Only accepted audio output for SDL is \"embedded\".\n");
|
|
} else {
|
|
log_msg(LOG_LEVEL_ERROR, "[SDL] Unsupported/unknown flag passed.\n");
|
|
}
|
|
return NULL;
|
|
}
|
|
} else {
|
|
s->play_audio = FALSE;
|
|
}
|
|
|
|
keycontrol_register_key(get_module(get_root_module(parent), "control"), 'q', "execute exit", "quit");
|
|
|
|
return (void *)s;
|
|
}
|
|
|
|
static void display_sdl_done(void *state)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
|
|
assert(s->magic == MAGIC_SDL);
|
|
|
|
cleanup_screen(s);
|
|
|
|
/*FIXME: free all the stuff */
|
|
SDL_ShowCursor(SDL_ENABLE);
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
|
|
SDL_Quit();
|
|
#ifdef HAVE_MACOSX
|
|
autorelease_pool_destroy(s->autorelease_pool);
|
|
#endif
|
|
|
|
while (s->free_frame_queue.size() > 0) {
|
|
struct video_frame *buffer = s->free_frame_queue.front();
|
|
s->free_frame_queue.pop();
|
|
vf_free(buffer);
|
|
}
|
|
|
|
delete s;
|
|
}
|
|
|
|
static struct video_frame *display_sdl_getf(void *state)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
assert(s->magic == MAGIC_SDL);
|
|
|
|
lock_guard<mutex> lock(s->lock);
|
|
|
|
while (s->free_frame_queue.size() > 0) {
|
|
struct video_frame *buffer = s->free_frame_queue.front();
|
|
s->free_frame_queue.pop();
|
|
if (video_desc_eq(video_desc_from_frame(buffer), s->current_desc)) {
|
|
return buffer;
|
|
} else {
|
|
vf_free(buffer);
|
|
}
|
|
}
|
|
|
|
return vf_alloc_desc_data(s->current_desc);
|
|
}
|
|
|
|
static int display_sdl_putf(void *state, struct video_frame *frame, long long nonblock)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
|
|
assert(s->magic == MAGIC_SDL);
|
|
|
|
if (nonblock == PUTF_DISCARD) {
|
|
vf_free(frame);
|
|
return 0;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lk(s->lock);
|
|
if (s->buffered_frames_count >= MAX_BUFFER_SIZE && nonblock != PUTF_BLOCKING
|
|
&& frame != NULL) {
|
|
vf_free(frame);
|
|
printf("1 frame(s) dropped!\n");
|
|
return 1;
|
|
}
|
|
s->frame_consumed_cv.wait(lk, [s]{return s->buffered_frames_count < MAX_BUFFER_SIZE;});
|
|
s->buffered_frames_count += 1;
|
|
lk.unlock();
|
|
SDL_Event event;
|
|
event.type = SDL_USER_NEWFRAME;
|
|
event.user.data1 = frame;
|
|
SDL_PushEvent(&event);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int display_sdl_get_property(void *state, int property, void *val, size_t *len)
|
|
{
|
|
UNUSED(state);
|
|
codec_t codecs[] = {UYVY, YUYV, RGBA, RGB};
|
|
|
|
switch (property) {
|
|
case DISPLAY_PROPERTY_CODECS:
|
|
if(sizeof(codecs) <= *len) {
|
|
memcpy(val, codecs, sizeof(codecs));
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
*len = sizeof(codecs);
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void sdl_audio_callback(void *userdata, Uint8 *stream, int len) {
|
|
struct state_sdl *s = (struct state_sdl *)userdata;
|
|
if (ring_buffer_read(s->audio_buffer, (char *) stream, len) != len)
|
|
{
|
|
fprintf(stderr, "[SDL] audio buffer underflow!!!\n");
|
|
usleep(500);
|
|
}
|
|
}
|
|
|
|
static void configure_audio(struct state_sdl *s)
|
|
{
|
|
s->audio_frame.data = NULL;
|
|
|
|
SDL_Init(SDL_INIT_AUDIO);
|
|
|
|
if(SDL_GetAudioStatus() != SDL_AUDIO_STOPPED) {
|
|
s->play_audio = FALSE;
|
|
fprintf(stderr, "[SDL] Audio init failed - driver is already used (testcard?)\n");
|
|
return;
|
|
}
|
|
|
|
s->audio_buffer = ring_buffer_init(1<<20);
|
|
}
|
|
|
|
static int display_sdl_reconfigure_audio(void *state, int quant_samples, int channels,
|
|
int sample_rate) {
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
if (!s->play_audio) {
|
|
return FALSE;
|
|
}
|
|
SDL_AudioSpec desired, obtained;
|
|
int sample_type;
|
|
|
|
s->audio_frame.bps = quant_samples / 8;
|
|
s->audio_frame.sample_rate = sample_rate;
|
|
s->audio_frame.ch_count = channels;
|
|
|
|
if(s->audio_frame.data != NULL) {
|
|
free(s->audio_frame.data);
|
|
SDL_CloseAudio();
|
|
}
|
|
|
|
if(quant_samples % 8 != 0) {
|
|
fprintf(stderr, "[SDL] audio format isn't supported: "
|
|
"channels: %d, samples: %d, sample rate: %d\n",
|
|
channels, quant_samples, sample_rate);
|
|
goto error;
|
|
}
|
|
switch(quant_samples) {
|
|
case 8:
|
|
sample_type = AUDIO_S8;
|
|
break;
|
|
case 16:
|
|
sample_type = AUDIO_S16LSB;
|
|
break;
|
|
/* TO enable in sdl 1.3
|
|
* case 32:
|
|
sample_type = AUDIO_S32;
|
|
break; */
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
desired.freq=sample_rate;
|
|
desired.format=sample_type;
|
|
desired.channels=channels;
|
|
|
|
/* Large audio buffer reduces risk of dropouts but increases response time */
|
|
desired.samples=1024;
|
|
|
|
/* Our callback function */
|
|
desired.callback=sdl_audio_callback;
|
|
desired.userdata=s;
|
|
|
|
|
|
/* Open the audio device */
|
|
if ( SDL_OpenAudio(&desired, &obtained) < 0 ){
|
|
fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
|
|
goto error;
|
|
}
|
|
|
|
s->audio_frame.max_size = 5 * (quant_samples / 8) * channels *
|
|
sample_rate;
|
|
s->audio_frame.data = (char *) malloc (s->audio_frame.max_size);
|
|
|
|
/* Start playing */
|
|
SDL_PauseAudio(0);
|
|
|
|
return TRUE;
|
|
error:
|
|
s->play_audio = FALSE;
|
|
s->audio_frame.max_size = 0;
|
|
s->audio_frame.data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
static void display_sdl_put_audio_frame(void *state, const struct audio_frame *frame) {
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
char *tmp;
|
|
|
|
if(!s->play_audio)
|
|
return;
|
|
|
|
if(frame->bps == 4 || frame->bps == 3) {
|
|
tmp = (char *) malloc(frame->data_len / frame->bps * 2);
|
|
change_bps(tmp, 2, frame->data, frame->bps, frame->data_len);
|
|
ring_buffer_write(s->audio_buffer, tmp, frame->bps * 2);
|
|
free(tmp);
|
|
} else {
|
|
ring_buffer_write(s->audio_buffer, frame->data, frame->data_len);
|
|
}
|
|
}
|
|
|
|
static void display_sdl_probe(struct device_info **available_cards, int *count, void (**deleter)(void *)) {
|
|
UNUSED(deleter);
|
|
*count = 1;
|
|
*available_cards = (struct device_info *) calloc(1, sizeof(struct device_info));
|
|
strcpy((*available_cards)[0].dev, "");
|
|
strcpy((*available_cards)[0].name, "SDL SW display");
|
|
(*available_cards)[0].repeatable = true;
|
|
}
|
|
|
|
static const struct video_display_info display_sdl_info = {
|
|
display_sdl_probe,
|
|
display_sdl_init,
|
|
display_sdl_run,
|
|
display_sdl_done,
|
|
display_sdl_getf,
|
|
display_sdl_putf,
|
|
display_sdl_reconfigure,
|
|
display_sdl_get_property,
|
|
display_sdl_put_audio_frame,
|
|
display_sdl_reconfigure_audio,
|
|
DISPLAY_NO_GENERIC_FPS_INDICATOR,
|
|
};
|
|
|
|
REGISTER_MODULE(sdl, &display_sdl_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION);
|
|
|