1

My problem changed a bit, please take a look at the EDIT 2 below

I am learning how to work with Services and audio recording on Android.

I want to create an app, that will do nothing except starting a service: after requesting permissions (RECORD_AUDIO, INTERNET) the app only calls startService() from the MainActivity.

The service will then record audio and stream it to given IP address. I took my inspiration for the audio streaming server and client from the answers to this question. I am testing the app on Android 6.0 in the Android Studio Emulator so far.

This is the onStartCommand() method of my service. I create a new thread where I wait until the user has clicked "Allow" in the permissions request dialog. When I receive the permissions I initialize the AudioRecord and then I read from it in a while-loop and send the data away.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Thread streamThread = new Thread(new Runnable() {

        @Override
        public void run() {
            waitForPermissions();
            try {
                DatagramSocket socket = new DatagramSocket();
                byte[] buffer = new byte[minBufSize];
                DatagramPacket packet;
                final InetAddress destination = InetAddress.getByName(address);
                recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, audioFormat, minBufSize * 10);
                recorder.startRecording();
                while (status == true) {
                    minBufSize = recorder.read(buffer, 0, buffer.length);
                    packet = new DatagramPacket(buffer, buffer.length, destination, port);
                    socket.send(packet);
                }
            } catch (...) {...}
        }
    });
    streamThread.start();
    return START_STICKY;
}

In onDestroy() I set the flag status to false so that the loop in the thread created in onStartCommand() terminates. Then I release the recorder so that it can be initialized and used again.

@Override      
public void onDestroy(){
    status = false;
    recorder.stop();
    recorder.release();
    super.onDestroy();
}

This app works works only partially:

  • When I launch the listening server on the computer and the app in the emulator, I can hear the audio normally.
  • When I minimize the app (tap Home button), the audio stream is still OK.
  • My problem is that when I kill the app (swipe it right in the running apps screen), the audio stops. In the Logcat I can see that the service has been restarted and runs as usual (initializes the AudioRecord and everything, no exceptions thrown), only I can't hear anything. When I run the app again (and therefore call the startService() again), I see an exception in the Logcat telling me that the AudioRecord start failed with an error code -38 (meaning that the microphone is in use by the previous instance of the service).

What am I doing wrong here? Why is the service running, but audio not streaming when I kill the app?

Many thanks for your answers.

EDIT:

I know this approach won't work on newer Android versions. This app is just for private purposes and will be run on one of my old phones, either with Android 5.1.1 or 6.0.

After I read the article in this answer I moved what was previously in onDestroy to onTaskRemoved. However, nothing changed.

The Service is apparently still sending some packets after it is restarted. I looked at them in Wireshark and the packets are still leaving the device and the Server is still receiving them. The payload of the packets is nonzero, unique for every packet.

EDIT 2:

I changed the buffer size to AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) * 10 in the Android app and AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) * 20 in the Server application. Now when I swipe the app, the Service restarts and then I can hear some audio. But the sound is very chunky after the restart and I don't know why. It also gets chunky when I uninstall the app, keep the server running and then install and run the app again.

So the problem is probably on the Server side. What could be the problem with the server? Am I working correctly with the SourceDataLine? The Server code looks like this (taken from here):

...
static int sampleRate = 8000;
static int bufferLen = minBufSize * 20;

public static void main(String args[]) throws Exception {

    DatagramSocket serverSocket = new DatagramSocket(port);
    byte[] receiveData = new byte[bufferLen];
    format = new AudioFormat(sampleRate, 16, 1, true, false);
    dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
    sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
    sourceDataLine.open(format);
    sourceDataLine.start();

    FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
    volumeControl.setValue(volumeControl.getMaximum());

    while (status == true) {
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        serverSocket.receive(receivePacket);
        toSpeaker(receivePacket.getData(), receivePacket.getLength());
    }
    sourceDataLine.drain();
    sourceDataLine.close();
}
public static void toSpeaker(byte soundbytes[], int length) {
    try {
        sourceDataLine.write(soundbytes, 0, length);
    } catch (...) {...}
}
...

The sample rate of the server is identical to the Android app. Format is AudioFormat.ENCODING_PCM_16BIT.

Topper Harley
  • 375
  • 4
  • 17
  • Keep in mind that this approach won't work on Android 9 or later. On those Android versions you can only record audio while your app is in the foreground, or from a foreground service that shows a notification while active. – Michael Oct 16 '19 at 16:22
  • @Michael I know about this. This app I am trying to write is just for personal learning purposes and is aimed at my old phone with Android 6. – Topper Harley Oct 16 '19 at 17:13

1 Answers1

0

Read this article: What exactly happens to running services when you swipe an android app from recent app list ?

In Your case the service stops working, in recreation because of the start sticky it seems you have a problem that recording fails which I don't know what it is. In the sense of improving your service use IntentService or a foreground service to solve the problem.

BTW, if you want to use your app in android 8 or higher you probably has to use ForeGrounService only since IntentService will be killed by system after several minutes if app is in background. Read this: Background Execution Limits

So after testing all the solutions you might be agree with me on using a foreground service: Android Foreground Service Example

Sina
  • 2,683
  • 1
  • 13
  • 25