javaCV開發詳解之8:轉封裝在rtsp轉rtmp流中的應用(無須轉碼,更低的資源消耗)

javaCV系列文章:java

javacv開發詳解之1:調用本機攝像頭視頻服務器

javaCV開發詳解之2:推流器實現,推本地攝像頭視頻到流媒體服務器以及攝像頭錄製視頻功能實現(基於javaCV-FFMPEG、javaCV-openCV)tcp

javaCV開發詳解之3:收流器實現,錄製流媒體服務器的rtsp/rtmp視頻文件(基於javaCV-FFMPEG)ide

javaCV開發詳解之4:轉流器實現(也可做爲本地收流器、推流器,新增添加圖片及文字水印,視頻圖像幀保存),實現rtsp/rtmp/本地文件轉發到rtmp流媒體服務器(基於javaCV-FFMPEG)性能

javaCV開發詳解之5:錄製音頻(錄製麥克風)到本地文件/流媒體服務器(基於javax.sound、javaCV-FFMPEG)ui

javaCV開發詳解之6:本地音頻(話筒設備)和視頻(攝像頭)抓取、混合並推送(錄製)到服務器(本地)this

javaCV開發詳解之7:讓音頻轉換更加簡單,實現通用音頻編碼格式轉換、重採樣等音頻參數的轉換功能(以pcm16le編碼的wav轉mp3爲例)編碼

javaCV開發詳解之8:轉封裝在rtsp轉rtmp流中的應用(無須轉碼,更低的資源消耗,更好的性能,更低延遲)spa

補充篇:.net

音視頻編解碼問題:javaCV如何快速進行音頻預處理和解複用編解碼(基於javaCV-FFMPEG)

音視頻編解碼問題:16/24/32位位音頻byte[]轉換爲小端序short[],int[],以byte[]轉short[]爲例

實現給圖片增長圖片水印或者文字水印(也支持視頻圖像幀添加水印)

java原生實現屏幕設備遍歷和屏幕採集(捕獲)等功能

補充:解決javaCV的FFmpegFrameRecorder中dts爲空致使播放器過快解碼進而致使畫面時快時慢等影響視頻正常解碼播放的問題,目前解決辦法以下:

注意:本代碼已提交給javacv,目前1.4.4-snapshot版本已修復該問題

修改 FFmpegFrameRecorder中的recordPacket(AVPacket pkt) 方法

(1)註釋掉pkt.dts(AV_NOPTS_VALUE);

(2)在視頻幀writePacket以前增長:

pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), video_st.time_base(),(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));

(3)在音視幀writePacket以前增長: 

pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), audio_st.time_base(),(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));

javaCV開發詳解之4:轉流器實現中咱們使用了Grabber和Recorder的garbFrame和recordFrame實現轉流,可是這種方式消耗很大,經過javacv源碼發現garbFrame實際上進行decode操做(也就是把h264編碼數據解碼爲yuv數據並保存到Frame對象中,而後在recordFrame中把Frame中的yuv圖像像素數據又經過encode爲h264編碼數據,音頻部分則是在garbFrame時先解碼成pcm數據,而後在garbFrame中編碼爲aac),這兩部分的編解碼操做很是耗資源,顯然會影響到轉流的總體效率。

既然rtsp和rtmp自己都支持h264視頻編碼,那麼視頻編碼這塊徹底能夠跳過視頻編解碼的步驟,音頻若是也都是aac編碼那固然更好,這樣咱們能夠避免不少沒必要要的編解碼操做。

假設rtsp的視頻編碼和音頻編碼是h264和aac,那麼咱們只須要一步轉封裝便可完成轉流,代碼參考以下:

import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;

import java.io.IOException;

import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FrameGrabber.Exception;

/**
 * rtsp轉rtmp(轉封裝方式)
 * @author eguid
 */
public class ConvertVideoPakcet {
    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorderPlus record = null;
    int width = -1, height = -1;

    // 視頻參數
    protected int audiocodecid;
    protected int codecid;
    protected double framerate;// 幀率
    protected int bitrate;// 比特率

    // 音頻參數
    // 想要錄製音頻,這三個參數必須有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
    private int audioChannels;
    private int audioBitrate;
    private int sampleRate;

    /**
     * 選擇視頻源
     * @param src
     * @author eguid
     * @throws Exception
     */
    public ConvertVideoPakcet from(String src) throws Exception {
        // 採集/抓取器
        grabber = new FFmpegFrameGrabber(src);
        if(src.indexOf("rtsp")>=0) {
            grabber.setOption("rtsp_transport","tcp");
        }
        grabber.start();// 開始以後ffmpeg會採集視頻信息,以後就能夠獲取音視頻信息
        if (width < 0 || height < 0) {
            width = grabber.getImageWidth();
            height = grabber.getImageHeight();
        }
        // 視頻參數
        audiocodecid = grabber.getAudioCodec();
        System.err.println("音頻編碼:" + audiocodecid);
        codecid = grabber.getVideoCodec();
        framerate = grabber.getVideoFrameRate();// 幀率
        bitrate = grabber.getVideoBitrate();// 比特率
        // 音頻參數
        // 想要錄製音頻,這三個參數必須有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
        audioChannels = grabber.getAudioChannels();
        audioBitrate = grabber.getAudioBitrate();
        if (audioBitrate < 1) {
            audioBitrate = 128 * 1000;// 默認音頻比特率
        }
        return this;
    }

    /**
     * 選擇輸出
     * @param out
     * @author eguid
     * @throws IOException
     */
    public ConvertVideoPakcet to(String out) throws IOException {
        // 錄製/推流器
        record = new FFmpegFrameRecorderPlus(out, width, height);
        record.setVideoOption("crf", "18");
        record.setGopSize(2);
        record.setFrameRate(framerate);
        record.setVideoBitrate(bitrate);

        record.setAudioChannels(audioChannels);
        record.setAudioBitrate(audioBitrate);
        record.setSampleRate(sampleRate);
        AVFormatContext fc = null;
        if (out.indexOf("rtmp") >= 0 || out.indexOf("flv") > 0) {
            // 封裝格式flv
            record.setFormat("flv");
            record.setAudioCodecName("aac");
            record.setVideoCodec(codecid);
            fc = grabber.getFormatContext();
        }
        record.start(fc);
        return this;
    }
    
    /**
     * 轉封裝
     * @author eguid
     * @throws IOException
     */
    public ConvertVideoPakcet go() throws IOException {
        long err_index = 0;//採集或推流致使的錯誤次數
        //連續五次沒有采集到幀則認爲視頻採集結束,程序錯誤次數超過1次即中斷程序
        for(int no_frame_index=0;no_frame_index<5||err_index>1;) {
            AVPacket pkt=null;
            try {
                //沒有解碼的音視頻幀
                pkt=grabber.grabPacket();
                if(pkt==null||pkt.size()<=0||pkt.data()==null) {
                    //空包記錄次數跳過
                    no_frame_index++;
                    continue;
                }
                //不須要編碼直接把音視頻幀推出去
                err_index+=(record.recordPacket(pkt)?0:1);//若是失敗err_index自增1
                av_free_packet(pkt);
            }catch (Exception e) {//推流失敗
                err_index++;
            } catch (IOException e) {
                err_index++;
            }
        }
        return this;
    }

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

//運行,設置視頻源和推流地址
        new ConvertVideoPakcet().from("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov")
        .to("rtmp://eguid.cc:1935/rtmp/eguid")
        .go();
    }
}

資源佔用下降了十倍都不止,性能提高仍是不錯的,延遲也下降不少。

感謝支持eguid原創

相關文章
相關標籤/搜索