shm.c 9.44 KB
Newer Older
1 2 3 4 5 6 7
/**
 * @file shm.c
 * @brief Shared memory frame buffer capture module for VLC media player
 */
/*****************************************************************************
 * Copyright © 2011 Rémi Denis-Courmont
 *
8 9 10 11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18 19 20
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 22 23 24 25 26 27
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdarg.h>
28
#include <math.h>
29
#include <errno.h>
30
#include <fcntl.h>
31
#include <unistd.h>
32 33 34 35
#ifdef HAVE_SYS_SHM_H
# include <sys/ipc.h>
# include <sys/shm.h>
#endif
36 37 38 39 40 41 42 43 44 45

#include <vlc_common.h>
#include <vlc_demux.h>
#include <vlc_fs.h>
#include <vlc_plugin.h>

#define FPS_TEXT N_("Frame rate")
#define FPS_LONGTEXT N_( \
    "How many times the screen content should be refreshed per second.")

46 47 48 49
#define DEPTH_TEXT N_("Frame buffer depth")
#define DEPTH_LONGTEXT N_( \
    "Pixel depth of the frame buffer, or zero for XWD file")

50 51
#define WIDTH_TEXT N_("Frame buffer width")
#define WIDTH_LONGTEXT N_( \
52
    "Pixel width of the frame buffer (ignored for XWD file)")
53 54 55

#define HEIGHT_TEXT N_("Frame buffer height")
#define HEIGHT_LONGTEXT N_( \
56
    "Pixel height of the frame buffer (ignored for XWD file)")
57 58 59 60 61 62 63 64 65 66 67 68 69 70

#define ID_TEXT N_("Frame buffer segment ID")
#define ID_LONGTEXT N_( \
    "System V shared memory segment ID of the frame buffer " \
    "(this is ignored if --shm-file is specified).")

#define FILE_TEXT N_("Frame buffer file")
#define FILE_LONGTEXT N_( \
    "Path of the memory mapped file of the frame buffer")

static int  Open (vlc_object_t *);
static void Close (vlc_object_t *);

static const int depths[] = {
71
    0, 8, 15, 16, 24, 32,
72 73 74
};

static const char *const depth_texts[] = {
75
    N_("XWD file (autodetect)"),
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    N_("8 bits"), N_("15 bits"), N_("16 bits"), N_("24 bits"), N_("32 bits"),
};

/*
 * Module descriptor
 */
vlc_module_begin ()
    set_shortname (N_("Framebuffer input"))
    set_description (N_("Shared memory framebuffer"))
    set_category (CAT_INPUT)
    set_subcategory (SUBCAT_INPUT_ACCESS)
    set_capability ("access_demux", 0)
    set_callbacks (Open, Close)

    add_float ("shm-fps", 10.0, FPS_TEXT, FPS_LONGTEXT, true)
91 92 93
    add_integer ("shm-depth", 0, DEPTH_TEXT, DEPTH_LONGTEXT, true)
        change_integer_list (depths, depth_texts)
        change_safe ()
94 95 96 97 98 99 100 101 102 103 104 105
    add_integer ("shm-width", 800, WIDTH_TEXT, WIDTH_LONGTEXT, false)
        change_integer_range (0, 65535)
        change_safe ()
    add_integer ("shm-height", 480, HEIGHT_TEXT, HEIGHT_LONGTEXT, false)
        change_integer_range (0, 65535)
        change_safe ()

    /* We need to "trust" the memory segment. If it were shrunk while we copy
     * its content our process may crash - or worse. So we pass the shared
     * memory location via an unsafe variable rather than the URL. */
    add_string ("shm-file", NULL, FILE_TEXT, FILE_LONGTEXT, false)
        change_volatile ()
106
#ifdef HAVE_SYS_SHM_H
107 108
    add_integer ("shm-id", (int64_t)IPC_PRIVATE, ID_TEXT, ID_LONGTEXT, false)
        change_volatile ()
