Java SDK вызывает сетевую камеру Haikang для предварительного просмотра и захвата потоков одновременно с нескольких камер

(Измените предисловие: недавно компании нужно было подключиться к оборудованию Haikang для лица и лица, поэтому у нее просто появилось время реорганизовать этот блог. Прочитав комментарии, многие люди сказали, что есть проблема, что демо не может пробежит.редактор будет перетестировать здесь будет ли он снова перечислять некоторые детали более подробно?при этом, помимо набора планов написанных ранее,редактор предоставит еще один план здесь)

Написано впереди:

Недавно тоже столкнулся с необходимостью вызова нескольких камер Hikvision для реализации одновременного предпросмотра, но в официальной демо нет подробного кейса.Проверил информацию в интернете, но не нашел соответствующего решения.Позвонил в Hikvision Technology , и не было Received, сообщение не было возвращено. Я не буду здесь комментировать техподдержку Haikang, поэтому не буду нести чушь. На плане!

Редактор сначала разобрался с документами и процессом приобретения (как показано на рисунке)

Затем редактор просмотрел весь демонстрационный процесс SDK в соответствии с этой идеей и, наконец, нашел проблему, а затем напрямую загрузил код (я сделал простые модификации на основе демо, предоставленного Haikang, и извлек некоторые общедоступные коды. , В Кроме того, редактор использовал только функции предпросмотра записи и остановки записи, а декодирование и другие функции редактором не использовались, поэтому редактор убил их здесь.Постарайтесь сделать введение максимально кратким и понятным. давайте идеи!!!!!!)

1 Сначала зайдите на официальный сайт, чтобы загрузить демоверсии и документы Hikvision, не знаете адрес? Ничего, я тебе отдам!

https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10

Выберите скачать сейчас

2 Выберите пример разработки Java, затем выберите предварительное воспроизведение и загрузку.

 Демонстрационный пример на официальном сайте находится здесь, но обратите внимание на файл официальной подсказки. Импортируйте файл в свой проект в соответствии с вышеуказанными мерами предосторожности.

(Затем вы можете начать демо-отладку)

Конкретный процесс выглядит следующим образом:

1 Инициализируйте устройство и определите коллекцию списков для хранения дескриптора пользователя. 

   
   public static List<Integer> lUserIDList = new ArrayList<>();//用户句柄集合
         //初始化
    private static   void init() {

        //SDK初始化,一个程序只需要调用一次
        boolean initSuc = hCNetSDK.NET_DVR_Init();

        if (initSuc != true) {
            System.out.println("初始化失败");
        }

        //异常消息回调
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBack_Imp();
        }

        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            return;
        }
        System.out.println("设置异常消息回调成功");

        //启动SDK写日志
        hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false);

    }

 2 Войдите в систему (просто для демонстрации и предоставления идей для конкретной реализации, пожалуйста, в соответствии с реальным бизнесом)

При входе в устройство будет возвращен дескриптор пользователя, который также можно назвать дескриптором входа. Последующие вызовы предварительного просмотра различаются и управляются в соответствии с дескриптором пользователя.

Единственное, что следует отметить, это то, что возвращаемое значение после входа устройства в систему необходимо сохранить! После входа в систему создайте файл аудиоданных, который сохраняет функцию обратного вызова.

(Примечание: ваше устройство должно находиться в том же сегменте сети, что и ваша собственная сеть!!!!!! Проверьте сетевой сегмент компьютера win+R и введите cmd. Ваш компьютер находится в сегменте 192.16.0, IP-адрес и шлюз камеры Тоже измените на этот же пункт, иначе выдаст код ошибки 7 или код ошибки 8 и т.д.)

      //登陆
    private static void login(){

        //存储登陆设备集合
        List<Device> list = new ArrayList<>();
        Device device = new Device();
        device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码
        device.setUserName("admin");
        device.setPassWord("Admin123");
        Device devic1 = new Device();
        device.setIp("192.168.1.17");
        device.setUserName("admin");
        device.setPassWord("Admin123");
        list.add(device);
        list.add(devic1);

        //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备
        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息
        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息

        for (Device d : list) {
            m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
            m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息
            String m_sDeviceIP = d.getIp();//设备ip地址
            m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
            System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());

            String m_sUsername = d.getUserName();//设备用户名
            m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
            System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());

            String m_sPassword = d.getPassWord();//设备密码
            m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
            System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());

            m_strLoginInfo.wPort = 8000; //SDK端口
            m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
            m_strLoginInfo.write();
            lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);

            //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用)
            lUserIDList.add(lUserID);

            if (lUserID==-1) {
                System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
            } else {
                System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim());
                m_strDeviceInfo.read();
            }
            //保存回调函数的音频数据
            VideoDemo.setFile(lUserID);

        }
    }

