package cn.api.media.utils;

import com.jiggawatt.jt.tools.adpcm.ADPCMDecoder;
import com.jiggawatt.jt.tools.adpcm.ADPCMDecoderConfig;
import com.jiggawatt.jt.tools.adpcm.ADPCMEncoder;
import com.jiggawatt.jt.tools.adpcm.ADPCMEncoderConfig;
import javax.sound.sampled.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;


/**
 * 音频文件转换工具类，不需要实例化，直接使用静态方法进行转换。 目前提供PCM到WAV格式转换。
 */

public class Converter {
    private Converter() {
    }

    /**
     * 将PCM原始文件转换为WAV格式
     *
     * @param numChannels   通道数
     * @param sampleRate    采样率
     * @param bitsPerSample 位数
     * @param src           原始文件
     * @param target        目标文件
     * @return 转换成功返回0，失败返回-1。
     * @throws IOException 如果文件不存在，或读写错误，抛出异常。
     */
    public static int pcmToWavByFile(short numChannels, int sampleRate, short bitsPerSample, String src, String target)
            throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(target);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int len;
            while (-1 != (len = fis.read(buf))) {
                baos.write(buf, 0, len);
            }

            WaveHelper helper = new WaveHelper(numChannels, sampleRate, bitsPerSample, baos.size());
            byte[] h = helper.getHeader();
            if (null == h) {
                return -1;
            }
            // write header
            fos.write(h);
            // write data stream
            fos.write(baos.toByteArray());
            return 0;
        } finally {
            if (null != fis) {
                fis.close();
            }
            if (null != fos) {
                fos.close();
            }
        }
    }

    /**
     * 将PCM原始文件转换为WAV格式
     *
     * @param numChannels   通道数
     * @param sampleRate    采样率
     * @param bitsPerSample 位数
     * @param baos          原始流
     * @param target        目标文件
     * @return 转换成功返回0，失败返回-1。
     * @throws IOException 如果文件不存在，或读写错误，抛出异常。
     */
    public static void pcmToWavFileByStream(short numChannels, int sampleRate, short bitsPerSample, ByteArrayOutputStream baos, String target) {
        FileOutputStream fos = null;
        try {
            new File(target).deleteOnExit();
            fos = new FileOutputStream(target);

            WaveHelper helper = new WaveHelper(numChannels, sampleRate, bitsPerSample, baos.size());
            byte[] h = helper.getHeader();
            if (null == h) {
                return;
            }
            // write header
            fos.write(h);
            // write data stream
            fos.write(baos.toByteArray());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fos) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 辅助类，主要用于生成WAV文件头数据。
     *
     * @author wang
     */
    private static class WaveHelper {
        private String chunkId = "RIFF";
        private int chunkSize;
        private String format = "WAVE";
        private String subChunk1Id = "fmt ";
        private int subChunk1Size = 16;
        private short audioFormat = 1;
        private short numChannels;
        private int sampleRate;
        private int byteRate;
        private short blockAlign;
        private short bitsPerSample;
        private String subChunk2Id = "data";
        private int subChunk2Size;

        /**
         * @param numChannels   通道数
         * @param sampleRate    采样率
         * @param bitsPerSample 位数
         * @param PCMSize       原始PCM文件大小
         */
        public WaveHelper(short numChannels, int sampleRate, short bitsPerSample, int PCMSize) {
            this.numChannels = numChannels;
            this.sampleRate = sampleRate;
            this.bitsPerSample = bitsPerSample;
            this.blockAlign = (short) (this.bitsPerSample * this.numChannels / 8);
            this.byteRate = this.sampleRate * this.blockAlign;
            this.subChunk2Size = PCMSize;
            this.chunkSize = this.subChunk2Size + (44 - 8);
        }

        /**
         * 返回wav文件头字节数组，如果异常返回NULL
         *
         * @return 文件头字节数组
         */
        public byte[] getHeader() {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);
            try {
                dos.write(this.chunkId.getBytes());
                dos.writeInt(Integer.reverseBytes(this.chunkSize));
                dos.write(this.format.getBytes());
                dos.write(this.subChunk1Id.getBytes());
                dos.writeInt(Integer.reverseBytes(this.subChunk1Size));
                dos.writeShort(Short.reverseBytes(this.audioFormat));
                dos.writeShort(Short.reverseBytes(this.numChannels));
                dos.writeInt(Integer.reverseBytes(this.sampleRate));
                dos.writeInt(Integer.reverseBytes(this.byteRate));
                dos.writeShort(Short.reverseBytes(this.blockAlign));
                dos.writeShort(Short.reverseBytes(this.bitsPerSample));
                dos.write(this.subChunk2Id.getBytes());
                dos.writeInt(Integer.reverseBytes(this.subChunk2Size));
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
            return baos.toByteArray();
        }
    }

    /**
     * 播放pcm 文件
     *
     * @param file
     */
    public static void playPcmFile(File file) {
        try {
            int offset = 0;
            int bufferSize = Integer.valueOf(String.valueOf(file.length()));
            byte[] audioData = new byte[bufferSize];
            InputStream in = new FileInputStream(file);
            in.read(audioData);


            float sampleRate = 16000;
            int sampleSizeInBits = 16;
            int channels = 1;
            boolean signed = true;
            boolean bigEndian = false;
            // sampleRate - 每秒的样本数
            // sampleSizeInBits - 每个样本中的位数
            // channels - 声道数（单声道 1 个，立体声 2 个）
            // signed - 指示数据是有符号的，还是无符号的
            // bigEndian - 指示是否以 big-endian 字节顺序存储单个样本中的数据（false 意味着
            // little-endian）。
            AudioFormat af = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);

            SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, af, bufferSize);
            SourceDataLine sdl = (SourceDataLine) AudioSystem.getLine(info);
            sdl.open(af);
            sdl.start();
            while (offset < audioData.length) {
                offset += sdl.write(audioData, offset, bufferSize);
            }
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 播放pcm 流
     *
     * @param baos
     */
    public static void playPcmStream(ByteArrayOutputStream baos) {
        try {
            int offset = 0;
            int bufferSize = baos.size();
            byte[] audioData = baos.toByteArray();

            float sampleRate = 16000;
            int sampleSizeInBits = 16;
            int channels = 1;
            boolean signed = true;
            boolean bigEndian = false;
            // sampleRate - 每秒的样本数
            // sampleSizeInBits - 每个样本中的位数
            // channels - 声道数（单声道 1 个，立体声 2 个）
            // signed - 指示数据是有符号的，还是无符号的
            // bigEndian - 指示是否以 big-endian 字节顺序存储单个样本中的数据（false 意味着
            // little-endian）。
            AudioFormat af = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);

            SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, af, bufferSize);
            SourceDataLine sdl = (SourceDataLine) AudioSystem.getLine(info);
            sdl.open(af);
            sdl.start();
            while (offset < audioData.length) {
                offset += sdl.write(audioData, offset, bufferSize);
            }
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }

    /**
     * pcm 转wav
     *
     * @param source
     * @param target
     * @throws Exception
     */
    public static void parseFile(String source, String target) throws Exception {
        float sampleRate = 16000;
        int sampleSizeInBits = 16;
        int channels = 1;
        boolean signed = true;
        boolean bigEndian = false;
        AudioFormat af = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
        File sourceFile = new File(source);
        FileOutputStream out = new FileOutputStream(new File(target));
        AudioInputStream audioInputStream = new AudioInputStream(new FileInputStream(sourceFile), af, sourceFile.length());
        AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
        audioInputStream.close();
        out.flush();
        out.close();
    }

    /**
     * pcm 编码为 AD pcm
     * @param pcmInput 输入
     * @param channels 声道
     * @param sampleRate 采样率
     * @param shape 降噪
     * @return ByteBuffer
     */
    ByteBuffer encodePcm(ShortBuffer pcmInput, int channels, int sampleRate, boolean shape) throws IOException {
        ADPCMEncoderConfig cfg =
                ADPCMEncoder.configure()
                        // 1 for mono, 2 for stereo
                        .setChannels    (channels)
                        // sample rate in Hz
                        .setSampleRate  (sampleRate)
                        // noise shaping; true=on, false=off
                        .setNoiseShaping(shape)
                        // compute block size automatically
                        .setBlockSize   (ADPCMDecoderConfig.AUTO_BLOCK_SIZE)
                        // create the configuration object
                        .end();

        // ADPCMEncoderConfig.computeOutputSize(ShortBuffer) computes the minimum output buffer size
        ByteBuffer adpcmOutput = ByteBuffer.allocate(cfg.computeOutputSize(pcmInput));

        // this returns the adpcmOutput buffer
        return (ByteBuffer) new ADPCMEncoder(cfg).encode(pcmInput, adpcmOutput).rewind();
    }

    /**
     * AD pcm 编码为pcm
     * @param adpcmInput 输入
     * @param channels 声道
     * @param samples 采样精度
     * @param sampleRate 采样率
     * @return ShortBuffer
     */
    ShortBuffer decodeADpcm(ByteBuffer adpcmInput, int channels, int samples, int sampleRate) throws IOException {
        ADPCMDecoderConfig cfg =
                ADPCMDecoder.configure()
                        // 1 for mono, 2 for stereo
                        .setChannels  (channels)
                        // sample rate in Hz
                        .setSampleRate(sampleRate)
                        // compute block size with the formula used by the encoder
                        .setBlockSize (ADPCMDecoderConfig.AUTO_BLOCK_SIZE)
                        // create the configuration object
                        .end();

        // if `samples` is the length of the sound in samples, we need to double the length of the buffer for stereo data
        ShortBuffer pcmOutput = ShortBuffer.allocate(channels * samples);

        // this returns the pcmOutput buffer
        return (ShortBuffer) new ADPCMDecoder(cfg).decode(adpcmInput, pcmOutput).rewind();
    }

    public static int toInt(byte[] b) {
        return ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + (b[0] << 0));
    }

    public static short toShort(byte[] b) {
        return (short) ((b[1] << 8) + (b[0] << 0));
    }


    public static byte[] read(RandomAccessFile rdf, int pos, int length) throws IOException {
        rdf.seek(pos);
        byte result[] = new byte[length];
        for (int i = 0; i < length; i++) {
            result[i] = rdf.readByte();
        }
        return result;
    }

    /**
     * 音频采样率，声道，字节，识别
     *
     * @param filePath
     */
    public static void soutAudioInfo(String filePath) {

        File f = new File(filePath);
        RandomAccessFile rdf = null;
        try {
            rdf = new RandomAccessFile(f, "r");
            // 声音尺寸
            System.out.println("audio size: " + toInt(read(rdf, 4, 4)));
            // 音频格式 1 = PCM
            System.out.println("audio format: " + toShort(read(rdf, 20, 2)));
            // 1 单声道 2 双声道
            System.out.println("num channels: " + toShort(read(rdf, 22, 2)));
            // 采样率、音频采样级别 8000 = 8KHz
            System.out.println("sample rate: " + toInt(read(rdf, 24, 4)));
            // 每秒波形的数据量
            System.out.println("byte rate: " + toInt(read(rdf, 28, 4)));
            // 采样帧的大小
            System.out.println("block align: " + toShort(read(rdf, 32, 2)));
            // 采样位数
            System.out.println("bits per sample: " + toShort(read(rdf, 34, 2)));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                rdf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * wav 转 pcm
     *
     * @param wavFile
     * @return
     */
    public String wavToPcmFilePath(String wavFile) {
        try {
            byte[] buffer = new byte[1024];
            //wav 和pcm的区别就是wav在pcm的前面多了44字节
            byte[] preBuffer = new byte[44];
            int readByte = 0;
            FileInputStream fis = new FileInputStream(wavFile);
            String new_audio = wavFile.substring(0, wavFile.lastIndexOf(".") + 1) + "pcm";
            FileOutputStream fos = new FileOutputStream(new_audio);
            //提出44位的wav前缀
            if (fis.read(preBuffer) == -1) {
                return null;
            }
            //复制pcm内容
            while ((readByte = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, readByte);
            }
            fos.flush();
            fos.close();
            fis.close();
            return new_audio;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) throws Exception {

//        parse("D:\\testtest.pcm", "D:\\test.wav");


        File file = new File("D:\\test.pcm");
        parseFile("D:\\test.pcm", "D:\\test.wav");
//        playPcm(file);


/*        File file = new File("D:\\download\\a (5).pcm");


        FileInputStream inputStream = new FileInputStream(file);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IoUtil.copy(inputStream, baos);
        playPcmStream(baos);*/
/*
        try {

            soutAudioInfo("d:/test.wav");
//            File audioFile = new File("D:\\test.pcm");
//        File audioFile  = new File("E:\\voice_cache.wav");
            File audioFile = new File("d:/test.wav");
//        File audioFile  = new File("D:\\download\\audio\\test2.wav");

            if (audioFile.canRead()) {

                FileInputStream fileInputStream = new FileInputStream(audioFile);
                byte[] byt = new byte[fileInputStream.available()];
                fileInputStream.read(byt);

                wav wavefile = new wav();
                wavefile.GetFromBytes(byt);


                String result = ApiUtils.SendToASRTServer("qwertasd", 16000, wavefile.samples);
                System.out.println("result:" + result);

            }

        } catch (Exception e) {
            e.printStackTrace();
        }*/

    }
}
