/* * Copyright (c) Linux community. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include "gst-recorder.h" #include "rec-manager.h" #include "media-profiles.h" #include "log.h" #include "support.h" #include "dconf.h" #include "utility.h" #include "pulseaudio.h" #include "timer.h" #include "gst-pipeline.h" #define AUDIO_RECORDER 101 #define AUDIO_RECORDER_ERR1 -1 // GStreamer pipeline static GstElement *g_pipeline; // Flag to test if EOS (end of stream) message was seen static gboolean g_got_EOS_message = FALSE; static gboolean rec_level_message_cb(GstBus *bus, GstMessage *message, void *user_data); GstElement *rec_create_pipeline(gchar *audio_source, GList *dev_list, gchar *enc_pipeline, gchar *output_filename, gboolean append_to_file, GError **error); void rec_set_state_to_null(); static gchar *rec_create_filename(gchar *track, gchar *artist, gchar *album); static gchar *rec_generate_unique_filename(); static gchar *check_audio_folder(gchar *audio_folder); static gchar *rec_get_media_profile_id(); void rec_module_init() { LOG_DEBUG("Init gst-recorder.c.\n"); g_pipeline = NULL; } void rec_module_exit() { LOG_DEBUG("Clean up gst-recorder.c.\n"); // Stop evt. recording rec_stop_recording(FALSE); } void rec_set_state_to_null() { // Copyright notice: // I have copied this function from gnome-media-2.30.0/grecord. LOG_DEBUG("\n--------- rec_set_state_to_null() ----------\n"); // Set state of pipeline to GST_STATE_NULL. GstMessage *msg; GstState cur_state, pending; GstBus *bus; if (!GST_IS_PIPELINE(g_pipeline)) return; gst_element_get_state(g_pipeline, &cur_state, &pending, 0); if (cur_state == GST_STATE_NULL && pending == GST_STATE_VOID_PENDING) return; if (cur_state == GST_STATE_NULL && pending != GST_STATE_VOID_PENDING) { gst_element_set_state (g_pipeline, GST_STATE_NULL); return; } gst_element_set_state(g_pipeline, GST_STATE_READY); gst_element_get_state(g_pipeline, NULL, NULL, -1); bus = gst_element_get_bus(g_pipeline); if (GST_IS_BUS(bus)) { while ((msg = gst_bus_pop(bus))) { gst_bus_async_signal_func(bus, msg, NULL); if (msg) { gst_message_unref(msg); } } gst_object_unref(bus); } gst_element_set_state(g_pipeline, GST_STATE_NULL); } void rec_pause_recording() { // Valid pipeline? if (!GST_IS_PIPELINE(g_pipeline)) return; // Get recording state gint state = -1; gint pending = -1; rec_get_state(&state, &pending); // Already paused? if (state == GST_STATE_PAUSED) return; LOG_DEBUG("\n--------- rec_pause_recording() ----------\n"); // Reset timer timer_module_reset(GST_STATE_PAUSED); // Pause the stream gst_element_set_state(g_pipeline, GST_STATE_PAUSED); } void rec_continue_recording() { // Valid pipeline? if (!GST_IS_PIPELINE(g_pipeline)) return; // Get recording state gint state = -1; gint pending = -1; rec_get_state(&state, &pending); // Already playing? if (state == GST_STATE_PLAYING) return; LOG_DEBUG("\n--------- rec_continue_recording() ----------\n"); // Reset timer timer_module_reset(GST_STATE_PLAYING); // Continue recording gst_element_set_state(g_pipeline, GST_STATE_PLAYING); } void rec_update_gui() { // Recording state has changed (PLAYING, PAUSED, STOPPED/NULL). // Set widgets and GUI. rec_manager_update_gui(); } gboolean rec_start_recording() { LOG_DEBUG("\n--------- rec_start_recording() ----------\n"); // Get recording state gint state = -1; gint pending = -1; rec_get_state(&state, &pending); // Already recording? if (state == GST_STATE_PLAYING) { return TRUE; // Is it paused? } else if (state == GST_STATE_PAUSED) { // Continue recording rec_continue_recording(); return TRUE; } // Call stop rec_stop_recording(FALSE); // Reset timer (prepare for GST_STATE_PLAYING) timer_module_reset(GST_STATE_PLAYING); // Create a new GStreamer pipeline and start recording. // Clear static variables (call with NULLs) rec_level_message_cb(NULL, NULL, NULL); // Variables gboolean ret = FALSE; gchar *filename = NULL; // Audio source, eg "pulsesrc", " gchar *audio_source = NULL; // Device list GList *dev_list = NULL; gchar *encoder_pipeline = NULL; gchar *track_name = NULL; gint track_pos = 0; gint track_len = 0; gchar *artist_name = NULL; gchar *album_name = NULL; // Get data from GConf registry. // Track name conf_get_string_value("track/track-name", &track_name); str_trim(track_name); purify_filename(track_name, TRUE/*purify_all*/); // Artist conf_get_string_value("track/artist-name", &artist_name); str_trim(artist_name); purify_filename(artist_name, TRUE/*purify_all*/); // Album conf_get_string_value("track/album-name", &album_name); str_trim(album_name); purify_filename(album_name, TRUE/*purify_all*/); conf_get_int_value("track/track-pos", &track_pos); conf_get_int_value("track/track-len", &track_len); LOG_DEBUG("Start recording. track=%s artist=%s album=%s track pos/len=%d/%d.\n", track_name, artist_name, album_name, track_pos, track_len); // Get last (saved) track name gchar *last_track_name = NULL; conf_get_string_value("track/last-track-name", &last_track_name); str_trim(last_track_name); // Append to file? gboolean append_to_file = FALSE; conf_get_boolean_value("append-to-file", &append_to_file); if (append_to_file && g_file_test(last_track_name, G_FILE_TEST_IS_REGULAR)) { // Record to an existing filename filename = g_strdup(last_track_name); } // Did we get a track name (output filename) from a Media Player, Skype? else if (str_length(track_name, 2048) > 0) { // We have track, album and artist names. Create filename from these. filename = rec_create_filename(track_name, artist_name, album_name); } else { // Simply generate a new, unique filename from date+time pattern filename = rec_generate_unique_filename(); } // Purify the final filename, remove illegal characters. Edit in place. purify_filename(filename, FALSE); // Can we write to path/filename? if (!is_file_writable(filename)) { gchar *msg = g_strdup_printf(_("Cannot write to file \"%s\".\n"), filename); // Show error text LOG_DEBUG("Cannot write to file \"%s\".\n", filename); rec_manager_set_error_text(msg); g_free(msg); goto LBL_1; } // Save the last file name conf_save_string_value("track/last-track-name", filename); // Get the saved media profile id (aac, mp3, cdlossless, cdlossy, etc). gchar *media_profile_id = rec_get_media_profile_id(); // Get partial pipline from GConf for this media_profile_id encoder_pipeline = media_profiles_get_pipeline(media_profile_id); g_free(media_profile_id); #if 0 // Debugging: gchar *device_id = NULL; conf_get_string_value("audio-device-id", &device_id); gchar *device_name = NULL; conf_get_string_value("audio-device-name", &device_name); gint device_type = -1; conf_get_int_value("audio-device-type", &device_type); LOG_DEBUG("audio-device-id=%s\n", audio_device_id); LOG_DEBUG("audio-device-name=%s\n", audio_device_name); LOG_DEBUG("audio-device-type=%d\n", audio_device_type); g_free(device_name); g_free(device_id); #endif // Set/show current filename rec_manager_set_filename_label(filename); // Get audio source and device list dev_list = audio_sources_get_device_NEW(&audio_source); // Now build a GStreamer pipeline with these values GError *error = NULL; g_pipeline = rec_create_pipeline(audio_source, dev_list, encoder_pipeline, filename, append_to_file, &error); ret = TRUE; // Ok? if (error) { // Got errors. // Display error message in the GUI (set red label) rec_manager_set_error_text(error->message); // Stop and reset everything rec_stop_and_reset(); g_error_free(error); ret = FALSE; } else { // Alles Ok. // Clear error messages. rec_manager_set_error_text(NULL); } LOG_DEBUG("------------------------\n"); LBL_1: g_free(filename); g_free(track_name); g_free(artist_name); g_free(album_name); g_free(encoder_pipeline); g_free(audio_source); // Free dev_list str_list_free(dev_list); dev_list = NULL; return ret; } gint64 rec_get_stream_time() { // Return current recording time in seconds GstFormat format = GST_FORMAT_TIME; gint64 val = -1; gint secs = 0L; // Valid pipeline? if (!GST_IS_ELEMENT(g_pipeline)) return 0L; // Stream/recording time if (gst_element_query_position(g_pipeline, &format, &val) && val != -1) { secs = val / GST_SECOND; } return secs; } void rec_stop_recording(gboolean delete_file) { // Stop recording, terminate pipeline if (!GST_IS_PIPELINE(g_pipeline)) return; // Get recording state gint state = -1; gint pending = -1; rec_get_state(&state, &pending); // Already stopped? if (state != GST_STATE_NULL) { // Reset timer timer_module_reset(GST_STATE_NULL); } LOG_DEBUG("rec_stop_recording(%s)\n", (delete_file ? "delete_file=TRUE" : "delete_file=FALSE")); // Send EOS message. This will terminate the stream/file properly. This is very important for ACC (.m4a) files. gst_element_send_event(g_pipeline, gst_event_new_eos()); gst_element_send_event(g_pipeline, gst_event_new_eos()); g_usleep(GST_USECOND * 5); // Set pipeline state to NULL rec_set_state_to_null(); // Shutdown the entire pipeline gst_element_set_state(g_pipeline, GST_STATE_NULL); // Destroy it if (GST_IS_OBJECT(g_pipeline)) gst_object_unref(GST_OBJECT(g_pipeline)); g_pipeline = NULL; LOG_DEBUG("--------- Pipeline closed and destroyed ----------\n\n"); // Delete the recorded file? if (delete_file) { // Get last_track_name gchar *filename = NULL; conf_get_string_value("track/last-track-name", &filename); LOG_DEBUG("Deleted file:\"%s\"\n", filename); // Remove it g_remove(filename); g_free(filename); // Erase last_track_name conf_save_string_value("track/last-track-name", ""); // Update filename in the GUI rec_manager_set_filename_label(""); } // Notice: // The rec_state_changed_cb() function will reset the GUI by calling rec_manager_update_gui(); } void rec_stop_and_reset() { // Stop recording rec_stop_recording(FALSE); // Reset window and its widgets rec_manager_update_gui(); } void rec_get_state(gint *state, gint *pending) { /* state: GST_STATE_VOID_PENDING GST_STATE_NULL GST_STATE_READY GST_STATE_PAUSED GST_STATE_PLAYING */ *state = GST_STATE_NULL; *pending = GST_STATE_NULL; // The pipeline has been created? if (!GST_IS_ELEMENT(g_pipeline)) return; // The state is? // Ref: http://www.gstreamer.net/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-get-state gst_element_get_state(g_pipeline, (GstState*)state, (GstState*)pending, 0); } gboolean rec_is_recording() { // Get recording state gint state = -1; gint pending = -1; rec_get_state(&state, &pending); gboolean ret = FALSE; // Is playing? switch (state) { case GST_STATE_PLAYING: ret = TRUE; break; } return ret; } static gboolean rec_level_message_cb(GstBus *bus, GstMessage *message, void *user_data) { if (!GST_IS_MESSAGE(message)) return TRUE; static guint64 last_stream_time_t = 0L; static guint64 last_stream_time_fz = 0L; // Calling with NULL arguments? // This will reset the static variables, then exit. if (!message) { last_stream_time_t = 0L; last_stream_time_fz = 0L; return TRUE; } guint64 stream_time = 0L; if (message->type == GST_MESSAGE_ELEMENT) { const GstStructure *s = gst_message_get_structure (message); const gchar *name = gst_structure_get_name(s); if (g_str_equal(name, "level")) { // Ref: http://www.gstreamer.net/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-level.html GstClockTime endtime; if (!gst_structure_get_clock_time(s, "endtime", &endtime)) { LOG_ERROR("Pipeline error: Cannot read endtime.\n"); } else { stream_time = GST_TIME_AS_SECONDS(endtime); } const GValue *list; list = gst_structure_get_value(s, "rms"); gint channels = gst_value_list_get_size(list); if (channels < 1) goto LBL_1; const GValue *value; list = gst_structure_get_value (s, "peak"); value = gst_value_list_get_value (list, 0); gdouble peak_dB = g_value_get_double(value); gdouble norm_peak = exp(peak_dB / 20); if (norm_peak > 1.0) norm_peak = 1.0; // Update level (progress) bar rec_manager_update_level_bar(norm_peak * 0.95, peak_dB); // (0.95, just cheating a bit, space for value-text) // Update the time label (GUI). if (stream_time - last_stream_time_t >= 1/*seconds*/) { guint hours = (guint)(stream_time / 3600); stream_time = stream_time - (hours*3600); // Count only to 23 hours // if (hours > 99) hours = 23; guint minutes = (guint)(stream_time / 60); guint seconds = stream_time - (minutes*60); // Show stream time gchar *time_txt = g_strdup_printf("%02d:%02d:%02d", hours, minutes, seconds); rec_manager_set_time_label(time_txt); g_free(time_txt); // Save last stream_time last_stream_time_t = stream_time; } // Update the time label (GUI). if (stream_time < 10 || (stream_time - last_stream_time_fz > 3/*seconds*/)) { // Update more frequently if stream_time < 10 seconds, after that, update each 3.rd second. gchar *filename = rec_manager_get_output_filename(); gchar *size_txt = NULL; // Get file size struct stat fstat; if (stat(filename, &fstat)) LOG_ERROR("Cannot get file information of %s.\n", filename); else size_txt = format_file_size(fstat.st_size); // Show file size rec_manager_set_size_label(size_txt); g_free(filename); g_free(size_txt); // Save last stream_time last_stream_time_fz = stream_time; } } } LBL_1: // TRUE: Continue calling this function return TRUE; } static void rec_state_changed_cb(GstBus *bus, GstMessage *msg, void *userdata) { if (!GST_IS_MESSAGE(msg)) return; // We are only interested in the top-level pipeline. if (msg->src != GST_OBJECT(g_pipeline)) return; GstState old_state, new_state; gst_message_parse_state_changed(msg, &old_state, &new_state, NULL); GstState state, pending; gst_element_get_state(g_pipeline, &state, &pending, 0); LOG_DEBUG("Pipeline state changed from %s to: %s (stat=%s pending=%s).\n", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state), gst_element_state_get_name(state), gst_element_state_get_name(pending)); // Pipeline state changed from PAUSED to: READY (stat=READY pending=VOID_PENDING). switch (new_state) { case GST_STATE_PLAYING: // We are playing. Inform the GUI. rec_manager_update_gui(); break; case GST_STATE_READY: // Recording may have stopped. Inform the GUI. rec_manager_update_gui(); break; case GST_STATE_PAUSED: // Goes from GST_STATE_PLAYING to GST_STATE_PAUSED state? if (old_state == GST_STATE_PLAYING) { // Recording has paused. Inform the GUI. rec_manager_update_gui(); } break; case GST_STATE_NULL: // Recording has stopped. Inform the GUI. rec_manager_update_gui(); break; default: break; } } static void rec_pipeline_error_cb(GstBus *bus, GstMessage *msg, void *userdata) { if (!GST_IS_MESSAGE(msg)) return; GError *error = NULL; gchar *dbg = NULL; gst_message_parse_error(msg, &error, &dbg); g_return_if_fail(error != NULL); LOG_DEBUG("\nGot pipeline error: %s.\n", error->message); if (dbg) { g_free(dbg); } if (error->code == GST_RESOURCE_ERROR_BUSY) { g_error_free(error); return; } if (error) { g_error_free(error); } } static void rec_eos_msg_cb(GstBus *bus, GstMessage *msg, void *userdata) { LOG_DEBUG("Got EOS message. Finishing recording.\n"); // We've seen EOS. Set g_got_EOS_message to TRUE. g_got_EOS_message = TRUE; } GstElement *rec_create_pipeline(gchar *audio_source, GList *dev_list, gchar *enc_pipeline, gchar *output_filename, gboolean append_to_file, GError **error) { // Create a GStreamer pipeline for audio recording. gchar *err_msg = NULL; // Print debug info LOG_DEBUG("----------------------------\n"); LOG_DEBUG("rec_create_pipeline, The parameters are:\n"); LOG_DEBUG("audio_source=%s\n", audio_source); LOG_DEBUG("Device list is:\n"); GList *n = g_list_first(dev_list); while (n) { gchar *device = (gchar*)n->data; (void)device; // Avoid unused var message LOG_DEBUG("\t%s\n", device); n = g_list_next(n); } LOG_DEBUG("encoder_pipeline (from GConf)=%s\n",enc_pipeline); LOG_DEBUG("filename=%s\n", output_filename); LOG_DEBUG("append_to_file=%s\n", (append_to_file ? "TRUE" : "FALSE")); // Create pipeline GstElement *pipeline = pipeline_create(audio_source, dev_list, enc_pipeline, "filesink", &err_msg); // Errors? if (!GST_IS_PIPELINE(pipeline) || err_msg) { goto LBL_1; } // Get "output-sink" element GstElement *filesink = gst_bin_get_by_name(GST_BIN(pipeline), "output-sink"); if (!GST_IS_ELEMENT(filesink)) { err_msg = g_strdup_printf(_("Cannot find audio element %s.\n"), "filesink"); goto LBL_1; } // And set location (filename) and append mode g_object_set(G_OBJECT(filesink), "location", output_filename, NULL); g_object_set(G_OBJECT(filesink), "append", append_to_file, NULL); g_object_unref(filesink); // Add a message handler GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); // gst_bus_add_watch (bus, bus_info_cb, NULL); gst_bus_add_signal_watch(bus); // Detect state changes g_signal_connect(bus, "message::state-changed", G_CALLBACK(rec_state_changed_cb), NULL); // Monitor sound level/amplitude g_signal_connect(bus, "message::element", G_CALLBACK(rec_level_message_cb), NULL); // Catch error messages g_signal_connect(bus, "message::error", G_CALLBACK(rec_pipeline_error_cb), NULL); // EOS g_signal_connect(bus, "message::eos", G_CALLBACK(rec_eos_msg_cb), NULL); gst_object_unref(bus); // Assume all is OK gboolean ret = TRUE; // Set the pipeline to "playing" state if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { err_msg = g_strdup(_("Cannot start reading from the stream/pipeline.\n")); ret = FALSE; } else { LOG_DEBUG("Pipeline is OK. Starting recording to %s.\n", output_filename); } // Are we finally ok? if (ret) { return pipeline; } // Error occured LBL_1: // Set the error object if (!err_msg) err_msg = g_strdup_printf(_("Cannot create audio pipeline. %s.\n"), ""); LOG_ERROR(err_msg); // Destroy pipeline if (G_IS_OBJECT(pipeline)) { gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); } // Set error g_set_error(error, AUDIO_RECORDER, /* Error domain */ AUDIO_RECORDER_ERR1, /* Error code */ err_msg, ""); g_free(err_msg); // Return NULL return NULL; } gchar *rec_get_output_filename() { // Return the current output filename // Valid pipeline? if (!GST_IS_ELEMENT(g_pipeline)) return NULL; // Get the "output-sink" element (by name) GstElement *filesink = gst_bin_get_by_name(GST_BIN(g_pipeline), "output-sink"); // Got filesink? if (!GST_IS_ELEMENT(filesink)) return NULL; gchar *filename = NULL; g_object_get(G_OBJECT(filesink), "location", &filename, NULL); g_object_unref(filesink); // The caller should g_free() the value return filename; } // ------------------------------------------------------------ // Support functions // ------------------------------------------------------------ static gchar *rec_get_media_profile_id() { // Return the saved media profile id. Typical values are: "aac", "mp3", "cdlossless" and "cdlossy". // See gconf-editor, key: system -> gstreamer -> 0.10 -> audio -> profiles. gchar *id = NULL; conf_get_string_value("media-format", &id); // Is this id real? GMAudioProfile *media_profile = media_profiles_get_profile(id); if (!media_profile) { g_free(id); id = g_strdup("cdlossy"); // = .ogg audio format } // The caller should g_free() this value return id; } static gchar *check_audio_folder(gchar *audio_folder) { // Create the folder, this may fail. g_mkdir(audio_folder, 0755); // Directory exists? if (!g_file_test(audio_folder, G_FILE_TEST_IS_DIR)) { // Replace the old audio_folder g_free(audio_folder); // Set it to $HOME audio_folder = get_home_dir(); } return audio_folder; } static gchar *rec_generate_unique_filename() { // Generate a new unique filename. // filename = file_prefix + pattern + "." + extension. // Get the saved media profile id (aac, mp3, cdlossless, cdlossy, etc). gchar *media_profile_id = rec_get_media_profile_id(); // Take the file extension from media_profile_id; .ogg, .mp3, etc. gchar *file_ext = media_profiles_get_extension(media_profile_id); // Last used filename (if we need to add_to_file) gchar *last_track_name = NULL; conf_get_string_value("track/last-track-name", &last_track_name); str_trim(last_track_name); // Audio folder gchar *audio_folder = get_audio_folder(); // Check if audio_folder is writable audio_folder = check_audio_folder(audio_folder); // Pattern to generate the filename (includes base + date + time pattern) gchar *filename_pattern = get_filename_pattern(FALSE); gboolean append_to_file = FALSE; conf_get_boolean_value("append-to-file", &append_to_file); LOG_DEBUG("last_track_name=%s pattern=%s append_to_file=%d\n", last_track_name, filename_pattern, append_to_file); // Auto generate filename? if ((!append_to_file) || str_length(last_track_name, 4000) < 2) { // Generate a filename from the pattern g_free(last_track_name); last_track_name = substitute_time_and_date_pattern(filename_pattern); // Invalid pattern? if (str_length(last_track_name, 4000) < 1) { // Filename not given, set it to "Some filename" last_track_name= g_strdup(_("Some filename")); } } // Split the last filename gchar *path=NULL, *basename=NULL, *ext=NULL; split_filename3(last_track_name, &path, &basename, &ext); // Remove old and set real (.ogg, .mp3, etc.) file extension gchar *s = g_strdup_printf("%s.%s", basename, file_ext); g_free(path); g_free(basename); g_free(ext); // Build the entire filename; path + file.ext gchar *fname = g_build_filename(audio_folder, s, NULL); // Free the values g_free(s); g_free(media_profile_id); g_free(file_ext); g_free(last_track_name); g_free(audio_folder); g_free(filename_pattern); LOG_DEBUG("Auto generated filename is:%s.\n", fname); // Return filename. The caller should g_free() this value. return fname; } static gchar *rec_create_filename(gchar *track, gchar *artist, gchar *album) { // Note: we are not using album name here. Maybe in the next version. gchar *filename = NULL; // Do we have a track name? if (str_length(track, 2048) < 2) { // No. // Auto-generate a new filename from the date+time pattern return rec_generate_unique_filename(); } // Get Audio/ folder gchar *audio_folder = get_audio_folder(); audio_folder = check_audio_folder(audio_folder); // Get the saved media profile id (aac, mp3, cdlossless, cdlossy, etc). gchar *media_profile_id = rec_get_media_profile_id(); // Take the file extension from media_profile_id; .ogg, .mp3, etc. gchar *file_ext = media_profiles_get_extension(media_profile_id); // Make track + file extension gchar *fname = g_strdup_printf("%s.%s", track, file_ext); gchar *path = NULL; // Do we have artist name? if (str_length(artist, 1024) > 0) { // Build path: audio_folder / artist // Eg. "/home/moma/Audio/Salomon Burke/" or from Skype "/home/moma/Audio/Skype recordings/" path = g_build_filename(audio_folder, artist, NULL); // Create directory if (g_mkdir_with_parents(path, 0755) == -1) { // Ref: http://library.gnome.org/devel/glib/unstable/glib-File-Utilities.html#g-mkdir-with-parents LOG_ERROR("Cannot create path \"%s\"\n", path); g_free(path); path = NULL; } } if (path) { // audio_folder / artist + track.ext filename = g_build_filename(path, fname, NULL); } else { // audio_folder + track.ext filename = g_build_filename(audio_folder, fname, NULL); } // TODO: Should we delete existing audio files? // Delete existing file g_remove(filename); // Filename was write-protected? if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { // Yes. // Generate new, unique name by using the pattern g_free(filename); // Pattern to generate a unique filename (date + time pattern) gchar *filename_pattern = get_filename_pattern(TRUE/*remove "rec-" from the pattern*/); // Re-build the filename gchar *t = g_strdup_printf("%s/%s-%s.%s", (path ? path : audio_folder), track, filename_pattern, file_ext); // Substitute date+time values filename = substitute_time_and_date_pattern(t); g_free(t); g_free(filename_pattern); } g_free(fname); g_free(path); g_free(media_profile_id); g_free(file_ext); g_free(audio_folder); // The caller should g_free() this value return filename; }