109
#endif
110 111 112 113
    add_shortcut ("shm")
vlc_module_end ()

static int Control (demux_t *, int, va_list);
114 115
static void DemuxFile (void *);
static void CloseFile (demux_sys_t *);
116
#ifdef HAVE_SYS_SHM_H
117 118
static void DemuxIPC (void *);
static void CloseIPC (demux_sys_t *);
119
#endif
120 121 122

struct demux_sys_t
{
123
    /* Everything is read-only when timer is armed. */
124 125 126 127 128 129 130 131 132
    union
    {
        int fd;
        struct
        {
             const void  *addr;
             size_t       length;
        } mem;
    };
133 134 135 136 137 138 139 140
    es_out_id_t *es;
    vlc_timer_t  timer;
    void (*detach) (demux_sys_t *);
};

static int Open (vlc_object_t *obj)
{
    demux_t *demux = (demux_t *)obj;
141
    demux_sys_t *sys = vlc_obj_malloc(obj, sizeof (*sys));
142 143 144 145
    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    uint32_t chroma;
146
    uint16_t width = 0, height = 0;
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    uint8_t bpp;
    switch (var_InheritInteger (demux, "shm-depth"))
    {
        case 32:
            chroma = VLC_CODEC_RGB32; bpp = 32;
            break;
        case 24:
            chroma = VLC_CODEC_RGB24; bpp = 24;
            break;
        case 16:
            chroma = VLC_CODEC_RGB16; bpp = 16;
            break;
        case 15:
            chroma = VLC_CODEC_RGB15; bpp = 16;
            break;
        case 8:
            chroma = VLC_CODEC_RGB8; bpp = 8;
            break;
165 166 167
        case 0:
            chroma = VLC_CODEC_XWD; bpp = 0;
            break;
168
        default:
169
            return VLC_EGENERIC;
170
    }
171 172 173 174 175
    if (bpp != 0)
    {
        width = var_InheritInteger (demux, "shm-width");
        height = var_InheritInteger (demux, "shm-height");
    }
176

177
    static void (*Demux) (void *);
178 179 180 181

    char *path = var_InheritString (demux, "shm-file");
    if (path != NULL)
    {
182 183
        sys->fd = vlc_open (path, O_RDONLY);
        if (sys->fd == -1)
184 185
            msg_Err (demux, "cannot open file %s: %s", path,
                     vlc_strerror_c(errno));
186 187
        free (path);
        if (sys->fd == -1)
188
            return VLC_EGENERIC;
189

190 191
        sys->detach = CloseFile;
        Demux = DemuxFile;
192 193 194
    }
    else
    {
195
#ifdef HAVE_SYS_SHM_H
196 197
        sys->mem.length = width * height * (bpp >> 3);
        if (sys->mem.length == 0)
198
            return VLC_EGENERIC;
199

200 201
        int id = var_InheritInteger (demux, "shm-id");
        if (id == IPC_PRIVATE)
202
            return VLC_EGENERIC;
203 204 205 206
        void *mem = shmat (id, NULL, SHM_RDONLY);

        if (mem == (const void *)(-1))
        {
207 208
            msg_Err (demux, "cannot attach segment %d: %s", id,
                     vlc_strerror_c(errno));
209
            return VLC_EGENERIC;
210
        }
211 212 213
        sys->mem.addr = mem;
        sys->detach = CloseIPC;
        Demux = DemuxIPC;
214 215 216
#else
        goto error;
#endif
217 218 219 220
    }

    /* Initializes format */
    float rate = var_InheritFloat (obj, "shm-fps");
221
    if (rate <= 0.f)
222 223
        goto error;

224 225
    mtime_t interval = llroundf((float)CLOCK_FREQ / rate);
    if (!interval)
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
        goto error;

    es_format_t fmt;
    es_format_Init (&fmt, VIDEO_ES, chroma);
    fmt.video.i_chroma = chroma;
    fmt.video.i_bits_per_pixel = bpp;
    fmt.video.i_sar_num = fmt.video.i_sar_den = 1;
    fmt.video.i_frame_rate = 1000 * rate;
    fmt.video.i_frame_rate_base = 1000;
    fmt.video.i_visible_width = fmt.video.i_width = width;
    fmt.video.i_visible_height = fmt.video.i_height = height;

    sys->es = es_out_Add (demux->out, &fmt);

    /* Initializes demux */
    if (vlc_timer_create (&sys->timer, Demux, demux))
        goto error;
243
    vlc_timer_schedule (sys->timer, false, 1, interval);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

    demux->p_sys = sys;
    demux->pf_demux   = NULL;
    demux->pf_control = Control;
    return VLC_SUCCESS;

error:
    sys->detach (sys);
    return VLC_EGENERIC;
}


