mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-21 11:40:22 +00:00
Replaced all other autoconf-defined platform macros (HAVE_LINUX and HAVE_MACOSX, WIN32 already done) with those ones defined by compiler. Not yet remove the definitions from autoconf, in case someone will use the old macros anyways. Remove in future.
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 __APPLE__
|
|
#include "utils/autorelease_pool.h"
|
|
extern "C" void NSApplicationLoad();
|
|
#elif defined __linux__
|
|
#include "x11_common.h"
|
|
#endif /* __APPLE__ */
|
|
|
|
#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 __APPLE__
|
|
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 __APPLE__
|
|
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 bool display_sdl_putf(void *state, struct video_frame *frame, long long nonblock);
|
|
static bool display_sdl_reconfigure(void *state, struct video_desc desc);
|
|
static bool 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 bool display_sdl_reconfigure(void *state, struct video_desc desc)
|
|
{
|
|
struct state_sdl *s = (struct state_sdl *)state;
|
|
|
|
s->current_desc = desc;
|
|
return true;
|
|
}
|
|
|
|
static bool 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 __APPLE__
|
|
/* 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 __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 __APPLE__
|
|
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 bool 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 true;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
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 true;
|
|
}
|
|
|
|
static bool 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 bool 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);
|
|
|