利用文件随机读取类RandomAccessFile做断点重读
模拟通过文件读写方式对音视频流进行格式转换后,获取音频内容,并支持读取中断后再起线程读取,断开重读的关键在于随机读取文件,它支持保持一个类似游标的文件字节索引,只要每次读取都能把这个游标记录在内存中,那么就算当前的线程意外死掉,新的线程也能直接拿到游标值,按最后一次读取的位置重新开始读。直接看代码:
import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.Frame; import java.io.*; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.concurrent.TimeUnit; /** * 一个线程把音视频转单声道16位16K赫兹小端点pcm音频,另一个线程读取后意外中断,再起一个线程接着读 */ public class PCMFileProduceAndConsumer { static boolean isGrabeFinish = false; static boolean isFirstCome = true; static boolean isFirstThread = true; static int someOffset = 33333333; static final int interval = 500; // 间隔时间,单位毫秒 static long offset = 0; // 随机读取的下标位置,从头开始读 static String sourcePath = "E:\\BaiduNetdiskDownload\\roundDeskS02E01.mp4"; //待转音视频 static String targetPath = "E:\\BaiduNetdiskDownload\\tmp.pcm"; //目标文件 public static void main(String[] args) throws Exception { // 拉起一个线程把MP4转pcm音频文件 new Thread(new FileInput()).start(); // 先等两秒,让音频数据写进去一些 TimeUnit.SECONDS.sleep(1); // 拉起另一个线程读取音频文件,支持断连后再重连读取 new Thread(new FileOutput()).start(); // 上面的线程中断后拉起一个新的线程,等待上个线程死掉 while (offset < someOffset) { Thread.sleep(20); } new Thread(new FileOutput()).start(); } /** * 文件写入 */ static class FileInput implements Runnable { @Override public void run() { try { OutputStream os = new BufferedOutputStream(new FileOutputStream(targetPath)); FFmpegFrameGrabber frameGrabber = FFmpegFrameGrabber.createDefault(sourcePath); frameGrabber.setSampleRate(16000); //16K赫兹采样率 frameGrabber.setAudioChannels(1); //单声道 frameGrabber.start(); Frame frame; Buffer buffer; short[] shorts; byte[] bytes; System.out.println("开始写入"); while ((frame = frameGrabber.grabSamples()) != null) { if (frame.samples == null) { continue; } buffer = frame.samples[0]; shorts = new short[buffer.limit()]; ((ShortBuffer) buffer).get(shorts); bytes = shortArr2byteArr(shorts, buffer.limit()); os.write(bytes); } os.close(); // 关闭写文件 frameGrabber.close(); // 直接关闭拉流 isGrabeFinish = true; } catch (Exception e) { e.printStackTrace(); } System.out.println("写入结束"); } } /** * 文件读出 */ static class FileOutput implements Runnable { long timestart; @Override public void run() { byte[] buf = new byte[1280]; System.out.println("开始读取.最开始的位置:" + offset); try (RandomAccessFile raf = new RandomAccessFile(targetPath, "r")) { int len; raf.seek(offset); // 定位到指定下标位置 // 当文件在写入时,尚未读取到文件内容时,等等写入线程 while ((len = raf.read(buf)) != -1 || !isGrabeFinish) { // 有可能读取速度快于写入速度,空转CPU等待 if (len == -1) { Thread.sleep(10); continue; } // 获取每10秒的第一次开始时间 if (isFirstCome) { timestart = System.currentTimeMillis(); isFirstCome = false; } if (System.currentTimeMillis() - timestart > interval) { System.out.println("当前读取位置: " + offset); isFirstCome = true; } // 模拟线程突然被中断,拉起另一个线程继续读取 if (isFirstThread && offset >= someOffset) { isFirstThread = false; break; } offset += len; raf.seek(offset); // 定位到下次准备读取位置 } } catch (Exception e) { e.printStackTrace(); } System.out.println("读取结束. 最后的位置: " + offset); } } /** * 8位字节数组转16位字节数组,也就是16位的采样位深,转小端点字节数组 * * @param shortArr * @param shortArrLen * @return */ private static byte[] shortArr2byteArr(short[] shortArr, int shortArrLen) { byte[] byteArr = new byte[shortArrLen * 2]; ByteBuffer.wrap(byteArr).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shortArr); return byteArr; } }
运行结果:
开始读取.最开始的位置:0 开始写入 当前读取位置: 956258 当前读取位置: 2296700 当前读取位置: 4213894 当前读取位置: 7486824 当前读取位置: 9374884 当前读取位置: 12292798 当前读取位置: 14785692 当前读取位置: 18087756 当前读取位置: 20425354 当前读取位置: 23694724 当前读取位置: 27250164 当前读取位置: 32015270 读取结束. 最后的位置: 33333750 开始读取.最开始的位置:33333750 当前读取位置: 33333750 当前读取位置: 37663106 当前读取位置: 43335462 当前读取位置: 47724590 当前读取位置: 53208958 当前读取位置: 60212088 当前读取位置: 65910460 当前读取位置: 72629014 写入结束 读取结束. 最后的位置: 76616902 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'E:\BaiduNetdiskDownload\roundDeskS02E01.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf58.20.100 Duration: 00:39:54.27, start: 0.000000, bitrate: 815 kb/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1080x606, 744 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc (default) Metadata: handler_name : VideoHandler Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 64 kb/s (default) Metadata: handler_name : SoundHandler Process finished with exit code 0