四、JAVA调用海康威视SDK实现摄像头内网推流到阿里云

接上一章:三、JAVA调用海康威视SDK实现摄像头本地存储

本章实现摄像头内网推流到阿里云服务器。

环境准备

  1. 开发工具:eclipse
  2. Jdk版本:jdk1.8
  3. 开发语言:java,界面使用swing开发
  4. 摄像头:DS-2CD1221D-I3
  5. 海康威视SDK下载地址:海康威视开放平台
  6. SDK版本:CH-HCNetSDKV6.0.2.35_build20190411_Win64
  7. EasyRTMPLive:EasyRTMPLive

转发流程

image

账号准备

  1. 海康威视摄像头账号密码及rtsp推拉地址
  2. 阿里云账号,需开通阿里云视频直播相关功能,需获取阿里云推流地址、推流KEY及推流APPNAME,具体参考:快速入门 - 视频直播 - 阿里云

推流域名配置:

image

推流域名设置:
image

推流KEY:
image

代码实现

EasyPushProcess

package com.kx.hcws;

import java.util.Date;

import com.kx.hcws.easy.EasyRtspToRtmpLiveSDK;
import com.kx.hcws.easy.EasyRtspToRtmpLiveSDK.EASY_FRAME_INFO;
import com.kx.hcws.easy.EasyRtspToRtmpLiveSDK.PushLiveInfoCallBack;
import com.kx.hcws.easy.EasyRtspToRtmpLiveSDK.StatusCallBack;
import com.kx.hcws.ui.Player;

/**
 * 
 * 类: EasyPushProcess,描述:
 * 
 * <p style="padding-left:20px">
 * 推流处理器
 * </p>
 *
 * @author kxy
 *
 * @date 2020年5月27日 上午10:45:51
 *
 * @version V1.0.0
 */
public class EasyPushProcess {
    // 日志
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(HCSaveProcess.class);
    public int width = 860, height = 480;

    public boolean pushing = false;

    protected String pushurl = "";

    public long errorcount = 0, counttime = 0, alltime = 0, laststarttime = -1, framesize = 0;

    private Long deviceId;
    private String m_sDeviceIP;
    private String username;
    private String password;
    private int port;
    private long iChannelNum;
    protected String mediaappname;

    private static Player player;