/**
 * Releases resources
 */
static void Close (vlc_object_t *obj)
{
    demux_t *demux = (demux_t *)obj;
    demux_sys_t *sys = demux->p_sys;

    vlc_timer_destroy (sys->timer);
    sys->detach (sys);
}

/**
 * Control callback
 */
static int Control (demux_t *demux, int query, va_list args)
{
    switch (query)
    {
        case DEMUX_GET_POSITION:
        {
            float *v = va_arg (args, float *);
            *v = 0.;
            return VLC_SUCCESS;
        }

        case DEMUX_GET_LENGTH:
        case DEMUX_GET_TIME:
        {
            int64_t *v = va_arg (args, int64_t *);
            *v = 0;
            return VLC_SUCCESS;
        }

        case DEMUX_GET_PTS_DELAY:
        {
            int64_t *v = va_arg (args, int64_t *);
293
            *v = INT64_C(1000) * var_InheritInteger (demux, "live-caching");
294 295 296 297 298 299 300 301
            return VLC_SUCCESS;
        }

        case DEMUX_CAN_PAUSE:
        case DEMUX_CAN_CONTROL_PACE:
        case DEMUX_CAN_CONTROL_RATE:
        case DEMUX_CAN_SEEK:
        {
302
            bool *v = va_arg (args, bool *);
303 304 305
            *v = false;
            return VLC_SUCCESS;
        }
306 307 308

        case DEMUX_SET_PAUSE_STATE:
            return VLC_SUCCESS; /* should not happen */
309 310 311 312 313 314 315 316
    }

    return VLC_EGENERIC;
}

/**
 * Processing callback
 */
317
static void DemuxFile (void *data)
318 319 320 321 322
{
    demux_t *demux = data;
    demux_sys_t *sys = demux->p_sys;

    /* Copy frame */
323
    block_t *block = block_File(sys->fd, true);
324 325
    if (block == NULL)
        return;
326
    block->i_pts = block->i_dts = mdate ();
327 328

    /* Send block */
329
    es_out_SetPCR(demux->out, block->i_pts);
330 331
    es_out_Send (demux->out, sys->es, block);
}
332 333 334

static void CloseFile (demux_sys_t *sys)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
335
    vlc_close (sys->fd);
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
}

#ifdef HAVE_SYS_SHM_H
static void DemuxIPC (void *data)
{
    demux_t *demux = data;
    demux_sys_t *sys = demux->p_sys;

    /* Copy frame */
    block_t *block = block_Alloc (sys->mem.length);
    if (block == NULL)
        return;
    memcpy (block->p_buffer, sys->mem.addr, sys->mem.length);
    block->i_pts = block->i_dts = mdate ();

    /* Send block */
352
    es_out_SetPCR(demux->out, block->i_pts);
353 354 355 356 357 358 359 360
    es_out_Send (demux->out, sys->es, block);
}

static void CloseIPC (demux_sys_t *sys)
{
    shmdt (sys->mem.addr);
}
#endif