From f36c13c6651adc5ba99ca2787384c1fa91800324 Mon Sep 17 00:00:00 2001 From: Laurent Aimar Date: Mon, 24 May 2010 20:10:31 +0200 Subject: [PATCH] Added an image demuxer. It replaces the "fake" demuxer and decoder, and partially the mjpeg one. It does not rely on the extensions to detect the format. The detected picture formats are: XCF, GIF, PNG, TIFF, BMP, PCX, LBM, PNM, JFIF and SPIFF(Jpeg). The reload feature of the fake:// demuxer is lost, as well as the deinterlace feature of the fake decoder, patches are welcome. It closes #3107 and #3388, and modifies #2295. --- modules/demux/Modules.am | 2 + modules/demux/image.c | 568 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 570 insertions(+) create mode 100644 modules/demux/image.c diff --git a/modules/demux/Modules.am b/modules/demux/Modules.am index f47739e5c6..1903386fb7 100644 --- a/modules/demux/Modules.am +++ b/modules/demux/Modules.am @@ -31,6 +31,7 @@ SOURCES_vc1 = vc1.c SOURCES_demux_cdg = cdg.c SOURCES_smf = smf.c SOURCES_dirac = dirac.c +SOURCES_image = image.c libvlc_LTLIBRARIES += \ libaiff_plugin.la \ @@ -58,6 +59,7 @@ libvlc_LTLIBRARIES += \ libvoc_plugin.la \ libwav_plugin.la \ libxa_plugin.la \ + libimage_plugin.la \ $(NULL) BUILT_SOURCES += dummy.cpp diff --git a/modules/demux/image.c b/modules/demux/image.c new file mode 100644 index 0000000000..de35b4c7d1 --- /dev/null +++ b/modules/demux/image.c @@ -0,0 +1,568 @@ +/***************************************************************************** + * image.c: Image demuxer + ***************************************************************************** + * Copyright (C) 2010 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int Open (vlc_object_t *); +static void Close(vlc_object_t *); + +#define ID_TEXT N_("ES ID") +#define ID_LONGTEXT N_( \ + "Set the ID of the elementary stream") + +#define GROUP_TEXT N_("Group") +#define GROUP_LONGTEXT N_(\ + "Set the group of the elementary stream") + +#define DECODE_TEXT N_("Decode") +#define DECODE_LONGTEXT N_( \ + "Decode at the demuxer stage") + +#define CHROMA_TEXT N_("Forced chroma") +#define CHROMA_LONGTEXT N_( \ + "If non empty and image-decode is true, the image will be " \ + "converted to the specified chroma.") + +#define DURATION_TEXT N_("Duration in second") +#define DURATION_LONGTEXT N_( \ + "Duration in second before simulating an end of file. " \ + "A negative value means an unlimited play time.") + +#define FPS_TEXT N_("Frame rate") +#define FPS_LONGTEXT N_( \ + "Frame rate of the elementary stream produced.") + +#define RT_TEXT N_("Real-time") +#define RT_LONGTEXT N_( \ + "Use real-time mode suitable for being used as a master input and " \ + "real-time input slaves.") + +vlc_module_begin() + set_description(N_("Image demuxer")) + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_DEMUX) + add_integer("image-id", -1, NULL, ID_TEXT, ID_LONGTEXT, true) + change_safe() + add_integer("image-group", 0, NULL, GROUP_TEXT, GROUP_LONGTEXT, true) + change_safe() + add_bool("image-decode", true, NULL, DECODE_TEXT, DECODE_LONGTEXT, true) + change_safe() + add_string("image-chroma", "", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true) + change_safe() + add_float("image-duration", 10, NULL, DURATION_TEXT, DURATION_LONGTEXT, true) + change_safe() + add_string("image-fps", "10/1", NULL, FPS_TEXT, FPS_LONGTEXT, true) + change_safe() + add_bool("image-realtime", false, NULL, RT_TEXT, RT_LONGTEXT, true) + change_safe() + set_capability("demux", 10) + set_callbacks(Open, Close) +vlc_module_end() + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +struct demux_sys_t +{ + block_t *data; + es_out_id_t *es; + mtime_t duration; + bool is_realtime; + mtime_t pts_origin; + mtime_t pts_next; + date_t pts; +}; + +static block_t *Load(demux_t *demux) +{ + const int max_size = 4096 * 4096 * 8; + const int64_t size = stream_Size(demux->s); + if (size < 0 || size > max_size) { + msg_Err(demux, "Rejecting image based on its size (%"PRId64" > %d)", size, max_size); + return NULL; + } + + if (size > 0) + return stream_Block(demux->s, size); + /* TODO */ + return NULL; +} + +static block_t *Decode(demux_t *demux, + video_format_t *fmt, vlc_fourcc_t chroma, block_t *data) +{ + image_handler_t *handler = image_HandlerCreate(demux); + if (!handler) { + block_Release(data); + return NULL; + } + + video_format_t decoded; + video_format_Init(&decoded, chroma); + + picture_t *image = image_Read(handler, data, fmt, &decoded); + image_HandlerDelete(handler); + + if (!image) + return NULL; + + video_format_Clean(fmt); + *fmt = decoded; + + size_t size = 0; + for (int i = 0; i < image->i_planes; i++) + size += image->p[i].i_visible_pitch * + image->p[i].i_visible_lines; + + data = block_New(demux, size); + if (!data) { + picture_Release(image); + return NULL; + } + + size_t offset = 0; + for (int i = 0; i < image->i_planes; i++) { + const plane_t *src = &image->p[i]; + for (int y = 0; y < src->i_visible_lines; y++) { + memcpy(&data->p_buffer[offset], + &src->p_pixels[y * src->i_pitch], + src->i_visible_pitch); + offset += src->i_visible_pitch; + } + } + + picture_Release(image); + return data; +} + +static int Demux(demux_t *demux) +{ + demux_sys_t *sys = demux->p_sys; + + if (!sys->data) + return 0; + + mtime_t deadline; + const mtime_t pts_first = sys->pts_origin + date_Get(&sys->pts); + if (sys->pts_next > VLC_TS_INVALID) { + deadline = sys->pts_next; + } else if (sys->is_realtime) { + deadline = mdate(); + const mtime_t max_wait = CLOCK_FREQ / 50; + if (deadline + max_wait < pts_first) { + es_out_Control(demux->out, ES_OUT_SET_PCR, deadline); + /* That's ugly, but not yet easily fixable */ + mwait(deadline + max_wait); + return 1; + } + } else { + deadline = 1 + pts_first; + } + + for (;;) { + const mtime_t pts = sys->pts_origin + date_Get(&sys->pts); + if (sys->duration >= 0 && pts >= sys->pts_origin + sys->duration) + return 0; + + if (pts >= deadline) + return 1; + + block_t *data = block_Duplicate(sys->data); + if (!data) + return -1; + + data->i_dts = + data->i_pts = VLC_TS_0 + pts; + es_out_Control(demux->out, ES_OUT_SET_PCR, data->i_pts); + es_out_Send(demux->out, sys->es, data); + + date_Increment(&sys->pts, 1); + } +} + +static int Control(demux_t *demux, int query, va_list args) +{ + demux_sys_t *sys = demux->p_sys; + + switch (query) { + case DEMUX_GET_POSITION: { + double *position = va_arg(args, double *); + if (sys->duration > 0) + *position = date_Get(&sys->pts) / (double)sys->duration; + else + *position = 0; + return VLC_SUCCESS; + } + case DEMUX_SET_POSITION: { + if (sys->duration < 0 || sys->is_realtime) + return VLC_EGENERIC; + double position = va_arg(args, double); + date_Set(&sys->pts, position * sys->duration); + return VLC_SUCCESS; + } + case DEMUX_GET_TIME: { + int64_t *time = va_arg(args, int64_t *); + *time = sys->pts_origin + date_Get(&sys->pts); + return VLC_SUCCESS; + } + case DEMUX_SET_TIME: { + if (sys->duration < 0 || sys->is_realtime) + return VLC_EGENERIC; + int64_t time = va_arg(args, int64_t); + date_Set(&sys->pts, __MIN(__MAX(time - sys->pts_origin, 0), + sys->duration)); + return VLC_SUCCESS; + } + case DEMUX_SET_NEXT_DEMUX_TIME: { + int64_t pts_next = VLC_TS_0 + va_arg(args, int64_t); + if (sys->pts_next <= VLC_TS_INVALID) + sys->pts_origin = pts_next; + sys->pts_next = pts_next; + return VLC_SUCCESS; + } + case DEMUX_GET_LENGTH: { + int64_t *length = va_arg(args, int64_t *); + *length = __MAX(sys->duration, 0); + return VLC_SUCCESS; + } + case DEMUX_GET_FPS: { + double *fps = va_arg(args, double *); + *fps = (double)sys->pts.i_divider_num / sys->pts.i_divider_den; + return VLC_SUCCESS; + } + case DEMUX_GET_META: + case DEMUX_HAS_UNSUPPORTED_META: + case DEMUX_GET_ATTACHMENTS: + default: + return VLC_EGENERIC; + } +} + +static bool IsBmp(stream_t *s) +{ + const uint8_t *header; + if (stream_Peek(s, &header, 18) < 18) + return false; + if (memcmp(header, "BM", 2) && + memcmp(header, "BA", 2) && + memcmp(header, "CI", 2) && + memcmp(header, "CP", 2) && + memcmp(header, "IC", 2) && + memcmp(header, "PT", 2)) + return false; + uint32_t file_size = GetDWLE(&header[2]); + uint32_t data_offset = GetDWLE(&header[10]); + uint32_t header_size = GetDWLE(&header[14]); + if (file_size != 14 && file_size != 14 + header_size && + file_size <= data_offset) + return false; + if (data_offset < header_size + 14) + return false; + if (header_size != 12 && header_size < 40) + return false; + return true; +} + +static bool IsPcx(stream_t *s) +{ + const uint8_t *header; + if (stream_Peek(s, &header, 66) < 66) + return false; + if (header[0] != 0x0A || /* marker */ + (header[1] != 0x00 && header[1] != 0x02 && + header[1] != 0x03 && header[1] != 0x05) || /* version */ + (header[2] != 0 && header[2] != 1) || /* encoding */ + (header[3] != 1 && header[3] != 2 && + header[3] != 4 && header[3] != 8) || /* bits per pixel per plane */ + header[64] != 0 || /* reserved */ + header[65] == 0 || header[65] > 4) /* plane count */ + return false; + if (GetWLE(&header[4]) > GetWLE(&header[8]) || /* xmin vs xmax */ + GetWLE(&header[6]) > GetWLE(&header[10])) /* ymin vs ymax */ + return false; + return true; +} + +static bool IsLbm(stream_t *s) +{ + const uint8_t *header; + if (stream_Peek(s, &header, 12) < 12) + return false; + if (memcmp(&header[0], "FORM", 4) || + GetDWBE(&header[4]) <= 4 || + (memcmp(&header[8], "ILBM", 4) && memcmp(&header[8], "PBM ", 4))) + return false; + return true; +} +static bool IsPnmBlank(uint8_t v) +{ + return v == ' ' || v == '\t' || v == '\r' || v == '\n'; +} +static bool IsPnm(stream_t *s) +{ + const uint8_t *header; + int size = stream_Peek(s, &header, 256); + if (size < 3) + return false; + if (header[0] != 'P' || + header[1] < '1' || header[1] > '6' || + !IsPnmBlank(header[2])) + return false; + + int number_count = 0; + for (int i = 3, parsing_number = 0; i < size && number_count < 2; i++) { + if (IsPnmBlank(header[i])) { + if (parsing_number) { + parsing_number = 0; + number_count++; + } + } else { + if (header[i] < '0' || header[i] > '9') + break; + parsing_number = 1; + } + } + if (number_count < 2) + return false; + return true; +} + +static uint8_t FindJpegMarker(int *position, const uint8_t *data, int size) +{ + for (int i = *position; i + 1 < size; i++) { + if (data[i + 0] != 0xff || data[i + 1] == 0x00) + return 0xff; + if (data[i + 1] != 0xff) { + *position = i + 2; + return data[i + 1]; + } + } + return 0xff; +} +static bool IsJfif(stream_t *s) +{ + const uint8_t *header; + int size = stream_Peek(s, &header, 256); + int position = 0; + + if (FindJpegMarker(&position, header, size) != 0xd8) + return false; + if (FindJpegMarker(&position, header, size) != 0xe0) + return false; + position += 2; /* Skip size */ + if (position + 5 > size) + return false; + if (memcmp(&header[position], "JFIF\0", 5)) + return false; + return true; +} + +static bool IsSpiff(stream_t *s) +{ + const uint8_t *header; + if (stream_Peek(s, &header, 36) < 36) /* SPIFF header size */ + return false; + if (header[0] != 0xff || header[1] != 0xd8 || + header[2] != 0xff || header[3] != 0xe8) + return false; + if (memcmp(&header[6], "SPIFF\0", 6)) + return false; + return true; +} + +typedef struct { + vlc_fourcc_t codec; + int marker_size; + const uint8_t marker[14]; + bool (*detect)(stream_t *s); +} image_format_t; + +#define VLC_CODEC_XCF VLC_FOURCC('X', 'C', 'F', ' ') +#define VLC_CODEC_LBM VLC_FOURCC('L', 'B', 'M', ' ') +static const image_format_t formats[] = { + { .codec = VLC_CODEC_XCF, + .marker_size = 9 + 4 + 1, + .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ', + 'f', 'i', 'l', 'e', '\0' } + }, + { .codec = VLC_CODEC_XCF, + .marker_size = 9 + 4 + 1, + .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ', + 'v', '0', '0', '1', '\0' } + }, + { .codec = VLC_CODEC_XCF, + .marker_size = 9 + 4 + 1, + .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ', + 'v', '0', '0', '2', '\0' } + }, + { .codec = VLC_CODEC_PNG, + .marker_size = 8, + .marker = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } + }, + { .codec = VLC_CODEC_GIF, + .marker_size = 6, + .marker = { 'G', 'I', 'F', '8', '7', 'a' } + }, + { .codec = VLC_CODEC_GIF, + .marker_size = 6, + .marker = { 'G', 'I', 'F', '8', '9', 'a' } + }, + /* XXX TIFF detection may be a bit weak */ + { .codec = VLC_CODEC_TIFF, + .marker_size = 4, + .marker = { 'I', 'I', 0x2a, 0x00 }, + }, + { .codec = VLC_CODEC_TIFF, + .marker_size = 4, + .marker = { 'M', 'M', 0x00, 0x2a }, + }, + { .codec = VLC_CODEC_BMP, + .detect = IsBmp, + }, + { .codec = VLC_CODEC_PCX, + .detect = IsPcx, + }, + { .codec = VLC_CODEC_LBM, + .detect = IsLbm, + }, + { .codec = VLC_CODEC_PNM, + .detect = IsPnm, + }, + { .codec = VLC_CODEC_JPEG, + .detect = IsJfif, + }, + { .codec = VLC_CODEC_JPEG, + .detect = IsSpiff, + }, + { .codec = 0 } +}; + +static int Open(vlc_object_t *object) +{ + demux_t *demux = (demux_t*)object; + + /* Detect the image type */ + const image_format_t *img; + + const uint8_t *peek; + int peek_size = 0; + for (int i = 0; ; i++) { + img = &formats[i]; + if (!img->codec) + return VLC_EGENERIC; + + if (img->detect) { + if (img->detect(demux->s)) + break; + } else { + if (peek_size < img->marker_size) + peek_size = stream_Peek(demux->s, &peek, img->marker_size); + if (peek_size >= img->marker_size && + !memcmp(peek, img->marker, img->marker_size)) + break; + } + } + msg_Dbg(demux, "Detected image: %s", + vlc_fourcc_GetDescription(VIDEO_ES, img->codec)); + + /* Load and if selected decode */ + es_format_t fmt; + es_format_Init(&fmt, VIDEO_ES, img->codec); + fmt.video.i_chroma = fmt.i_codec; + + block_t *data = Load(demux); + if (data && var_InheritBool(demux, "image-decode")) { + char *string = var_InheritString(demux, "image-chroma"); + vlc_fourcc_t chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, string); + free(string); + + data = Decode(demux, &fmt.video, chroma, data); + fmt.i_codec = fmt.video.i_chroma; + } + fmt.i_id = var_InheritInteger(demux, "image-id"); + fmt.i_group = var_InheritInteger(demux, "image-group"); + if (var_InheritURational(demux, + &fmt.video.i_frame_rate, + &fmt.video.i_frame_rate_base, + "image-fps") || + fmt.video.i_frame_rate <= 0 || fmt.video.i_frame_rate_base <= 0) { + msg_Err(demux, "Invalid frame rate, using 10/1 instead"); + fmt.video.i_frame_rate = 10; + fmt.video.i_frame_rate_base = 1; + } + + /* If loadind failed, we still continue to avoid mis-detection + * by other demuxers. */ + if (!data) + msg_Err(demux, "Failed to load the image"); + + /* */ + demux_sys_t *sys = malloc(sizeof(*sys)); + if (!sys) { + if (data) + block_Release(data); + es_format_Clean(&fmt); + return VLC_ENOMEM; + } + + sys->data = data; + sys->es = es_out_Add(demux->out, &fmt); + sys->duration = CLOCK_FREQ * var_InheritFloat(demux, "image-duration"); + sys->is_realtime = var_InheritBool(demux, "image-realtime"); + sys->pts_origin = sys->is_realtime ? mdate() : 0; + sys->pts_next = VLC_TS_INVALID; + date_Init(&sys->pts, fmt.video.i_frame_rate, fmt.video.i_frame_rate_base); + date_Set(&sys->pts, 0); + + es_format_Clean(&fmt); + + demux->pf_demux = Demux; + demux->pf_control = Control; + demux->p_sys = sys; + return VLC_SUCCESS; +} + +static void Close(vlc_object_t *object) +{ + demux_t *demux = (demux_t*)object; + demux_sys_t *sys = demux->p_sys; + + if (sys->data) + block_Release(sys->data); + free(sys); +} + -- 2.39.2