Android:如何为我的应用播放的任何音乐文件创建淡入/淡出音效?

Posted

技术标签:

【中文标题】Android:如何为我的应用播放的任何音乐文件创建淡入/淡出音效?【英文标题】:Android: How to create fade-in/fade-out sound effects for any music file that my app plays? 【发布时间】:2011-10-16 14:36:57 【问题描述】:

我正在开发的应用程序播放音乐文件。如果计时器到期,我希望音乐淡出。我怎么做。我正在使用 MediaPlayer 播放音乐,并且音乐文件存在于我的应用程序的原始文件夹中。

【问题讨论】:

【参考方案1】:

这是我的整个 android MediaPlayer 处理程序类。查看 play() 和 pause() 函数。两者都包含褪色或不褪色的能力。 updateVolume() 函数是让声音线性增加/减少的关键。

package com.***.utilities;

import java.io.File;
import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;

public class MusicHandler 
    private MediaPlayer mediaPlayer;
    private Context context;
    private int iVolume;

    private final static int INT_VOLUME_MAX = 100;
    private final static int INT_VOLUME_MIN = 0;
    private final static float FLOAT_VOLUME_MAX = 1;
    private final static float FLOAT_VOLUME_MIN = 0;

    public MusicHandler(Context context) 
        this.context = context;
    

    public void load(String path, boolean looping) 
        mediaPlayer = MediaPlayer.create(context, Uri.fromFile(new File(path)));
        mediaPlayer.setLooping(looping);
    

    public void load(int address, boolean looping) 
        mediaPlayer = MediaPlayer.create(context, address);
        mediaPlayer.setLooping(looping);
    

    public void play(int fadeDuration) 
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MIN;
        else
            iVolume = INT_VOLUME_MAX;

        updateVolume(0);

        // Play music
        if (!mediaPlayer.isPlaying())
            mediaPlayer.start();

        // Start increasing volume in increments
        if (fadeDuration > 0) 
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask() 
                @Override
                public void run() 
                    updateVolume(1);
                    if (iVolume == INT_VOLUME_MAX) 
                        timer.cancel();
                        timer.purge();
                    
                
            ;

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        
    

    public void pause(int fadeDuration) 
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MAX;
        else
            iVolume = INT_VOLUME_MIN;

        updateVolume(0);

        // Start increasing volume in increments
        if (fadeDuration > 0) 
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask() 
                @Override
                public void run() 
                    updateVolume(-1);
                    if (iVolume == INT_VOLUME_MIN) 
                        // Pause music
                        if (mediaPlayer.isPlaying())
                            mediaPlayer.pause();
                        timer.cancel();
                        timer.purge();
                    
                
            ;

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        
    

    private void updateVolume(int change) 
        // increment or decrement depending on type of fade
        iVolume = iVolume + change;

        // ensure iVolume within boundaries
        if (iVolume < INT_VOLUME_MIN)
            iVolume = INT_VOLUME_MIN;
        else if (iVolume > INT_VOLUME_MAX)
            iVolume = INT_VOLUME_MAX;

        // convert to float value
        float fVolume = 1 - ((float) Math.log(INT_VOLUME_MAX - iVolume) / (float) Math.log(INT_VOLUME_MAX));

        // ensure fVolume within boundaries
        if (fVolume < FLOAT_VOLUME_MIN)
            fVolume = FLOAT_VOLUME_MIN;
        else if (fVolume > FLOAT_VOLUME_MAX)
            fVolume = FLOAT_VOLUME_MAX;

        mediaPlayer.setVolume(fVolume, fVolume);
    

【讨论】:

【参考方案2】:

一种方法是使用MediaPlayer.setVolume(right, left) 并在每次迭代后递减这些值。这是一个粗略的想法

float volume = 1;
float speed = 0.05f;

public void FadeOut(float deltaTime)

    mediaPlayer.setVolume(volume, volume);
    volume -= speed* deltaTime


public void FadeIn(float deltaTime)

    mediaPlayer.setVolume(volume, volume);
    volume += speed* deltaTime


一旦您的计时器到期,就应该调用FadeIn()FadeOut()。该方法不需要使用 deltaTime,但它会更好,因为它会在所有设备上以相同的速率降低音量。

【讨论】:

