Skip to content

Commit

Permalink
Merge pull request #31 from brarcher/unsync-fading
Browse files Browse the repository at this point in the history
Simplify playback to one thread that loops over one AudioTrack
  • Loading branch information
brarcher authored Sep 3, 2016
2 parents d8e1b01 + 936c8d9 commit e94a138
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 146 deletions.
222 changes: 81 additions & 141 deletions app/src/main/java/protect/babysleepsounds/LoopingAudioPlayer.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package protect.babysleepsounds;

import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.PowerManager;
import android.util.Log;

import java.io.File;
Expand All @@ -13,191 +15,129 @@ public class LoopingAudioPlayer
{
public static final String TAG = "BabySleepSounds";

private static final int FREQUENCY = 44100;
private static final int CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_OUT_STEREO;
private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

private final File _wavFile;
private Thread _thread1;
private Thread _thread2;
private final PowerManager.WakeLock _wakeLock;

public LoopingAudioPlayer(File wavFile)
public LoopingAudioPlayer(Context context, File wavFile)
{
_wavFile = wavFile;

PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if(powerManager != null)
{
_wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
else
{
Log.w(TAG, "Failed to acquire a wakelock");
_wakeLock = null;
}
}

public void start()
{
stop();

ToggleWaiter first = new ToggleWaiter();
ToggleWaiter second = new ToggleWaiter();

_thread1 = new Thread(new FadingAudioPlayer(_wavFile, first, second), "PlayThread1");
_thread2 = new Thread(new FadingAudioPlayer(_wavFile, second, first), "PlayThread2");

_thread1.start();
_thread2.start();
if(_playbackThread != null)
{
if(_wakeLock != null)
{
_wakeLock.acquire();
}

// Start the first thread playing
first.set();
_playbackThread.start();
}
else
{
Log.w(TAG, "Audio playback already stopped, cannot start again");
}
}

public void stop()
{
Log.i(TAG, "Requesting audio playback to stop");

if(_thread1 != null && _thread2 != null)
if(_playbackThread != null)
{
_thread1.interrupt();
_thread2.interrupt();
_playbackThread.interrupt();
_playbackThread = null;

_thread1 = null;
_thread2 = null;
if(_wakeLock != null)
{
_wakeLock.release();
}
}
}
}

class FadingAudioPlayer implements Runnable
{
public static final String TAG = "BabySleepSounds";

private static final int OVERLAP_AUDIO_START_SEC = 3;

private static final int FREQUENCY = 44100;
private static final int CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_OUT_STEREO;
private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

private final File _wavFile;
private final ToggleWaiter _waitToggle;
private final ToggleWaiter _releaseToggle;

public FadingAudioPlayer(File wavFile, ToggleWaiter waitToggle, ToggleWaiter releaseToggle)
{
_wavFile = wavFile;
_waitToggle = waitToggle;
_releaseToggle = releaseToggle;
}

public void run()
private Thread _playbackThread = new Thread(new Runnable()
{
Log.i(TAG, "Setting up stream");

final int bufferSize = AudioTrack.getMinBufferSize(FREQUENCY, CHANNEL_CONFIGURATION, AUDIO_ENCODING);
final int byteBufferSize = bufferSize*2;

final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
FREQUENCY,
CHANNEL_CONFIGURATION,
AUDIO_ENCODING,
bufferSize,
AudioTrack.MODE_STREAM);

audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener()
@Override
public void run()
{
@Override
public void onMarkerReached(AudioTrack track)
{
Log.d(TAG, "Starting other audio thread to overlap");
_releaseToggle.set();
}
Log.i(TAG, "Setting up audio playback");

@Override
public void onPeriodicNotification(AudioTrack track)
{
// Nothing to do
}
});
final int bufferSize = AudioTrack.getMinBufferSize(FREQUENCY, CHANNEL_CONFIGURATION, AUDIO_ENCODING);
final int byteBufferSize = bufferSize*2;

int framesInFile = (int)_wavFile.length()/2/2;
int fadeOutFrames = OVERLAP_AUDIO_START_SEC * FREQUENCY;
int fadeOutFrameStart = framesInFile - fadeOutFrames;
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
FREQUENCY,
CHANNEL_CONFIGURATION,
AUDIO_ENCODING,
bufferSize,
AudioTrack.MODE_STREAM);

FileInputStream is = null;
FileInputStream is = null;

try
{
while(Thread.currentThread().isInterrupted() == false)
try
{
// Wait to start
_waitToggle.waitUntilSet();

// The file contains bytes, but the PCM data in 16 bit, so /2 to get samples.
// The file also is stereo instead of mono, so /2 for two different tracks.

audioTrack.setNotificationMarkerPosition(fadeOutFrameStart);

is = new FileInputStream(_wavFile);
final byte [] buffer = new byte[byteBufferSize];
int read;

audioTrack.play();

while(Thread.currentThread().isInterrupted() == false)
{
read = is.read(buffer);
Log.d(TAG, "Restarting audio file");
is = new FileInputStream(_wavFile);
int read;

if(read <= 0)
while(Thread.currentThread().isInterrupted() == false)
{
break;
}
read = is.read(buffer);

audioTrack.write(buffer, 0, read);
}
if(read <= 0)
{
break;
}

audioTrack.stop();
}
}
catch(IOException e)
{
Log.d(TAG, "Failed to read file", e);
}
catch(InterruptedException e)
{
Log.d(TAG, "Terminating due to being interrupted", e);
}
finally
{
audioTrack.release();
audioTrack.write(buffer, 0, read);
}

try
{
if(is != null)
{
is.close();
// File completed playback, start again
}
}
catch(IOException e)
{
Log.d(TAG, "Failed to close file", e);
Log.d(TAG, "Failed to read file", e);
}
}

