Upto: Table of Contents of full book "Programming and Using Linux Sound"

Android

Android is a Linux-based system from Google now found on phones, tablets and various miscellaneous devices.

Resources

Identifying devices

Android doesn't have a means of listing the input and output devices. Instead there are methods to query the state of various assumed or possible devices. The page Dealing with Audio Output Hardware gives the code using AudioManager methods

	
if (isBluetoothA2dpOn()) {
    // Adjust output for Bluetooth.
} else if (isSpeakerphoneOn()) {
    // Adjust output for Speakerphone.
} else if (isWiredHeadsetOn()) {
    // Adjust output for headsets
} else { 
    // If audio plays and noone can hear it, is it still playing?
}
	
      

My Android experience

I have an Android device designed to be hooked up to a TV (or similar) and used as a media player, browser, etc. It is branded in various ways: in Australia it was sold by Kogan as the Agora Adroid TV, and is available in other countries under different names while the OEM appears to be jointech.com.hk selling it as the JTV220 Android TV.

This device is stuck on Android 2.2 and the OEM has not released any upgrades. Android 2.2 was not the greatest for media support and will not - for example - stream Ogg Vorbis audio files from HTTP servers.

I also have an Asus TF101 Transformer running 4.0.3. Media support is much more advanced.

The list of supported types is Android Supported Media Formats .

Playing files

An app can get media from various sources

I'm not so much interested in the first two. They are discussed in books such as "Pro Android Media Developing Graphics, Music, Video, and Rich Media Apps for Smartphones and Tablets" by Shawn Van Every from APress.

An SD card on my ASUS Transformer is mounted into /Removable/SD. I put audio files into a subdirectory Music. Files from here can be played using an Android MediaPlayer. There is a state machine model for setting up the media player to play files: here it consists of setting a data source and then calling prepare() and start(). The only wrinkle in this is actually finding the path to the data file: in the Android, the File class doesn't like a path with embedded '/'. So you have to walk the file path one directory at a time till you get to the file itself. Note that the ASUS Transformer has an internal SD card, and the "recommended" way of getExternalStorageDirectory returns the internal card and not the external one!

I'm not going to go through any of how to set up an environment such as Eclipse or how to build projects. Once done, an app can be "exported" to an SD card and installed from the file browser. A player for a single file on the SD disk looks like

      
      package jan.newmarch.DiskPlayer;

import java.io.File;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.view.Menu;

public class DiskPlayerActivity extends Activity {

    private String filePath = "/Removable/SD/Music/audio_01.ogg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_disk_player);
	try {
	    String[] array=filePath.split("/");
	    File f = null;
	    for(int t = 0; t < array.length - 1; t++)
		{
		    f = new File(f, array[t]);
		}
	    File songFile=new File(f, array[array.length-1]);
		
	    MediaPlayer player = new MediaPlayer();
	    player.setAudioStreamType(AudioManager.STREAM_MUSIC);
	    player.setDataSource(songFile.getAbsolutePath());
	    player.prepare()	;
	    player.start();
	} catch (Exception e) {
	    AlertDialog.Builder builder = new AlertDialog.Builder(this);
	    builder.setMessage(e.toString());
	    builder.create();
	    builder.show();
	}
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
	// Inflate the menu; this adds items to the action bar if it is present.
	getMenuInflater().inflate(R.menu.activity_disk_player, menu);
	return true;
    }

}

      
    

This app requires access to read the SD card. In the AndroidManifest file this is given by

	
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
	
      

Streaming audio

The MediaPlayer will take the URL of an audio file - in some versions of Android and for some file types. This is sad, particularly for me since Android 2.2 won't stream Ogg files and my media player can't be upgraded from 2.2.

When an app can't stream a file from the web, we have to do silly tricks like downloading it locally and then playing it. That code looks like

      
      package jan.newmarch.HttpPlayer;

import jan.newmarch.HttpPlayer.R;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.view.Menu;

public class HttpPlayerActivity extends Activity {