这是我不想做的事情,但由于缺乏更好的解决方案而最终还是做了。我一直在寻找更好、更优雅的解决方案,但似乎没有。所以我会接受这个答案。 您能再添加一些示例代码吗?我想我理解你在这个例子中的意图,但它仍然没有为我点击。比如,“deltaTime”从何而来?你会在一个循环中完成这一切吗? @Chris 如果我想同时为两首歌曲使用 FadeOut 和 FadeIn 怎么办?意味着我想同时对歌曲 1 使用 FadeOut,对歌曲 2 使用 FadeIn。那么它是如何工作的????你能帮我解决这个问题吗?!! 你是怎么做到的?有什么线索吗? 在哪里调用淡出,在哪里调用淡入?尝试从 onPreparedListener 调用淡入和从 onCompletionListener 淡出,但它不起作用。有什么想法吗?【参考方案3】:

这是一个非常好的类 sngreco。

为了让它更完整,我将添加 stop() 函数来停止播放器,并添加 stopAndRelease() 来停止播放器并安全地释放资源,在调用 onStop() 或 onDestroy() 等 Activity 方法时非常有用。

两种方法:

    public void stop(int fadeDuration)

    try 
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MAX;
        else
            iVolume = INT_VOLUME_MIN;

        updateVolume(0);

        // Start increasing volume in increments
        if (fadeDuration > 0)
        
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask()
            
                @Override
                public void run()
                
                    updateVolume(-1);
                    if (iVolume == INT_VOLUME_MIN)
                    
                        // Pause music
                        mediaPlayer.stop();
                        timer.cancel();
                        timer.purge();
                    
                
            ;

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        
     catch (Exception e) 
        e.printStackTrace();
    



public void stopAndRelease(int fadeDuration) 
    try 
        final Timer timer = new Timer(true);
        TimerTask timerTask = new TimerTask()
        
            @Override
            public void run()
            
                updateVolume(-1);
                if (iVolume == INT_VOLUME_MIN)
                
                    // Stop and Release player after Pause music
                    mediaPlayer.stop();
                    mediaPlayer.release();
                    timer.cancel();
                    timer.purge();
                
            
        ;

        timer.schedule(timerTask, fadeDuration);
     catch (Exception e) 
        e.printStackTrace();
    

【讨论】:

【参考方案4】:

我一直在努力,希望对您有所帮助:D:

private static void crossFade() 
    MediaPlayerManager.fadeOut(currentPlayer, 2000);
    MediaPlayerManager.fadeIn(auxPlayer, 2000);
    currentPlayer = auxPlayer;
    auxPlayer = null;


public static void fadeOut(final MediaPlayer _player, final int duration) 
    final float deviceVolume = getDeviceVolume();
    final Handler h = new Handler();
    h.postDelayed(new Runnable() 
        private float time = duration;
        private float volume = 0.0f;

        @Override
        public void run() 
            if (!_player.isPlaying())
                _player.start();
            // can call h again after work!
            time -= 100;
            volume = (deviceVolume * time) / duration;
            _player.setVolume(volume, volume);
            if (time > 0)
                h.postDelayed(this, 100);
            else 
                _player.stop();
                _player.release();
            
        
    , 100); // 1 second delay (takes millis)




public static void fadeIn(final MediaPlayer _player, final int duration) 
    final float deviceVolume = getDeviceVolume();
    final Handler h = new Handler();
    h.postDelayed(new Runnable() 
        private float time = 0.0f;
        private float volume = 0.0f;

        @Override
        public void run() 
            if (!_player.isPlaying())
                _player.start();
            // can call h again after work!
            time += 100;
            volume = (deviceVolume * time) / duration;
            _player.setVolume(volume, volume);
            if (time < duration)
                h.postDelayed(this, 100);
        
    , 100); // 1 second delay (takes millis)


public static float getDeviceVolume() 
    int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);

    return (float) volumeLevel / maxVolume;

【讨论】:

如果我想同时使用 FadeOut 和 FadeIn 怎么办。我的意思是我正在播放一首歌曲,然后我按下淡入淡出按钮,然后播放歌曲音量缓慢下降,同时下一首歌曲音量增加。当第一首歌曲音量变为 0 时,它会停止播放,而第二首歌曲继续播放。【参考方案5】:

这是我对股票 Android 闹钟淡入实现的简化改编。

它不是定义步进/增量的数量,然后逐步增加音量(如在此问题的其他答案中),它每 50 毫秒(可配置值)调整音量,在 -40dB(接近静音)和 0dB(最大;相对于流音量)基于:

预设效果持续时间(可以硬编码或由用户设置) 播放开始后经过的时间

请参阅下面的computeVolume() 了解精彩内容。

完整的原始代码可以在这里找到:Google Source

private MediaPlayer mMediaPlayer;
private long mCrescendoDuration = 0;
private long mCrescendoStopTime = 0;

// Default settings
private static final boolean DEFAULT_CRESCENDO = true;
private static final int CRESCENDO_DURATION = 1;