Log.i(TAG, "Finished playback");
}
}

class ToggleWaiter
{
private boolean isSet = false;

public void set()
{
synchronized(this)
{
isSet = true;
notify();
}
}

public void waitUntilSet() throws InterruptedException
{
synchronized(this)
{
while(isSet == false)
finally
{
wait();
audioTrack.release();

try
{
if(is != null)
{
is.close();
}
}
catch(IOException e)
{
Log.d(TAG, "Failed to close file", e);
}
}

isSet = false;
Log.i(TAG, "Finished playback");
}
}


}, "PlaybackThread");
}
11 changes: 6 additions & 5 deletions app/src/main/java/protect/babysleepsounds/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.TextView;
import android.widget.Toast;

import com.github.hiteshsondhi88.libffmpeg.FFmpegExecuteResponseHandler;
import com.google.common.collect.ImmutableMap;
Expand Down Expand Up @@ -71,9 +70,11 @@ protected void onCreate(Bundle savedInstanceState)
setSupportActionBar(toolbar);

// These sound files by convention are:
// ~10 seconds long
// Have a 2 second fade-in and fade-out
// mp3 file, 128kbps, stereo
// - take a ~10 second clip
// - Apply a 2 second fade-in and fade-out
// - Cut the first 3 seconds, and place it over the last three seconds
// which should create a seamless track appropriate for looping
// - Save as a mp3 file, 128kbps, stereo
_soundMap = ImmutableMap.<String, Integer>builder()
.put(getResources().getString(R.string.dryer), R.raw.dryer)
.put(getResources().getString(R.string.fan), R.raw.fan)
Expand Down Expand Up @@ -310,7 +311,7 @@ public void onSuccess(String message)
{
Log.d(TAG, "ffmpeg execute onSuccess(): " + message);

_mediaPlayer = new LoopingAudioPlayer(processed);
_mediaPlayer = new LoopingAudioPlayer(MainActivity.this, processed);
_mediaPlayer.start();
updateToPlaying();
}
Expand Down
Binary file modified app/src/main/res/raw/dryer.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/fan.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/ocean.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/rain.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/refrigerator.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/shower.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/stream.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/vacuum.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/water.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/waterfall.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/waves.mp3
Binary file not shown.
Binary file modified app/src/main/res/raw/white_noise.mp3
Binary file not shown.

0 comments on commit e94a138

Please sign in to comment.