讀書筆記-第五課

前言

查看了相關文章然後一筆一筆打代碼再調試成功出結果,
eguid的博客
不保證代碼能夠原封不動就能運行,
這裏做一下記錄。
ps:代碼內容有改動,原版的可以看原作者的。

代碼

package net.w2p.JCVStudio.zhiboStudy;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FrameRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/***
 * 音頻錄製以及推流--推到rtmp或者直接保存到本地。
 * https://blog.csdn.net/eguid_1/article/details/52702385
 * 錄製音頻(錄製麥克風)到本地文件/流媒體服務器(基於javax.sound、javaCV-FFMPEG)
 * **/
public class Lesson05 {
    static final ConcurrentHashMap<String,Object> map4Middel=new ConcurrentHashMap<>(3);


    Logger logger= LoggerFactory.getLogger("第五課");
/**
 * 設置音頻編碼器 最好是系統支持的格式,否則getLine() 會發生錯誤
 * 採樣率:44.1k;採樣率位數:16位;立體聲(stereo);是否簽名;true:
 * big-endian字節順序,false:little-endian字節順序(詳見:ByteOrder類)
 */
    private Runnable recordTask(final String outputUrl) throws Exception{
        final int AUDIO_DEVICE_INDEX=0;
        AudioFormat audioFormat=new AudioFormat(44100.0F,16,2,true,false);
        logger.info("準備開啓音頻...");

        // 通過AudioSystem獲取本地音頻混合器信息
        Mixer.Info[] minfoSet= AudioSystem.getMixerInfo();
        // 通過AudioSystem獲取本地音頻混合器

        Mixer mixer=AudioSystem.getMixer(minfoSet[AUDIO_DEVICE_INDEX]);


        // 通過設置好的音頻編解碼器獲取數據線信息
        DataLine.Info dataLineInfo=new DataLine.Info(TargetDataLine.class,audioFormat);

        // 打開並開始捕獲音頻
        // 通過line可以獲得更多控制權
        // 獲取設備:TargetDataLine line
        // =(TargetDataLine)mixer.getLine(dataLineInfo);

        Line dataline=null;
        try{
            dataline=AudioSystem.getLine(dataLineInfo);
        }
        catch (Exception ed){
            ed.printStackTrace();
            logger.info("開啓失敗");
            return null;
        }

        TargetDataLine line=(TargetDataLine)dataline;
        try{
            line.open(audioFormat);
        }
        catch (Exception ed){
            line.stop();
            ed.printStackTrace();
            try{
                line.open(audioFormat);
            }
            catch (Exception ed2){
                line.stop();
                ed2.printStackTrace();
                logger.info("按照指定音頻解碼器打開失敗,程序終止。");
                return null;
            }
        }

        line.start();
        logger.info("已經成功開啓音頻...");

        // 獲得當前音頻採樣率
        int sampleRate=(int)audioFormat.getSampleRate();
        // 獲取當前音頻通道數量
        int numChannels=audioFormat.getChannels();
        // 初始化音頻緩衝區(size是音頻採樣率*通道數)
        int audioBufferSize=sampleRate*numChannels;
        byte[] audioBytes=new byte[audioBufferSize];

        logger.info("音軌數量:{}",numChannels);

        Runnable crabAudio=new Runnable() {
            ShortBuffer sBuff=null;
            int nBytesRead;
            int nSamplesRead;

            @Override
            public void run() {
                if(Thread.interrupted()){
                    logger.info("線程已經關閉了,無須執行其他操作。");
                    return;
                }
                if(map4Middel.get("stop")!=null){
                    logger.info("已經停止錄音");
                    FFmpegFrameRecorder recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
                    if(recorder!=null){
                        try{
                            recorder.stop();
                            recorder.release();
                        }
                        catch (Exception ed){
                            ed.printStackTrace();
                        }
                    }
                    return;
                }
                logger.info("讀取音頻數據...");

                // 非阻塞方式讀取
                nBytesRead=line.read(audioBytes,0,line.available());
                // 因爲我們設置的是16位音頻格式,所以需要將byte[]轉成short[]
                nSamplesRead=nBytesRead/2;
                short[] samples=new short[nSamplesRead];
                /**
                 * ByteBuffer.wrap(audioBytes)-將byte[]數組包裝到緩衝區
                 * ByteBuffer.order(ByteOrder)-按little-endian修改字節順序,解碼器定義的
                 * ByteBuffer.asShortBuffer()-創建一個新的short[]緩衝區
                 * ShortBuffer.get(samples)-將緩衝區裏short數據傳輸到short[]
                 */

                ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                sBuff=ShortBuffer.wrap(samples,0,nSamplesRead);
// 按通道錄製shortBuffer
                FFmpegFrameRecorder recorder=null;
                if(map4Middel.get("recorder")==null){

                    recorder=new FFmpegFrameRecorder(outputUrl,1920,1080,2);
                    recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264,編碼
//                        recorder.setFormat("flv");
                    recorder.setFormat("flv");//封裝格式,如果是推送到rtmp就必須是flv封裝格式
                    recorder.setPixelFormat(0);
                    recorder.setFrameRate(25);


                    recorder.setInterleaved(true);

                    recorder.setVideoOption("preset", "ultrafast");
                    recorder.setVideoOption("tune", "zerolatency");
                    recorder.setVideoOption("fflags", "nobuffer");
                    recorder.setVideoOption("analyzeduration", "0");

                    /**添加audio相關參數**/
                    // 不可變(固定)音頻比特率
                    recorder.setAudioOption("crf", "0");
                    // 最高質量
                    recorder.setAudioQuality(0);
                    // 音頻比特率
                    recorder.setAudioBitrate(192000);
                    // 音頻採樣率
                    recorder.setSampleRate(44100);
                    // 雙通道(立體聲)
                    recorder.setAudioChannels(numChannels);
                    // 音頻編/解碼器
                    recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
                    /**添加audio相關參數 end**/
//
                    map4Middel.put("recorder",recorder);
                    try {
                        recorder.start();
                    }
                    catch (Exception ed){
                        ed.printStackTrace();
                    }
                }
                else{
                    recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
                }

                try{
                    logger.info("錄製音頻數據.....");
                    recorder.recordSamples(sampleRate,numChannels,sBuff);

                }
                catch (Exception ed){
                    ed.printStackTrace();
                }


            }
            @Override
            protected void finalize() throws Throwable {
                sBuff.clear();
                sBuff = null;
                super.finalize();
            }

        };

        return crabAudio;


    }