// Internal message codes
private static final int EVENT_VOLUME = 3;


// Create a message Handler
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() 
    @Override
    public void handleMessage(Message msg) 
        switch (msg.what) 
            ...

            case EVENT_VOLUME:
            if (adjustVolume()) 
                scheduleVolumeAdjustment();
            
            break;

            ...
        
    
;


// Obtain user preferences
private void getPrefs() 
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    ...

    final boolean crescendo = prefs.getBoolean(SettingsActivity.KEY_CRESCENDO, DEFAULT_CRESCENDO);
    if (crescendo) 
        // Convert mins to millis
        mCrescendoDuration = CRESCENDO_DURATION * 1000 * 60;
     else 
        mCrescendoDuration = 0;
    

    ...



// Start the playback
private void play(Alarm alarm) 
    ...

    // Check to see if we are already playing
    stop();

    // Obtain user preferences
    getPrefs();

    // Check if crescendo is enabled. If it is, set alarm volume to 0.
    if (mCrescendoDuration > 0) 
        mMediaPlayer.setVolume(0, 0);
    

    mMediaPlayer.setDataSource(this, alarm.alert);
    startAlarm(mMediaPlayer);

    ...



// Do the common stuff when starting the alarm.
private void startAlarm(MediaPlayer player) throws java.io.IOException, IllegalArgumentException, IllegalStateException 

    final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

    // Do not play alarms if stream volume is 0
    // (typically because ringer mode is silent).
    if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) 
        player.setAudiostreamType(AudioManager.STREAM_ALARM);
        player.setLooping(true);
        player.prepare();
        player.start();

        // Schedule volume adjustment
        if (mCrescendoDuration > 0) 
            mCrescendoStopTime = System.currentTimeMillis() + mCrescendoDuration;
            scheduleVolumeAdjustment();
        
    



// Stop the playback
public void stop() 
    ...

    if (mMediaPlayer != null) 
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
    

    mCrescendoDuration = 0;
    mCrescendoStopTime = 0;

    ...



// Schedule volume adjustment 50ms in the future.
private void scheduleVolumeAdjustment() 
    // Ensure we never have more than one volume adjustment queued.
    mHandler.removeMessages(EVENT_VOLUME);
    // Queue the next volume adjustment.
    mHandler.sendMessageDelayed( mHandler.obtainMessage(EVENT_VOLUME, null), 50);



// Adjusts the volume of the ringtone being played to create a crescendo effect.
private boolean adjustVolume() 
    // If media player is absent or not playing, ignore volume adjustment.
    if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) 
        mCrescendoDuration = 0;
        mCrescendoStopTime = 0;
        return false;
    
    // If the crescendo is complete set the volume to the maximum; we're done.
    final long currentTime = System.currentTimeMillis();
    if (currentTime > mCrescendoStopTime) 
        mCrescendoDuration = 0;
        mCrescendoStopTime = 0;
        mMediaPlayer.setVolume(1, 1);
        return false;
    
    // The current volume of the crescendo is the percentage of the crescendo completed.
    final float volume = computeVolume(currentTime, mCrescendoStopTime, mCrescendoDuration);
    mMediaPlayer.setVolume(volume, volume);

    // Schedule the next volume bump in the crescendo.
    return true;



/**
 * @param currentTime current time of the device
 * @param stopTime time at which the crescendo finishes
 * @param duration length of time over which the crescendo occurs
 * @return the scalar volume value that produces a linear increase in volume (in decibels)
 */
private static float computeVolume(long currentTime, long stopTime, long duration) 
    // Compute the percentage of the crescendo that has completed.
    final float elapsedCrescendoTime = stopTime - currentTime;
    final float fractionComplete = 1 - (elapsedCrescendoTime / duration);
    // Use the fraction to compute a target decibel between -40dB (near silent) and 0dB (max).
    final float gain = (fractionComplete * 40) - 40;
    // Convert the target gain (in decibels) into the corresponding volume scalar.
    final float volume = (float) Math.pow(10f, gain/20f);
    //LOGGER.v("Ringtone crescendo %,.2f%% complete (scalar: %f, volume: %f dB)", fractionComplete * 100, volume, gain);
    return volume;

【讨论】:

以上是关于Android:如何为我的应用播放的任何音乐文件创建淡入/淡出音效?的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack Compose 如何为 LazyColumn 懒惰地获取音乐文件及其元数据

Android:尝试使用输入播放列表从我的应用启动音乐播放器

android - 如何在我的音乐播放器中从文件管理器播放 mp3 文件?

如何为音乐文件创建专辑缩略图

android推送通知的定制设计

在android中播放两个音频文件