// ********************************************************************************************************************************************************************************** // Fichier AUDIO.C // // Contient toutes les fonctions liées à l'audio. // ********************************************************************************************************************************************************************************** #define __DEBUG_AUDIO //Décommenter cette ligne pour avoir les messages de debug concernant l'audio // Include : #include "main.h" // Prototypes : void gestion_audio(void); int8_t init_audio(void); int8_t emission_bip(char * nom_fichier, uint16_t nb_repetitions, uint16_t periode_boucles, uint16_t nb_boucles); int8_t lecture_mp3(char * nom_fichier, uint16_t nb_repetitions); int8_t mise_a_jour_etat_jack(); int8_t set_volume(float volume, bool echo); int8_t alimentation_ampli_audio(void); // Variables globales : struct Struct_audio audio; //********************************************************************************************************************************************************************************** // Fonction : Gestion de l'audio en tâche de fond // Reçoit : Rien // Retourne : Rien //********************************************************************************************************************************************************************************** void gestion_audio(void) { char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; switch(audio.sequenceur.etat) { //-------------------------------------------------------- case ETAT_SEQUENCEUR_AUDIO_INIT : { if (audio.sequenceur.compteur == 0) { audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_REPOS; } break; } //-------------------------------------------------------- case ETAT_SEQUENCEUR_AUDIO_REPOS : { if (audio.sequenceur.compteur == 0) { audio.sequenceur.compteur = AUDIO_PERIODE_SURVEILLANCE; mise_a_jour_etat_jack(); } break; } //-------------------------------------------------------- case ETAT_SEQUENCEUR_AUDIO_DEBUT_BOUCLE : { mise_a_jour_etat_jack(); // Pour que la mise à jour se fasse quand même de temps en temps... lecture_mp3(audio.boucle.nom_fichier, audio.boucle.nb_repetitions); audio.sequenceur.compteur = audio.boucle.periode_boucles; audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_TEMPO_BOUCLE; break; } //-------------------------------------------------------- case ETAT_SEQUENCEUR_AUDIO_TEMPO_BOUCLE : { if (audio.sequenceur.compteur == 0) { if (audio.boucle.compteur_boucles > 0) { audio.boucle.compteur_boucles --; audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_DEBUT_BOUCLE; } else { audio.boucle.nom_fichier[0] = 0; audio.boucle.nb_repetitions = 0; audio.boucle.periode_boucles = 0; audio.boucle.nb_boucles = 0; audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_REPOS; } } break; } //-------------------------------------------------------- default : { sprintf(tmp, "L'état du séquenceur Audio est inconnu [%i]. Il est repositionné au REPOS", audio.sequenceur.etat); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_ERREUR); audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_REPOS; break; } } } //================================================================================================================================================================== // Fonction : Initialise les fonctions audio // Reçoit : Rien // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t init_audio(void) { // Ajustement des variables selon la génération de ShipBase en cours : if (systeme.generation_shipbase == GENERATION_SHIPBASE_V1) { audio.sortie_gpio_cm_do_audio_sd = CM3P_DO_AUDIO_SD; audio.entree_gpio_cm_di_jack_detect = 0; // Non disponible sur V1 } else { audio.sortie_gpio_cm_do_audio_sd = CM4_DO_AUDIO_SD; audio.entree_gpio_cm_di_jack_detect = CM4_DI_JACK_DETECT; } // Mise à jour du niveau de son (sans émission de bip) : // if (set_volume(audio.consigne_volume, AUDIO_SET_VOLUME_ECHO_OFF) == -1) return(-1); // La valeur a été récupérée dans la BDD par 'init_bdd()' set_volume(audio.consigne_volume, AUDIO_SET_VOLUME_ECHO_OFF); // Mise à jour du volume au niveau matériel. NB : l'alimentation de l'ampli est faite en fin de 'set_volume()' et tient compte de l'état de 'Fonction_jack' // Lancement musique d'accueil : if (systeme.generation_shipbase == GENERATION_SHIPBASE_V1) lecture_mp3("netflix.mp3", 0); else lecture_mp3("battement_coeur.mp3", 0); // Affichage du message de débug : affichage_debug(__FILE__, __LINE__, "Initialisation audio\t\t: OK", TYPE_MESSAGE_DEBUG_OK); return(0); } //================================================================================================================================================================== // Fonction : Demande l'émission d'un bip qui peut être complexe (un ou plusieurs bips, répétés un certain nombre de fois à un intervalle défini). A noter que cette // fonction permet aussi d'arrêter la séquence par anticipation si besoin. // Reçoit : Le nom du fichier MP3 à utiliser comme son de base (ou "Off" pour stopper), le nombre de répétitions du son de base (joués à la suite les uns des autres), // la tempo d'attente entre chaque boucles et le nombre de boucles. // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t emission_bip(char * nom_fichier, uint16_t nb_repetitions, uint16_t periode_boucles, uint16_t nb_boucles) { FILE *file; char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; // Détection d'une commande d'arrêt de lecture : // Nb : le nom spécifique "Off" est traité de façon insensible à la casse (donc "OFF", "Off", "off" et tous les autres intermédiaires fonctionnent) if ( (strlen(nom_fichier) == 3) && (tolower(nom_fichier[0]) == 'o') && (tolower(nom_fichier[1]) == 'f') && (tolower(nom_fichier[2]) == 'f') ) { audio.boucle.nom_fichier[0] = 0; audio.boucle.nb_repetitions = 0; audio.boucle.periode_boucles = 0; audio.boucle.nb_boucles = 0; audio.boucle.compteur_boucles = 0; audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_REPOS; return (0); } // Test du fichier : sprintf(tmp, "%s/%s", REPERTOIRE_CONFIG_SONS, nom_fichier); // Chemin complet du fichier dont le nom a été reçu file = fopen(tmp, "r"); // Tentative d'ouverture en lecture du fichier if (file == NULL) { sprintf(tmp, "Le fichier audio à lire [%s] n'existe pas dans le répertoire SONS [%s]. La lecture n'est pas lancée...", nom_fichier, REPERTOIRE_CONFIG_SONS); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_ERREUR); return(-1); // Si erreur d'ouverture, sortie en erreur (et, surtout, pas de lancementd e lecture) } else fclose(file); // Si pas d'erreur, on referme immédiatement le fichier avantd e continuer... // Cas où le fichier existe bien : strcpy(audio.boucle.nom_fichier, nom_fichier); audio.boucle.nb_repetitions = nb_repetitions; audio.boucle.periode_boucles = periode_boucles; audio.boucle.nb_boucles = nb_boucles; audio.boucle.compteur_boucles = nb_boucles; audio.sequenceur.etat = ETAT_SEQUENCEUR_AUDIO_DEBUT_BOUCLE; return(0); } //================================================================================================================================================================== // Fonction : Lance le son correspondant au fichier MP3 passé en paramètre. // NB : la fonction lance un appel système non bloquant. Le fichier doit être dans REPERTOIRE_CONFIG_SONS. // Reçoit : Nom complet du fichier (depuis REPERTOIRE_CONFIG_SONS) // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t lecture_mp3(char * nom_fichier, uint16_t nb_repetitions) { char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; char loop[15]; if (nb_repetitions > 0) sprintf(loop, "--loop %i", nb_repetitions); else loop[0] = 0; sprintf(tmp, "sudo mpg123 %s %s/'%s' > /dev/null 2>&1 &", loop , REPERTOIRE_CONFIG_SONS, nom_fichier); // Attention : le '&' en fin de commande est nécessaire pour que l'appli n'attende pas la fin de la commande, donc de la lecture du morceau...! system(tmp); //printf("\tLancement lecture du fichier MP3 via la commande : [%s]\n", tmp); return(0); } //================================================================================================================================================================== // Fonction : Effectue une lecture matérielle pour définir si une prise jack est connectée ou pas sur la sortie audio de ShipBase // NB : valable uniquement sur V2. // NB 2 : cd SDIR #232 // Reçoit : Rien // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t mise_a_jour_etat_jack() { bool flag; char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; if (audio.entree_gpio_cm_di_jack_detect != 0) // Les V1 ne sont pas concernées... { // flag = (bool) lecture_entree(TYPE_ENTREES_GPIO, 0, audio.entree_gpio_cm_di_jack_detect, FALSE); // 0 = adresse ShipBase flag = TRUE; // SDIR #232 if (flag != audio.etat_presence_jack) { audio.etat_presence_jack = flag; alimentation_ampli_audio(); #ifdef __DEBUG_AUDIO sprintf(tmp, "La présence d'une prise jack sur la sortie audio a changé d'état. Elle est maintenant : %s. NB : Fonction jack = %i", audio.etat_presence_jack ? "Présente" : "Absente", audio.fonction_jack); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_PAR_DEFAUT); #endif } } return (0); } //================================================================================================================================================================== // Fonction : Met à jour le niveau de volume du son de la carte. La consigne est celle reçue en paramètre et la fonction met à jour les paramètres de AlsaMixer. // Dans le cas où la consigne est à 0, la sortie de commande de l'ampli est carrément coupée. // Reçoit : Le niveau de volume attendu (de 0 à 100%), en centièmes de pourcent. // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t set_volume(float volume, bool echo) { long min, max; snd_mixer_t *handle; snd_mixer_selem_id_t *sid; const char *card = "default"; const char *selem_name = "PCM"; char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; // Vérification de la nouvelle valeur de consigne : if ((volume < AUDIO_CONSIGNE_MIN) || (volume > AUDIO_CONSIGNE_MAX)) { sprintf(tmp, "La nouvelle consigne de volume audio n'est pas dans la plage autorisée : %.2f pour [%i ; %i%%]. La commande est ignorée...", volume, AUDIO_CONSIGNE_MIN, AUDIO_CONSIGNE_MAX); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_ERREUR); return(-1); } // Mise à jour de la nouvelle valeur : audio.consigne_volume = volume; // Envoi vers le driver : snd_mixer_open(&handle, 0); snd_mixer_attach(handle, card); snd_mixer_selem_register(handle, NULL, NULL); snd_mixer_load(handle); snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_name(sid, selem_name); snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); snd_mixer_selem_get_playback_volume_range(elem, &min, &max); snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100); snd_mixer_close(handle); // Activation ampli audio : alimentation_ampli_audio(); // Enregistrement dans l'historique : enregistrement_historique(TYPE_SOURCE_HISTORIQUE_SYSTEME, SYSTEME_VOLUME_CHANGEMENT_CONSIGNE, 0, audio.consigne_volume, NULL); #ifdef __DEBUG_AUDIO sprintf(tmp, "Nouvelle consigne de volume = %.2f %%", audio.consigne_volume); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_PAR_DEFAUT); #endif // Bip de mise à jour s'il est demandé : // if (echo) lecture_mp3("button-8.mp3", 0); if (echo) lecture_mp3("beep-07.mp3", 0); return(0); } //================================================================================================================================================================== // Fonction : Active ou désactive l'alimentation de l'ampli audio (alimentant le HP intégré) selon la présence ou non d'une prise jack sur la sortie AUDIO // et selon le paramètre de configuration 'Fonction_jack' // Reçoit : Rien // Renvoit : 0 si aucune erreur, -1 si une erreur a été détectée //================================================================================================================================================================== int8_t alimentation_ampli_audio(void) { #ifdef __DEBUG_AUDIO char tmp[MAX_LONGUEUR_BUFFER_TEMPORAIRE_DEBUG]; #endif if ( ((audio.etat_presence_jack == TRUE) && (audio.fonction_jack == AUDIO_FONCTION_JACK_HP_OFF)) || // Si une fiche jack est présente sur la sortie Audio ET que le mode de fonctionnement est de couper le HP dans ce cas... (audio.consigne_volume == 0) ) // ... OU le volume est à 0 { // Coupure de l'alimentation de la sortie audio : ecriture_sortie(TYPE_SORTIE_GPIO, 0, audio.sortie_gpio_cm_do_audio_sd, OFF); // 0 est l'adresse de ShipBase #ifdef __DEBUG_AUDIO sprintf(tmp, "L'alimentation de l'ampli audio est passée à OFF car Présence Jack = %s et Fonction Jack = %i", (audio.etat_presence_jack ? "Présente" : "Absente"), audio.fonction_jack); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_PAR_DEFAUT); #endif } else // Dans tous les autres cas, l'ampli est alimenté. { // Alimentation de la sortie audio : ecriture_sortie(TYPE_SORTIE_GPIO, 0, audio.sortie_gpio_cm_do_audio_sd, ON); // 0 est l'adresse de ShipBase #ifdef __DEBUG_AUDIO sprintf(tmp, "L'alimentation de l'ampli audio est passée à ON car Présence Jack = %s et Fonction Jack = %i", (audio.etat_presence_jack ? "Présente" : "Absente"), audio.fonction_jack); affichage_debug(__FILE__, __LINE__, tmp, TYPE_MESSAGE_DEBUG_PAR_DEFAUT); #endif } return(0); }