[vlc-devel] [PATCH 2/3] core: Add a thumbnailing API
Hugo Beauzée-Luyssen
hugo at beauzee.fr
Fri Oct 5 16:37:55 CEST 2018
ref #17368
---
include/vlc_input.h | 11 ++
include/vlc_thumbnailer.h | 92 +++++++++++++++++
src/Makefile.am | 2 +
src/input/decoder.c | 45 +++++++-
src/input/es_out.c | 5 +-
src/input/input.c | 19 +++-
src/input/input_internal.h | 1 +
src/input/thumbnailer.c | 206 +++++++++++++++++++++++++++++++++++++
src/input/var.c | 2 +
src/libvlccore.sym | 5 +
10 files changed, 380 insertions(+), 8 deletions(-)
create mode 100644 include/vlc_thumbnailer.h
create mode 100644 src/input/thumbnailer.c
diff --git a/include/vlc_input.h b/include/vlc_input.h
index 88d1da091f..7179eed1ea 100644
--- a/include/vlc_input.h
+++ b/include/vlc_input.h
@@ -388,6 +388,9 @@ typedef enum input_event_type_e
/* (pre-)parsing events */
INPUT_EVENT_SUBITEMS,
+ /* Thumbnail generation */
+ INPUT_EVENT_THUMBNAIL_READY,
+
} input_event_type_e;
#define VLC_INPUT_CAPABILITIES_SEEKABLE (1<<0)
@@ -514,6 +517,8 @@ struct vlc_input_event
struct vlc_input_event_vout vout;
/* INPUT_EVENT_SUBITEMS */
input_item_node_t *subitems;
+ /* INPUT_EVENT_THUMBNAIL_READY */
+ picture_t *thumbnail;
};
};
@@ -607,6 +612,12 @@ VLC_API input_thread_t *input_CreatePreparser(vlc_object_t *obj,
void *events_data, input_item_t *item)
VLC_USED;
+VLC_API
+input_thread_t *input_CreateThumbnailer(vlc_object_t *obj,
+ input_thread_events_cb events_cb,
+ void *events_data, input_item_t *item)
+VLC_USED;
+
VLC_API int input_Start( input_thread_t * );
VLC_API void input_Stop( input_thread_t * );
diff --git a/include/vlc_thumbnailer.h b/include/vlc_thumbnailer.h
new file mode 100644
index 0000000000..94c5cbdf23
--- /dev/null
+++ b/include/vlc_thumbnailer.h
@@ -0,0 +1,92 @@
+/*****************************************************************************
+ * vlc_thumbnailer.h: Thumbnailing API
+ *****************************************************************************
+ * Copyright (C) 1998-2018 VLC authors and VideoLAN
+ *
+ * Authors: Hugo Beauzée-Luyssen <hugo at beauzee.fr>
+ *
+ * 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.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifndef VLC_THUMBNAILER_H
+#define VLC_THUMBNAILER_H
+
+#include <vlc_common.h>
+
+typedef struct vlc_thumbnailer_t vlc_thumbnailer_t;
+typedef struct vlc_thumbnailer_request_t vlc_thumbnailer_request_t;
+
+/**
+ * \brief vlc_thumbnailer_cb defines a callback invoked on thumbnailing completion or error
+ *
+ * This callback will always be called, provided vlc_thumbnailer_Request returned
+ * a non NULL request, and provided the request is not cancelled before its
+ * completion.
+ * In case of failure, p_thumbnail will be NULL.
+ * The picture, if any, is owned by the thumbnail, and must be acquired by using
+ * \link picture_Hold \endlink to use it passed the callback's scope.
+ *
+ * \param data Is the opaque pointer passed as vlc_thumbnailer_Request last parameter
+ * \param p_thumbnail The generated thumbnail, or NULL in case of failure
+ */
+typedef void(*vlc_thumbnailer_cb)( void* data, picture_t* p_thumbnail );
+
+/**
+ * \brief vlc_thumbnailer_Create Creates a thumbnailer object
+ * \param p_parent A VLC object
+ * \return A thumbnailer object, or NULL in case of failure
+ */
+VLC_API vlc_thumbnailer_t*
+vlc_thumbnailer_Create( vlc_object_t* p_parent )
+VLC_USED;
+
+/**
+ * \brief vlc_thumbnailer_Request
+ * \param p_thumbnailer A thumbnailer object
+ * \param p_input_item The input item for which to generate the thumbnail
+ * \param i_time The time at which the thumbnail must be generated
+ * \param p_cb A callback to be invoked upon completion or error
+ * \param p_user_data An opaque user-provided value
+ * \return An opaque request object, or NULL in case of failure
+ *
+ * If this function returns a valid request object, the callback is guaranteed
+ * to be called, even in case of later failure.
+ * The returned request object must not be used after the callback has been
+ * invoked. That object is owned by the thumbnailer, and must not be released.
+ */
+VLC_API vlc_thumbnailer_request_t*
+vlc_thumbnailer_Request( vlc_thumbnailer_t *p_thumbnailer,
+ input_item_t* p_input_item, vlc_tick_t i_time,
+ vlc_thumbnailer_cb p_cb, void* p_user_data );
+
+/**
+ * \brief vlc_thumbnailer_Cancel Cancel a thumbnail request
+ * \param p_thumbnailer A thumbnailer object
+ * \param p_request An opaque thumbnail request object
+ *
+ * Cancelling a request will *not* invoke the completion callback.
+ * The behavior is undefined if the request is cancelled after its completion.
+ */
+VLC_API void
+vlc_thumbnailer_Cancel( vlc_thumbnailer_t* p_thumbnailer,
+ vlc_thumbnailer_request_t* p_request );
+
+/**
+ * \brief vlc_thumbnailer_Release releases a thumbnailer and cancel all pending requests
+ * \param p_thumbnailer A thumbnailer object
+ */
+VLC_API void vlc_thumbnailer_Release( vlc_thumbnailer_t* p_thumbnailer );
+
+#endif // VLC_THUMBNAILER_H
diff --git a/src/Makefile.am b/src/Makefile.am
index c81339f961..e65870e4f7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -94,6 +94,7 @@ pluginsinclude_HEADERS = \
../include/vlc_threads.h \
../include/vlc_tick.h \
../include/vlc_timestamp_helper.h \
+ ../include/vlc_thumbnailer.h \
../include/vlc_tls.h \
../include/vlc_url.h \
../include/vlc_variables.h \
@@ -267,6 +268,7 @@ libvlccore_la_SOURCES = \
input/stream_filter.c \
input/stream_memory.c \
input/subtitles.c \
+ input/thumbnailer.c \
input/var.c \
audio_output/aout_internal.h \
audio_output/common.c \
diff --git a/src/input/decoder.c b/src/input/decoder.c
index a280eacc72..2f50bf83a5 100644
--- a/src/input/decoder.c
+++ b/src/input/decoder.c
@@ -1126,6 +1126,37 @@ static void DecoderQueueVideo( decoder_t *p_dec, picture_t *p_pic )
p_owner->pf_update_stat( p_owner, 1, i_lost );
}
+static int thumbnailer_update_format( decoder_t *p_dec )
+{
+ VLC_UNUSED(p_dec);
+ return 0;
+}
+
+static picture_t *thumbnailer_buffer_new( decoder_t *p_dec )
+{
+ struct decoder_owner *p_owner = dec_get_owner( p_dec );
+ /* Avoid decoding more than one frame when a thumbnail was
+ * already generated */
+ if( !p_owner->b_first )
+ return NULL;
+ return picture_NewFromFormat( &p_dec->fmt_out.video );
+}
+
+static void DecoderQueueThumbnail( decoder_t *p_dec, picture_t *p_pic )
+{
+ struct decoder_owner *p_owner = dec_get_owner( p_dec );
+ if( !p_owner->b_first )
+ {
+ picture_Release( p_pic );
+ return;
+ }
+ input_SendEvent(p_owner->p_input, &(struct vlc_input_event) {
+ .type = INPUT_EVENT_THUMBNAIL_READY,
+ .thumbnail = p_pic
+ });
+ p_owner->b_first = false;
+}
+
static void DecoderPlayAudio( decoder_t *p_dec, block_t *p_audio,
unsigned *restrict pi_lost_sum )
{
@@ -1695,6 +1726,15 @@ static const struct decoder_owner_callbacks dec_video_cbs =
},
.get_attachments = DecoderGetInputAttachments,
};
+static const struct decoder_owner_callbacks dec_thumbnailer_cbs =
+{
+ .video = {
+ .format_update = thumbnailer_update_format,
+ .buffer_new = thumbnailer_buffer_new,
+ .queue = DecoderQueueThumbnail,
+ },
+ .get_attachments = DecoderGetInputAttachments,
+};
static const struct decoder_owner_callbacks dec_audio_cbs =
{
.audio = {
@@ -1810,7 +1850,10 @@ static decoder_t * CreateDecoder( vlc_object_t *p_parent,
switch( fmt->i_cat )
{
case VIDEO_ES:
- p_dec->cbs = &dec_video_cbs;
+ if( !input_priv( p_input )->b_thumbnailing )
+ p_dec->cbs = &dec_video_cbs;
+ else
+ p_dec->cbs = &dec_thumbnailer_cbs;
p_owner->pf_update_stat = DecoderUpdateStatVideo;
break;
case AUDIO_ES:
diff --git a/src/input/es_out.c b/src/input/es_out.c
index b215c3bce1..743118d067 100644
--- a/src/input/es_out.c
+++ b/src/input/es_out.c
@@ -1789,6 +1789,7 @@ static void EsOutSelectEs( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = container_of(out, es_out_sys_t, out);
input_thread_t *p_input = p_sys->p_input;
+ bool b_thumbnailing = input_priv(p_input)->b_thumbnailing;
if( EsIsSelected( es ) )
{
@@ -1827,7 +1828,7 @@ static void EsOutSelectEs( es_out_t *out, es_out_id_t *es )
}
else if( es->fmt.i_cat == AUDIO_ES )
{
- if( !var_GetBool( p_input, b_sout ? "sout-audio" : "audio" ) )
+ if( !var_GetBool( p_input, b_sout ? "sout-audio" : "audio" ) || b_thumbnailing )
{
msg_Dbg( p_input, "audio is disabled, not selecting ES 0x%x",
es->fmt.i_id );
@@ -1836,7 +1837,7 @@ static void EsOutSelectEs( es_out_t *out, es_out_id_t *es )
}
if( es->fmt.i_cat == SPU_ES )
{
- if( !var_GetBool( p_input, b_sout ? "sout-spu" : "spu" ) )
+ if( !var_GetBool( p_input, b_sout ? "sout-spu" : "spu" ) || b_thumbnailing )
{
msg_Dbg( p_input, "spu is disabled, not selecting ES 0x%x",
es->fmt.i_id );
diff --git a/src/input/input.c b/src/input/input.c
index 8594cf03b8..1c06330f06 100644
--- a/src/input/input.c
+++ b/src/input/input.c
@@ -63,7 +63,7 @@ static void *Run( void * );
static void *Preparse( void * );
static input_thread_t * Create ( vlc_object_t *, input_thread_events_cb, void *,
- input_item_t *, const char *, bool,
+ input_item_t *, const char *, bool, bool,
input_resource_t *, vlc_renderer_item_t * );
static int Init ( input_thread_t *p_input );
static void End ( input_thread_t *p_input );
@@ -132,7 +132,7 @@ input_thread_t *input_Create( vlc_object_t *p_parent,
vlc_renderer_item_t *p_renderer )
{
return Create( p_parent, events_cb, events_data, p_item, psz_log, false,
- p_resource, p_renderer );
+ false, p_resource, p_renderer );
}
#undef input_Read
@@ -147,7 +147,7 @@ int input_Read( vlc_object_t *p_parent, input_item_t *p_item,
input_thread_events_cb events_cb, void *events_data )
{
input_thread_t *p_input = Create( p_parent, events_cb, events_data, p_item,
- NULL, false, NULL, NULL );
+ NULL, false, false, NULL, NULL );
if( !p_input )
return VLC_EGENERIC;
@@ -165,7 +165,14 @@ input_thread_t *input_CreatePreparser( vlc_object_t *parent,
input_thread_events_cb events_cb,
void *events_data, input_item_t *item )
{
- return Create( parent, events_cb, events_data, item, NULL, true, NULL, NULL );
+ return Create( parent, events_cb, events_data, item, NULL, true, false, NULL, NULL );
+}
+
+input_thread_t *input_CreateThumbnailer(vlc_object_t *obj,
+ input_thread_events_cb events_cb,
+ void *events_data, input_item_t *item)
+{
+ return Create( obj, events_cb, events_data, item, NULL, false, true, NULL, NULL );
}
/**
@@ -310,7 +317,8 @@ input_item_t *input_GetItem( input_thread_t *p_input )
static input_thread_t *Create( vlc_object_t *p_parent,
input_thread_events_cb events_cb, void *events_data,
input_item_t *p_item, const char *psz_header,
- bool b_preparsing, input_resource_t *p_resource,
+ bool b_preparsing, bool b_thumbnailing,
+ input_resource_t *p_resource,
vlc_renderer_item_t *p_renderer )
{
/* Allocate descriptor */
@@ -347,6 +355,7 @@ static input_thread_t *Create( vlc_object_t *p_parent,
priv->is_running = false;
priv->is_stopped = false;
priv->b_recording = false;
+ priv->b_thumbnailing = b_thumbnailing;
priv->i_rate = INPUT_RATE_DEFAULT;
memset( &priv->bookmark, 0, sizeof(priv->bookmark) );
TAB_INIT( priv->i_bookmark, priv->pp_bookmark );
diff --git a/src/input/input_internal.h b/src/input/input_internal.h
index 6ce7690ef3..618d06d521 100644
--- a/src/input/input_internal.h
+++ b/src/input/input_internal.h
@@ -123,6 +123,7 @@ typedef struct input_thread_private_t
bool is_running;
bool is_stopped;
bool b_recording;
+ bool b_thumbnailing;
int i_rate;
/* Playtime configuration and state */
diff --git a/src/input/thumbnailer.c b/src/input/thumbnailer.c
new file mode 100644
index 0000000000..05591b92ce
--- /dev/null
+++ b/src/input/thumbnailer.c
@@ -0,0 +1,206 @@
+/*****************************************************************************
+ * thumbnailer.c: Thumbnailing API
+ *****************************************************************************
+ * Copyright (C) 1998-2018 VLC authors and VideoLAN
+ *
+ * Authors: Hugo Beauzée-Luyssen <hugo at beauzee.fr>
+ *
+ * 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.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_thumbnailer.h>
+#include <vlc_input.h>
+#include <vlc_fs.h>
+#include <vlc_block.h>
+#include <vlc_atomic.h>
+#include "misc/background_worker.h"
+
+struct vlc_thumbnailer_t
+{
+ vlc_object_t* p_parent;
+ struct background_worker* worker;
+};
+
+struct vlc_thumbnailer_request_t
+{
+ vlc_thumbnailer_t *p_thumbnailer;
+ input_thread_t *p_input_thread;
+ vlc_tick_t i_time;
+ input_item_t *p_item;
+
+ vlc_thumbnailer_cb p_cb;
+ void* p_user_data;
+
+ vlc_mutex_t lock;
+ bool b_done;
+ picture_t* p_picture;
+
+ struct vlc_list list_item;
+};
+
+static void
+on_thumbnailer_input_event( input_thread_t *input,
+ const struct vlc_input_event *event, void *userdata )
+{
+ VLC_UNUSED(input);
+ if ( event->type != INPUT_EVENT_THUMBNAIL_READY &&
+ ( event->type != INPUT_EVENT_STATE || event->state != ERROR_S ) )
+ return;
+
+ vlc_thumbnailer_request_t* p_request = userdata;
+ picture_t *p_pic = NULL;
+
+ if ( event->type == INPUT_EVENT_THUMBNAIL_READY )
+ {
+ // Stop the input thread ASAP, delegate its release to
+ // thumbnailer_request_Release
+ input_Stop( p_request->p_input_thread );
+ p_pic = event->thumbnail;
+ }
+ vlc_mutex_lock( &p_request->lock );
+ p_request->b_done = true;
+ p_request->p_picture = p_pic;
+ // If the request has not been cancelled, we can invoke the completion
+ // callback. A request that was cancelled early in its processing might
+ // send an error state, which we don't want to propagate.
+ if ( p_request->p_cb )
+ p_request->p_cb( p_request->p_user_data, p_request->p_picture );
+ vlc_mutex_unlock( &p_request->lock );
+ background_worker_RequestProbe( p_request->p_thumbnailer->worker );
+}
+
+static void thumbnailer_request_Hold( void* p_data )
+{
+ VLC_UNUSED(p_data);
+}
+
+static void thumbnailer_request_Release( void* p_data )
+{
+ vlc_thumbnailer_request_t* p_request = p_data;
+ if ( p_request->p_input_thread )
+ input_Close( p_request->p_input_thread );
+
+ input_item_Release( p_request->p_item );
+ vlc_mutex_destroy( &p_request->lock );
+ if ( p_request->p_picture )
+ picture_Release( p_request->p_picture );
+ free( p_request );
+}
+
+static int thumbnailer_request_Start( void* owner, void* entity, void** out )
+{
+ vlc_thumbnailer_t *p_thumbnailer = owner;
+ vlc_thumbnailer_request_t *p_request = entity;
+ input_thread_t *p_input = p_request->p_input_thread =
+ input_CreateThumbnailer( p_thumbnailer->p_parent,
+ on_thumbnailer_input_event, p_request,
+ p_request->p_item );
+ if ( unlikely( p_input == NULL ) )
+ return VLC_EGENERIC;
+ var_SetFloat( p_input, "start-time", secf_from_vlc_tick( p_request->i_time ) );
+ if ( input_Start(p_input) != VLC_SUCCESS )
+ return VLC_EGENERIC;
+ *out = p_request;
+ return VLC_SUCCESS;
+}
+
+static void thumbnailer_request_Stop( void* owner, void* handle )
+{
+ VLC_UNUSED(owner);
+ vlc_thumbnailer_request_t *p_request = handle;
+ assert( p_request->p_input_thread != NULL );
+ input_Stop( p_request->p_input_thread );
+}
+
+static int thumbnailer_request_Probe( void* owner, void* handle )
+{
+ VLC_UNUSED(owner);
+ vlc_thumbnailer_request_t *p_request = handle;
+ vlc_mutex_lock( &p_request->lock );
+ int res = p_request->b_done;
+ vlc_mutex_unlock( &p_request->lock );
+ return res;
+}
+
+vlc_thumbnailer_request_t*
+vlc_thumbnailer_Request( vlc_thumbnailer_t* p_thumbnailer,
+ input_item_t* p_input_item, vlc_tick_t i_time,
+ vlc_thumbnailer_cb p_cb, void* p_user_data )
+{
+ vlc_thumbnailer_request_t *p_request = malloc( sizeof( *p_request ) );
+ if ( unlikely( p_request == NULL ) )
+ return NULL;
+ p_request ->p_thumbnailer = p_thumbnailer;
+ p_request->p_input_thread = NULL;
+ p_request->i_time = i_time;
+ p_request->p_item = input_item_Hold( p_input_item );
+ p_request->b_done = false;
+ p_request->p_picture = NULL;
+ p_request->p_cb = p_cb;
+ p_request->p_user_data = p_user_data;
+ vlc_mutex_init( &p_request->lock );
+
+ if ( background_worker_Push( p_thumbnailer->worker, p_request,
+ p_request, -1 ) != VLC_SUCCESS )
+ {
+ thumbnailer_request_Release( p_request );
+ return NULL;
+ }
+ return p_request;
+}
+
+void vlc_thumbnailer_Cancel( vlc_thumbnailer_t* p_thumbnailer,
+ vlc_thumbnailer_request_t* p_req )
+{
+ vlc_mutex_lock( &p_req->lock );
+ // Ensure we won't invoke the callback if the input was running.
+ p_req->p_cb = NULL;
+ vlc_mutex_unlock( &p_req->lock );
+ background_worker_Cancel( p_thumbnailer->worker, p_req );
+}
+
+vlc_thumbnailer_t *vlc_thumbnailer_Create( vlc_object_t* p_parent)
+{
+ vlc_thumbnailer_t *p_thumbnailer = malloc( sizeof( *p_thumbnailer ) );
+ if ( unlikely( p_thumbnailer == NULL ) )
+ return NULL;
+ p_thumbnailer->p_parent = p_parent;
+ struct background_worker_config cfg = {
+ .default_timeout = vlc_tick_from_secf( 5.f ),
+ .max_threads = 1,
+ .pf_release = thumbnailer_request_Release,
+ .pf_hold = thumbnailer_request_Hold,
+ .pf_start = thumbnailer_request_Start,
+ .pf_probe = thumbnailer_request_Probe,
+ .pf_stop = thumbnailer_request_Stop,
+ };
+ p_thumbnailer->worker = background_worker_New( p_thumbnailer, &cfg );
+ if ( unlikely( p_thumbnailer->worker == NULL ) )
+ {
+ free( p_thumbnailer );
+ return NULL;
+ }
+ return p_thumbnailer;
+}
+
+void vlc_thumbnailer_Release( vlc_thumbnailer_t *p_thumbnailer )
+{
+ background_worker_Delete( p_thumbnailer->worker );
+ free( p_thumbnailer );
+}
diff --git a/src/input/var.c b/src/input/var.c
index 30f9d7e94f..da1d62521b 100644
--- a/src/input/var.c
+++ b/src/input/var.c
@@ -416,6 +416,8 @@ void input_LegacyEvents( input_thread_t *p_input,
break;
case INPUT_EVENT_SUBITEMS:
break;
+ case INPUT_EVENT_THUMBNAIL_READY:
+ break;
}
Trigger( p_input, event->type );
}
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index a1709cb9f6..292a775b9b 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -154,6 +154,7 @@ input_Control
input_Create
input_CreateFilename
input_CreatePreparser
+input_CreateThumbnailer
input_DecoderCreate
input_DecoderDelete
input_DecoderDecode
@@ -784,3 +785,7 @@ vlc_es_id_Hold
vlc_es_id_Release
vlc_es_id_GetInputId
vlc_es_id_GetCat
+vlc_thumbnailer_Create
+vlc_thumbnailer_Request
+vlc_thumbnailer_Cancel
+vlc_thumbnailer_Release
--
2.19.0
More information about the vlc-devel
mailing list