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);
Related
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.
Can I record sound from a microphone in a file at the same time when running YandexSpeechKit Recognizer?
There was a need for simultaneous speech recognition (using the class Recognizer) and recording sound from a devices microphone to a file. Use a standard mechanism MediaRecord is not possible, because MediaRecord and YandexSpeechKit used native methods and the same resource. It is causing the fall of some of the processes (MediaRecord or Recognizer).
I'm trying use RecognizerListener -> onSoundDataRecorded(Recognizer recognizer, byte[] bytes) code is bellow:
#Override
public void onSoundDataRecorded(Recognizer recognizer, byte[] bytes) {
Logger.d(TAG, "onSoundDataRecorded");
write(bytes);
}
public void write(byte[] bytes) {
File file = getTmpFile();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
fos.write(bytes);
} catch (IOException e1) {
e1.printStackTrace();
} finally {
if(fos != null) {
try {
fos.flush();
fos.close();
} catch(IOException e) {
}
}
}
}
But while the resulting file is not possible to play.
Can somebody help me?
Thanks!
Yandex SpeechKit returns raw PCM (16 kHz mono 16 bit) data. You should add WAV header or play as PCM. For example in unix-like OS via sox:
play -r 16000 -b 16 -c 1 -e signed-integer filename.pcm
For adding WAV header you can use this class https://github.com/MohammadAG/Android-SoundRecorder/blob/master/src/com/mohammadag/soundrecorder/WavConverter.java with parameters
private static final long SAMPLE_RATE = 16000;
private static final int RECORDER_BPP = 16;
private static final int CHANNELS = 1;
private static final long BYTE_RATE = RECORDER_BPP * SAMPLE_RATE * CHANNELS/8;
#Override
public void onRecognizerRecordingBegin() {
try {
tempFileName = getFilename();
os = new FileOutputStream(tempFileName, true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
#Override
public void onRecognizerRecordingDone() {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
int bufferSize = AudioRecord.getMinBufferSize(
16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
WavConverter.copyWaveFile(tempFileName, getFilename(), bufferSize);
deleteTempFile();
}
#Override
public void onRecognizerSoundDataRecorded(byte[] bytes) {
try {
os.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
l want to send live camera from phone 1 to phone 2 via wifi. What is the easiest way to do this ?
I Can sending my voice from phone 1 to phone 2 successful. but I can not send live camera to phon2.
I found " libstreaming " and " Sipdroid " that Used for communication stream(audio or live camera ) Between two devices. But I do not understand them And it is very difficult.
Is there an easier way to do it ?
the code of voice receive
Thread receiveThread = new Thread (new Runnable() {
#Override
public void run() {
//minimum buffer size. need to be careful. might cause problems. try setting manually if any problems faced
int minBufSize =AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2;
speaker = new AudioTrack(AudioManager.STREAM_MUSIC ,sampleRate,channelConfig,audioFormat,1600,AudioTrack.MODE_STREAM);
speaker.play();
try {
DatagramSocket socket1 = new DatagramSocket(9000);
log2( "Socket Created");
byte[] buffer = new byte[1600];
while( true) {
DatagramPacket packet = new DatagramPacket(buffer,1600);
socket1.receive(packet);
log2("Packet data read into buffer");
speaker.write(packet.getData(), 0, packet.getLength());
log2("Writing buffer content to speaker");
}
} catch (SocketException e) {
log2("SocketException");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
receiveThread.start();
the code of voice send
Thread streamThread = new Thread(new Runnable() {
#Override
public void run() {
try {
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*10;
log2("Socket Created");
DatagramPacket packet;
final InetAddress destination = InetAddress.getByName("192.168.49.1");
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,minBufSize);
log2("Recorder initialized");
byte[] buffer = new byte[1600];
recorder.startRecording();
int bytes_read = 0;
DatagramSocket socket1 = new DatagramSocket();
while(true) {
//reading data from MIC into buffer
bytes_read = recorder.read(buffer, 0, 1600);
//putting buffer in the packet
packet = new DatagramPacket (buffer, bytes_read,destination,9000);
socket1.send(packet);
}
} catch (IOException e) {
//Log.e("VS", "IOException");
}
}
});
streamThread.start();
The above code is for Send and receive voice.. But i want a simple way for sending camera live from phone 1 to another... please help me..
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();
}
I have a question that I want to read bytes from video resided in sdcard in chunk size 1024,
means I have to read 1024 bytes from the file at a time. I am able to fetch number of bytes from the video but I can't get it in chunks, I don't know how to achieve this. Please suggest me the right solution regarding the same.
Thanks in advance.
import java.io.*;
public class FileUtil {
private final int BUFFER_SIZE = 1024;
public void readFile(String fileName) {
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(fileName));
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
byte[] buffer = new byte[BUFFER_SIZE];
try {
int n = 0;
while ((n = in.read(buffer, 0, BUFFER_SIZE)) > 0) {
/* do whatever you want with buffer here */
}
}
catch(Exception e) {
e.printStackTrace();
}
finally { // always close input stream
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Based on the code from http://www.xinotes.org/notes/note/648/