I use AudioRecord to record audio from Android, and then reply it using AudioTrack. However, the result is very terrible, it is somehow similar to what it recorded, but it maybe slower (or maybe because it is modified, so I feel like that). When I analyze it carefully, I realize that there are many "gaps" (0 value) in my short array.
This is the graph I received:
And the gaps (it repeat that pattern, every about 630bytes of data, there is about 630bytes of 0s):
Here is my code for recording:
protected void onRecordButtonClick() {
if (this.recording) {
this.recording = false;
this.butRecord.setText("Record");
} else {
this.recordBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
this.recorder = new AudioRecord(
MediaRecorder.AudioSource.DEFAULT,
44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
this.recordBufferSize);
this.recordThread = new Thread(new Runnable() {
#Override
public void run() {
MainActivity.this.onRecording();
}
});
this.recordThread.setPriority(Thread.MAX_PRIORITY);
this.recording = true;
this.butRecord.setText("Stop recording");
this.recordThread.start();
}
}
protected void onRecording() {
this.recordData.clear();
final short[] temp = new short[this.recordBufferSize];
this.recorder.startRecording();
while (this.recording) {
this.recorder.read(temp, 0, this.recordBufferSize);
for (int i = 0; i < temp.length; i ++) {
this.recordData.add(temp[i]);
}
if (this.recordData.size() >= 220500) { this.recording = false; }
}
this.recorder.stop();
// Complete data
this.currentData = new short[this.recordData.size()];
for (int i = 0; i < this.currentData.length; i++) {
this.currentData[i] = this.recordData.get(i);
}
{ // Write to SD Card the result
final File file = new File(Environment.getExternalStorageDirectory(), "record.pcm");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
for (int i = 0; i < this.currentData.length; i++) {
oos.writeShort(this.currentData[i]);
}
oos.flush();
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
this.handler.post(new Runnable() {
#Override
public void run() {
MainActivity.this.waveform.setData(MainActivity.this.currentData);
MainActivity.this.butRecord.setText("Record");
final Bitmap bitmap = MainActivity.this.waveform.getDrawingCache();
try {
FileOutputStream fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory(), "cache.png"));
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
Related
I have a socket that recieves images via one InputStream that doesnt get closed. I want to send images continiously that way. But now the images get recieved with a delay of 1 image (the first one after I sent the second one, the second one after I sent the third one, ....). What am I doing wrong?
Server
public static void readImages(InputStream stream) throws IOException {
stream = new BufferedInputStream(stream);
BufferedImage image = null;
int j = 0;
while (true) {
stream.mark(MAX_IMAGE_SIZE);
ImageInputStream imgStream = ImageIO.createImageInputStream(stream);
Iterator<ImageReader> i = ImageIO.getImageReaders(imgStream);
if (!i.hasNext()) {
System.out.println("No more image readers");
break;
}
ImageReader reader = i.next();
reader.setInput(imgStream);
image = reader.read(0);
ImageIO.write(image,"jpg",new File("current" + j + ".jpg"));
System.out.println("Save an image " + j);
if (image == null) {
System.out.println("Image is null");
break;
}
long bytesRead = imgStream.getStreamPosition();
stream.reset();
stream.skip(bytesRead);
j++;
}
}
Client
new Thread(new Runnable() {
#Override
public void run() {
try {
OutputStream outputStream = server.getOutputStream();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmapToSend = Bitmap.createScaledBitmap(bitmapToSend, 900, 800, true);
bitmapToSend.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
outputStream.write(byteArray);
outputStream.flush();
} catch (IOException e) {
System.out.println("Socket not created");
e.printStackTrace();
}
}
}).start();
Note I dont close the output stream of the client, so I can send pictures all the time.
Using ImageIO.getImageReaders(imgStream) doesn't seem logical for socket streams, since it probably expects all images to be available at once. That may be the reason of your delay.
Secondly, for decompressing images there is a simple method BitmapFactory.decodeStream().
Thirdly, since client already creates a "JPG" format, the server just needs to store it. You only need to send at start the number of bytes and zero after all files have been sent.
Client:
new Thread(new Runnable() {
#Override
public void run() {
try {
ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
Bitmap bitmapToSend =
Bitmap.createScaledBitmap(bitmapToSend, 900, 800, true);
bitmapToSend.compress(
Bitmap.CompressFormat.JPEG,100, memoryStream);
byte[] byteArray = memoryStream.toByteArray();
memoryStream = null;
DataOutputStream outputStream =
new DataOutputStream(server.getOutputStream());
outputStream.writeInt(byteArray.length);
outputStream.write(byteArray);
outputStream.flush();
} catch (IOException e) {
System.out.println("Socket not created");
e.printStackTrace();
}
}
}).start();
Server:
public static void readImages(InputStream stream) {
DataInputStream imgInput = new DataInputStream(stream);
int index = 0;
int byteLength;
try {
while ((byteLength = imgInput.readInt())>0) {
byte[] buffer = new byte[byteLength];
imgInput.readFully(buffer);
OutputStream imgOutput = new FileOutputStream("current" + (index++) + ".jpg");
imgOutput.write(buffer);
imgOutput.close();
}
} catch (IOException ex) {
// .............
} finally {
try {
imgInput.close();
} catch (IOException ex1) {
//...........
}
}
}
In the output of my text to Speech, I need to set Sampling rate about to 32000 Hz with Pitch - 1 and SpeechRate - 0.2 (which I already did). But I can't set Sample Rate.
tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if(status != TextToSpeech.ERROR) {
tts.setLanguage(Locale.US);
tts.setSpeechRate((float) 0.2);
tts.setPitch((float) 1);
}
}
}, TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
I used AudioTrack to set Sample Rate but it took lots of time because I have to first TTS synthesizeToFile then I play it in AudioTrack.
HashMap<String, String> myHasRead = new HashMap<String, String>();
myHasRead.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, outPutS);
String StorePath = Environment.getExternalStorageDirectory().getAbsolutePath();
File myF = new File(StorePath+"/tempAudio.wav");
try {
myF.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
tts.setOnUtteranceProgressListener(new TtsUtteranceListener());
tts.synthesizeToFile("Bla Bla bla",myHasRead, StorePath+"/tempAudio.wav");
....
private class TtsUtteranceListener extends UtteranceProgressListener {
#Override
public void onStart(String utteranceId) {
}
#Override
public void onDone(String utteranceId) {
playWav();
}
#Override
public void onError(String utteranceId) {
}
}
public void playWav(){
int minBufferSize = AudioTrack.getMinBufferSize(32000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
int bufferSize = 512;
AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 32000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
String filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
int i = 0;
byte[] s = new byte[bufferSize];
try {
FileInputStream fin = new FileInputStream(filepath + "/tempAudio.wav");
DataInputStream dis = new DataInputStream(fin);
at.play();
while((i = dis.read(s, 0, bufferSize)) > -1){
at.write(s, 0, i);
}
at.stop();
at.release();
dis.close();
fin.close();
} catch (FileNotFoundException e) {
// TODO
e.printStackTrace();
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
There is any way to set sample rate Direct to TTS like tts.setSampleRate(32000); or get Stream from TTS to AudioTrack like DataInputStream dis = new DataInputStream(tts.speak("bla bla bla").getDataInputStream); . In Short I need Chipmunk's Text to Speech for Android but without synthesizeToFile or direct stream TTS voice Data in AudioTrack without saving output of TTS.
You can't set TTS sampling Rate directly:
I did something like this in a project ( Dint use TTS )
This might help you,
To play record with different voice type :-
waveSampling=90000; (Chipmunk)
waveSampling=24200; ("SLOW MOTION")
waveSampling=30000;("BANE") /batman character
waveSampling=18000;(Ghost)
waveSampling=70000;(Bee)
waveSampling=60000;(Woman)
waveSampling=37000; (Normal)
void playRecord() throws IOException {
int minBufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
int bufferSize = 512;
at = new AudioTrack(AudioManager.STREAM_MUSIC, waveSampling, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
String filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
int i = 0;
byte[] s = new byte[bufferSize];
try {
FileInputStream fin = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/temp/"+filename+".wav");
DataInputStream dis = new DataInputStream(fin);
at.play();
while((i = dis.read(s, 0, bufferSize)) > -1){
at.write(s, 0, i);
}
at.stop();
at.release();
dis.close();
fin.close();
openmenu();
} catch (FileNotFoundException e) {
// TODO
e.printStackTrace();
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
To save the Audio :-
public void save() throws IOException {
Random r = new Random();
final int i1 = r.nextInt(80 - 65) + 65;
File tempfile2=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/temp/"+i1+filename+".wav");
savedfile=Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/"+"VOICE CHANGER"+i1+filename+".mp3";
Toast.makeText(this, "File Saved", Toast.LENGTH_SHORT).show();
rawToWave(tempfile,tempfile2);
File wavFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/temp/"+i1+filename+".wav");
IConvertCallback callback = new IConvertCallback() {
#Override
public void onSuccess(File convertedFile) {
File newfile=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/"+"VOICE CHANGER"+i1+filename+".mp3");
File savedmp3=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Voice Changer/temp/"+i1+filename+".mp3");
Toast.makeText(MainActivity.this, "SUCCESS: " + newfile.getPath(), Toast.LENGTH_LONG).show();
try {
copyit(savedmp3,newfile);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Exception error) {
Toast.makeText(MainActivity.this, "ERROR: " + error.getMessage(), Toast.LENGTH_LONG).show();
}
};
Toast.makeText(this, "Converting audio file...", Toast.LENGTH_SHORT).show();
AndroidAudioConverter.with(this)
.setFile(wavFile)
.setFormat(cafe.adriel.androidaudioconverter.model.AudioFormat.MP3)
.setCallback(callback)
.convert();
}
The output will be a .mp3 file. If you want the output fast you can use .wav format.
I am trying to record audio. Unfortunately the code is so dense that the phone skips samples when it is recording. Does anyone have any advice on how to simplify the code and make it easier on the processor? A point where I was hoping to get help was to reduce the for loop to something simpler.
recordingThread2 = new Thread(new Runnable() {
#Override
public void run() {
File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");
try {
file.createNewFile();
OutputStream outputStream = new FileOutputStream(file);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);
short[] audioData = new short[bufferSize];
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE,
RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING,
bufferSize);
audioRecord.startRecording();
recordingThread = new Thread(new Runnable() {
#Override
public void run() {
writeAudioDataToFile();
}
}, "AudioRecorder Thread");
recordingThread.start();
while (!recordbuttonstatus) {
int numberOfShort = audioRecord.read(audioData, 0, bufferSize);
for (int i = 0; i < numberOfShort; i++) {
dataOutputStream.writeShort(audioData[i]);
}
}
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
recordingThread2.start();
I'm trying to build an audio recorder app for Android Wear. Right now, I'm able to capture the audio on the watch, stream it to phone and save it on a file. However, the audio file is presenting gaps or cropped parts.
I found this aswered questions related to my problem link1, link2, but they couldn't help me.
Here is my code:
First, on the watch side, I create the channel using the channelAPI and sucessfully send the audio being captured on the watch to the smartphone.
//here are the variables values that I used
//44100Hz is currently the only rate that is guaranteed to work on all devices
//but other rates such as 22050, 16000, and 11025 may work on some devices.
private static final int RECORDER_SAMPLE_RATE = 44100;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
int BufferElements2Rec = 1024;
int BytesPerElement = 2;
//start the process of recording audio
private void startRecording() {
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLE_RATE, RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING, BufferElements2Rec * BytesPerElement);
recorder.startRecording();
isRecording = true;
recordingThread = new Thread(new Runnable() {
public void run() {
writeAudioDataToPhone();
}
}, "AudioRecorder Thread");
recordingThread.start();
}
private void writeAudioDataToPhone(){
short sData[] = new short[BufferElements2Rec];
ChannelApi.OpenChannelResult result = Wearable.ChannelApi.openChannel(googleClient, nodeId, "/mypath").await();
channel = result.getChannel();
Channel.GetOutputStreamResult getOutputStreamResult = channel.getOutputStream(googleClient).await();
OutputStream outputStream = getOutputStreamResult.getOutputStream();
while (isRecording) {
// gets the voice output from microphone to byte format
recorder.read(sData, 0, BufferElements2Rec);
try {
byte bData[] = short2byte(sData);
outputStream.write(bData);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Then, on the smartphone side, I receive the audio data from the channel and write it to a PCM file.
public void onChannelOpened(Channel channel) {
if (channel.getPath().equals("/mypath")) {
Channel.GetInputStreamResult getInputStreamResult = channel.getInputStream(mGoogleApiClient).await();
inputStream = getInputStreamResult.getInputStream();
writePCMToFile(inputStream);
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainActivity.this, "Audio file received!", Toast.LENGTH_SHORT).show();
}
});
}
}
public void writePCMToFile(InputStream inputStream) {
OutputStream outputStream = null;
try {
// write the inputStream to a FileOutputStream
outputStream = new FileOutputStream(new File("/sdcard/wearRecord.pcm"));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
System.out.println("Done writing PCM to file!");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
// outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
What am I doing wrong or what are your suggestions to achieve a perfect gapless audio file on the smartphone? Thanks in advance.
I noticed in your code that you are reading everything into a short[] array, and then converting it to a byte[] array for the Channel API to send. Your code also creates a new byte[] array through each iteration of the loop, which will create a lot of work for the garbage collector. In general, you want to avoid allocations inside loops.
I would allocate one byte[] array at the top, and let the AudioRecord class store it directly into the byte[] array (just make sure you allocate twice as many bytes as you did shorts), with code like this:
mAudioTemp = new byte[bufferSize];
int result;
while ((result = mAudioRecord.read(mAudioTemp, 0, mAudioTemp.length)) > 0) {
try {
mAudioStream.write(mAudioTemp, 0, result);
} catch (IOException e) {
Log.e(Const.TAG, "Write to audio channel failed: " + e);
}
}
I also tested this with a 1 second audio buffer, using code like this, and it worked nicely. I'm not sure what the minimum buffer size is before it starts to have problems:
int bufferSize = Math.max(
AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT),
44100 * 2);
I get clicks at the start and end of playing a sound (a wav from the sdcard). It must be something to do with the track buffering but I dont know the solution. Also, I create a new one of these every time the sound plays, is this ok or is there a better way? There are many sounds playing over and over. Here is the code:
public void PlayAudioTrack(final String filePath, final Float f) throws IOException
{
new Thread(new Runnable() { public void run() {
//play sound here
int minSize = AudioTrack.getMinBufferSize( 44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT );
AudioTrack track = new AudioTrack( AudioManager.STREAM_MUSIC, 44100,
AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT,
minSize, AudioTrack.MODE_STREAM);
track.setPlaybackRate((int) (44100*f));
if (filePath==null)
return;
int count = 512 * 1024;
//Read the file..
byte[] byteData = null;
File file = null;
file = new File(filePath);
byteData = new byte[(int)count];
FileInputStream in = null;
try {
in = new FileInputStream( file );
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int bytesread = 0, ret = 0;
int size = (int) file.length();
while (bytesread < size) {
try {
ret = in.read( byteData,0, count);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
track.play();
if (ret != -1) {
// Write the byte array to the track
track.write(byteData,0, ret); bytesread += ret;
}
else break; }
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} track.stop(); track.release();
}
}).start();
}
Many thanks for any help
Isn't it possible that you play the PCM wave file header too?
Each PCM wave file has a small header at the beginning of the file, if you play that, you play the header bytes which could result in a click at te beginning.
I have had these same clicks at the beginning of each track using AudioTrack. I solved it by turning track volume off, waiting half a second, and then restoring normal volume. I no longer have any clicks. Here's the relevant bit of the code.
at.play();
at.setStereoVolume (0.0f, 0.0f);
new Thread (new Runnable(){
public void run() {
try{
Thread.sleep(500);
} catch (InterruptedException ie) { ; }
at.setStereoVolume (1.0f, 1.0f);
}
}).start();
new Thread (new Runnable(){
public void run() {
int i = 0;
try{
buffer = new byte[512];
while(((i = is.read(buffer)) != -1) && !paused){
at.write(buffer, 0, i);
position += i;
}
} catch (IOException e) {
e.printStackTrace();
}
if (!paused){
parent.trackEnded();
}
}
}).start();
}