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:
committed by
Android Git Automerger
commit
57a8b612fa
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user