    // 状态回调
    private static StatusCallBack statusCallBack = new StatusCallBack() {
        @Override
        public void invoke(int _channelId, int status) {
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_DISCONNECTED) {
                logger.info("设备m_sDeviceIPRTMP断开连接 :");
            }
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_CONNECT_ABORT) {
                logger.info("设备m_sDeviceIPRTMP连接异常中断 :");
            }
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_CONNECT_FAILED) {
                logger.info("设备m_sDeviceIPRTMP连接失败 :");
            }
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_START_ERROR) {
                logger.info("设备m_sDeviceIPRTSP启动异常 :");
            }
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_ERROR) {
                logger.info("设备m_sDeviceIPRTSP链接异常 :");
            }
            if (status == EasyRtspToRtmpLiveSDK.EASY_RTMP_STATE_TIMEOUT) {// 链接超时或者RTMP创建异常
                logger.info("设备m_sDeviceIPRTMP链接超时自动退出:");
            }
        }
    };
    // 推流信息回调
    private static PushLiveInfoCallBack pushLiveInfoCallBack = new PushLiveInfoCallBack() {
        @Override
        public void invoke(int _channelId, int _mediatype, EASY_FRAME_INFO frameinfo) {
            if (frameinfo != null) {
                Double ss = Double.parseDouble(frameinfo.bitrate + "");
                logger.info("推流帧率:" + ss.longValue());
                player.setPushBitrate(ss.longValue());
            }
        }
    };
    // SDK
    private EasyRtspToRtmpLiveSDK easyRtspToRtmpLiveSDK;
    // 推流帧信息
    private EASY_FRAME_INFO frameinfo = null;
    // 通道号
    private int channelId = -1;

    public EasyPushProcess(String m_sDeviceIP, String username, String password, int port, Long deviceId,
            String pushkey, String pushurl, String mediaappname, Player player) {
        this.deviceId = deviceId;
        this.m_sDeviceIP = m_sDeviceIP;
        this.username = username;
        this.password = password;
        this.port = port;
        this.mediaappname = mediaappname;
        this.player = player;

        pushurl = getPushUrl(pushkey, pushurl, mediaappname);

        /**
         * 海康威视IP摄像头rtsp协议地址如下:
         * 
         * rtsp://[username]:[passwd]@[ip]:[port]/[codec]/[channel]/[subtype]/
         * av_stream
         * 
         * 说明:
         * 
         * username:用户名,例如admin
         * 
         * passwd:密码,例如12345
         * 
         * ip:设备的ip地址,例如192.0.0.64
         * 
         * port:端口号默认554,若为默认可以不写
         * 
         * codec:有h264、MPEG-4、mpeg4这几种
         * 
         * channel:通道号,起始为1
         * 
         * subtype:码流类型,主码流为main,子码流为sub
         * 
         * rtsp://admin:GNEHIB@192.168.2.18:554/h265/ch1/sub/av_stream
         * 
         */
        String src = "rtsp://" + username + ":" + password + "@" + m_sDeviceIP + ":554/h265/ch1/main/av_stream";// 组织海康威视rtsp拉流地址

        easyRtspToRtmpLiveSDK = EasyRtspToRtmpLiveSDK.create();
        channelId = easyRtspToRtmpLiveSDK.push(src, pushurl, deviceId.intValue());
        easyRtspToRtmpLiveSDK.registerStatusCallBack(channelId, statusCallBack);
        easyRtspToRtmpLiveSDK.registerInfoCallBack(channelId, pushLiveInfoCallBack);
    }

    public void start() {
        if (pushing) {
            return;
        }

        errorcount = counttime = alltime = framesize = 0;
        laststarttime = System.currentTimeMillis();

        pushing = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!easyRtspToRtmpLiveSDK.start(channelId)) {// 返回后会自动停止
                    stop_();
                }
            }
        }).start();
    }

    public void stop_() {
        pushing = false;
    }

    /**
     * 
     * 方法: info ,描述:
     * 
     * <p style="padding-left:20px">
     * 取得推流信息
     * </p>
     * kangxiaoyu
     * 
     * @return
     */
    public EASY_FRAME_INFO info() {
        if (channelId >= 0 && isPushing()) {
            return frameinfo;
        }
        return null;
    }

    /**
     * 是否正在推流
     * 
     * @return
     */
    public boolean isPushing() {
        return pushing && easyRtspToRtmpLiveSDK.ispushing(channelId);
    }

    /**
     * 
     * 方法: ispushing_ ,描述:
     * 
     * <p style="padding-left:20px">
     * 判断SDK是否推流
     * </p>
     * kangxiaoyu
     * 
     * @param channelId
     * @return
     */
    public boolean isSdkPushing(int channelId) {
        return easyRtspToRtmpLiveSDK.ispushing(channelId);
    }

    /**
     * 
     * 方法: isSdkStop ,描述:
     * 
     * <p style="padding-left:20px">
     * 调用SDK停止推流
     * </p>
     * kangxiaoyu
     * 
     * @param channelId
     * @return
     */
    public String isSdkStop(int channelId) {
        return easyRtspToRtmpLiveSDK.stop(channelId) + "";
    }

    public int getChannelId() {
        return channelId;
    }

    /**
     * 组织推流地址
     * 
     * @param pushkey
     *            阿里云推流KEY
     * @param pushurl
     *            阿里云推流地址
     * @param mediaappname
     *            阿里云推流APPNAME
     * @return
     */
    public String getPushUrl(String pushkey, String pushurl, String mediaappname) {
        String url = pushurl;
        if (url.contains(";")) {
            url = url.split(";")[(int) (deviceId % pushurl.split(";").length)];
        }
        if (pushkey.contains(";")) {
            pushkey = pushkey.split(";")[(int) (deviceId % pushkey.split(";").length)];
        }
        url += deviceId;

        Date date = new Date();
        String time = date.getTime() + "";
        System.out.println("/" + mediaappname + "/" + deviceId + "-" + time + "-0-0-" + pushkey);
        String auth_key = MD5Utils.getMd5("/" + mediaappname + "/" + deviceId + "-" + time + "-0-0-" + pushkey);
        System.out.println("推流地址:" + url + "?auth_key=" + time + "-0-0-" + auth_key);
        // return url + "?auth_key=" + time + "-0-0-" + auth_key;
        return url;
    }
}

