• 利用文件随机读取类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
    oundDeskS02E01.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
  • 相关阅读:
    Best HTTP
    Unity3D游戏轻量级xlua热修复框架
    线段树
    7.1
    BZOJ 3011: [Usaco2012 Dec]Running Away From the Barn( dfs序 + 主席树 )
    BZOJ 3585: mex( 离线 + 线段树 )
    2015暑假集训
    BZOJ 3398: [Usaco2009 Feb]Bullcow 牡牛和牝牛( dp )
    BZOJ 2693: jzptab( 莫比乌斯反演 )
    2015.7.31
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/13463334.html
Copyright © 2020-2023  润新知