I'm working on a music player, which receives a playlist with remote mp3 files (HTTP) and play them subsequently.
I want to have it start streaming the first track, if enough of the song is buffered to play it through, it should already begin to buffer the following song into memory. That is to make up for the unstable internet connection the program is supposed to run on.
How do I tell the BufferedInputStream to just download the whole file?
I'm happy to hear other suggestions on how to solve this, too.
I'm using the JLayer/BasicPlayer library to play audio, this is the code.
String mp3Url = "http://ia600402.us.archive.org/6/items/Stockfinster.-DeadLinesutemos025/01_Push_Push.mp3";
URL url = new URL(mp3Url);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
BasicPlayer player = new BasicPlayer();
player.open(bis);
player.play();
Here you will get an example for how to prebuffer audio file in java
Ok, to answer my question, here's a working implementation:
/**
* <code>DownloaderInputStream</code>
*/
public class DownloaderInputStream extends InputStream {
/**
* <code>IDownloadNotifier</code> - download listener.
*/
public static interface IDownloadListener {
/**
* Notifies about download completion.
*
* #param buf
* #param offset
* #param length
*/
public void onComplete(final byte[] buf, final int offset, final int length);
}
/**
* <code>ByteArrayOutputStreamX</code> - {#link ByteArrayOutputStream}
* extension that exposes buf variable (to avoid copying).
*/
private final class ByteArrayOutputStreamX extends ByteArrayOutputStream {
/**
* Constructor.
*
* #param size
*/
public ByteArrayOutputStreamX(final int size) {
super(size);
}
/**
* Returns inner buffer.
*
* #return inner buffer
*/
public byte[] getBuffer() {
return buf;
}
}
private final class Downloader extends Object implements Runnable {
// fields
private final InputStream is;
/**
* Constructor.
*
* #param is
*/
public Downloader(final InputStream is) {
this.is = is;
}
// Runnable implementation
public void run() {
int read = 0;
byte[] buf = new byte[16 * 1024];
try {
while ((read = is.read(buf)) != -1) {
if (read > 0) {
content.write(buf, 0, read);
downloadedBytes += read;
} else {
Thread.sleep(50);
}
}
} catch (Exception e) {
e.printStackTrace();
}
listener.onComplete(content.getBuffer(), 0 /*
* offset
*/, downloadedBytes);
}
}
// fields
private final int contentLength;
private final IDownloadListener listener;
// state
private ByteArrayOutputStreamX content;
private volatile int downloadedBytes;
private volatile int readBytes;
/**
* Constructor.
*
* #param contentLength
* #param is
* #param listener
*/
public DownloaderInputStream(final int contentLength, final InputStream is, final IDownloadListener listener) {
this.contentLength = contentLength;
this.listener = listener;
this.content = new ByteArrayOutputStreamX(contentLength);
this.downloadedBytes = 0;
this.readBytes = 0;
new Thread(new Downloader(is)).start();
}
/**
* Returns number of downloaded bytes.
*
* #return number of downloaded bytes
*/
public int getDownloadedBytes() {
return downloadedBytes;
}
/**
* Returns number of read bytes.
*
* #return number of read bytes
*/
public int getReadBytes() {
return readBytes;
}
// InputStream implementation
#Override
public int available() throws IOException {
return downloadedBytes - readBytes;
}
#Override
public int read() throws IOException {
// not implemented (not necessary for BasicPlayer)
return 0;
}
#Override
public int read(byte[] b, int off, int len) throws IOException {
if (readBytes == contentLength) {
return -1;
}
int tr = 0;
while ((tr = Math.min(downloadedBytes - readBytes, len)) == 0) {
try {
Thread.sleep(100);
} catch (Exception e) {/*
* ignore
*/
}
}
byte[] buf = content.getBuffer();
System.arraycopy(buf, readBytes, b, off, tr);
readBytes += tr;
return tr;
}
#Override
public long skip(long n) throws IOException {
// not implemented (not necessary for BasicPlayer)
return n;
}
}
Related
Let's say I have simple code:
public static void Main(){
throw new NullPointerException("this is an npe");
}
How with slf4j do you make sure that that exception is logged to the log file. Is there a default setting in spring that you can set in application properties, or in the logback file, that makes sure this gets captured, even though it is not in the try catch?
Please note, I am NOT looking for a solution to a webcontroller, or how to use log.error(). I know how to do those. I am looking for an overarching setting so that we don't lose exceptions throughout the application. This can apply to OOM, or printing a heap dump etc. I am including spring as a tag just in case the solution is in the applications.properties, as this is a spring project.
Ok as you have said, you can add a controller advice:
#ControllerAdvice
public class YourCustomHandler extends ResponseEntityExceptionHandler {
#Autowired
MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// you can do logs here and pass a custom object that will parse to json object
return buildResponseEntity(apiError);
}
#ExceptionHandler(YourCustomException.class)
protected ResponseEntity<Object> handleTthException(
YourCustomException ex, Locale locale) {
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
Based off of this: https://sysgears.com/articles/how-to-redirect-stdout-and-stderr-writing-to-a-log4j-appender/, I have found something I believe works.
At beginning of psvm:
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
System.setErr(new PrintStream(new LoggingOutputStream(), true));
}
This says to print errors to my LoggingOutputStream.
Code for this class:
public class LoggingOutputStream extends OutputStream {
/**
* Default number of bytes in the buffer.
*/
private static final int DEFAULT_BUFFER_LENGTH = 2048;
/**
* Indicates stream state.
*/
private boolean hasBeenClosed = false;
/**
* Internal buffer where data is stored.
*/
private byte[] buf;
/**
* The number of valid bytes in the buffer.
*/
private int count;
/**
* Remembers the size of the buffer.
*/
private int curBufLength;
/**
* The logger to write to.
*/
private Logger log = LoggerFactory.getLogger(LoggingOutputStream.class);
public LoggingOutputStream(){
curBufLength = DEFAULT_BUFFER_LENGTH;
buf = new byte[curBufLength];
count = 0;
}
/**
*
* Writes the specified byte to this output stream.
*
* #param b the byte to write
* #throws IOException if an I/O error occurs.
*/
public void write(final int b) throws IOException {
if (hasBeenClosed) {
log.error("The stream has been closed.");
throw new IOException("The stream has been closed.");
}
// don't log nulls
if (b == 0) {
return;
}
// would this be writing past the buffer?
if (count == curBufLength) {
// grow the buffer
final int newBufLength = curBufLength +
DEFAULT_BUFFER_LENGTH;
final byte[] newBuf = new byte[newBufLength];
System.arraycopy(buf, 0, newBuf, 0, curBufLength);
buf = newBuf;
curBufLength = newBufLength;
}
buf[count] = (byte) b;
count++;
}
/**
* Flushes this output stream and forces any buffered output
* bytes to be written out.
*/
#Override
public void flush() {
if (count == 0) {
return;
}
final byte[] bytes = new byte[count];
System.arraycopy(buf, 0, bytes, 0, count);
String str = new String(bytes);
log.error(str);
count = 0;
}
/**
* Closes this output stream and releases any system resources
* associated with this stream.
*/
#Override
public void close() {
flush();
hasBeenClosed = true;
}
}
Now, all errors are "intercepted" and routed to my log file.
My Mediarecorder gives me a PCM File as an output when I record the phone's microphone. Now when trying to listen to this File that it created all I hear is static and I think, if I have understood correctly, I get a PCM file from Mediarecorder not AAC and I need to add ADTS header to the PCM to be able to listen to it.
I have seen threads with custom Encoders but I can not seem to figure out where and what I need to do with them.
I make an output File from microphone recoridng like this:
private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private static final int SAMPLE_RATE = 44100; //44.1kHz
private static final int BUFFER_SIZE = 2048;
public Status status = Status.IDLE;
private AudioRecordingHandler arh;
private File outputFile;
private Context context;
/**
* Starts script for running. Needs output file to work!
*/
public void start() {
if (outputFile == null) { return; }
System.out.println("Start reading stream...");
aacEncoder = new AACEncoder(SAMPLE_RATE, micOutputPCM);
new Thread(new Runnable() {
#Override
public void run() {
record.startRecording();
byte[] data = new byte[BUFFER_SIZE];
float[] audioFloatBuffer = new float[BUFFER_SIZE/2];
Yin y = new Yin(SAMPLE_RATE, BUFFER_SIZE/2);
while(status == Status.RECORDING) {
record.read(data, 0, BUFFER_SIZE);
audioFloatBuffer = ConverterUtil.toFloatArray(data, 0, audioFloatBuffer,
0, audioFloatBuffer.length);
PitchDetectionResult pdr = y.getPitch(audioFloatBuffer);
aacEncoder.writeIntoOutputfile(data);
arh.handlePitch(pdr.getPitch());
}
aacEncoder.stopEncoding();
}
}).start();
}
/**
* Stops script
*/
public void stop() {
status = Status.IDLE;
record.stop();
arh.finishedRecording(micOutputPCM);
}
Here is how I get the byte[] from the File and where I try to encode the ADTS header to them.
public static File addHeaderToAac(File micOutputPCM, File output) throws IOException {
byte[] pcmFile = fullyReadFileToBytes(micOutputPCM);
int bufferSize = 2048;
//addADTSHeader to byte[] and return a File object
return fileWithADTSHeader;
}
public static byte[] fullyReadFileToBytes(File f) throws IOException {
int size = (int) f.length();
byte bytes[] = new byte[size];
byte tmpBuff[] = new byte[size];
FileInputStream fis= new FileInputStream(f);;
try {
int read = fis.read(bytes, 0, size);
if (read < size) {
int remain = size - read;
while (remain > 0) {
read = fis.read(tmpBuff, 0, remain);
System.arraycopy(tmpBuff, 0, bytes, size - remain, read);
remain -= read;
}
}
} catch (IOException e){
throw e;
} finally {
fis.close();
}
return bytes;
}
My question is, does anyone have an Encoder that can accept a File or byte[] or ByteStream as an input and return a File.
Because ultimately I want to make a mp4parser AACTrackImpl, which can be found here : https://github.com/sannies/mp4parser
AACTrackImpl aacTrack2 = new MP3TrackImpl(new FileDataSourceImpl(micOutputPCM));
Also If I am missing some important details about how to convert and what I should do to be able to play it then that information will also be useful.
If I need provide more information in order to answer this question, then I will gladly do so.
Edit:
I've been trying to make an encoder that would do what I need, but so far I have had no success.
public static File addHeaderToAac(File pcmFile1, File output, Context context) throws IOException {
byte[] pcmFile = fullyReadFileToBytes(pcmFile1);
int bufferSize = 2048;
AACEncoder encoder = new AACEncoder(44100, output);
encoder.encodeAudioFrameToAAC(pcmFile);
return output;
}
I am trying to encode the PCM to AAC with this encoder, but this encoder writes the output file to memory, but I need an object. And when I give it my byte[] it also gives me an error :
W/System.err: at java.nio.ByteBuffer.put(ByteBuffer.java:642)
And the error is coming from this line :
inputBuf.put(frameData);
Finally, my encoder:
public class AACEncoder {
final String TAG = "UEncoder Processor";
final int sampleRate;
File outputFile;
FileOutputStream fos;
final int TIMEOUT_USEC = 10000 ;
MediaCodec encoder;
boolean isEncoderRunning = false;
boolean outputDone = false;
MediaCodec.BufferInfo info;
public AACEncoder(final int sampleRate, File outputFile) {
this.sampleRate = sampleRate;
this.info = new MediaCodec.BufferInfo();
this.outputFile = outputFile;
openFileStream();
initEncoder();
}
/**
* Initializes CrappyEncoder for AAC-LC (Low complexity)
* #throws Exception
*/
public void initEncoder() {
try {
encoder = MediaCodec.createEncoderByType("audio/mp4a-latm");
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException ex) {
Log.e(TAG, "Failed to create CrappyEncoder");
ex.printStackTrace();
}
}
int generateIndex = 0;
public void encodeAudioFrameToAAC(byte[] frameData) {
if (encoder == null) return;
if (!isEncoderRunning) {
encoder.start();
isEncoderRunning = true;
}
ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
if (fos != null) {
int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
long ptsUsec = (System.currentTimeMillis() * 1000) / 10000;
if (outputDone) {
encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex];
inputBuf.clear();
inputBuf.put(frameData);
encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec, 0);
}
generateIndex++;
}
tryEncodeOutputBuffer();
}
checkIfOutputDone();
}
/**
* Gets data from output buffer and encodes it to
* AAC-LC encoding with ADTS header attached before every frame
*/
private void tryEncodeOutputBuffer() {
ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
//If >= 0 then valid response
int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
if (encoderStatus >= 0) {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
encodedData.position(info.offset);
encodedData.limit(info.offset + info.size + 7);
byte[] data = new byte[info.size + 7];
addADTStoPacket(data, info.size + 7);
encodedData.get(data, 7, info.size);
encodedData.position(info.offset);
writeIntoOutputfile(data);
encoder.releaseOutputBuffer(encoderStatus, false);
}
}
private void checkIfOutputDone() {
if (outputDone) {
if (fos != null) {
try {
fos.close();
} catch (IOException ioe) {
Log.w(TAG, "failed closing debug file");
throw new RuntimeException(ioe);
}
fos = null;
}
}
}
/**
* Add ADTS header at the beginning of each and every AAC packet.
* This is needed as MediaCodec CrappyEncoder generates a packet of raw
* AAC data.
*
* Note the packetLen must count in the ADTS header itself.
**/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
// fill in ADTS data
packet[0] = (byte)0xFF;
packet[1] = (byte)0xF9;
packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
packet[4] = (byte)((packetLen&0x7FF) >> 3);
packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
packet[6] = (byte)0xFC;
}
private void openFileStream() {
fos = null;
try {
fos = new FileOutputStream(outputFile, false);
} catch (FileNotFoundException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
/**
* Writes data into file
* #param data
*/
public void writeIntoOutputfile(byte[] data) {
try {
fos.write(data);
} catch (IOException ioe) {
Log.w(TAG, "failed writing debug data to file");
throw new RuntimeException(ioe);
}
}
public void stopEncoding() {
isEncoderRunning = false;
encoder.stop();
closeStream();
}
private void closeStream() {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
}
I'm receiving a file that I want to save to disc, this has the highest priority. But I want to "split"/"share" this stream with two other operations.
My approach has so far been to have a MainStream that can create subStreams that reads from a buffer in the MainStream.
If this is a suitable approach I need some way to determine where in the stream the subStreams are. How can I do that? Or is it a better way to solve my main problem?
If I/O is not you bottle neck, you can use multi-thread write file.
The code below is just an example:
/**
* #author lichengwu
* #version 1.0
* #created 2013-01-08 12:11 AM
*/
public class MultiWrite {
private static final int SIZE = 1024 * 1024 * 1024;
ExecutorService exec = Executors.newFixedThreadPool(5);
public void start() {
final File source = new File("");
long size = source.length();
final File store = new File("");
for (long position = 0; position < size; position = position + SIZE) {
exec.execute(new WriteTask(source, store, position));
}
}
public class WriteTask implements Runnable {
private final File store;
private final File source;
private final long position;
public WriteTask(File source, File store, long position) {
this.store = store;
this.position = position;
this.source = source;
}
public void run() {
try {
RandomAccessFile in = new RandomAccessFile(source, "r");
// lock part of store
RandomAccessFile out = new RandomAccessFile(store, "rw");
FileChannel channel = out.getChannel();
FileLock lock;
while (true) {
try {
lock = channel.tryLock(position, SIZE, false);
break;
} catch (Exception e) {
// deal with
}
}
out.seek(position);
in.seek(position);
byte[] data = new byte[SIZE];
in.read(data);
out.write(data);
// release
lock.release();
channel.close();
out.close();
in.close();
} catch (IOException e) {
// deal with
}
}
}
}
I have a couple of questions about a school project I'm working on. The code is as follows.
public class Player
{
private PlayList playList;
private Track track;
private int tracksPlayed;
private int totalTrackTime;
private double averageTrackTime;
/**
* Constructor ...
*/
public Player()
{
playList = new PlayList("audio");
track = playList.getTrack(0);
this.tracksPlayed = tracksPlayed;
this.totalTrackTime = totalTrackTime;
this.averageTrackTime = averageTrackTime;
}
/**
* Return the track collection currently loaded in this player.
*/
public PlayList getPlayList()
{
return playList;
}
/**
*
*/
public void play()
{
track.play();
tracksPlayed++;
int trackDuration = track.getDuration();
totalTrackTime = totalTrackTime + trackDuration;
averageTrackTime = totalTrackTime / tracksPlayed;
}
/**
*
*/
public void stop()
{
track.stop();
}
/**
*
*/
public void setTrack(int trackNumber)
{
int currentTrack = trackNumber;
track = playList.getTrack(currentTrack);
}
/**
*
*/
public String getTrackName()
{
String currentTrack = track.getName();
return currentTrack;
}
/**
*
*/
public String getTrackInfo()
{
String currentTrack = track.getName();
int trackDuration = track.getDuration();
return currentTrack + " " + "(" + trackDuration + ")";
}
/**
*
*/
public int getNumberOfTracksPlayed()
{
return tracksPlayed;
}
/**
*
*/
public int getTotalPlayedTrackLength()
{
return totalTrackTime;
}
/**
*
*/
public double averageTrackLength()
{
return averageTrackTime;
}
}
public class Track
{
private Clip soundClip;
private String name;
/**
* Create a track from an audio file.
*/
public Track(File soundFile)
{
soundClip = loadSound(soundFile);
name = soundFile.getName();
}
/**
* Play this sound track. (The sound will play asynchronously, until
* it is stopped or reaches the end.)
*/
public void play()
{
if(soundClip != null) {
soundClip.start();
}
}
/**
* Stop this track playing. (This method has no effect if the track is not
* currently playing.)
*/
public void stop()
{
if(soundClip != null) {
soundClip.stop();
}
}
/**
* Reset this track to its start.
*/
public void rewind()
{
if(soundClip != null) {
soundClip.setFramePosition(0);
}
}
/**
* Return the name of this track.
*/
public String getName()
{
return name;
}
/**
* Return the duration of this track, in seconds.
*/
public int getDuration()
{
if (soundClip == null) {
return 0;
}
else {
return (int) soundClip.getMicrosecondLength()/1000000;
}
}
/**
* Set the playback volume of the current track.
*
* #param vol Volume level as a percentage (0..100).
*/
public void setVolume(int vol)
{
if(soundClip == null) {
return;
}
if(vol < 0 || vol > 100) {
vol = 100;
}
double val = vol / 100.0;
try {
FloatControl volControl =
(FloatControl) soundClip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float)(Math.log(val == 0.0 ? 0.0001 : val) / Math.log(10.0) * 20.0);
volControl.setValue(dB);
} catch (Exception ex) {
System.err.println("Error: could not set volume");
}
}
/**
* Return true if this track has successfully loaded and can be played.
*/
public boolean isValid()
{
return soundClip != null;
}
/**
* Load the sound file supplied by the parameter.
*
* #return The sound clip if successful, null if the file could not be decoded.
*/
private Clip loadSound(File file)
{
Clip newClip;
try {
AudioInputStream stream = AudioSystem.getAudioInputStream(file);
AudioFormat format = stream.getFormat();
// we cannot play ALAW/ULAW, so we convert them to PCM
//
if ((format.getEncoding() == AudioFormat.Encoding.ULAW) ||
(format.getEncoding() == AudioFormat.Encoding.ALAW))
{
AudioFormat tmp = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
format.getSampleRate(),
format.getSampleSizeInBits() * 2,
format.getChannels(),
format.getFrameSize() * 2,
format.getFrameRate(),
true);
stream = AudioSystem.getAudioInputStream(tmp, stream);
format = tmp;
}
DataLine.Info info = new DataLine.Info(Clip.class,
stream.getFormat(),
((int) stream.getFrameLength() *
format.getFrameSize()));
newClip = (Clip) AudioSystem.getLine(info);
newClip.open(stream);
return newClip;
} catch (Exception ex) {
return null;
}
}
}
public class PlayList
{
private List<Track> tracks;
/**
* Constructor for objects of class TrackCollection
*/
public PlayList(String directoryName)
{
tracks = loadTracks(directoryName);
}
/**
* Return a track from this collection.
*/
public Track getTrack(int trackNumber)
{
return tracks.get(trackNumber);
}
/**
* Return the number of tracks in this collection.
*/
public int numberOfTracks()
{
return tracks.size();
}
/**
* Load the file names of all files in the given directory.
* #param dirName Directory (folder) name.
* #param suffix File suffix of interest.
* #return The names of files found.
*/
private List<Track> loadTracks(String dirName)
{
File dir = new File(dirName);
if(dir.isDirectory()) {
File[] allFiles = dir.listFiles();
List<Track> foundTracks = new ArrayList<Track>();
for(File file : allFiles) {
//System.out.println("found: " + file);
Track track = new Track(file);
if(track.isValid()) {
foundTracks.add(track);
}
}
return foundTracks;
}
else {
System.err.println("Error: " + dirName + " must be a directory");
return null;
}
}
/**
* Return this playlist as an array of strings with the track names.
*/
public String[] asStrings()
{
String[] names = new String[tracks.size()];
int i = 0;
for(Track track : tracks) {
names[i++] = track.getName();
}
return names;
}
}
I understand that to call the play methods in the player class, I have to initialize a Track class variable. I also need to initialize it using the PlayList getTrack method. However, how can I initialize it without defining a starting index variable (i.e I don't want it to automatically initialize to a specific song in the index, I want the user to have to select a song first)?
Also, how do I code the play method in the player class to stop a existing song if one is playing before starting a new song and to restart a song if the play method is called again one the same song?
1.
how can I initialize it without defining a starting index variable
public Player (int initTrack){
...
track = playList.getTrack(initTrack);
...
}
2.
You should try with a field storing the track that it is currently playing!
How would I receive a file over a serial port in Java using the XMODEM protocol?
Here it is.
I found this in the JModem source. If you look at where it writes the data out, you can see its doing an SOH, blocknum, ~blocknum, data, and checksum. It uses a sector size of 128. Those together make up the standard XModem protocol. Its simple enough to do XModem1K, YModem, and ZModem from here, too.
/**
* a tiny version of Ward Christensen's MODEM program for UNIX.
* Written ~ 1980 by Andrew Scott Beals. Last revised 1982.
* A.D. 2000 - dragged from the archives for use in Java Cookbook.
*
* #author C version by Andrew Scott Beals, sjobrg.andy%mit-oz#mit-mc.arpa.
* #author Java version by Ian F. Darwin, ian#darwinsys.com
* $Id: TModem.java,v 1.8 2000/03/02 03:40:50 ian Exp $
*/
class TModem {
protected final byte CPMEOF = 26; /* control/z */
protected final int MAXERRORS = 10; /* max times to retry one block */
protected final int SECSIZE = 128; /* cpm sector, transmission block */
protected final int SENTIMOUT = 30; /* timeout time in send */
protected final int SLEEP = 30; /* timeout time in recv */
/* Protocol characters used */
protected final byte SOH = 1; /* Start Of Header */
protected final byte EOT = 4; /* End Of Transmission */
protected final byte ACK = 6; /* ACKnowlege */
protected final byte NAK = 0x15; /* Negative AcKnowlege */
protected InputStream inStream;
protected OutputStream outStream;
protected PrintWriter errStream;
/** Construct a TModem */
public TModem(InputStream is, OutputStream os, PrintWriter errs) {
inStream = is;
outStream = os;
errStream = errs;
}
/** Construct a TModem with default files (stdin and stdout). */
public TModem() {
inStream = System.in;
outStream = System.out;
errStream = new PrintWriter(System.err);
}
/** A main program, for direct invocation. */
public static void main(String[] argv) throws
IOException, InterruptedException {
/* argc must == 2, i.e., `java TModem -s filename' */
if (argv.length != 2)
usage();
if (argv[0].charAt(0) != '-')
usage();
TModem tm = new TModem();
tm.setStandalone(true);
boolean OK = false;
switch (argv[0].charAt(1)){
case 'r':
OK = tm.receive(argv[1]);
break;
case 's':
OK = tm.send(argv[1]);
break;
default:
usage();
}
System.out.print(OK?"Done OK":"Failed");
System.exit(0);
}
/* give user minimal usage message */
protected static void usage()
{
System.err.println("usage: TModem -r/-s file");
// not errStream, not die(), since this is static.
System.exit(1);
}
/** If we're in a standalone app it is OK to System.exit() */
protected boolean standalone = false;
public void setStandalone(boolean is) {
standalone = is;
}
public boolean isStandalone() {
return standalone;
}
/** A flag used to communicate with inner class IOTimer */
protected boolean gotChar;
/** An inner class to provide a read timeout for alarms. */
class IOTimer extends Thread {
String message;
long milliseconds;
/** Construct an IO Timer */
IOTimer(long sec, String mesg) {
milliseconds = 1000 * sec;
message = mesg;
}
public void run() {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
// can't happen
}
/** Implement the timer */
if (!gotChar)
errStream.println("Timed out waiting for " + message);
die(1);
}
}
/*
* send a file to the remote
*/
public boolean send(String tfile) throws IOException, InterruptedException
{
char checksum, index, blocknumber, errorcount;
byte character;
byte[] sector = new byte[SECSIZE];
int nbytes;
DataInputStream foo;
foo = new DataInputStream(new FileInputStream(tfile));
errStream.println( "file open, ready to send");
errorcount = 0;
blocknumber = 1;
// The C version uses "alarm()", a UNIX-only system call,
// to detect if the read times out. Here we do detect it
// by using a Thread, the IOTimer class defined above.
gotChar = false;
new IOTimer(SENTIMOUT, "NAK to start send").start();
do {
character = getchar();
gotChar = true;
if (character != NAK && errorcount < MAXERRORS)
++errorcount;
} while (character != NAK && errorcount < MAXERRORS);
errStream.println( "transmission beginning");
if (errorcount == MAXERRORS) {
xerror();
}
while ((nbytes=inStream.read(sector))!=0) {
if (nbytes<SECSIZE)
sector[nbytes]=CPMEOF;
errorcount = 0;
while (errorcount < MAXERRORS) {
errStream.println( "{" + blocknumber + "} ");
putchar(SOH); /* here is our header */
putchar(blocknumber); /* the block number */
putchar(~blocknumber); /* & its complement */
checksum = 0;
for (index = 0; index < SECSIZE; index++) {
putchar(sector[index]);
checksum += sector[index];
}
putchar(checksum); /* tell our checksum */
if (getchar() != ACK)
++errorcount;
else
break;
}
if (errorcount == MAXERRORS)
xerror();
++blocknumber;
}
boolean isAck = false;
while (!isAck) {
putchar(EOT);
isAck = getchar() == ACK;
}
errStream.println( "Transmission complete.");
return true;
}
/*
* receive a file from the remote
*/
public boolean receive(String tfile) throws IOException, InterruptedException
{
char checksum, index, blocknumber, errorcount;
byte character;
byte[] sector = new byte[SECSIZE];
DataOutputStream foo;
foo = new DataOutputStream(new FileOutputStream(tfile));
System.out.println("you have " + SLEEP + " seconds...");
/* wait for the user or remote to get his act together */
gotChar = false;
new IOTimer(SLEEP, "receive from remote").start();
errStream.println("Starting receive...");
putchar(NAK);
errorcount = 0;
blocknumber = 1;
rxLoop:
do {
character = getchar();
gotChar = true;
if (character != EOT) {
try {
byte not_ch;
if (character != SOH) {
errStream.println( "Not SOH");
if (++errorcount < MAXERRORS)
continue rxLoop;
else
xerror();
}
character = getchar();
not_ch = (byte)(~getchar());
errStream.println( "[" + character + "] ");
if (character != not_ch) {
errStream.println( "Blockcounts not ~");
++errorcount;
continue rxLoop;
}
if (character != blocknumber) {
errStream.println( "Wrong blocknumber");
++errorcount;
continue rxLoop;
}
checksum = 0;
for (index = 0; index < SECSIZE; index++) {
sector[index] = getchar();
checksum += sector[index];
}
if (checksum != getchar()) {
errStream.println( "Bad checksum");
errorcount++;
continue rxLoop;
}
putchar(ACK);
blocknumber++;
try {
foo.write(sector);
} catch (IOException e) {
errStream.println("write failed, blocknumber " + blocknumber);
}
} finally {
if (errorcount != 0)
putchar(NAK);
}
}
} while (character != EOT);
foo.close();
putchar(ACK); /* tell the other end we accepted his EOT */
putchar(ACK);
putchar(ACK);
errStream.println("Receive Completed.");
return true;
}
protected byte getchar() throws IOException {
return (byte)inStream.read();
}
protected void putchar(int c) throws IOException {
outStream.write(c);
}
protected void xerror()
{
errStream.println("too many errors...aborting");
die(1);
}
protected void die(int how)
{
if (standalone)
System.exit(how);
else
System.out.println(("Error code " + how));
}
}