EasyRtspToRtmpLiveSDK

package com.kx.hcws.easy;

import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.win32.StdCallLibrary;

public interface EasyRtspToRtmpLiveSDK extends StdCallLibrary {
    // 日志
    static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(EasyRtspToRtmpLiveSDK.class);

    public static int EASY_RTMP_STATE_CONNECTING = 1; /* 连接中 */
    public static int EASY_RTMP_STATE_CONNECTED = 2; /* 连接成功 */
    public static int EASY_RTMP_STATE_CONNECT_FAILED = 3; /* 连接失败 */
    public static int EASY_RTMP_STATE_CONNECT_ABORT = 4; /* 连接异常中断 */
    public static int EASY_RTMP_STATE_PUSHING = 5; /* 推流中 */
    public static int EASY_RTMP_STATE_DISCONNECTED = 6; /* 断开连接 */
    public static int EASY_RTMP_STATE_ERROR = 7;
    public static int EASY_RTMP_STATE_TIMEOUT = -1;
    public static int EASY_RTMP_STATE_START_ERROR = -2;

    public static int EASY_ACTIVATE_INVALID_KEY = -1; /* 无效Key */
    public static int EASY_ACTIVATE_TIME_ERR = -2; /* 时间错误 */
    public static int EASY_ACTIVATE_PROCESS_NAME_LEN_ERR = -3; /* 进程名称长度不匹配 */
    public static int EASY_ACTIVATE_PROCESS_NAME_ERR = -4; /* 进程名称不匹配 */
    public static int EASY_ACTIVATE_VALIDITY_PERIOD_ERR = -5; /* 有效期校验不一致 */
    public static int EASY_ACTIVATE_PLATFORM_ERR = -6; /* 平台不匹配 */
    public static int EASY_ACTIVATE_COMPANY_ID_LEN_ERR = -7; /* 授权使用商不匹配 */
    public static int EASY_ACTIVATE_SUCCESS = 0; /* 激活成功 */

    public static int Easy_NoErr = 0;
    public static int Easy_RequestFailed = -1;
    public static int Easy_Unimplemented = -2;
    public static int Easy_RequestArrived = -3;
    public static int Easy_OutOfState = -4;
    public static int Easy_NotAModule = -5;
    public static int Easy_WrongVersion = -6;
    public static int Easy_IllegalService = -7;
    public static int Easy_BadIndex = -8;
    public static int Easy_ValueNotFound = -9;
    public static int Easy_BadArgument = -10;
    public static int Easy_ReadOnly = -11;
    public static int Easy_NotPreemptiveSafe = -12;
    public static int Easy_NotEnoughSpace = -13;
    public static int Easy_WouldBlock = -14;
    public static int Easy_NotConnected = -15;
    public static int Easy_FileNotFound = -16;
    public static int Easy_NoMoreData = -17;
    public static int Easy_AttrDoesntExist = -18;
    public static int Easy_AttrNameExists = -19;
    public static int Easy_InstanceAttrsNotAllowed = -20;
    public static int Easy_InvalidSocket = -21;
    public static int Easy_MallocError = -22;
    public static int Easy_ConnectError = -23;
    public static int Easy_SendError = -24;

    // EasyRtspToRtmpLiveSDK推流key,需要从官网获取
    public static String RTSPKEY = "6D75724D7A4969576B5A75414B304A646F7232332B2B394659584E35556C525455457870646D55755A58686C4B56634D5671446A652F34675A57467A65513D3D";
    public static String RTMPKEY = "79736C36655969576B5A75414B304A646F7232332B2B394659584E35556C525455457870646D55755A58686C4931634D5671446A652F34675A57467A65513D3D";

