Add Spectrograph sources and test files.
[dictix.git] / libdictix / dix-recorder.c
blob2a8133038eb90bf5d48fe977f909abd50aee4a9e
1 /**
2 * Dictix / DixRecorder - dix-record.c
4 * Copyright (C) Martin Blanchard 2011 <tinram@gmx.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 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 General Public License for more details.
16 * You should have received a copy of the GNU 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
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gio/gio.h>
31 #include <gst/gst.h>
33 #include "dix-recorder.h"
35 enum {
36 PROP_0,
37 PROP_LAST_RECORD_TIMEVAL,
38 PROP_RECORDS_PATH
41 enum {
42 RECORDING_STARTED,
43 RECORDING_STOPPED,
44 PIPELINE_ERROR,
45 LAST_SIGNAL
48 static guint signals[LAST_SIGNAL] = { 0 };
50 struct _DixRecorderPrivate
52 gchar *path;
53 gchar *id;
55 GstBus *bus;
57 GstElement *source;
58 GstElement *converter;
59 GstElement *encoder;
60 GstElement *sink;
62 GDateTime *last_record_timeval;
63 guint record_count;
66 static const gchar *id_characters = "0123456789abcdef";
68 static gchar *days[] = {
69 NULL,
70 N_("Mon."),
71 N_("Tue."),
72 N_("Wen."),
73 N_("Thu."),
74 N_("Fri."),
75 N_("Sat."),
76 N_("Sun.")
79 static gchar *months[] = {
80 NULL,
81 N_("January"),
82 N_("February"),
83 N_("March"),
84 N_("April"),
85 N_("May"),
86 N_("June"),
87 N_("July"),
88 N_("August"),
89 N_("September"),
90 N_("October"),
91 N_("November"),
92 N_("December")
95 #define DIX_RECORDER_ID_SIZE 32
96 #define DIX_RECORDER_ID_CHARACTERS_SIZE 16
98 #define DIX_RECORDER_GET_PRIVATE(obj) \
99 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DIX_TYPE_RECORDER, DixRecorderPrivate))
101 G_DEFINE_TYPE (DixRecorder, dix_recorder, GST_TYPE_PIPELINE)
103 static void dix_recorder_set_file (DixRecorder *recorder);
104 static void dix_recorder_write_title_tag (DixRecorder *recorder, GFile *file);
106 static void dix_recorder_error_cb (GstBus *bus, GstMessage *message, gpointer data);
107 static void dix_recorder_element_cb (GstBus *bus, GstMessage *message, gpointer data);
108 static void dix_recorder_state_changed_cb (GstBus *bus, GstMessage *message, gpointer data);
109 static void dix_recorder_eos_cb (GstBus *bus, GstMessage *message, gpointer data);
111 static void
112 dix_recorder_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
114 DixRecorder *recorder = (DixRecorder *) object;
115 DixRecorderPrivate *priv = recorder->priv;
116 gint64 timeval = 0;
118 switch (prop_id) {
119 case PROP_LAST_RECORD_TIMEVAL: {
120 timeval = (gint64) g_value_get_uint64 (value);
121 priv->last_record_timeval = g_date_time_new_from_unix_local (timeval);
123 break;
124 } case PROP_RECORDS_PATH: {
125 priv->path = g_strdup (g_value_get_string (value));
127 break;
128 } default: {
129 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
134 static void
135 dix_recorder_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
137 DixRecorder *recorder = (DixRecorder *) object;
138 DixRecorderPrivate *priv = recorder->priv;
140 switch (prop_id) {
141 case PROP_RECORDS_PATH: {
142 g_value_set_string (value, g_strdup (priv->path));
144 break;
145 } default: {
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
151 static void
152 dix_recorder_dispose (GObject *object)
154 DixRecorder *recorder = (DixRecorder *) object;
155 DixRecorderPrivate *priv = recorder->priv;
157 if (priv->bus != NULL) {
158 gst_object_unref (GST_OBJECT (priv->bus));
160 priv->bus = NULL;
162 if (priv->last_record_timeval != NULL) {
163 g_date_time_unref (priv->last_record_timeval);
165 priv->last_record_timeval = NULL;
167 G_OBJECT_CLASS (dix_recorder_parent_class)->dispose (object);
170 static void
171 dix_recorder_finalize (GObject *object)
173 DixRecorder *recorder = (DixRecorder *) object;
174 DixRecorderPrivate *priv = recorder->priv;
176 g_free (priv->path);
177 if (priv->id != NULL) {
178 g_free (priv->id);
180 priv->path = NULL;
181 priv->id = NULL;
183 G_OBJECT_CLASS (dix_recorder_parent_class)->finalize (object);
186 static void
187 dix_recorder_class_init (DixRecorderClass* klass)
189 GObjectClass *gobject_class = (GObjectClass *) klass;
191 gobject_class->set_property = dix_recorder_set_property;
192 gobject_class->get_property = dix_recorder_get_property;
193 gobject_class->dispose = dix_recorder_dispose;
194 gobject_class->finalize = dix_recorder_finalize;
196 g_object_class_install_property (gobject_class,
197 PROP_LAST_RECORD_TIMEVAL,
198 g_param_spec_uint64 ("last-record-timeval",
199 "Last record timeval",
200 "The timeval of last record (should be use only once at creation time).",
201 0, G_MAXUINT64, 0,
202 G_PARAM_WRITABLE));
204 g_object_class_install_property (gobject_class,
205 PROP_RECORDS_PATH,
206 g_param_spec_string ("record-path",
207 "Records directory",
208 "A path to the directory where records are put",
209 NULL,
210 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
212 signals[RECORDING_STARTED] = g_signal_new ("record-started",
213 G_TYPE_FROM_CLASS (gobject_class),
214 G_SIGNAL_RUN_LAST,
215 G_STRUCT_OFFSET (DixRecorderClass, record_started),
216 NULL, NULL,
217 g_cclosure_marshal_VOID__VOID,
218 G_TYPE_NONE, 0);
220 signals[RECORDING_STOPPED] = g_signal_new ("record-stopped",
221 G_TYPE_FROM_CLASS (gobject_class),
222 G_SIGNAL_RUN_LAST,
223 G_STRUCT_OFFSET (DixRecorderClass, record_stopped),
224 NULL, NULL,
225 g_cclosure_marshal_VOID__STRING,
226 G_TYPE_NONE, 1, G_TYPE_STRING);
228 /*signals[PIPELINE_ERROR] = g_signal_new ("pipeline-error",
229 G_TYPE_FROM_CLASS (gobject_class),
230 G_SIGNAL_RUN_LAST,
231 G_STRUCT_OFFSET (DixRecorderClass, record_stopped),
232 NULL, NULL,
233 g_cclosure_marshal_VOID__POINTER,
234 1, DIX_ERROR, G_TYPE_NONE, 0);*/
236 g_type_class_add_private (gobject_class, sizeof (DixRecorderPrivate));
239 static void
240 dix_recorder_init (DixRecorder* recorder)
242 DixRecorderPrivate *priv = DIX_RECORDER_GET_PRIVATE (recorder);
243 recorder->priv = priv;
245 gst_object_set_name (GST_OBJECT(recorder), "DictixRecorder");
247 priv->path = NULL;
248 priv->id = NULL;
250 priv->bus = NULL;
252 priv->source = NULL;
253 priv->converter = NULL;
254 priv->encoder = NULL;
255 priv->sink = NULL;
257 priv->last_record_timeval = NULL;
258 priv->record_count = 1;
261 DixRecorder*
262 dix_recorder_new (const gchar* directory)
264 g_return_val_if_fail ((directory != NULL), NULL);
266 return g_object_new (DIX_TYPE_RECORDER, "record-path", directory, NULL);
269 void
270 dix_recorder_start_recording (DixRecorder *recorder, GError **error)
272 g_return_if_fail (DIX_IS_RECORDER (recorder));
273 DixRecorderPrivate *priv = recorder->priv;
275 if (priv->id != NULL) {
276 /* We are alredy recording ... */
277 return;
280 if (G_UNLIKELY (priv->bus == NULL)) {
281 priv->source = gst_element_factory_make ("pulsesrc", "DictixRecorderSource");
282 priv->converter = gst_element_factory_make ("audioconvert", "DictixRecorderConverter");
283 priv->encoder = gst_element_factory_make ("flacenc", "DictixRecorderEncoder");
284 priv->sink = gst_element_factory_make ("giosink", "DictixRecorderSink");
286 if (priv->source == NULL || priv->converter == NULL
287 || priv->encoder == NULL || priv->sink == NULL) {
288 /* *error = g_error_new (DIX_ERROR, DIX_ERROR_GST_MISSING_PLUGINS,*/
289 /* _("You need the \"good\" set of gstreamer's plugin."));*/
291 return;
294 gst_bin_add_many (GST_BIN (recorder),
295 priv->source,
296 priv->converter,
297 priv->encoder,
298 priv->sink,
299 NULL);
301 gst_element_link_many (priv->source,
302 priv->converter,
303 priv->encoder,
304 priv->sink,
305 NULL);
307 priv->bus = gst_pipeline_get_bus (GST_PIPELINE (recorder));
308 gst_bus_add_signal_watch (priv->bus);
309 g_signal_connect (priv->bus, "message::error",
310 G_CALLBACK (dix_recorder_error_cb), recorder);
311 g_signal_connect (priv->bus, "message::element",
312 G_CALLBACK (dix_recorder_element_cb), recorder);
313 g_signal_connect (priv->bus, "message::state-changed",
314 G_CALLBACK (dix_recorder_state_changed_cb), recorder);
315 g_signal_connect (priv->bus, "message::eos",
316 G_CALLBACK (dix_recorder_eos_cb), recorder);
319 dix_recorder_set_file (recorder);
321 gst_element_set_state (GST_ELEMENT (recorder), GST_STATE_PLAYING);
324 void
325 dix_recorder_stop_recording (DixRecorder *recorder)
327 g_return_if_fail (DIX_IS_RECORDER (recorder));
328 DixRecorderPrivate *priv = recorder->priv;
330 if (G_UNLIKELY (priv->id == NULL)) {
331 /* We are not recording... */
332 return;
335 gst_element_set_state (priv->source, GST_STATE_NULL); /* Will emit EOS */
336 gst_element_get_state (priv->source, NULL, NULL, GST_CLOCK_TIME_NONE);
337 gst_element_set_locked_state (priv->source, TRUE);
340 static void
341 dix_recorder_set_file (DixRecorder *recorder)
343 DixRecorderPrivate *priv = recorder->priv;
344 gchar *id = NULL;
345 gchar *file, *uri;
346 gint i = 0;
348 if (priv->id != NULL) {
349 return;
352 id = g_malloc (sizeof (gchar) * (DIX_RECORDER_ID_SIZE + 1));
353 id[DIX_RECORDER_ID_SIZE] = 0;
355 while (i < DIX_RECORDER_ID_SIZE) {
356 gdouble random;
357 guint character;
359 random = g_random_double ();
360 character = random * (gdouble) (DIX_RECORDER_ID_CHARACTERS_SIZE - 1);
361 id[i] = id_characters[character];
362 ++i;
365 if (id == NULL) {
366 return;
369 file = g_build_filename (priv->path,
370 g_strconcat (id,
371 ".flac",
372 NULL),
373 NULL);
375 priv->id = g_strdup (id);
376 uri = g_filename_to_uri (file, NULL, NULL);
377 g_object_set (G_OBJECT (priv->sink), "location", uri, NULL);
379 g_free (uri);
380 g_free (file);
381 g_free (id);
384 static void
385 dix_recorder_write_title_tag (DixRecorder *recorder, GFile *file)
387 DixRecorderPrivate *priv = recorder->priv;
388 GError *error = NULL;
389 GFileInfo *info;
390 GDateTime *time = NULL;
391 GTimeSpan difference = 0;
392 gchar *title = NULL;
393 gchar *suffix = NULL;
394 gint64 date;
395 gint weekday, day, month, hour, minute;
397 if (file == NULL) {
398 title = _("Unknow record");
399 } else {
400 info = g_file_query_info (file,
401 G_FILE_ATTRIBUTE_TIME_MODIFIED,
402 G_FILE_QUERY_INFO_NONE,
403 NULL,
404 &error);
406 if (error != NULL) {
407 g_warning (error->message);
408 title = _("Unknow record");
409 } else {
410 date = (gint64) g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
411 time = g_date_time_new_from_unix_local (date);
413 g_object_unref (G_OBJECT (info));
415 weekday = g_date_time_get_day_of_week (time);
416 day = g_date_time_get_day_of_month (time);
417 month = g_date_time_get_month (time);
418 hour = g_date_time_get_hour (time);
419 minute = g_date_time_get_minute (time);
421 if (priv->last_record_timeval != NULL) {
422 difference = g_date_time_difference (time, priv->last_record_timeval);
423 difference /= G_TIME_SPAN_SECOND;
425 if (difference < 60) {
426 if (difference < (60 - g_date_time_get_seconds (priv->last_record_timeval))) {
427 priv->record_count++;
428 suffix = g_strconcat (" [",
429 g_strdup_printf ("%u", priv->record_count),
430 "]",
431 NULL);
432 } else {
433 priv->record_count = 1;
437 g_date_time_unref (priv->last_record_timeval);
439 priv->last_record_timeval = time;
441 title = g_strconcat (days[weekday],
442 g_strdup_printf (" %d ", day),
443 months[month],
444 g_strdup_printf (" %02d", hour),
445 g_strdup_printf (":%02d", minute),
446 suffix, /* May be NULL */
447 NULL);
451 if (g_utf8_validate (title, -1, NULL)) {
452 gst_tag_setter_add_tags (GST_TAG_SETTER (priv->encoder),
453 GST_TAG_MERGE_REPLACE,
454 GST_TAG_TITLE, title, /* Duplicate title ? */
455 NULL);
458 if (suffix != NULL) {
459 g_free (suffix);
461 g_free (title);
464 static void
465 dix_recorder_error_cb (GstBus *bus, GstMessage *message, gpointer data)
467 DixRecorder *recorder = (DixRecorder *) data;
468 DixRecorderPrivate *priv = recorder->priv;
469 GError *error;
471 /* error = g_error_new (DIX_ERROR, DIX_ERROR_GST_INTERNAL,*/
472 /* _("Audio engine internal error"));*/
475 static void
476 dix_recorder_element_cb (GstBus *bus, GstMessage *message, gpointer data)
478 DixRecorder *recorder = (DixRecorder *) data;
479 DixRecorderPrivate *priv = recorder->priv;
480 const GstStructure *structure;
482 if (message->src != GST_OBJECT (priv->sink)) {
483 return;
486 structure = gst_message_get_structure (message);
487 if (gst_structure_has_name (structure, "file-exists")) {
488 /* File alredy exists, shoud not happened... */
489 gst_bus_set_flushing (priv->bus, TRUE);
490 g_free (priv->id);
492 dix_recorder_set_file (recorder);
494 gst_bus_set_flushing (priv->bus, FALSE);
495 gst_element_set_state (GST_ELEMENT (recorder), GST_STATE_PLAYING);
499 static void
500 dix_recorder_state_changed_cb (GstBus *bus, GstMessage *message, gpointer data)
502 DixRecorder *recorder = (DixRecorder *) data;
503 DixRecorderPrivate *priv = recorder->priv;
504 GstState state;
505 GFile *file = NULL;
507 gst_message_parse_state_changed (message, NULL, &state, NULL);
508 if (message->src != GST_OBJECT (recorder)) {
509 return;
512 switch (state) {
513 case GST_STATE_NULL: {
514 break;
515 } case GST_STATE_READY: {
516 g_object_get (G_OBJECT (priv->sink), "file", &file, NULL);
517 dix_recorder_write_title_tag (recorder, file);
518 break;
519 } case GST_STATE_PLAYING: {
520 g_signal_emit (recorder, signals[RECORDING_STARTED], 0);
521 break;
522 } default: {
523 break;
528 static void
529 dix_recorder_eos_cb (GstBus *bus, GstMessage *message, gpointer data)
531 DixRecorder *recorder = (DixRecorder *) data;
532 DixRecorderPrivate *priv = recorder->priv;
533 gchar *uri = NULL;
535 gst_element_set_state (GST_ELEMENT (recorder), GST_STATE_NULL);
536 gst_element_get_state (GST_ELEMENT (recorder), NULL, NULL, GST_CLOCK_TIME_NONE);
538 g_object_get (G_OBJECT (priv->sink), "location", &uri, NULL);
540 g_signal_emit (recorder, signals[RECORDING_STOPPED], 0, uri);
542 g_free (priv->id);
543 priv->id = NULL;
545 gst_element_set_locked_state (priv->source, FALSE);