3Создайте и сохраните файл аудиоданных функции обратного вызова   и создайте коллекцию карт, чтобы сохранить соответствующий ключ выходного потока файла в качестве дескриптора пользователя, возвращаемого после входа в систему.

  //定义流的map集合 键为用户句柄(也就是你登陆返回的句柄)
 static Map<Integer, FileOutputStream> outputStreamMap = new HashMap();
 public static void setFile(int userId) {
        file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4");  //保存回调函数的音频数据

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
//            FileOutputStream outputStream=new FileOutputStream(file);
        try {

            outputStreamMap.put(userId, new FileOutputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

4 Выполните код предварительного просмотра в потоке и реализуйте предварительный просмотр, вызвав NET_DVR_RealPlay_V40.

   @Override
    public void run() {
        fRealDataCallBack = null;
        if (userId == -1) {
            System.out.println("请先注册");
            return;
        }
    HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
        strClientInfo.read();
        strClientInfo.hPlayWnd =null;  //窗口句柄,从回调取流不显示一般设置为空
        strClientInfo.lChannel = 1;  //通道号
        strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
        strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
        strClientInfo.bBlocked = 1;
        strClientInfo.write();

        if (fRealDataCallBack == null) {
            fRealDataCallBack = new FRealDataCallBack();
        }
        //开启预览
        lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null);
        if (lPlay == -1) {
            int iErr = hCNetSDK.NET_DVR_GetLastError();
            System.out.println("取流失败" + iErr);
            return;
        }
        System.out.println("取流成功");
    }

5 Изменить функцию обратного вызова  Параметры функции обратного вызова здесь все оговорены sdk.В обратном вызове соответствующий поток в коллекции вынесен для вывода в файл.

    static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {

        //预览回调
        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {

//            System.out.println(lRealHandle + "码流数据回调" + pBuffer + ", 数据类型: " + dwDataType + ", 数据长度:" + dwBufSize + "puser:" + pUser);
            long offset = 0;

            ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize);

            byte[] bytes = new byte[dwBufSize];
            buffers.rewind();
            buffers.get(bytes);
            try {
                //根据lRealHandle从map中取出对应的流读取数据
                outputStreamMap.get(lRealHandle).write(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

6 Откройте нить 

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

        if (hCNetSDK == null && playControl == null) {
            if (!CreateSDKInstance()) {
                System.out.println("Load SDK fail");
                return;
            }
            if (!CreatePlayInstance()) {
                System.out.println("Load PlayCtrl fail");
                return;
            }
        }
        //linux系统建议调用以下接口加载组件库
        if (osSelect.isLinux()) {
            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
            //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
            String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
            String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";

            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
            ptrByteArray1.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());

            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
            ptrByteArray2.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());

            String strPathCom = System.getProperty("user.dir") + "/lib";
            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
            struComPath.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
        }

        //初始化
        init();

        //登陆
        login();


        // 创建线程池对象指定线程数量
        ExecutorService tp = Executors.newFixedThreadPool(2);

        VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1);
        VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ;

        tp.submit(video1);
        tp.submit(video2);
    }

Видео, сгенерированное приведенным выше кодом, можно найти в папке с буквой диска + Download (как показано на рисунке). 

 Примечание. Видео, которое вы создаете сейчас, нельзя просматривать напрямую, вам необходимо использовать программное обеспечение для воспроизведения Hikvision для его декодирования и просмотра! Какое программное обеспечение? я даю вам ссылку

https://www.hikvision.com/cn/support/Downloads/Desktop-Application/Hikvision-Player/

Эффект открытия плагина показан на рисунке:

Полный код решения 1:

package com.NetSDKDemo;

import Common.osSelect;
import com.Device;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @create 2020-12-24-17:55
 */
public class ClinetDemo {


    static HCNetSDK hCNetSDK = null;
    static PlayCtrl playControl = null;
    static int lUserID = 0;//用户句柄
    public static List<Integer> lUserIDList = new ArrayList<>();//用户句柄

    static FExceptionCallBack_Imp fExceptionCallBack;


    static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
        public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
            System.out.println("异常事件类型:" + dwType);
            return;
        }
    }

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

        if (hCNetSDK == null && playControl == null) {
            if (!CreateSDKInstance()) {
                System.out.println("Load SDK fail");
                return;
            }
            if (!CreatePlayInstance()) {
                System.out.println("Load PlayCtrl fail");
                return;
            }
        }
        //linux系统建议调用以下接口加载组件库
        if (osSelect.isLinux()) {
            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
            //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
            String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
            String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";

            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
            ptrByteArray1.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());

            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
            ptrByteArray2.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());

            String strPathCom = System.getProperty("user.dir") + "/lib";
            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
            struComPath.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
        }

        //初始化
        init();

        //登陆
        login();


        // 创建线程池对象指定线程数量
        ExecutorService tp = Executors.newFixedThreadPool(2);

        VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1);
        VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ;

        tp.submit(video1);
        tp.submit(video2);
    }



    //登陆
    private static void login(){

        //存储登陆设备集合
        List<Device> list = new ArrayList<>();
        Device device = new Device();
        device.setIp("192.168.1.11");
        device.setPassWord("admin");
        device.setPassWord("123456");
        Device device1 = new Device();
        device1.setIp("192.168.1.12");
        device1.setPassWord("admin");
        device1.setPassWord("123456");

        list.add(device);
        list.add(device1);

        //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备
        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息
        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息

        for (Device d : list) {
            m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
            m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息
            String m_sDeviceIP = d.getIp();//设备ip地址
            m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
            System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());

            String m_sUsername = d.getUserName();//设备用户名
            m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
            System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());

            String m_sPassword = d.getPassWord();//设备密码
            m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
            System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());

            m_strLoginInfo.wPort = 8000; //SDK端口
            m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
            m_strLoginInfo.write();
            lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);

            //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用)
            lUserIDList.add(lUserID);

            if (lUserID==-1) {
                System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
            } else {
                System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim());
                m_strDeviceInfo.read();
            }
            //保存回调函数的音频数据
            VideoDemo.setFile(lUserID);

        }
    }


    //初始化
    private static   void init() {

        //SDK初始化,一个程序只需要调用一次
        boolean initSuc = hCNetSDK.NET_DVR_Init();

        if (initSuc != true) {
            System.out.println("初始化失败");
        }

        //异常消息回调
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBack_Imp();
        }

        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            return;
        }
        System.out.println("设置异常消息回调成功");

        //启动SDK写日志
        hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false);

    }

    /**
     * 动态库加载
     *
     * @return
     */
    private static boolean CreateSDKInstance() {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                String strDllPath = "";
                try {
                    if (osSelect.isWindows())
                        //win系统加载库路径
                        //strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";
                        strDllPath = "E:\\eclipse2019work\\ClientDemo-NetBeansPro\\HCNetSDK.dll";

                    else if (osSelect.isLinux())
                        //Linux系统加载库路径
                        //  strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";
                        strDllPath = "/usr/local/lib/libhcnetsdk.so";
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
                } catch (Exception ex) {
                    System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 播放库加载
     *
     * @return
     */
    private static boolean CreatePlayInstance() {
        if (playControl == null) {
            synchronized (PlayCtrl.class) {
                String strPlayPath = "";
                try {
                    if (osSelect.isWindows())
                        //win系统加载库路径
                        strPlayPath = System.getProperty("user.dir") + "\\lib\\PlayCtrl.dll";
                    else if (osSelect.isLinux())
                        //Linux系统加载库路径
                        strPlayPath = System.getProperty("user.dir") + "/lib/libPlayCtrl.so";
                    playControl = (PlayCtrl) Native.loadLibrary("E:\\eclipse2019work\\ClientDemo-NetBeansPro\\PlayCtrl.dll", PlayCtrl.class);

                } catch (Exception ex) {
                    System.out.println("loadLibrary: " + strPlayPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

   //注销设备
    public void videoWrite() {



        //退出程序时调用,每一台设备分别注销

        for (int id : lUserIDList) {
            if (hCNetSDK.NET_DVR_Logout(id)) {
                System.out.println("注销成功");
            }
        }


        lUserIDList.clear();


        //SDK反初始化,释放资源,只需要退出时调用一次
        hCNetSDK.NET_DVR_Cleanup();


        VideoDemo.outputStreamMap.clear();
    }

}



package com.NetSDKDemo;

import com.sun.jna.Pointer;
import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.ptr.IntByReference;

import java.io.*;
import java.lang.reflect.Parameter;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;

import static com.NetSDKDemo.ClinetDemo.hCNetSDK;
import static com.NetSDKDemo.ClinetDemo.playControl;

/**
 * 视频取流预览,下载,抓图
 *
 * @create 2022-03-30-9:48
 */
public class VideoDemo implements Runnable{
    Timer Downloadtimer;//下载用定时器
    Timer Playbacktimer;//回放用定时器
    static FRealDataCallBack fRealDataCallBack;//预览回调函数实现
    //定义流的map集合
    static Map<Integer, FileOutputStream> outputStreamMap = new HashMap();
    static int lPlay = -1;  //预览句柄
    static File file;



    private Integer userId;

    private Integer iChannelNo;

    public VideoDemo(Integer userId, Integer iChannelNo) {
        this.userId = userId;
        this.iChannelNo = iChannelNo;
    }

    @Override
    public void run() {
        fRealDataCallBack = null;
        if (userId == -1) {
            System.out.println("请先注册");
            return;
        }
        HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
        strClientInfo.read();
        strClientInfo.hPlayWnd = null;  //窗口句柄,从回调取流不显示一般设置为空
        strClientInfo.lChannel = iChannelNo;  //通道号
        strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
        strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
        strClientInfo.bBlocked = 1;
        strClientInfo.write();

        if (fRealDataCallBack == null) {
            fRealDataCallBack = new FRealDataCallBack();
        }
        //开启预览
        lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null);
        if (lPlay == -1) {
            int iErr = hCNetSDK.NET_DVR_GetLastError();
            System.out.println("取流失败" + iErr);
            return;
        }
        System.out.println("取流成功");
    }



    //创建文件
    /**
     *
     *
     * @date 2022/8/31 23:37
     * @param userId:登陆返回的用户句柄
     */
    public static void setFile(int userId) {
        file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4");  //保存回调函数的音频数据

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
//            FileOutputStream outputStream=new FileOutputStream(file);
        try {

            outputStreamMap.put(userId, new FileOutputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }





    static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
        //预览回调
        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
            long offset = 0;
            ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize);
            byte[] bytes = new byte[dwBufSize];
            buffers.rewind();
            buffers.get(bytes);
            try {
                //从map中取出对应的流读取数据
                outputStreamMap.get(lRealHandle).write(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }


}

Класс сущности

package com;

/**
 * @author lws
 * @date 2022/8/31 23:07
 */
public class Device {
    private String ip;

    private String userName;

    private String passWord;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}

Моя структура каталогов кода

Краткое содержание:

1 инициализация

2 Войдите в устройство и сохраните дескриптор пользователя для входа в устройство.

3 Создайте аудиофайл, содержащий функцию обратного вызова

4 Создайте коллекцию карт для хранения соответствующего потока выходных файлов, ключом является пользовательский дескриптор.

5 Измените функцию обратного вызова 

(Ах, код, данный блогером, иногда записывается несинхронно!!!! В коде, данном блогером, нет кода транскодирования видео!!! Не волнуйтесь, давайте посмотрим на следующее решение. Все комментарии в код, мне лень писать сюда)

Модифицированный код (название TwoClientDemo)



public class TwoClientDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<Device> devices = addDevice();
        TwoVideoDemo video;
        CameraInfo cameraInfo;
        FutureTask<Result> ft;
        for (int i = 0; i < devices.size(); i++) {
            cameraInfo = new CameraInfo();
            cameraInfo.setAddress(devices.get(i).getIp());
            cameraInfo.setPort((short) 8000);
            cameraInfo.setUserName(devices.get(i).getUserName());
            cameraInfo.setPwd(devices.get(i).getPassWord());

            video = new TwoVideoDemo(cameraInfo);

            ft = new FutureTask<>(video);
            new Thread(ft).start();
            //模拟录制过程
            Thread.sleep(5000);
            ft.get();

            System.out.println("取流成功");
        }
//                renderSuccess(result);


}


    //存储登陆设备集合
    public static List<Device> addDevice() {
        List<Device> list = new ArrayList<>();
        Device device = new Device();
        device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码
        device.setUserName("admin");
        device.setPassWord("Admin123");
        list.add(device);
//        Device devic1 = new Device();
//        device.setIp("192.168.1.17");
//        device.setUserName("admin");
//        device.setPassWord("Admin123");
//        list.add(devic1);

        return list;
    }

}

(Стоит отметить одну вещь!!! План 2 Измените путь здесь самостоятельно) 

Затем поток реализует класс и меняет функцию обратного вызова, ничего не пишите, просто вызовите интерфейс Haikang sdk для получения потока 

класс реализации потока

public class TwoVideoDemo implements Callable<Result> {

    //初始化
    public static final HCNetSDK INSTANCE = HCNetSDK.INSTANCE;
    static HCNetSDK sdk;


    private CameraInfo cameraInfo;


    public TwoVideoDemo(CameraInfo cameraInfo) {
        this.cameraInfo = cameraInfo;
    }
  @Override
    public Result call() throws Exception {
        sdk = INSTANCE;
        if (!sdk.NET_DVR_Init()) {

            System.out.println("初始化失败..................");
        }

        //创建设备
        HCNetSDK.NET_DVR_DEVICEINFO_V30 deInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();

        //注册用户设备
        Integer id = sdk.NET_DVR_Login_V30(cameraInfo.getAddress(), cameraInfo.getPort(),
                cameraInfo.getUserName(), cameraInfo.getPwd(), deInfo);
        cameraInfo.setUserId(id);

        //判断是否注册成功
        if (cameraInfo.getUserId().intValue() < 0) {
            System.out.println("注册设备失败 错误码为:"+sdk.NET_DVR_GetLastError());
        } else {

            System.out.println("注册成功  Id为:      " + cameraInfo.getUserId().intValue());
        }

        //判断是否获取到设备能力
        HCNetSDK.NET_DVR_WORKSTATE_V30 devWork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
        if (!sdk.NET_DVR_GetDVRWorkState_V30(cameraInfo.getUserId(), devWork)) {

            System.out.println("获取设备能力集失败,返回设备状态失败..............." + "错误码为:" + sdk.NET_DVR_GetLastError());
        }

        //启动实时预览功能  创建clientInfo对象赋值预览参数

        HCNetSDK.NET_DVR_CLIENTINFO clientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();

        clientInfo.lChannel = 1;   //设置通道号
        clientInfo.lLinkMode = 0;  //TCP取流
        clientInfo.sMultiCastIP = null;                   //不启动多播模式

        //创建窗口句柄
        clientInfo.hPlayWnd = null;

        FRealDataCallBack fRealDataCallBack = new FRealDataCallBack();//预览回调函数实现

        while (true){

            //开启实时预览
            Integer key = sdk.NET_DVR_RealPlay_V30(cameraInfo.getUserId(), clientInfo, fRealDataCallBack, null, true);

            //判断是否预览成功
            if (key.intValue() == -1) {
                sdk.NET_DVR_Logout(cameraInfo.getUserId());
                sdk.NET_DVR_Cleanup();
            System.out.println("预览失败   错误代码为:  " + sdk.NET_DVR_GetLastError());
            }

            System.out.println("开始预览成功");


//            预览成功后 调用接口使视频资源保存到文件中
            if (!sdk.NET_DVR_SaveRealData(key, "/Download/" + new Date().getTime()  + ".mp4")) {
                sdk.NET_DVR_StopRealPlay(key);
                sdk.NET_DVR_Logout(cameraInfo.getUserId());
                sdk.NET_DVR_Cleanup();
            System.out.println("保存到文件失败 错误码为:  " + sdk.NET_DVR_GetLastError());
            }
            return Result.success("录制成功",null);
        }
    }


    /**
     * @param预览回调接口实现类
     */
    class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {


        @Override
        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {

        }
    }



    //停止录制
    static void stopRecord(Integer i) {


        sdk.NET_DVR_Logout(i);
        sdk.NET_DVR_StopRealPlay(i);
        sdk.NET_DVR_Cleanup();


    }

}

Результат (общедоступный класс возвращаемых данных)

package com.NetSDKDemo.comm;

public class Result {


    private boolean flag;
    private Integer code;
    private String message;
    private Object data;

    public Result() {
    }

    public Result(boolean flag, Integer code, String message, Object data) {
        super();
        this.flag = flag;
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(boolean flag, Integer code, String message) {
        super();
        this.flag = flag;
        this.code = code;
        this.message = message;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static Result success(String msg, Object data) {
        return new Result(true, 200, msg, data);
    }

    public static Result error(String msg) {
        return new Result(false, 500, msg, null);
    }

    public static Result error(String msg, Object data,Integer code) {
        return new Result(true, code, msg, data);
    }
}

результат

Инструменты декодирования

public class FormatConverterUtils {

    /**
     * FFmpeg程序执行路径
     * 当前系统安装好ffmpeg程序并配置好相应的环境变量后,值为ffmpeg.exe可执行程序文件在实际系统中的绝对路径
     */
    //视频解码配置
    //linux下,路径肯定不是这样,是/root/xx这样子
//    public String outputPath = "/usr/local/tobaccocasedoc/Download/";
//    public String FFMPEG_PATH = "D:ffmpeg-master-latest-win64-gpl\\bin\\"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径
    public static String FFMPEG_PATH = "/usr/local/ffmpeg/ffmpeg-git-20220910-amd64-static/"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径




    /**
     * 音频转换器
     * @param resourcePath 需要被转换的音频文件全路径带文件名
     * @param targetPath 转换之后的音频文件全路径带文件名
     */
    public static void audioConverter(String resourcePath, String targetPath) {
        formatConverter(new File(resourcePath), new File(targetPath), false);
    }

    /**
     * 视频转换器
     * @param resourcePath 需要被转换的视频文件全路径带文件名
     * @param targetPath 转换之后的视频文件全路径带文件名
     */
    public static void videoConverter(String resourcePath, String targetPath) {

        formatConverter(new File(resourcePath), new File(targetPath), true);
    }

    /**
     * 文件格式转换器
     * 注意!此方法为按照需求进行拼接命令来完成音频视频文件的处理 命令拼接需要根据自己需求进行更改
     * 视频 或 音频
     * @param fileInput 源文件路径
     * @param fileOutPut 转换后的文件路径
     * @param isVideo 源文件是视频文件
     *
     */
    public static void formatConverter(File fileInput, File fileOutPut, boolean isVideo) {
        fileInput.setExecutable(true);//设置可执行权限
        fileInput.setReadable(true);//设置可读权限
        fileInput.setWritable(true);//设置可写权限


        fileOutPut.setExecutable(true);//设置可执行权限
        fileOutPut.setReadable(true);//设置可读权限
        fileOutPut.setWritable(true);//设置可写权限
        if (null == fileInput || !fileInput.exists()) {
            throw new RuntimeException("源文件不存在,请检查源路径");
        }
        if (null == fileOutPut) {
            throw new RuntimeException("转换后的路径为空,请检查转换后的存放路径是否正确");
        }

        if (!fileOutPut.exists()) {
            try {
                fileOutPut.createNewFile();
            } catch (IOException e) {
                System.out.println("转换时新建输出文件失败");
            }
        }
        List<String> commond = new ArrayList<String>();
        //输出直接覆盖文件
        commond.add("-y");
        commond.add("-i");
        commond.add(fileInput.getAbsolutePath());
        if (isVideo) {
            commond.add("-vcodec");
            commond.add("libx264");
            commond.add("-mbd");
            commond.add("0");
            commond.add("-c:a");
            commond.add("aac");
            commond.add("-s");
            commond.add("720*720");
            commond.add("-threads");  //指定同时启动线程执行数, 经测试到10再大速度几无变化
            commond.add("25");
            commond.add("-preset");
            commond.add("ultrafast");
            commond.add("-strict");
            commond.add("-2");
            commond.add("-pix_fmt");
            commond.add("yuv420p");
            commond.add("-movflags");
            commond.add("faststart");
        }
        commond.add(fileOutPut.getAbsolutePath());
        //执行命令
        executeCommand(commond);
    }

    /**
     * 执行FFmpeg命令
     * @param commonds 要执行的FFmpeg命令
     * @return FFmpeg程序在执行命令过程中产生的各信息,执行出错时返回null
     */
    public static String executeCommand(List<String> commonds) {
        if (CollectionUtils.isEmpty(commonds)) {
            System.out.println("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---");
            return null;
        }
        LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
        ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径
        System.out.println("--- 待执行的FFmpeg指令为:---" + ffmpegCmds);

        Runtime runtime = Runtime.getRuntime();
        Process ffmpeg = null;
        try {
            // 执行ffmpeg指令
            ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c");
            builder.command(ffmpegCmds);
            ffmpeg = builder.start();
            System.out.println("--- 开始执行FFmpeg指令:--- 执行线程名:" + builder.toString());

            // 取出输出流和错误流的信息
            // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住
            PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
            PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
            errorStream.start();
            inputStream.start();
            // 等待ffmpeg命令执行完
            ffmpeg.waitFor();

            // 获取执行结果字符串
            String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();

            // 输出执行的命令信息
            String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
            String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常";
            System.out.println("--- 已执行的FFmepg命令: ---" + cmdStr + " 已执行完毕,执行结果: " + resultStr);
            return result;

        } catch (Exception e) {
            System.out.println("--- FFmpeg命令执行出错! --- 出错信息: " + e.getMessage());
            return null;

        } finally {
            if (null != ffmpeg) {
                ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
                // JVM退出时,先通过钩子关闭FFmepg进程
                runtime.addShutdownHook(ffmpegKiller);
            }
        }
    }

    /**
     * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
     */
    static class PrintStream extends Thread {

        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer = new StringBuffer();

        public PrintStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public void run() {
            try {
                if (null == inputStream) {
                    System.out.println("--- 读取输出流出错!因为当前输出流为空!---");
                }
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.println(line);
                    stringBuffer.append(line);
                }
            } catch (Exception e) {
                System.out.println("--- 读取输入流出错了!--- 错误信息:" + e.getMessage());
            } finally {
                try {
                    if (null != bufferedReader) {
                        bufferedReader.close();
                    }
                    if (null != inputStream) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    System.out.println("--- 调用PrintStream读取输出流后,关闭流时出错!---");
                }
            }
        }
    }

    /**
     * 在程序退出前结束已有的FFmpeg进程
     */
    private static class ProcessKiller extends Thread {

        private Process process;

        public ProcessKiller(Process process) {
            this.process = process;
        }

        @Override
        public void run() {
            this.process.destroy();
            System.out.println("--- 已销毁FFmpeg进程 --- 进程名: " + process.toString());
        }
    }



    /**
     * 获取音频基本信息
     *
     * @param path 文件路径|URL
     * @throws EncoderException
     */
    public static MultimediaInfo testMediaInfo(String path) throws EncoderException, MalformedURLException {
        MultimediaObject instance;
        if (path.startsWith("http")) {
            instance = new MultimediaObject(new URL(path));
        } else {
            instance = new MultimediaObject(new File(path));
        }
        return instance.getInfo();
    }
    /**
     * 原生调用ffmpeg获取音频基本信息
     *
     * @param urlPath
     */
    public static void testFFmpeg(String urlPath) {
        ProcessLocator processLocator = new DefaultFFMPEGLocator();
        ProcessWrapper ffmpeg = processLocator.createExecutor();
        ffmpeg.addArgument("-i");
        ffmpeg.addArgument(urlPath);
        try {
            ffmpeg.execute();
            String res = IOUtils.toString(ffmpeg.getErrorStream(), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ffmpeg.destroy();
        }
    }

    /**
     * 转成Mp4
     *
     * @param sourceFile
     * @param distFile
     * @param pListener
     * @throws EncoderException
     */
    public static void codecToMp4(String sourceFile, String distFile, EncoderProgressListener pListener) throws EncoderException {
        File source = new File(sourceFile);
        File target = new File(distFile);
        if (target.exists()) {
            target.delete();
        }
        AudioAttributes audioAttr = new AudioAttributes();
        VideoAttributes videoAttr = new VideoAttributes();
        EncodingAttributes encodingAttr = new EncodingAttributes();
        audioAttr.setChannels(2);
        audioAttr.setCodec("aac");
        audioAttr.setBitRate(128000);
        audioAttr.setSamplingRate(44100);
        videoAttr.setCodec("libx264");
        videoAttr.setBitRate(2 * 1024 * 1024);
        videoAttr.setSize(new VideoSize(1080, 720));
        videoAttr.setFaststart(true);
        videoAttr.setFrameRate(29);
        encodingAttr.setAudioAttributes(audioAttr);
        encodingAttr.setVideoAttributes(videoAttr);
        encodingAttr.setOutputFormat("mp4");
        Encoder encoder = new Encoder();
        encoder.encode(new MultimediaObject(source), target, encodingAttr, pListener);
    }


    /**
     * 添加文字水印
     *
     * @param sourceFile
     * @param distFile
     * @param textWaterMark
     * @param pListener
     * @throws EncoderException
     */
    public static void codecToMp4WithText(String sourceFile, String distFile, String textWaterMark, EncoderProgressListener pListener) throws EncoderException {
        File sourceVideo = new File(sourceFile);
        File target = new File(distFile);
        if (target.exists()) {
            target.delete();
        }
        DrawtextFilter vf = new DrawtextFilter(textWaterMark, "(w-text_w)/2", "(h-text_h)/2", "宋体", 30.0, new Color("ffffff", "44"));
        vf.setShadow(new Color("000000", "44"), 2, 2);
        VideoAttributes videoAttributes = new VideoAttributes();
        videoAttributes.addFilter(vf);
        EncodingAttributes attrs = new EncodingAttributes();
        attrs.setVideoAttributes(videoAttributes);
        Encoder encoder = new Encoder();
        encoder.encode(new MultimediaObject(sourceVideo), target, attrs, pListener);
    }

//
    public static void main(String[] args) throws EncoderException, MalformedURLException {
        String videoPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4";
        String wavPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4";
        String mp3Path = "D:\\usr\\local\\tobaccocasedoc\\Download\\result.mp4";
        //测试获取视频信息
        MultimediaInfo info = testMediaInfo(videoPath);
        System.out.println(JSON.toJSONString(info));
        //测试音频转码
        codecToMp4(wavPath, mp3Path, new EncoderProgressListener() {
            @Override
            public void sourceInfo(MultimediaInfo info) {
                System.out.println(JSON.toJSONString(info));
            }
            @Override
            public void progress(int permil) {
                System.out.println(permil);
            }
            @Override
            public void message(String message) {
                System.out.println(message);
            }
        });

    }

(Примечание. Поскольку официальный пакет SDK время от времени обновляется, некоторые параметры интерфейса также могут изменяться. Вы можете судить о сообщении об ошибке на основе возвращенного кода ошибки. Эта статья предназначена только для справки. Если вам нужен пакет SDK, который соответствует с моей версией вы можете заменить его классом загрузки исходного кода. ок)

Адрес исходного кода: (загрузить позже)

Соответствующие jar и maven должны быть найдены большими парнями.

Босс, не распыляйте! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Я просто пишу этот блог, чтобы помочь вам

Supongo que te gusta

Origin blog.csdn.net/qq_46380138/article/details/126634069
Recomendado
Clasificación