    public static LogCallBack logCallBack = new LogCallBack() {
        @Override
        public void invoke(String logText) {
            logger.info(logText);
        }
    };

    public static EasyRtspToRtmpLiveSDK create() {
        System.setProperty("jna.debug_load", "true");
        System.setProperty("jna.protected", "true");
        System.setProperty("jna.encoding", "GBK");
        EasyRtspToRtmpLiveSDK easyRtspToRtmpLiveSDK = (EasyRtspToRtmpLiveSDK) Native
                .loadLibrary("EasyRtspToRtmpLiveSDK", EasyRtspToRtmpLiveSDK.class);

        easyRtspToRtmpLiveSDK.SetDisplayLog(logCallBack);
        int res = easyRtspToRtmpLiveSDK.init(RTSPKEY, RTMPKEY);
        if (res == 0) {
            logger.info("初始化结果:" + (res == 0 ? "初始化成功" : "初始化失败:" + res));
        } else {
            switch (res) {
            case EASY_ACTIVATE_INVALID_KEY:
                logger.info("初始化结果:" + "初始化失败,无效Key");
                break;
            case EASY_ACTIVATE_TIME_ERR:
                logger.info("初始化结果:" + "初始化失败,时间错误");
                break;
            case EASY_ACTIVATE_PROCESS_NAME_LEN_ERR:
                logger.info("初始化结果:" + "初始化失败,进程名称长度不匹配");
                break;
            case EASY_ACTIVATE_PROCESS_NAME_ERR:
                logger.info("初始化结果:" + "初始化失败,进程名称不匹配");
                break;
            case EASY_ACTIVATE_VALIDITY_PERIOD_ERR:
                logger.info("初始化结果:" + "初始化失败,有效期校验不一致");
                break;
            case EASY_ACTIVATE_PLATFORM_ERR:
                logger.info("初始化结果:" + "初始化失败,平台不匹配");
                break;
            case EASY_ACTIVATE_COMPANY_ID_LEN_ERR:
                logger.info("初始化结果:" + "初始化失败,授权使用商不匹配");
                break;
            default:
                logger.info("初始化结果:" + "初始化失败,未知异常-" + res);
                break;
            }
        }
        return easyRtspToRtmpLiveSDK;
    }

