[PYTHON] SmartVolumer, une application Android qui ajuste le volume de lecture de la musique en fonction du volume environnant

Aperçu

Ceci est une transcription de l'application que j'ai créée lorsque j'étais en troisième année d'école de premier cycle.

Je pense que vous écoutez souvent de la musique avec des écouteurs lorsque vous voyagez en train. À quel point écoutez-vous? Certaines personnes peuvent vouloir écouter à un volume élevé. J'aime le volume pour pouvoir entendre un peu les sons environnants. C'est triste de ne pas entendre la musique, et au contraire, c'est ennuyeux qu'elle soit toujours bruyante. Quand j'ai remarqué, je me suis retrouvé à cliquer sur le bouton de volume de la voiture. Comme c'est gênant, j'ai décidé de créer une application qui ajuste automatiquement le volume de lecture de la musique, etc. à un degré légèrement plus fort que le bruit ambiant.

Méthode de réalisation

Principe de base

Il a été transformé en une application pour smartphone (Android). Le volume ambiant est mesuré à l'aide d'un microphone de smartphone, le volume de lecture est déterminé par le modèle de détermination du volume de lecture (décrit plus loin) et le volume est modifié. J'utilise AudioManager ou AudioRecord.

Tout d'abord, il y a le problème de combien de millisecondes pour mesurer le volume ambiant. S'il est trop court, vous ne pourrez pas obtenir correctement le volume ambiant. Ensuite, il y a le problème de la fréquence à laquelle le cycle (acquérir le volume ambiant -> changer le volume de lecture) est effectué. Si la fréquence est trop élevée, le volume changera trop rapidement, ce qui est ennuyeux.

J'ai essayé différentes choses, mais en conclusion, j'ai décidé d'obtenir le volume périphérique une fois toutes les 100 millisecondes comme valeur maximale parmi les valeurs mesurées plusieurs fois. Cela a été répété 10 fois et la valeur moyenne de ces valeurs mesurées a été traitée comme la valeur finale du volume ambiant. Pour plus de commodité, le cycle de (Obtenir le volume ambiant -> Modifier le volume de lecture) est effectué toutes les 1000 millisecondes.

Ce processus peut être plus facile à comprendre si vous lisez le code source. Le code source qui exécute ce cycle est le suivant.

package com.gmail.axis38akasira.autovolumer;

import android.os.Handler;
import android.support.annotation.NonNull;
import android.widget.TextView;

import com.gmail.axis38akasira.autovolumer.notifications.NotificationWrapper;

class VolumeManager implements Runnable {

    private AudioResources aRes;
    private Handler handler;
    private TextView textView_envVol, textView_playingVol;
    private NotificationWrapper notificationWrapper;
    private int micSenseCnt = 0, micSenseSum = 0;

    VolumeManager(@NonNull AudioResources aRes, @NonNull Handler handler,
                  @NonNull TextView textView_envVol, @NonNull TextView textView_playingVol,
                  @NonNull NotificationWrapper notificationWrapper) {
        this.aRes = aRes;
        this.handler = handler;
        this.textView_envVol = textView_envVol;
        this.textView_playingVol = textView_playingVol;
        this.notificationWrapper = notificationWrapper;
    }

    @Override
    public void run() {
        if (aRes.getMicAccessAllowed()) {
            final short[] buffer = aRes.readByAudioRecorder();

            //maximum
            int max_val = Integer.MIN_VALUE;
            for (short x : buffer) {
                max_val = Math.max(max_val, x);
            }

            //Mesurez plusieurs fois et utilisez la valeur moyenne comme résultat de la mesure pendant cet intervalle de temps.
            micSenseSum += max_val;
            if (micSenseCnt != 9) micSenseCnt++;
            else {
                final double inputLevel = micSenseSum / 10.0;
                micSenseSum = 0;
                micSenseCnt = 0;

                textView_envVol.setText(String.valueOf(inputLevel));
                final int outLevel = aRes.applyPlayingVolume(inputLevel, textView_playingVol);
                if (outLevel != -1) {
                    notificationWrapper.post(
                            MainActivity.class, "Le réglage automatique du volume est activé",
                            notificationWrapper.getActivity().getString(R.string.vol_playing)
                                    + String.valueOf(outLevel)
                    );
                }
            }
        }
        handler.postDelayed(this, 100);
    }
}

