1 /*****************************************************************************
2 * fingerprinter.c: Audio fingerprinter module
3 *****************************************************************************
4 * Copyright (C) 2012 VLC authors and VideoLAN
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
26 #include <vlc_common.h>
27 #include <vlc_plugin.h>
28 #include <vlc_stream.h>
29 #include <vlc_modules.h>
33 #include <vlc_input.h>
34 #include <vlc_fingerprinter.h>
35 #include "webservices/acoustid.h"
36 #include "../stream_out/chromaprint_data.h"
38 /*****************************************************************************
40 *****************************************************************************/
42 struct fingerprinter_sys_t
61 static int Open (vlc_object_t
*);
62 static void Close (vlc_object_t
*);
63 static void CleanSys (fingerprinter_sys_t
*);
64 static void *Run(void *);
66 /*****************************************************************************
68 ****************************************************************************/
70 set_category(CAT_ADVANCED
)
71 set_subcategory(SUBCAT_ADVANCED_MISC
)
72 set_shortname(N_("acoustid"))
73 set_description(N_("Track fingerprinter (based on Acoustid)"))
74 set_capability("fingerprinter", 10)
75 set_callbacks(Open
, Close
)
78 /*****************************************************************************
80 *****************************************************************************/
82 static int EnqueueRequest( fingerprinter_thread_t
*f
, fingerprint_request_t
*r
)
84 fingerprinter_sys_t
*p_sys
= f
->p_sys
;
85 vlc_mutex_lock( &p_sys
->incoming
.lock
);
86 int i_ret
= vlc_array_append( &p_sys
->incoming
.queue
, r
);
87 vlc_mutex_unlock( &p_sys
->incoming
.lock
);
91 static void QueueIncomingRequests( fingerprinter_sys_t
*p_sys
)
93 vlc_mutex_lock( &p_sys
->incoming
.lock
);
95 for( size_t i
= vlc_array_count( &p_sys
->incoming
.queue
); i
> 0 ; i
-- )
97 fingerprint_request_t
*r
= vlc_array_item_at_index( &p_sys
->incoming
.queue
, i
- 1 );
98 if( vlc_array_append( &p_sys
->processing
.queue
, r
) )
99 fingerprint_request_Delete( r
);
101 vlc_array_clear( &p_sys
->incoming
.queue
);
102 vlc_mutex_unlock(&p_sys
->incoming
.lock
);
105 static fingerprint_request_t
* GetResult( fingerprinter_thread_t
*f
)
107 fingerprint_request_t
*r
= NULL
;
108 fingerprinter_sys_t
*p_sys
= f
->p_sys
;
109 vlc_mutex_lock( &p_sys
->results
.lock
);
110 if ( vlc_array_count( &p_sys
->results
.queue
) )
112 r
= vlc_array_item_at_index( &p_sys
->results
.queue
, 0 );
113 vlc_array_remove( &p_sys
->results
.queue
, 0 );
115 vlc_mutex_unlock( &p_sys
->results
.lock
);
119 static void ApplyResult( fingerprint_request_t
*p_r
, size_t i_resultid
)
121 if ( i_resultid
>= vlc_array_count( & p_r
->results
.metas_array
) ) return;
123 vlc_meta_t
*p_meta
= (vlc_meta_t
*)
124 vlc_array_item_at_index( & p_r
->results
.metas_array
, i_resultid
);
125 input_item_t
*p_item
= p_r
->p_item
;
126 vlc_mutex_lock( &p_item
->lock
);
127 vlc_meta_Merge( p_item
->p_meta
, p_meta
);
128 vlc_mutex_unlock( &p_item
->lock
);
131 static void InputEvent( input_thread_t
*p_input
, void *p_user_data
,
132 const struct vlc_input_event
*p_event
)
134 VLC_UNUSED( p_input
);
135 fingerprinter_sys_t
*p_sys
= p_user_data
;
136 if( p_event
->type
== INPUT_EVENT_STATE
)
138 if( p_event
->state
>= PAUSE_S
)
140 vlc_mutex_lock( &p_sys
->processing
.lock
);
141 p_sys
->processing
.b_working
= false;
142 vlc_cond_signal( &p_sys
->processing
.cond
);
143 vlc_mutex_unlock( &p_sys
->processing
.lock
);
148 static void DoFingerprint( fingerprinter_thread_t
*p_fingerprinter
,
149 acoustid_fingerprint_t
*fp
,
150 const char *psz_uri
)
152 input_item_t
*p_item
= input_item_New( NULL
, NULL
);
153 if ( unlikely(p_item
== NULL
) )
156 char *psz_sout_option
;
157 /* Note: need at -max- 2 channels, but we can't guess it before playing */
158 /* the stereo upmix could make the mono tracks fingerprint to differ :/ */
159 if ( asprintf( &psz_sout_option
,
160 "sout=#transcode{acodec=%s,channels=2}:chromaprint",
161 ( VLC_CODEC_S16L
== VLC_CODEC_S16N
) ? "s16l" : "s16b" )
164 input_item_Release( p_item
);
168 input_item_AddOption( p_item
, psz_sout_option
, VLC_INPUT_OPTION_TRUSTED
);
169 free( psz_sout_option
);
170 input_item_AddOption( p_item
, "vout=dummy", VLC_INPUT_OPTION_TRUSTED
);
171 input_item_AddOption( p_item
, "aout=dummy", VLC_INPUT_OPTION_TRUSTED
);
172 if ( fp
->i_duration
)
174 if ( asprintf( &psz_sout_option
, "stop-time=%u", fp
->i_duration
) == -1 )
176 input_item_Release( p_item
);
179 input_item_AddOption( p_item
, psz_sout_option
, VLC_INPUT_OPTION_TRUSTED
);
180 free( psz_sout_option
);
182 input_item_SetURI( p_item
, psz_uri
) ;
184 input_thread_t
*p_input
= input_Create( p_fingerprinter
, InputEvent
,
185 p_fingerprinter
->p_sys
,
186 p_item
, "fingerprinter", NULL
, NULL
);
187 input_item_Release( p_item
);
189 if( p_input
== NULL
)
192 chromaprint_fingerprint_t chroma_fingerprint
;
194 chroma_fingerprint
.psz_fingerprint
= NULL
;
195 chroma_fingerprint
.i_duration
= fp
->i_duration
;
197 var_Create( p_input
, "fingerprint-data", VLC_VAR_ADDRESS
);
198 var_SetAddress( p_input
, "fingerprint-data", &chroma_fingerprint
);
200 if( input_Start( p_input
) != VLC_SUCCESS
)
201 input_Close( p_input
);
204 p_fingerprinter
->p_sys
->processing
.b_working
= true;
205 while( p_fingerprinter
->p_sys
->processing
.b_working
)
207 vlc_cond_wait( &p_fingerprinter
->p_sys
->processing
.cond
,
208 &p_fingerprinter
->p_sys
->processing
.lock
);
210 input_Stop( p_input
);
211 input_Close( p_input
);
213 fp
->psz_fingerprint
= chroma_fingerprint
.psz_fingerprint
;
214 if( !fp
->i_duration
) /* had not given hint */
215 fp
->i_duration
= chroma_fingerprint
.i_duration
;
219 /*****************************************************************************
221 *****************************************************************************/
222 static int Open(vlc_object_t
*p_this
)
224 fingerprinter_thread_t
*p_fingerprinter
= (fingerprinter_thread_t
*) p_this
;
225 fingerprinter_sys_t
*p_sys
= calloc(1, sizeof(fingerprinter_sys_t
));
230 p_fingerprinter
->p_sys
= p_sys
;
232 vlc_array_init( &p_sys
->incoming
.queue
);
233 vlc_mutex_init( &p_sys
->incoming
.lock
);
235 vlc_array_init( &p_sys
->processing
.queue
);
236 vlc_mutex_init( &p_sys
->processing
.lock
);
237 vlc_cond_init( &p_sys
->processing
.cond
);
239 vlc_array_init( &p_sys
->results
.queue
);
240 vlc_mutex_init( &p_sys
->results
.lock
);
242 p_fingerprinter
->pf_enqueue
= EnqueueRequest
;
243 p_fingerprinter
->pf_getresults
= GetResult
;
244 p_fingerprinter
->pf_apply
= ApplyResult
;
246 var_Create( p_fingerprinter
, "results-available", VLC_VAR_BOOL
);
247 if( vlc_clone( &p_sys
->thread
, Run
, p_fingerprinter
,
248 VLC_THREAD_PRIORITY_LOW
) )
250 msg_Err( p_fingerprinter
, "cannot spawn fingerprinter thread" );
262 /*****************************************************************************
264 *****************************************************************************/
265 static void Close(vlc_object_t
*p_this
)
267 fingerprinter_thread_t
*p_fingerprinter
= (fingerprinter_thread_t
*) p_this
;
268 fingerprinter_sys_t
*p_sys
= p_fingerprinter
->p_sys
;
270 vlc_cancel( p_sys
->thread
);
271 vlc_join( p_sys
->thread
, NULL
);
277 static void CleanSys( fingerprinter_sys_t
*p_sys
)
279 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->incoming
.queue
); i
++ )
280 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->incoming
.queue
, i
) );
281 vlc_array_clear( &p_sys
->incoming
.queue
);
282 vlc_mutex_destroy( &p_sys
->incoming
.lock
);
284 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->processing
.queue
); i
++ )
285 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->processing
.queue
, i
) );
286 vlc_array_clear( &p_sys
->processing
.queue
);
287 vlc_mutex_destroy( &p_sys
->processing
.lock
);
288 vlc_cond_destroy( &p_sys
->processing
.cond
);
290 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->results
.queue
); i
++ )
291 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->results
.queue
, i
) );
292 vlc_array_clear( &p_sys
->results
.queue
);
293 vlc_mutex_destroy( &p_sys
->results
.lock
);
296 static void fill_metas_with_results( fingerprint_request_t
*p_r
, acoustid_fingerprint_t
*p_f
)
298 for( unsigned int i
=0 ; i
< p_f
->results
.count
; i
++ )
300 acoustid_result_t
*p_result
= & p_f
->results
.p_results
[ i
];
301 for ( unsigned int j
=0 ; j
< p_result
->recordings
.count
; j
++ )
303 musicbrainz_recording_t
*p_record
= & p_result
->recordings
.p_recordings
[ j
];
304 vlc_meta_t
*p_meta
= vlc_meta_New();
307 vlc_meta_Set( p_meta
, vlc_meta_Title
, p_record
->psz_title
);
308 vlc_meta_Set( p_meta
, vlc_meta_Artist
, p_record
->psz_artist
);
309 vlc_meta_AddExtra( p_meta
, "musicbrainz-id", p_record
->s_musicbrainz_id
);
310 if( vlc_array_append( & p_r
->results
.metas_array
, p_meta
) )
311 vlc_meta_Delete( p_meta
);
317 /*****************************************************************************
319 *****************************************************************************/
320 static void *Run( void *opaque
)
322 fingerprinter_thread_t
*p_fingerprinter
= opaque
;
323 fingerprinter_sys_t
*p_sys
= p_fingerprinter
->p_sys
;
325 vlc_mutex_lock( &p_sys
->processing
.lock
);
326 mutex_cleanup_push( &p_sys
->processing
.lock
);
331 vlc_tick_sleep( CLOCK_FREQ
);
333 QueueIncomingRequests( p_sys
);
337 bool results_available
= false;
338 while( vlc_array_count( &p_sys
->processing
.queue
) )
340 int canc
= vlc_savecancel();
341 fingerprint_request_t
*p_data
= vlc_array_item_at_index( &p_sys
->processing
.queue
, 0 );
343 char *psz_uri
= input_item_GetURI( p_data
->p_item
);
344 if ( psz_uri
!= NULL
)
346 acoustid_fingerprint_t acoustid_print
;
348 memset( &acoustid_print
, 0, sizeof (acoustid_print
) );
349 /* overwrite with hint, as in this case, fingerprint's session will be truncated */
350 if ( p_data
->i_duration
)
351 acoustid_print
.i_duration
= p_data
->i_duration
;
353 DoFingerprint( p_fingerprinter
, &acoustid_print
, psz_uri
);
356 DoAcoustIdWebRequest( VLC_OBJECT(p_fingerprinter
), &acoustid_print
);
357 fill_metas_with_results( p_data
, &acoustid_print
);
359 for( unsigned j
= 0; j
< acoustid_print
.results
.count
; j
++ )
360 free_acoustid_result_t( &acoustid_print
.results
.p_results
[j
] );
361 if( acoustid_print
.results
.count
)
362 free( acoustid_print
.results
.p_results
);
363 free( acoustid_print
.psz_fingerprint
);
365 vlc_restorecancel(canc
);
368 vlc_mutex_lock( &p_sys
->results
.lock
);
369 if( vlc_array_append( &p_sys
->results
.queue
, p_data
) )
370 fingerprint_request_Delete( p_data
);
372 results_available
= true;
373 vlc_mutex_unlock( &p_sys
->results
.lock
);
375 // the fingerprint request must not exist both in the
376 // processing and results queue, even in case of thread
377 // cancellation, so remove it immediately
378 vlc_array_remove( &p_sys
->processing
.queue
, 0 );
383 if ( results_available
)
385 var_TriggerCallback( p_fingerprinter
, "results-available" );
390 vlc_assert_unreachable();