1.随机文件访问接口
对MP3解码时需要随机读取MP3文件,读取的文件既包括本地磁盘文件,也包括来自于网络的远程文件,两类文件按同一规范访问,为了实现这一目标,先定义一个随机文件访问接口,IRandomAccess.java,源码如下:
/* * IrandomAccess.java -- 随机访问文件接口 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; public interface IRandomAccess { public int read() throws Exception; public int read(byte b[]) throws Exception; public int read(byte b[], int off, int len) throws Exception; public int dump(int src_off, byte b[], int dst_off, int len) throws Exception; public void seek(long pos) throws Exception; public long length(); public long getFilePointer(); public void close(); }
2.读/写通信接口
从远程文件读取的数据写入缓冲区,由于读/写位于不同的线程,为了实现读/写同步,定义读/写通信接口IWriterCallBack.java,源码如下:
/* * IWriterCallBack.java -- 读/写通信的接口 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; /* * 读/写通信的接口.类似于C++的回调函数 * * 例: * class BuffRandAcceURL 内实现本接口的方法tryWriting() * class BuffRandAcceURL 内new Writer(this, ...)传值到Writer.objIWCB * class Writer 内调用objIWCB.tryWriting() */ public interface IWriterCallBack { public int tryWriting() throws InterruptedException; public void updateBuffer(int i, int len); public void updateWriterCount(); public int getWriterCount(); public void terminateWriters(); }
这里的读/写通信的接口类似于C++的回调函数功能:
class BuffRandAcceURL内实现本接口的方法tryWriting()
class BuffRandAcceURL 内new Writer(this, ...)传值到Writer.objIWCB
class Writer 内调用objIWCB.tryWriting()
3.带缓冲区的本地文件随机读取
以缓冲方式读取文件的效率肯定比不带缓冲高,JAVA类不提供带缓冲区的文件读写,没有现成的可用,只有自己构造。缓冲的方法简单讲就是从文件读取一块数据扔进缓冲区byteInBuf[],如何对缓冲区进行简单而有效的管理,是一个值得深入探讨问题,我的方法只是问题的一种可行解,并不是最优的。BuffRandAcceFile.java源码:
/* * BuffRandAcceFile.java -- 带缓冲区的本地文件随机读取 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; import java.io.RandomAccessFile; import java.io.IOException; import tag.ID3Tag; public class BuffRandAcceFile implements IRandomAccess { private static final int DEFAULT_BUFSIZE=2048; private static RandomAccessFile rafCurFile; private byte byteInBuf[]; private long longStartPos; private long longEndPos = -1; private int intBufSize; private long longCurPos; private boolean boolBufDirty; private long longFileEndPos; private int intBufUsedSize; private long longBufSizeMask; private long longFileSize; public BuffRandAcceFile(String strName) throws Exception { this(strName, DEFAULT_BUFSIZE); } public BuffRandAcceFile(String strName, int intBufSize) throws Exception { rafCurFile = new RandomAccessFile(strName, "r"); this.intBufSize = intBufSize; byteInBuf = new byte[intBufSize]; longFileEndPos = rafCurFile.length() - 1; this.longBufSizeMask = ~((long) this.intBufSize - 1L); this.boolBufDirty = false; long lFrameOffset = 0; //第一帧的偏移量 long lFrameSize = longFileSize = rafCurFile.length(); ID3Tag objID3Tag = new ID3Tag(); byte[] byteTmpBuf = new byte[128]; //ID3 v2 this.seek(0); this.read(byteTmpBuf, 0, 10); int size_v2 = objID3Tag.checkID3V2(byteTmpBuf,0); if (size_v2 > 0) { lFrameOffset = size_v2; lFrameSize -= size_v2; size_v2 -= 10; // header: 10 bytes byte[] b = new byte[size_v2]; this.read(b, 0, size_v2); objID3Tag.parseID3V2(b,0); b = null; } //ID3 v1 this.seek(longFileSize - 128); this.read(byteTmpBuf); if(objID3Tag.checkID3V1(byteTmpBuf)) { lFrameSize -= 128; objID3Tag.parseID3V1(byteTmpBuf); } this.seek(lFrameOffset); objID3Tag.printTag(); objID3Tag.destroy(); objID3Tag = null; byteTmpBuf = null; } public int read(long pos) throws IOException { if (pos < this.longStartPos || pos > this.longEndPos) { this.flushbuf(); this.seek(pos); if ((pos < this.longStartPos) || (pos > this.longEndPos)) { return -1; } } this.longCurPos = pos; return this.byteInBuf[(int) (pos - this.longStartPos)] & 0xff; } public int read() throws IOException { int iret = byteInBuf[(int) (this.longCurPos - this.longStartPos)] & 0xff; this.seek(this.longCurPos + 1); return iret; } public int read(byte b[]) throws IOException { return this.read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { long readendpos = this.longCurPos + len - 1; if (readendpos <= this.longEndPos && readendpos <= this.longFileEndPos) { System.arraycopy(this.byteInBuf, (int) (this.longCurPos - this.longStartPos), b, off, len); } else { if (readendpos > this.longFileEndPos) { len = (int) (rafCurFile.length() - this.longCurPos + 1); if(len <= 0) return -1; } // if(this.longCurPos != rafCurFile.getFilePointer()) { //增加一条判断 rafCurFile.seek(this.longCurPos); // System.out.println("1. rafCurFile.seek("+longCurPos+")");//// // } len = rafCurFile.read(b, off, len); readendpos = this.longCurPos + len - 1; } this.seek(readendpos + 1); return len; } /* * 从当前位置复制,不移动文件"指针" */ public int dump(int src_off, byte b[], int dst_off, int len) throws IOException { long rpos = this.longCurPos + src_off; long readendpos = rpos + len - 1; if (readendpos <= this.longEndPos && readendpos <= this.longFileEndPos) { System.arraycopy(this.byteInBuf, (int) (rpos - this.longStartPos), b, dst_off, len); } else { if (readendpos > this.longFileEndPos) { len = (int) (rafCurFile.length() - rpos + 1);// ??????? if(len <= 0) return -1; } rafCurFile.seek(rpos); len = rafCurFile.read(b, dst_off, len); rafCurFile.seek(this.longCurPos); } return len; } public long length() { return longFileSize; } public void seek(long pos) throws IOException { if ((pos < this.longStartPos) || (pos > this.longEndPos)) { this.flushbuf(); if ((pos >= 0) && (pos <= this.longFileEndPos) && (this.longFileEndPos != 0)) { this.longStartPos = pos & this.longBufSizeMask; this.intBufUsedSize = this.fillbuf(); } else if (((pos == 0) && (this.longFileEndPos == 0)) || (pos == this.longFileEndPos + 1)) { this.longStartPos = pos; this.intBufUsedSize = 0; } this.longEndPos = this.longStartPos + this.intBufSize - 1; } this.longCurPos = pos; } public long getFilePointer() { return this.longCurPos; } public void close() { try { rafCurFile.close(); } catch (IOException e) {} } private int fillbuf() throws IOException { rafCurFile.seek(this.longStartPos); //System.out.println("2. rafCurFile.seek("+this.longStartPos+")");//// this.boolBufDirty = false; return rafCurFile.read(this.byteInBuf); } private void flushbuf() throws IOException { if (this.boolBufDirty == true) { if (rafCurFile.getFilePointer() != this.longStartPos) { rafCurFile.seek(this.longStartPos); //System.out.println("3. rafCurFile.seek(" + this.longStartPos + ")");//// } rafCurFile.write(this.byteInBuf, 0, this.intBufUsedSize); this.boolBufDirty = false; } } }
4. 用环形缓冲方式读取远程文件
如果用HttpURLConnection类的方法打开连接,然后用InputStream类获得输入流,再用BufferedInputStream构造出带缓冲区的输入流,如果网速太慢的话,无论缓冲区设置多大,听起来都是断断续续的,达不到真正缓冲的目的。于是尝试编写代码实现用缓冲方式读取远程文件。我是不怎么赞同使用多线程下载的,加之有的链接下载速度本身就比较快,所以在下载速度足够的情况下,就让下载线程退出,直到只剩下一个下载线程。当然,多线程中令人头痛的死锁问题、HttpURLConnection的超时阻塞问题都会使代码看起来异常复杂。
简要介绍一下实现多线程环形缓冲的方法。将缓冲区buf[]分为16块,每块32K,下载线程负责向缓冲区写数据,每次写一块;读线程(BuffRandAcceURL类)每次读小于32K的任意字节。同步描述:写/写互斥等待空闲块;写/写并发填写buf[];读/写并发使用buf[]。
经过我很长一段时间使用,我认为比较满意地实现了我的目标,同其它MP3播放器对比,我的这种方法能够比较流畅、稳定地下载并播放。读取远程文件的BuffRandAcceURL.java源码如下:
/* * BuffRandAcceURL.java -- 用环形缓冲方式读取HTTP协议远程文件 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; import java.net.URL; import java.net.URLDecoder; import tag.ID3Tag; /* * FIFO方式共享环形缓冲区buf[] * byteBuf[]逻辑上分成16块, 每一块的长度UNIT_LENGTH=32K不小于最大帧长1732 * * 同步: 写/写 -- 互斥等待空闲块 * 读/写 -- 并发访问buf[] * * 简单有效解决死锁? */ public final class BuffRandAcceURL implements IRandomAccess, IWriterCallBack { public static final int UNIT_LENGTH_BITS = 15; public static final int UNIT_LENGTH = 1 << UNIT_LENGTH_BITS; //2^16=32K public static final int BUF_LENGTH = UNIT_LENGTH << 4; public static final int UNIT_COUNT = BUF_LENGTH >> UNIT_LENGTH_BITS; //16块 public static final int BUF_LENGTH_MASK = (BUF_LENGTH - 1); private static final int MAX_WRITER = 5; private static long longFilePointer; private static int longReadCursor; private static int longReaded; private static byte[] byteBuf; //同时作写线程同步锁 private static int[] intUnitSize; //同时作读线程互斥锁 private static int intAllocPos; private static URL objURL; private static boolean boolAlive = true; private static int intWriterCounter; private static long longFileSize; private static long longAllFrameSize; private static int intFreeUnits = UNIT_COUNT; // "信号量"计数器 public BuffRandAcceURL(String sURL) throws Exception { this(sURL,MAX_WRITER); } public BuffRandAcceURL(String sURL, int download_threads) throws Exception { byteBuf = new byte[BUF_LENGTH]; intUnitSize = new int[UNIT_COUNT]; objURL = new URL(sURL); // 打印文件名 try { String s = URLDecoder.decode(sURL, "GBK"); System.out.println("播放>> " + s.substring(s.lastIndexOf("/") + 1)); } catch (Exception e) { System.out.println(sURL); } // 获取文件长度 // 为何同一URL(如http://mat1.qq.com/news/wmv/tang/03.mp3)文件长度有时对有时不对? longAllFrameSize = longFileSize = objURL.openConnection().getContentLength(); if (longFileSize == -1) throw new Exception("ContentLength=-1"); // 以异步方式解析tag new TagThread(objURL, longFileSize); // 创建"写"线程 // 线程被创建后立即连接URL开始下载,由于服务器限制了同一IP每秒最大连接次数,频繁连接 // 会被服务器拒绝,因此延时. intWriterCounter = download_threads; for (int i = 0; i < download_threads; i++) { new Writer(this, objURL, byteBuf, i + 1); Thread.sleep(200); } // 缓冲 tryCaching(); // 跳过 ID3 v2 ID3Tag tag = new ID3Tag(); int v2_size = tag.checkID3V2(byteBuf, 0); if (v2_size > 0) { longAllFrameSize -= v2_size; seek(v2_size); } tag = null; } private void tryCaching() throws InterruptedException { int cache_size = BUF_LENGTH; int bi = intUnitSize[longReadCursor >> UNIT_LENGTH_BITS]; if(bi != 0) cache_size -= UNIT_LENGTH - bi; while (longReaded < cache_size) { if (intWriterCounter == 0 || boolAlive == false) return; System.out.printf("\r[缓冲%1$6.2f%%] ",(float)longReaded / cache_size * 100); synchronized (intUnitSize) { intUnitSize.wait(200); //wait(200)错过通知也可结束循环? } } System.out.printf("\r"); } private int tryReading(int i, int len) throws Exception { int n = (i + 1) & (UNIT_COUNT - 1); int r = (intUnitSize[i] > 0) ? (intUnitSize[i] + intUnitSize[n]) : intUnitSize[i]; if (r < len) { if (intWriterCounter == 0 || boolAlive == false) return r; tryCaching(); } return len; } /* * 各个"写"线程互斥等待空闲块 * 空闲块按序号由小到大顺序分配;管理空闲块采用类似于C++的信号量机制. */ public int tryWriting() throws InterruptedException { int ret = -1; synchronized (byteBuf) { while (intFreeUnits == 0 && boolAlive) byteBuf.wait(); if(intAllocPos >= longFileSize || boolAlive == false) return -1; ret = intAllocPos; intAllocPos += UNIT_LENGTH; intFreeUnits--; } return ret; } /* * "写"线程向buf[]写完数据后调用,通知"读"线程 */ public void updateBuffer(int i, int len) { synchronized (intUnitSize) { intUnitSize[i] = len; longReaded += len; intUnitSize.notify(); } } /* * "写"线程准备退出时调用 */ public void updateWriterCount() { synchronized (intUnitSize) { intWriterCounter--; intUnitSize.notify(); } } public int getWriterCount() { return intWriterCounter; } /* * read方法内调用 */ public void notifyWriter() { synchronized (byteBuf) { byteBuf.notifyAll(); } } /* * 被某个"写"线程调用,用于终止其它"写"线程;isalive也影响"读"线程流程 */ public void terminateWriters() { synchronized (intUnitSize) { if (boolAlive) { boolAlive = false; System.out.println("\n读取文件超时。重试 " + HttpReader.MAX_RETRY + " 次后放弃,请您稍后再试。"); } intUnitSize.notify(); } notifyWriter(); } public int read() throws Exception { int iret = -1; int i = longReadCursor >> UNIT_LENGTH_BITS; if (intUnitSize[i] == 0) { if(intWriterCounter == 0) return -1; tryCaching(); } if(boolAlive == false) return -1; iret = byteBuf[longReadCursor] & 0xff; longReaded--; longFilePointer++; longReadCursor++; longReadCursor &= BUF_LENGTH_MASK; if (--intUnitSize[i] == 0) { intFreeUnits++; notifyWriter(); } return iret; } public int read(byte b[]) throws Exception { return read(b, 0, b.length); } public int read(byte[] b, int off, int len) throws Exception { int i = longReadCursor >> UNIT_LENGTH_BITS; // 1.等待有足够内容可读 if(tryReading(i, len) < len || boolAlive == false) return -1; // 2.读取 int tail = BUF_LENGTH - longReadCursor; // write_pos != BUF_LENGTH if (tail < len) { System.arraycopy(byteBuf, longReadCursor, b, off, tail); System.arraycopy(byteBuf, 0, b, off + tail, len - tail); } else System.arraycopy(byteBuf, longReadCursor, b, off, len); longReaded -= len; longFilePointer += len; longReadCursor += len; longReadCursor &= BUF_LENGTH_MASK; intUnitSize[i] -= len; if (intUnitSize[i] < 0) { int ni = longReadCursor >> UNIT_LENGTH_BITS; intUnitSize[ni] += intUnitSize[i]; intUnitSize[i] = 0; intFreeUnits++; notifyWriter(); } else if (intUnitSize[i] == 0) { intFreeUnits++; // 空闲块信号量计数加1 notifyWriter(); // 3.通知 } // 如果邻接的下一块未填满表示文件读完,第1步已处理一次读空两块的情况. return len; } /* * 从buf[]偏移src_off位置复制.不移动文件"指针",不发信号. */ public int dump(int src_off, byte b[], int dst_off, int len) throws Exception { int rpos = longReadCursor + src_off; if(tryReading(rpos >> UNIT_LENGTH_BITS, len) < len || boolAlive == false) return -1; int tail = BUF_LENGTH - rpos; if (tail < len) { System.arraycopy(byteBuf, rpos, b, dst_off, tail); System.arraycopy(byteBuf, 0, b, dst_off + tail, len - tail); } else System.arraycopy(byteBuf, rpos, b, dst_off, len); return len; } public long length() { return longFileSize; } public long getFilePointer() { return longFilePointer; } public void close() { boolAlive = false; notifyWriter(); } /* * 随机读取定位 */ public void seek(long pos) throws Exception { longReaded -= pos; longFilePointer = pos; longReadCursor = (int)pos; longReadCursor &= BUF_LENGTH_MASK; int units = longReadCursor >> UNIT_LENGTH_BITS; for (int i = 0; i < units; i++) { intUnitSize[i] = 0; notifyWriter(); } intUnitSize[units] -= pos; } }
5.HTTP协议读远程文件
BuffRandAcceURL.java内的class BuffRandAcceURL只是实现了读/写功能的封装,具体实现从缓冲区读取由HttpReader.java内的class HttpReader实现,把从远程文件读到的内容写入缓冲区由Writer.java内的class Writer实现。class Writer实现Runnable接口,实例化的对象一经创建立即开始下载工作,所以在BuffRandAcceURL.java的第68行采用延时语句,以防止同一IP频繁的连接遭到服务器拒绝而返回错误码。
HttpReader.java源码如下:
/* * HttpReader.java -- HTTP协议读远程文件 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public final class HttpReader { public static final int MAX_RETRY = 10; private URL objURL; private HttpURLConnection httpConn; private InputStream objIStream; private long longCurPos; //决定seek方法中是否执行文件读取定位 private int intConnectTimeout; private int intReadTimeout; public HttpReader(URL u) { this(u, 5000, 5000); } public HttpReader(URL u, int intConnectTimeout, int intReadTimeout) { this.intConnectTimeout = intConnectTimeout; this.intReadTimeout = intReadTimeout; objURL = u; } public int read(byte[] b, int off, int len) throws IOException { int r = objIStream.read(b, off, len); longCurPos += r; return r; } public int getData(byte[] b, int off, int len) throws IOException { int r, rema = len; while (rema > 0) { if ((r = objIStream.read(b, off, rema)) == -1) return -1; rema -= r; off += r; longCurPos += r; } return len; } public void close() { if (httpConn != null) { httpConn.disconnect(); httpConn = null; } if (objIStream != null) { try { objIStream.close(); } catch (IOException e) {} objIStream = null; } objURL = null; } /* * 抛出异常通知重试. * 例如响应码503可能是由某种暂时的原因引起的,同一IP频繁的连接请求会遭服务器拒绝. */ public void seek(long pos) throws IOException { if (pos == longCurPos && objIStream != null) return; if (httpConn != null) { httpConn.disconnect(); httpConn = null; } if (objIStream != null) { objIStream.close(); objIStream = null; } httpConn = (HttpURLConnection) objURL.openConnection(); httpConn.setConnectTimeout(intConnectTimeout); httpConn.setReadTimeout(intReadTimeout); String sProperty = "bytes=" + pos + "-"; httpConn.setRequestProperty("Range", sProperty); //httpConn.setRequestProperty("Connection", "Keep-Alive"); int responseCode = httpConn.getResponseCode(); if (responseCode < 200 || responseCode >= 300) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } throw new IOException("HTTP responseCode="+responseCode); } objIStream = httpConn.getInputStream(); longCurPos = pos; //System.out.println(Thread.currentThread().getName()+ ", longCurPos="+longCurPos); } }
class Writer用于创建“下载”线程,需要考虑与“读”线程同步,下载完一块数据后扔进事先拿到的缓冲区然后发信号通知可“读”。Writer.java源程序如下:
/* * Writer.java -- 用于创建“下载”线程 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; import java.net.URL; public final class Writer implements Runnable { private static boolean boolIsAlive = true; // 一个线程超时其它线程也能退出 private static byte[] byteBuf; private static IWriterCallBack objIWCB; private HttpReader objHR; public Writer(IWriterCallBack cb, URL u, byte[] b, int i) { objHR = new HttpReader(u); objIWCB = cb; byteBuf = b; Thread t = new Thread(this,"dt_"+i); t.setPriority(Thread.NORM_PRIORITY + 1); t.start(); } public void run() { int wpos = 0, rema = 0, retry = 0; int idxmask = BuffRandAcceURL.UNIT_COUNT - 1; boolean cont = true; int index = 0; // byteBuf[]内"块"索引号 int startpos = 0; // index对应的文件位置(相对于文件首的偏移量) long time0 = 0; while (cont) { try { // 1.等待空闲块 if (retry == 0) { if ((startpos = objIWCB.tryWriting()) == -1) break; index = (startpos >> BuffRandAcceURL.UNIT_LENGTH_BITS) & idxmask; wpos = startpos & BuffRandAcceURL.BUF_LENGTH_MASK; rema = BuffRandAcceURL.UNIT_LENGTH; time0 = System.currentTimeMillis(); } // 2.定位 objHR.seek(startpos); // 3.下载"一块" int w; while (rema > 0 && boolIsAlive) { w = (rema < 2048) ? rema : 2048; // 每次读几K合适? if ((w = objHR.read(byteBuf, wpos, w)) == -1) { cont = false; break; } rema -= w; wpos += w; startpos += w; // 能断点续传 } // 下载速度足够快就结束本线程 long t = System.currentTimeMillis() - time0; if (objIWCB.getWriterCount() > 1 && t < 500) cont = false; // 4.通知"读"线程 objIWCB.updateBuffer(index, BuffRandAcceURL.UNIT_LENGTH - rema); retry = 0; } catch (Exception e) { if (++retry == HttpReader.MAX_RETRY) { boolIsAlive = false; objIWCB.terminateWriters(); break; } } } objIWCB.updateWriterCount(); objHR.close(); objHR = null; } }
6. 读取远程文件的同时以异步方式解析MP3的标签
读取远程文件需要缓冲,根据设置的缓冲区大小和网络连接情况不同。缓冲可能要经历一个比较长的时间,可在文件缓冲的这段时间内解析MP3文件的标签信息并打印出来,感觉上觉得等待时间不长。TagThread.java内的class TagThread实现这一功能。同样,class TagThread实现了Runnable接口,对象一经实例化后立即开始解析远程MP3文件的信息。负责解析MP3文件的标签信息的类 class ID3Tag 在后续的文章中给出。TagThread.java源码如下:
/* * TagThread.java -- 解码远程MP3文件ID3 tag * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version.. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package instream; import java.io.IOException; import java.net.URL; import tag.ID3Tag; public class TagThread implements Runnable { private HttpReader objHR; private long longFileSize; public TagThread(URL u,long flen) throws IOException { objHR = new HttpReader(u); longFileSize = flen; new Thread(this).start(); } public void run() { byte[] tmp_buf, b; int retry = 0; ID3Tag objID3tag = new ID3Tag(); while (true) { try { tmp_buf = new byte[128]; // id3 v1 objHR.seek(longFileSize - 128); if (objHR.getData(tmp_buf, 0, 128) == 128) if (objID3tag.checkID3V1(tmp_buf)) objID3tag.parseID3V1(tmp_buf); // ID3 v2 int v2_size = 0; objHR.seek(0); if (objHR.getData(tmp_buf, 0, 10) == 10) { v2_size = objID3tag.checkID3V2(tmp_buf, 0); // System.out.println("v2_size = " + v2_size); if (v2_size > 0) { v2_size -= 10; // id3 v2 header b = new byte[v2_size]; if (objHR.getData(b, 0, v2_size) == v2_size) objID3tag.parseID3V2(b, 0); } } objID3tag.printTag(); break; } catch (Exception e) { if (++retry == HttpReader.MAX_RETRY) break; } } tmp_buf = null; b = null; objID3tag.destroy(); objID3tag = null; } }
【本程序下载地址】http://jmp123.sourceforge.net/
相关推荐
在Java编程语言中,读取文件是常见的任务之一,尤其对于处理文本文件,如TXT文件,以行为单位读取文件内容是一种效率较高且易于管理数据的方式。本篇将深入探讨如何使用Java进行逐行读取TXT文件,并提供相关示例代码...
用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字 Java波浪文字,一个利用...
这个压缩包文件的标题揭示了它的核心内容——一个使用jmp123解码器的MP3播放器项目。jmp123是一个开源的、用C语言编写的MP3解码库,它能够解析并解码MP3音频文件,将压缩的数字音频数据转化为可以播放的格式。这个...
【Java课程设计——电子音乐盒】是一个以Java技术为基础,利用Java Media Framework(JMF)开发的音乐播放软件。这个项目旨在让学生掌握Java编程语言在多媒体应用中的实践,特别是音频处理和用户界面设计方面的能力...
用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字 Java波浪文字,一个利用...
用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字 Java波浪文字,一个利用...
开发者需要了解如何加载音频文件,如MP3或WAV,然后使用AudioInputStream和Clip类来解码和播放音频流。 3. **进度条显示**:进度条用于显示当前音乐的播放进度。这涉及到监听音乐播放状态,通过获取音乐总时长和...
Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...
用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字 Java波浪文字,一个利用...
Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...
Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...
学习这个开源项目,我们可以了解到JAVA如何处理音频数据,包括如何读取MP3文件、如何解码音频流、如何使用JAVA Sound API进行播放等技术。同时,通过阅读源代码,我们可以看到如何组织和优化音频处理的算法,以及...
Java二维码(JAVA二维码)是一种广泛应用于移动设备和网络服务中的数据编码技术,它将大量信息如网址、文本、名片等编码成一个二维图形——二维码,然后通过扫描来快速读取和解析这些信息。在Java中处理二维码,通常...
在JMPlayer的实现中,可能就利用了这些API来解码和播放MP3文件。 2. **Eclipse IDE**:Eclipse是Java开发者常用的一款集成开发环境(IDE),它提供了代码编辑、调试、构建和管理项目等一系列功能。Eclipse的插件...
该项目用Java编写,但通过Java绑定,也可以在其他编程语言,如MATLAB中使用。 MATLAB是一个强大的数学计算和数据分析环境,但其原生功能并不包括二维码的处理。在MATLAB中调用ZXing解码二维码,需要进行一些额外的...
3. **上位机软件开发**:PC红外线遥控器的上位机软件可能用C#、Java或Python等语言编写,实现用户界面、信号发送和接收等功能。 4. **硬件设计**:电路图展示了单片机如何连接红外发射和接收模块,电源,以及其他...
可能还会包含音频格式转换的逻辑,因为不同的音乐文件可能需要不同的解码器来播放。 在开发过程中,Eclipse作为集成开发环境(IDE)被提及,这意味着源代码是在Eclipse中编写、编译和调试的。Eclipse提供了丰富的...
5. **解析器与解码器**:korau包含音频文件的解析器,能够读取和验证音频文件的头部信息,然后使用相应的解码器将压缩的数据转换为可播放的音频流。 6. **API友好**:korau的API设计简洁且直观,使得开发者可以轻松...
8. 格式化输出与输入:在文本编辑器的实现中,需要支持格式化文档的保存和读取,可能涉及到不同文件格式(如txt、bin)的编码和解码。 9. UI设计原则:设计简单易用的资源浏览器和文本编辑器时,需要遵循良好的用户...
FFmpeg的JNI接口会被用来在Java层调用C/C++编写的解码器,这样可以充分利用CPU的性能并降低内存消耗。 4. **Android多媒体框架**: Android系统本身提供了一个多媒体框架,包括MediaPlayer类,它可以播放本地和网络...