Modèle de détermination du volume de lecture

Tout d'abord, j'ai pensé à créer quelque chose comme une fonction qui détermine le volume de lecture à partir du niveau de volume environnant.

À ce stade, si le nombre d'étapes du volume de lecture est le même pour tous les appareils (et il est garanti qu'il ne changera pas même si le système d'exploitation est mis à jour), il semble que cela puisse être facilement réalisé en écrivant quelques ifs.

Input:Volume périphérique
return:Volume de lecture

fonction de détermination du volume de lecture func:
si volume périphérique< 750:
        return 0
si volume périphérique< 3750:
        return 1
si volume périphérique< 9750:
        return 2
(Suite pour le nombre d'étapes de volume de lecture)

Cependant, j'ai trouvé cela difficile à assumer (et c'était trop facile et pas intéressant). Par conséquent, j'ai décidé de créer un modèle mathématique qui dérive le volume de lecture du niveau de volume environnant sous la forme d'une fonction continue qui étend la sortie aux valeurs réelles. Plus précisément, j'ai essayé de créer un modèle qui renvoie le rapport du niveau de volume périphérique au volume maximum en tant que valeur réelle.

Tout d'abord, en utilisant un appareil réel (Samsung Galaxy Feel SC-04J), le volume de lecture idéal (rapport au volume maximum) a été mesuré par rapport au volume ambiant. (Cependant, il suffit d'examiner la partie frontière, car il est intuitivement clair que la fonction à créer augmentera de façon monotone au sens large.)

Sur la base des résultats de mesure, les données correspondantes du volume périphérique et du volume de lecture sont créées. Voici un diagramme de dispersion qui visualise cela.

image.png

Etant donné que le niveau de volume d'entrée du microphone est donné sous la forme d'un type entier 16 bits signé, la plage est [0, 32767). L'axe horizontal est réglé sur le niveau de volume brut * 10 ^ 5 afin qu'il puisse être traité facilement plus tard.

Sur la base de ces données, je voudrais ajuster le modèle et obtenir la fonction non lisse suivante (?).

image.png

Je ne veux pas qu'il prenne une forme graduelle, alors évitez les modèles très expressifs comme NN et utilisez un modèle aussi simple que possible. Les fonctions suivantes sont préparées comme candidats.

a+bx \\
a+bx+cx^2 \\
a+b \sqrt{x} \\
a+b \log{x}

Entraînons-nous sur Jupyter Notebook et voyons la précision et les paramètres de post-formation.

# a +Convient bx
from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(np.array(x).reshape(-1, 1), y)
print(lr.score(np.array(x).reshape(-1, 1),y), lr.intercept_, lr.coef_)

# a + bx + cx^Convient à 2
lr2 = LinearRegression().fit(np.dstack((np.power(np.array(x),2),np.array(x)))[0], y)
print(lr2.score(np.dstack((np.power(np.array(x),2),np.array(x)))[0],y), lr2.intercept_, lr2.coef_)

# a + b sqrt(x)Convient à
lr3 = LinearRegression().fit(np.array(np.sqrt(x)).reshape(-1, 1), y)
print(lr3.score(np.array(np.sqrt(x)).reshape(-1, 1),y), lr3.intercept_, lr3.coef_)

# a + b log(x)Convient à
log_r = LinearRegression().fit(np.array(np.log(x[1:])).reshape(-1, 1), y[1:])
print(log_r.score(np.array(np.log(x[1:])).reshape(-1, 1),y[1:]), log_r.intercept_, log_r.coef_)

Précision, terme constant, coefficient

0.9566515430381373 0.05703034713007743 [0.85320093]
0.9858850873387448 0.035720497728703726 [-1.91782117  1.43981898]
0.9981469854250034 -0.013011305980174026 [0.56593706]
0.9695780780725732 0.39569447022473625 [0.09291432]

Visualisons-le sous forme de graphique.

