Commit 29aa00f9 authored by Daniel Sonck's avatar Daniel Sonck
Browse files

Code cleanup and bugfixes

- Fixed NULL pointer exception when stopping with no network reception
- Fixed code warnings
- Refactored embedded classes to separate classes
parent cac1bfcc
......@@ -45,6 +45,9 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
}
}
externalNativeBuild {
cmake {
......
......@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.view.MenuInflater;
import android.view.View;
......@@ -32,16 +31,6 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
/*
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
*/
@NonNull
@Override
public MenuInflater getMenuInflater() {
......
......@@ -15,24 +15,23 @@ import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import java.util.List;
import fm.touhou.touhoufm.fragments.AlbumArtFragment;
import fm.touhou.touhoufm.PlaylistItem;
import fm.touhou.touhoufm.R;
import fm.touhou.touhoufm.fragments.AlbumArtFragment;
import fm.touhou.touhoufm.fragments.PlaylistFragment;
import fm.touhou.touhoufm.fragments.SongInfoFragment;
import fm.touhou.touhoufm.PlaylistItem;
import fm.touhou.touhoufm.service.RadioService;
public class MainActivity extends AppCompatActivity implements RadioService.Callbacks {
......@@ -44,7 +43,6 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
private boolean mLoggedIn;
private boolean mPlaying = false;
private MenuItem mLoginItem = null;
private ImageLoader loader;
......@@ -58,12 +56,12 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
loader = ImageLoader.getInstance();
loader.init(configuration);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
MainPagerAdapter mPagerAdapter = new MainPagerAdapter(getSupportFragmentManager());
ViewPager pager = (ViewPager) findViewById(R.id.main_pager);
ViewPager pager = findViewById(R.id.main_pager);
pager.setAdapter(mPagerAdapter);
......@@ -75,7 +73,8 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
serviceIntent = new Intent(MainActivity.this, RadioService.class);
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
((PagerTabStrip) findViewById(R.id.pager_tabs)).setTabIndicatorColorResource(R.color.colorPrimary);
PagerTabStrip strip = findViewById(R.id.pager_tabs);
strip.setTabIndicatorColorResource(R.color.colorPrimary);
// Restore preferences
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
......@@ -158,7 +157,7 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
public void setPlayingStatus(boolean status) {
mPlaying = status;
ImageButton b = (ImageButton) findViewById(R.id.play_button);
ImageButton b = findViewById(R.id.play_button);
if (status) {
b.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_av_pause));
......@@ -195,8 +194,8 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
@Override
public void setProgress(int progress, String time) {
((SeekBar) findViewById(R.id.song_progress)).setProgress(progress);
TextView posView = (TextView) findViewById(R.id.song_position);
TextView lenView = (TextView) findViewById(R.id.song_length);
TextView posView = findViewById(R.id.song_position);
TextView lenView = findViewById(R.id.song_length);
String[] data = time.split(" / ");
posView.setText(data[0]);
......@@ -223,12 +222,6 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
mPlaylist.removeSong(position);
}
@Override
public void setLogin(boolean loggedIn) {
if (mLoginItem != null)
mLoginItem.setChecked(loggedIn);
}
@Override
public void setPlaylist(List<PlaylistItem> playList) {
mPlaylist.setPlaylist(playList);
......@@ -249,6 +242,7 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
return SongInfoFragment.newInstance();
case 2:
return PlaylistFragment.newInstance();
default:
}
return null;
}
......@@ -262,6 +256,7 @@ public class MainActivity extends AppCompatActivity implements RadioService.Call
return getString(R.string.title_now_playing);
case 2:
return getString(R.string.title_playlist);
default:
}
return super.getPageTitle(position);
}
......
package fm.touhou.touhoufm.radio;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Build;
import android.util.Log;
import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
import static android.media.AudioFormat.ENCODING_PCM_16BIT;
import static fm.touhou.touhoufm.radio.RtpReceiver.SAMPLE_RATE;
/**
* Created by dsonck on 8-11-17.
*/
public class AndroidPlayer extends Thread {
private static final String TAG = AndroidPlayer.class.getName();
private final RingBuffer mRingBuffer;
private final int mMinBufSize;
private AudioTrack mStreamTrack;
public AndroidPlayer(RingBuffer buffer, int minBufSize) {
mRingBuffer = buffer;
mMinBufSize = minBufSize;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mStreamTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(ENCODING_PCM_16BIT)
.setSampleRate(SAMPLE_RATE)
.setChannelMask(CHANNEL_OUT_STEREO).build())
.setBufferSizeInBytes(mMinBufSize).build();
} else {
//noinspection deprecation
mStreamTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, mMinBufSize, AudioTrack.MODE_STREAM);
}
Log.d(TAG, "AudioTrack min buffer size: ---- " + mMinBufSize);
}
@Override
public void run() {
startup();
play();
shutDown();
}
private void startup() {
mStreamTrack.play();
Log.d(TAG, "Player started --- ");
}
private void play() {
try {
short[] fullBuffer = new short[mMinBufSize * 4];
while (!interrupted()) {
if (mRingBuffer.read(fullBuffer, fullBuffer.length) < 0) {
interrupt();
}
// Write out our full buffer
mStreamTrack.write(fullBuffer, 0, fullBuffer.length);
}
} catch (Exception e) {
Log.d(TAG, "Player error --- ", e);
}
}
private void shutDown() {
Log.d(TAG, "Player finished --- ");
if (mStreamTrack != null) {
mStreamTrack.pause();
mStreamTrack.flush();
mStreamTrack.release();
mStreamTrack = null;
}
}
}
package fm.touhou.touhoufm.radio;
import android.util.Log;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
......@@ -9,55 +11,58 @@ import java.util.concurrent.locks.ReentrantLock;
*/
public class RingBuffer {
private volatile short mBuffer[];
private volatile boolean mRunning = true;
private static final String TAG = RingBuffer.class.getName();
private short[] mBuffer;
private volatile boolean mRunning;
private volatile int mReadAmount;
private volatile int mWriteAmount;
private int mReadAmount;
private int mWriteAmount;
private Lock mLock = new ReentrantLock(true);
private Condition mNotFull = mLock.newCondition();
private Condition mNotEmpty = mLock.newCondition();
private Condition mCheckState = mLock.newCondition();
private volatile int mReadPos, mWritePos;
private volatile int mLowBuff, mHighBuff;
private int mReadPos;
private int mWritePos;
private int mLowBuff;
private int mHighBuff;
private volatile boolean mFilling;
private boolean mFilling;
private void checkLowBuffer() {
if(mFilling) {
// Lock the amount access
mLock.lock();
if (mFilling) {
mLock.unlock();
return;
}
{
// Lock the amount access
mLock.lock();
if (mReadAmount < mLowBuff) {
// If we have less than the low buffer of data, start filling and block readamount
mFilling = true;
mReadAmount = 0;
}
mLock.unlock();
if (mReadAmount < mLowBuff) {
// If we have less than the low buffer of data, start filling and block readamount
mFilling = true;
mReadAmount = 0;
Log.d(TAG, "Low buffer");
}
mLock.unlock();
}
private void checkHighBuffer() {
if(!mFilling) {
// Lock the amount access
mLock.lock();
if (!mFilling) {
mLock.unlock();
return;
}
{
// Lock the amount access
mLock.lock();
if(mWriteAmount < mHighBuff) {
// If we have less than High buffer of space left, stop filling
mFilling = false;
mReadAmount = mBuffer.length - mWriteAmount;
mNotEmpty.signal();
}
mLock.unlock();
if (mWriteAmount < mHighBuff) {
// If we have less than High buffer of space left, stop filling
mFilling = false;
mReadAmount = mBuffer.length - mWriteAmount;
mCheckState.signal();
Log.d(TAG, "Recovered buffer");
}
mLock.unlock();
}
public RingBuffer(int count, int lowBuffer, int highBuffer) {
......@@ -70,6 +75,7 @@ public class RingBuffer {
mLowBuff = lowBuffer;
mHighBuff = highBuffer;
mFilling = true;
mRunning = true;
}
public int read(short out[], int size) {
......@@ -77,23 +83,9 @@ public class RingBuffer {
try {
{
mLock.lock();
while (mRunning && mReadAmount < size) {
mNotEmpty.await();
}
if (!mRunning) {
return -1;
}
mReadAmount -= size;
if (!lockRead(size)) return -1;
mLock.unlock();
}
while(toDo > 0) {
while (toDo > 0) {
// Calculate how much to write
int toRead = Math.min(toDo, mBuffer.length - mReadPos);
......@@ -102,25 +94,21 @@ public class RingBuffer {
// Decrement the size to do and move the read position
toDo -= toRead;
mReadPos+= toRead;
mReadPos += toRead;
// Wrap the read position around
if(mReadPos >= mBuffer.length) {
if (mReadPos >= mBuffer.length) {
mReadPos = 0;
}
}
{
mLock.lock();
mWriteAmount += size;
mNotFull.signal();
mLock.unlock();
}
freeWrite(size);
} catch (InterruptedException e) {
// Clear the buffer as we couldn't acquire the lock
for(int I = 0; I < size; I++) {
for (int I = 0; I < size; I++) {
out[I] = 0;
}
Thread.currentThread().interrupt();
}
checkLowBuffer();
......@@ -128,25 +116,38 @@ public class RingBuffer {
return size;
}
public int write(short in[], int size) {
int toDo = size;
private void freeWrite(int size) {
mLock.lock();
mWriteAmount += size;
mCheckState.signal();
mLock.unlock();
}
try {
{
mLock.lock();
private boolean lockRead(int size) throws InterruptedException {
mLock.lock();
while(mRunning && mWriteAmount < size) {
mNotFull.await();
}
while (mRunning && mReadAmount < size) {
mCheckState.await();
}
if(!mRunning) return -1;
if (!mRunning) {
mLock.unlock();
return false;
}
mWriteAmount -= size;
mReadAmount -= size;
mLock.unlock();
}
mLock.unlock();
return true;
}
while(toDo > 0) {
public int write(short[] in, int size) {
int toDo = size;
try {
if (!lockWrite(size)) return -1;
while (toDo > 0) {
// Calculate how much to write
// Either do the full buffer or up to the end
int toWrite = Math.min(toDo, mBuffer.length - mWritePos);
......@@ -156,22 +157,24 @@ public class RingBuffer {
// Decrement the size to do and move the read position
toDo -= toWrite;
mWritePos+= toWrite;
mWritePos += toWrite;
// Wrap the read position around
if(mWritePos >= mBuffer.length) {
if (mWritePos >= mBuffer.length) {
mWritePos = 0;
}
}
if ( !mFilling ) {
mLock.lock();
mReadAmount += size;
mNotEmpty.signal();
mLock.lock();
if (!mFilling) {
mLock.unlock();
freeRead(size);
} else {
mLock.unlock();
}
} catch (InterruptedException e) {
// Do nothing as we couldn't acquire the lock
Thread.currentThread().interrupt();
}
checkHighBuffer();
......@@ -179,12 +182,36 @@ public class RingBuffer {
return size;
}
private void freeRead(int size) {
mLock.lock();
mReadAmount += size;
mCheckState.signal();
mLock.unlock();
}
private boolean lockWrite(int size) throws InterruptedException {
mLock.lock();
while (mRunning && mWriteAmount < size) {
mCheckState.await();
}
if (!mRunning) {
mLock.unlock();
return false;
}
mWriteAmount -= size;
mLock.unlock();
return true;
}
public void abort() {
mLock.lock();
mRunning = false;
mCheckState.signalAll();
mLock.unlock();
mNotEmpty.signalAll();
mNotFull.signalAll();
}
}
package fm.touhou.touhoufm.radio;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import fm.touhou.touhoufm.service.RadioService;
import fm.touhou.touhoufm.utils.OpusDecoder;
import fm.touhou.touhoufm.utils.RtpPacket;
/**
* Created by dsonck on 8-11-17.
*/
public class RtpReceiver extends Thread {
private static final String TAG = RtpReceiver.class.getName();
private static final int SAMPLE_LEN = 40;
public static final int SAMPLE_RATE = 48000;
public static final int FRAME_SIZE = SAMPLE_RATE * SAMPLE_LEN / 1000;
private static final int BITRATE = 320000;
private static final int OPUS_SIZE = SAMPLE_LEN * BITRATE / 8 / 1000;
private static final int BUF_SIZE = FRAME_SIZE * 2;
private OpusDecoder mOpusDecoder;
private long mLastHello;
private DatagramSocket mRecvSocket;
private InetAddress address;
private static final int RTP_SIZE = 16;
private RingBuffer mRingBuffer;
public RtpReceiver(RingBuffer buffer) {
mRingBuffer = buffer;
mOpusDecoder = new OpusDecoder();
try {
mOpusDecoder.init(SAMPLE_RATE, 2, FRAME_SIZE);
} catch (OpusDecoder.OpusInitializationException e) {
mOpusDecoder = null;
Log.e(TAG, "Decoder initialization failed", e);
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Decoder link error", e);
mOpusDecoder = null;
}
mLastHello = System.currentTimeMillis();
}
private void initUdpConn() {
try {
// connect
if (address == null) {
address = InetAddress.getByName(RadioService.STREAM_HOST);
}
String msg = "Hello";
DatagramPacket sendPacket = new DatagramPacket(msg.getBytes(), msg.length(), address, RadioService.STREAM_PORT);
mRecvSocket.send(sendPacket);
} catch (IOException e) {
Log.e(TAG, "Failed to init udp conn", e);
}
}