    public void recordSound(String outputUrl) throws Exception{



        int FRAME_RATE=25;
        final ScheduledThreadPoolExecutor exec=new ScheduledThreadPoolExecutor(1);
        Runnable crabAudio=recordTask(outputUrl);

        ScheduledFuture tasker=exec.scheduleAtFixedRate(crabAudio,0,(long)1000/FRAME_RATE, TimeUnit.MILLISECONDS);

        //使用java的JFrame顯示圖像
        CanvasFrame mainFrame = new CanvasFrame("",
                CanvasFrame.getDefaultGamma()/2.2);
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setTitle("錄音測試");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JLabel jl=new JLabel("這是使用JFrame類創建的窗口");    //創建一個標籤
        Container c=mainFrame.getContentPane();    //獲取當前窗口的內容窗格
        c.add(jl);    //將標籤組件添加到內容窗格上
        mainFrame.setVisible(true);
        mainFrame.setSize(200,200);
        mainFrame.setAlwaysOnTop(true);
        mainFrame.show();
        mainFrame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent e)
            {
                //在這裏面寫你要做的操作比如關閉服務器鏈接

                System.out.println("關閉窗口");
                map4Middel.put("stop",true);

                FFmpegFrameRecorder recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
                if(recorder!=null){
                    try{
                        recorder.stop();
                        recorder.release();
                    }
                    catch (Exception ed){
                        ed.printStackTrace();
                    }
                    logger.info("推流結束---》整理成功");
                }

                tasker.cancel(true);
                if(!exec.isShutdown()){
                    exec.shutdownNow();
                }

            }
        });






    }


    public void record(String outputUrl) throws Exception {
        final int DEVICE_INDEX=0;
// 44.1 sample rate, 16 bits, stereo, signed, little endian
        AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);

        Mixer.Info[] minfoSet = AudioSystem.getMixerInfo();
        Mixer mixer = AudioSystem.getMixer(minfoSet[DEVICE_INDEX]);
        DataLine.Info dataLineInfo=new DataLine.Info(TargetDataLine.class,audioFormat);