	private String uriStr = "http://192.168.1.9/music/test.ogg";
	private HttpPlayerActivity activity;
	private MediaPlayer player; 

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_http_player);

		activity = this;	
		try {
			// Try the URL directly (ok for Android 3.0 upwards)
			tryMediaPlayer(uriStr);
		} catch(Exception e) {
			// Try downloading the file and then playing it - needed for Android 2.2
			try {
				downloadToLocalFile(uriStr, "audiofile.ogg");
				File localFile = activity.getFileStreamPath("audiofile.ogg");
				tryMediaPlayer(localFile.getAbsolutePath());
			} catch(Exception e2) {
				System.out.println("File error " + e2.toString());
				AlertDialog.Builder builder = new AlertDialog.Builder(this);
				builder.setMessage(e.toString());
				builder.create();
				builder.show();
			}
		}
	}

	private void downloadToLocalFile(String uriStr, String filename) throws Exception {
		URL url = new URL(Uri.encode(uriStr, ":/"));
		BufferedInputStream reader = 
				new BufferedInputStream(url.openStream());
		FileOutputStream fOut = activity.openFileOutput(filename,
				Context.MODE_WORLD_READABLE);
		BufferedOutputStream writer = new BufferedOutputStream(fOut); 

		byte[] buff = new byte[1024]; 
		int nread;
		while ((nread = reader.read(buff, 0, 1024)) != -1) {
			writer.write(buff, 0, nread);
		}
		writer.close();
	}

	private void tryMediaPlayer(String uriStr) throws Exception {
		player = new MediaPlayer();
		player.setAudioStreamType(AudioManager.STREAM_MUSIC);
		player.setDataSource(uriStr);
		player.prepare();
		player.start();         
	}



	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_http_player, menu);
		return true;
	}

}

      
    

This app requires access to the internet to get the resource. In the AndroidManifest file this is given by

	
	  <uses-permission android:name="android.permission.INTERNET"/>
	
      

Recording audio

There are two ways: using MediaRecorder or AudioRecorder. The second gives you more control. An example using this is given as Android audio recording, part 2 by Krishnaraj Varma.

This app requires RECORD_AUDIO permission. In the AndroidManifest file this is given by

	
	  <uses-permission android:name="android.permission.INTERNET"/>
	
      

Playing audio from the microphone

Best so far: http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android


    void playSound(){
        final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length,
                AudioTrack.MODE_STATIC);
        audioTrack.write(generatedSnd, 0, generatedSnd.length);
        audioTrack.play();
    }

Similar stuff at http://www.speakingcode.com/2012/01/01/playing-with-android-audiotrack-to-generate-sounds/ Playing with Android AudioTrack to Generate Sounds, but no code

Code at http://masterex.github.com/archive/2012/05/28/android-audio-synthesis.html Android: Crafting a Metronome with Audio Synthesis

Replaying the audio recorded from the microphone to the output device makes use of two objects: an AudioRecord to capture data from the microphone and an AudioTrack to send it to the output device. The previous program for recording audio just needs to write the data to an AudioTrack object instead of to a file.

The code is PlayMicrophone.java:

      
      package jan.newmarch.playmicrophone;