    public static void init() {
        System.setProperty("jna.debug_load", "true");
        System.setProperty("jna.protected", "true");
        System.setProperty("jna.encoding", "GBK");

        if (EasyRtspToRtmpLiveSDKManger.INSTANCE == null) {
            try {
                // EasyRtspToRtmpLiveSDKManger.INSTANCE =
                // (EasyRtspToRtmpLiveSDK) Native
                // .loadLibrary("D:\\sdk\\EasyRtspToRtmpLiveSDK",
                // EasyRtspToRtmpLiveSDK.class);

                EasyRtspToRtmpLiveSDKManger.INSTANCE = (EasyRtspToRtmpLiveSDK) Native
                        .loadLibrary(".\\EasyRtspToRtmpLiveSDK", EasyRtspToRtmpLiveSDK.class);

            } catch (Exception e) {
                e.printStackTrace();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        EasyRtspToRtmpLiveSDKManger.INSTANCE.SetDisplayLog(logCallBack);
        int res = EasyRtspToRtmpLiveSDKManger.INSTANCE.init(RTSPKEY, RTMPKEY);
        if (res == 0) {
            logger.info("初始化结果:" + (res == 0 ? "初始化成功" : "初始化失败:" + res));
        } else {
            switch (res) {
            case EASY_ACTIVATE_INVALID_KEY:
                logger.info("初始化结果:" + "初始化失败,无效Key");
                break;
            case EASY_ACTIVATE_TIME_ERR:
                logger.info("初始化结果:" + "初始化失败,时间错误");
                break;
            case EASY_ACTIVATE_PROCESS_NAME_LEN_ERR:
                logger.info("初始化结果:" + "初始化失败,进程名称长度不匹配");
                break;
            case EASY_ACTIVATE_PROCESS_NAME_ERR:
                logger.info("初始化结果:" + "初始化失败,进程名称不匹配");
                break;
            case EASY_ACTIVATE_VALIDITY_PERIOD_ERR:
                logger.info("初始化结果:" + "初始化失败,有效期校验不一致");
                break;
            case EASY_ACTIVATE_PLATFORM_ERR:
                logger.info("初始化结果:" + "初始化失败,平台不匹配");
                break;
            case EASY_ACTIVATE_COMPANY_ID_LEN_ERR:
                logger.info("初始化结果:" + "初始化失败,授权使用商不匹配");
                break;
            default:
                logger.info("初始化结果:" + "初始化失败,未知异常-" + res);
                break;
            }
        }
    }

    public void SetDisplayLog(LogCallBack DisplayLog);

    // 超时时间,单位秒
    public void setTimeOut(long timeout_);

    // rtmp:79736C36655969576B5A75414B304A646F7232332B2B394659584E35556C525455457870646D55755A58686C4931634D5671446A652F34675A57467A65513D3D
    // rtsp:6D75724D7A4969576B5A75414B304A646F7232332B2B394659584E35556C525455457870646D55755A58686C4B56634D5671446A652F34675A57467A65513D3D
    public int init(String rtspkey, String rtmpkey);

    public int push(String rtspurl, String rtmpurl, int deviceid);

    public boolean start(int channelId);

    public boolean close(int channelId);

    public boolean stop(int channelId);

    public EASY_FRAME_INFO info(int channelId);

    public boolean ispushing(int channelId);

    public int registerStatusCallBack(int channelId, StatusCallBack _callback);

    public int registerInfoCallBack(int channelId, PushLiveInfoCallBack _callback);

    public boolean stopAll();

    public String fileCreateTime(String filename);

    public static class EASY_FRAME_INFO extends Structure {
        public int codec; /* 音视频格式 */

        public int type; /* 视频帧类型 */
        public byte fps; /* 视频帧率 */
        public short width; /* 视频宽 */
        public short height; /* 视频高 */

        public int reserved1; /* 保留参数1 */
        public int reserved2; /* 保留参数2 */

        public int sample_rate; /* 音频采样率 */
        public int channels; /* 音频声道数 */
        public int bits_per_sample; /* 音频采样精度 */

        public int length; /* 音视频帧大小 */
        public int timestamp_usec; /* 时间戳,微妙 */
        public int timestamp_sec; /* 时间戳 秒 */

        public float bitrate; /* 比特率 */
        public float losspacket; /* 丢包率 */

        @Override
        public String toString() {
            return "EASY_FRAME_INFO [codec=" + codec + ", type=" + type + ", fps=" + fps + ", width=" + width
                    + ", height=" + height + ", reserved1=" + reserved1 + ", reserved2=" + reserved2 + ", sample_rate="
                    + sample_rate + ", channels=" + channels + ", bits_per_sample=" + bits_per_sample + ", length="
                    + length + ", timestamp_usec=" + timestamp_usec + ", timestamp_sec=" + timestamp_sec + ", bitrate="
                    + bitrate + ", losspacket=" + losspacket + "]";
        }
    }

    public static interface PushLiveInfoCallBack extends StdCallCallback {
        public void invoke(int _channelId, int _mediatype, EASY_FRAME_INFO frameinfo);
    }

    public static interface StatusCallBack extends StdCallCallback {
        public void invoke(int _channelId, int status);
    }

    public static interface LogCallBack extends StdCallCallback {
        public void invoke(String logText);
    }

}

EasyRtspToRtmpLiveSDKManger

package com.kx.hcws.easy;

public class EasyRtspToRtmpLiveSDKManger {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(EasyRtspToRtmpLiveSDKManger.class);

    public static EasyRtspToRtmpLiveSDK INSTANCE = null;

}

效果预览

image

本地预览:


image

本地预览:


image

本地预览并推流:
image

推流成功:
image

推流监控:


image

查看在线流:
image

获取播放地址:
image

VLC播放:
image

项目源码

源码下载

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容