#Représentation graphique
RNG = np.linspace(0, 0.32, 100)
DIV_NUM = 15  #Volume de lecture maximal qui varie d'un appareil à l'autre
plt.figure(figsize=(18,9))
plt.xlabel("env_noise")
plt.ylabel("play_level")
plt.scatter(df["bruit"]/100000, df["Volume de lecture"]/DIV_NUM, label="data")
plt.plot(RNG, lr.intercept_ + lr.coef_ * RNG, label="a+bx", color="green")
plt.plot(RNG, lr2.intercept_ + lr2.coef_[1] * RNG + lr2.coef_[0] * RNG * RNG, label="a+bx+cx^2", color="red")
plt.plot(RNG, lr3.intercept_ + lr3.coef_ * np.sqrt(RNG), label="a+ b sqrt(x)", color="purple")
plt.plot(RNG, log_r.intercept_ + log_r.coef_ * np.log(RNG), label="a+ b log(x)", color="cyan")
plt.legend(loc='upper left', prop={'size':20})

Unknown.png

** Je ne sais pas lequel! ** **

Lors de la détermination réelle du volume de lecture, il est nécessaire de calculer (volume maximum * rapport de volume), puis de le convertir en un entier. Par conséquent, le volume de lecture (valeur entière) après le calcul a été affiché sur le diagramme de dispersion, et la différence par rapport aux données mesurées a été confirmée.

#Essayez de déterminer le volume de lecture de l'appareil en utilisant la fonction pour obtenir le volume de lecture à partir du bruit
#Arrondissez la valeur de la fonction pour en faire un entier

RNG = np.linspace(0.001, 0.32, 500)
DIV_NUM = 15  #Volume de lecture maximal qui varie d'un appareil à l'autre

plt.figure(figsize=(18,12))
plt.scatter(df["bruit"]/100000, df["Volume de lecture"], label="data")
plt.plot(RNG, np.round(DIV_NUM * (lr.intercept_ + lr.coef_[0] * RNG)), label="a+bx round", color="green")
plt.plot(RNG, np.round(DIV_NUM * (lr2.intercept_ + lr2.coef_[1] * RNG + lr2.coef_[0] * RNG * RNG)), label="a+bx+cx^2 round", color="red")
plt.plot(RNG, np.round(DIV_NUM * (lr3.intercept_ + lr3.coef_ * np.sqrt(RNG))), label="a+ b sqrt(x)", color="purple")
plt.plot(RNG, np.round(DIV_NUM * (log_r.intercept_ + log_r.coef_ * np.log(RNG))), label="a+ b log(x)", color="cyan")
plt.legend(loc='upper left', prop={'size':20})

J'ai choisi ceci car il semble que le volume de lecture correct puisse être déterminé en utilisant $ a + b \ sqrt {x} $ (je veux vraiment faire un jugement plus quantitatif). Du résultat quand ça va, à ce moment

\begin{align}
a &= 0.56593706 \\
b &= -0.013011305980174026
\end{align}

Tu peux voir ça. Enfin, ajoutez une fonction pour l'utiliser dans l'application. (Les noms de variables sont légèrement différents, mais veuillez deviner)

package com.gmail.axis38akasira.autovolumer;

class RegressionModel {
    private final static double[] beta = {-0.012529002932674477, 0.56377634};

    static Double infer(final double x) {
        return beta[0] + beta[1] * Math.sqrt(x);
    }
}

UI

J'ai fait une activité de manière appropriée.

Je pensais que ce serait mieux s'il était facile de comprendre si c'était valide ou non, j'ai donc permis d'afficher le statut dans la notification,

Code source

ici https://github.com/ryhoh/SmartVolumer

Résumé

Personnellement, c'est une application pratique. Je regrette que ce soit une bonne manière de déterminer le modèle. Cela a été réalisé en utilisant un microphone de smartphone, mais bien sûr, l'effet sera pire si le smartphone est mis dans un sac. En réalité, il est idéal si le microphone est situé près des écouteurs. À cet égard, je l'utilise avec des écouteurs sans fil, mais je me demande ce qui se passe lorsque j'utilise un écouteur filaire avec un microphone intégré.

Recommended Posts

SmartVolumer, une application Android qui ajuste le volume de lecture de la musique en fonction du volume environnant
Changer le volume de Pepper en fonction de l'environnement environnant (son)