// Open and start capturing audio


        Line dataline=null;
        try{
            dataline=AudioSystem.getLine(dataLineInfo);
        }
        catch (Exception ed){
            ed.printStackTrace();
            logger.info("開啓失敗");
            return;
        }
        TargetDataLine line=(TargetDataLine)dataline;

        line.open(audioFormat);
        line.start();
        // 獲得當前音頻採樣率
        int sampleRate=(int)audioFormat.getSampleRate();
        // 獲取當前音頻通道數量
        int numChannels=audioFormat.getChannels();
        // 初始化音頻緩衝區(size是音頻採樣率*通道數)
        int audioBufferSize = (int) audioFormat.getSampleRate() * audioFormat.getChannels();
        byte[] audioBytes = new byte[audioBufferSize];

        AudioInputStream isS = new AudioInputStream(line);
        FFmpegFrameRecorder  recorder=new FFmpegFrameRecorder(outputUrl,
                numChannels);
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264,編碼
//                        recorder.setFormat("flv");
        recorder.setFormat("flv");//封裝格式,如果是推送到rtmp就必須是flv封裝格式
        recorder.setPixelFormat(0);
        recorder.setFrameRate(25);

//                    recorder.setVideoOption("preset", "ultrafast");
//                    recorder.setVideoOption("tune", "zerolatency");
//                    recorder.setVideoOption("fflags", "nobuffer");
//                    recorder.setVideoOption("analyzeduration", "0");
//

        try {
            recorder.start();
        }
        catch (Exception ed){
            ed.printStackTrace();
        }

        //使用java的JFrame顯示圖像
        CanvasFrame mainFrame = new CanvasFrame("",
                CanvasFrame.getDefaultGamma()/2.2);
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setTitle("錄音測試");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JLabel jl=new JLabel("這是使用JFrame類創建的窗口");    //創建一個標籤
        Container c=mainFrame.getContentPane();    //獲取當前窗口的內容窗格
        c.add(jl);    //將標籤組件添加到內容窗格上
        mainFrame.setVisible(true);
        mainFrame.setSize(200,200);
        mainFrame.setAlwaysOnTop(true);
        mainFrame.show();
        mainFrame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent e)
            {
                //在這裏面寫你要做的操作比如關閉服務器鏈接

                System.out.println("關閉窗口");
                map4Middel.put("stop",true);
                try {
                    recorder.stop();
                    recorder.release();
                    logger.info("推流結束");
                }
                catch (Exception ed){
                    ed.printStackTrace();
                }


            }
        });

        while (!Thread.interrupted()&&map4Middel.get("stop")==null)
        {
            try
            {
                // Read from the line... non-blocking
//                int nBytesRead = isS.read(audioBytes, 0, line.available());
                int nBytesRead = line.read(audioBytes, 0, line.available());

                if (nBytesRead > 0)
                {
// Since we specified 16 bits in the AudioFormat, we need to convert our read byte[] to short[] (see source from FFmpegFrameRecorder.recordSamples for AV_SAMPLE_FMT_S16)
// Let's initialize our short[] array
                    int nSamplesRead = nBytesRead / 2;
                    short[] samples = new short[nSamplesRead];

// Let's wrap our short[] into a ShortBuffer and pass it to recordSamples
                    ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                    logger.info("採樣率等數據:nBytesRead:{},nSamplesRead:{}",nBytesRead,nSamplesRead);
                    ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);

                    // recorder is instance of org.bytedeco.javacv.FFmpegFrameRecorder
                    recorder.recordSamples((int) audioFormat.getSampleRate(), audioFormat.getChannels(), sBuff);
                }
            }
            catch (Exception e)

            {
                e.printStackTrace();
            }
        }


    }

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

        /**
         * 本次內容實現了,普通視頻-》推送到直播流
         * 直播流-》保存到本地
         * 直播流-》轉推其他直播流
         * **/
        Lesson05 test=new Lesson05();
        final String outFile="/home/too-white/temp/lesson05_output.flv";
        final String rtmpUrl="rtmp://localhost/live/livestream";
        /**
         * 推薦使用的rtmp直播平臺
         * https://blog.csdn.net/zhichaosong/article/details/89053009
         * **/
//        final String rtmpOnlineUrl="rtmp://202.69.69.180:443/webcast/bshdlive-pc";


//        test.frameRecord(inputFile,outFile,1);
//        test.frameRecord(inputFile,rtmpUrl,1);
//        test.frameRecord(rtmpOnlineUrl,outFile,1);
//        test.recordSound(outFile);
//        test.recordSound(outFile);
        //--推送到rtmp上面--備註,本地保存可以,但是推送到流媒體一直不成功。。需要覈查一下。
        test.recordSound(rtmpUrl);
//        test.recordSound(outFile);

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章