am 5acb33af: Make synthesizeToFile create file on a client side.

* commit '5acb33af357b56fffb055997718b1e4aa97f53fc':
  Make synthesizeToFile create file on a client side.
This commit is contained in:
Przemyslaw Szczepaniak
2013-02-18 03:58:51 -08:00
committed by Android Git Automerger
4 changed files with 80 additions and 81 deletions

View File

@@ -20,10 +20,12 @@ import android.os.FileUtils;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
/**
* Speech synthesis request that writes the audio to a WAV file.
@@ -39,16 +41,19 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
private static final short WAV_FORMAT_PCM = 0x0001;
private final Object mStateLock = new Object();
private final File mFileName;
private int mSampleRateInHz;
private int mAudioFormat;
private int mChannelCount;
private RandomAccessFile mFile;
private FileChannel mFileChannel;
private boolean mStarted = false;
private boolean mStopped = false;
private boolean mDone = false;
FileSynthesisCallback(File fileName) {
mFileName = fileName;
FileSynthesisCallback(FileChannel fileChannel) {
mFileChannel = fileChannel;
}
@Override
@@ -63,54 +68,23 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void cleanUp() {
closeFileAndWidenPermissions();
if (mFile != null) {
mFileName.delete();
}
closeFile();
}
/**
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void closeFileAndWidenPermissions() {
private void closeFile() {
try {
if (mFile != null) {
mFile.close();
mFile = null;
if (mFileChannel != null) {
mFileChannel.close();
mFileChannel = null;
}
} catch (IOException ex) {
Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
}
try {
// Make the written file readable and writeable by everyone.
// This allows the app that requested synthesis to read the file.
//
// Note that the directory this file was written must have already
// been world writeable in order it to have been
// written to in the first place.
FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw
} catch (SecurityException se) {
Log.e(TAG, "Security exception setting rw permissions on : " + mFileName);
Log.e(TAG, "Failed to close output file descriptor", ex);
}
}
/**
* Checks whether a given file exists, and deletes it if it does.
*/
private boolean maybeCleanupExistingFile(File file) {
if (file.exists()) {
Log.v(TAG, "File " + file + " exists, deleting.");
if (!file.delete()) {
Log.e(TAG, "Failed to delete " + file);
return false;
}
}
return true;
}
@Override
public int getMaxBufferSize() {
return MAX_AUDIO_BUFFER_SIZE;
@@ -132,25 +106,20 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
if (mFile != null) {
if (mStarted) {
cleanUp();
throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
}
if (!maybeCleanupExistingFile(mFileName)) {
return TextToSpeech.ERROR;
}
mStarted = true;
mSampleRateInHz = sampleRateInHz;
mAudioFormat = audioFormat;
mChannelCount = channelCount;
try {
mFile = new RandomAccessFile(mFileName, "rw");
// Reserve space for WAV header
mFile.write(new byte[WAV_HEADER_LENGTH]);
mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
Log.e(TAG, "Failed to write wav header to output file descriptor" + ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -168,15 +137,15 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
if (mFile == null) {
if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
mFile.write(buffer, offset, length);
mFileChannel.write(ByteBuffer.wrap(buffer, offset, length));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -197,21 +166,21 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
if (mFile == null) {
if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
// Write WAV header at start of file
mFile.seek(0);
int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
mFile.write(
mFileChannel.position(0);
int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH);
mFileChannel.write(
makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
closeFileAndWidenPermissions();
closeFile();
mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -226,7 +195,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
}
}
private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
int dataLength) {
// TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT?
int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
@@ -251,8 +220,9 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
header.putShort(bitsPerSample);
header.put(new byte[]{ 'd', 'a', 't', 'a' });
header.putInt(dataLength);
header.flip();
return headerBuf;
return header;
}
}

View File

@@ -18,6 +18,7 @@ package android.speech.tts;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.speech.tts.ITextToSpeechCallback;
/**
@@ -44,11 +45,12 @@ interface ITextToSpeechService {
* @param callingInstance a binder representing the identity of the calling
* TextToSpeech object.
* @param text The text to synthesize.
* @param filename The file to write the synthesized audio to.
* @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be
writable.
* @param param Request parameters.
*/
int synthesizeToFile(in IBinder callingInstance, in String text,
in String filename, in Bundle params);
int synthesizeToFileDescriptor(in IBinder callingInstance, in String text,
in ParcelFileDescriptor fileDescriptor, in Bundle params);
/**
* Plays an existing audio resource.

View File

@@ -27,11 +27,15 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -1225,8 +1229,29 @@ public class TextToSpeech {
return runAction(new Action<Integer>() {
@Override
public Integer run(ITextToSpeechService service) throws RemoteException {
return service.synthesizeToFile(getCallerIdentity(), text, filename,
getParams(params));
ParcelFileDescriptor fileDescriptor;
int returnValue;
try {
File file = new File(filename);
if(file.exists() && !file.canWrite()) {
Log.e(TAG, "Can't write to " + filename);
return ERROR;
}
fileDescriptor = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
fileDescriptor, getParams(params));
fileDescriptor.close();
return returnValue;
} catch (FileNotFoundException e) {
Log.e(TAG, "Opening file " + filename + " failed", e);
return ERROR;
} catch (IOException e) {
Log.e(TAG, "Closing file " + filename + " failed", e);
return ERROR;
}
}
}, ERROR, "synthesizeToFile");
}

View File

@@ -26,6 +26,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
@@ -33,7 +34,8 @@ import android.speech.tts.TextToSpeech.Engine;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Set;
@@ -654,19 +656,19 @@ public abstract class TextToSpeechService extends Service {
}
}
private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
private final File mFile;
private class SynthesisToFileSpeechDescriptorItem extends SynthesisSpeechItem {
private final FileDescriptor mFileDescriptor;
public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Bundle params, String text,
File file) {
public SynthesisToFileSpeechDescriptorItem(Object callerIdentity, int callerUid,
int callerPid, Bundle params, String text, FileDescriptor fileDescriptor) {
super(callerIdentity, callerUid, callerPid, params, text);
mFile = file;
mFileDescriptor = fileDescriptor;
}
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
return new FileSynthesisCallback(mFile);
FileOutputStream fileOutputStream = new FileOutputStream(mFileDescriptor);
return new FileSynthesisCallback(fileOutputStream.getChannel());
}
@Override
@@ -797,15 +799,15 @@ public abstract class TextToSpeechService extends Service {
}
@Override
public int synthesizeToFile(IBinder caller, String text, String filename,
Bundle params) {
if (!checkNonNull(caller, text, filename, params)) {
public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
fileDescriptor, Bundle params) {
if (!checkNonNull(caller, text, fileDescriptor, params)) {
return TextToSpeech.ERROR;
}
File file = new File(filename);
SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
Binder.getCallingPid(), params, text, file);
SpeechItem item = new SynthesisToFileSpeechDescriptorItem(caller, Binder.getCallingUid(),
Binder.getCallingPid(), params, text,
fileDescriptor.getFileDescriptor());
return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
}