import java.io.FileOutputStream;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class PlayMicrophoneActivity extends Activity {
	private static final int RECORDER_BPP = 16;
	private static final int RECORD_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
	private static final int PLAY_CHANNELS = AudioFormat.CHANNEL_OUT_MONO;
	private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

	private AudioRecord recorder = null;
	private AudioTrack player = null;
	private int bufferSize = 0;
	private Thread recordingThread = null;
	private boolean isRecording = false;
	private int sampleRate;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		setButtonHandlers();
		enableButtons(false);

		sampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
		int bSize1 = AudioRecord.getMinBufferSize(sampleRate,
				RECORD_CHANNELS, AUDIO_ENCODING);
		int bSize2 = AudioTrack.getMinBufferSize(sampleRate,
				PLAY_CHANNELS, AUDIO_ENCODING);
		bufferSize =  (bSize1 > bSize2) ? bSize1 : bSize2;
		Toast toast = Toast.makeText(getApplicationContext(), 
					"Sample rate: " + sampleRate + 
						" min record buf size: " + bSize1 +
							" min play buf size: " + bSize2, 
					Toast.LENGTH_SHORT);
		toast.show();
		if (bSize1 < 0 || bSize2 < 0) {
			fatal("No microphone or no speaker");
		}
	}
	
	private void fatal(String msg) {
		/*
		Toast toast = Toast.makeText(getApplicationContext(), 
				msg, 
				Toast.LENGTH_SHORT);
		toast.show();
		*/
		System.out.println(msg);
		finish();
	}

	private void setButtonHandlers() {
		((Button) findViewById(R.id.btnStart)).setOnClickListener(btnClick);
		((Button) findViewById(R.id.btnStop)).setOnClickListener(btnClick);
	}

	private void enableButton(int id, boolean isEnable) {
		((Button) findViewById(id)).setEnabled(isEnable);
	}

	private void enableButtons(boolean isRecording) {
		enableButton(R.id.btnStart, !isRecording);
		enableButton(R.id.btnStop, isRecording);
	}

	private void startRecording() {
		try {
		
		recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
				sampleRate, RECORD_CHANNELS,
				AUDIO_ENCODING, bufferSize);
				
		player = new AudioTrack(AudioManager.STREAM_MUSIC,
				sampleRate, PLAY_CHANNELS,
				AUDIO_ENCODING, bufferSize,
				AudioTrack.MODE_STREAM);
		recorder.startRecording();
		player.play();
		} catch(Exception e) {
			fatal("Can't create recorder or player");
		}

		isRecording = true;

		recordingThread = new Thread(new Runnable() {

			// @Override
			public void run() {
				writeAudioDataToSpeaker();
			}
		}, "AudioRecorder Thread");

		recordingThread.start();
	}

	private void writeAudioDataToSpeaker() {
		byte data[] = new byte[bufferSize];

		int read = 0;

		while (isRecording) {
			read = recorder.read(data, 0, bufferSize);

			if (AudioRecord.ERROR_INVALID_OPERATION != read) {
				player.write(data, 0, read);
			}
		}			
	}

	private void stopRecording() {
		if (null != recorder) {
			isRecording = false;

			recorder.stop();
			recorder.release();

			recorder = null;
			recordingThread = null;
			
			player.stop();
			player.release();
		}
	}

	private View.OnClickListener btnClick = new View.OnClickListener() {
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.btnStart: {
				enableButtons(true);
				startRecording();
				break;
			}
			case R.id.btnStop: {
				enableButtons(false);
				stopRecording();
				break;
			}
			}
			Toast toast = Toast.makeText(getApplicationContext(), 
					"Btn clicked handler called", 
					Toast.LENGTH_SHORT);
			toast.show();
		}
	};

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

}

      
    

This app requires RECORD_AUDIO permission. In the AndroidManifest file this is given by

	
	  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
	
      

On my ASUS Transformer with 4.0, the input and output devices both require a minimum buffer of 8192 bytes with a sample rate of 44100hz. The latency is poor: 330 milliseconds and is similar to other reports. It is not usable for realtime record and playback. The new APIs in 4.1 may improve the situation.

MIDI playback

The Android MediaPlayer supports the playback of MIDI files. See The state of MIDI support on Android . It uses the Sonivox EAS (Embedded Audio Synthesis).

OpenMAX

The audio/video components of Android systems are Java layered on top of OpenMAX - a C-based system for low-end devices. This is partly covered in the chapter OpenMAX and and OpenSL . The links using NDK will be explored inlater editions of this book.

Conclusion

Android offers a Java API for dealing with audio. It suffers from version dependencies and the failure to have an open model for the ROMs means that you can be stuck with an old version of Android which you can't upgrade.



Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
"Programming and Using Linux Sound - in depth" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/LinuxSound/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book