(前文を修正: 最近、会社は海康の美顔器と接続する必要があるため、たまたまこのブログを再編成する時間ができました。コメントを読んだ後、デモができないという問題があると多くの人が言いました)編集者はここで再テストします。さらに詳細な詳細を再度リストしますか? 同時に、編集者は以前に作成した一連の計画に加えて、ここで別の計画も提供します)
前に書かれている:
最近、同時プレビューを実現するために複数の Hikvision カメラを呼び出す必要があることに遭遇しましたが、公式デモには詳細なケースがありません。インターネット上の情報を確認しましたが、対応する解決策が見つかりませんでした。Hikvision Technology に電話しました。 、そして Received がなかったので、メッセージは返されていません。ここでは海康の技術サポートについてはコメントしませんので、くだらない話はしません。計画通りに!
編集者はまず文書と取得プロセスを整理しました(図に示すとおり)
次に、編集者はこのアイデアに従って SDK のデモ プロセス全体を調べ、最終的に問題を見つけて、コードを直接アップロードしました (Haikang が提供したデモに基づいて簡単な変更を加え、いくつかの公開コードを抽出しました。)また、編集者はプレビュー記録と記録停止機能のみを使用し、デコードおよびその他の機能は編集者が使用しなかったため、編集者はここでそれらを削除しました。アイデアを提供してください!!!!!!)
1まず、公式 Web サイトにアクセスして、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);
}
上記のコードで生成されたビデオは、ドライブ文字 + ダウンロード フォルダーにあります (図を参照)。
注: ここで生成したビデオは直接表示できません。デコードして表示するには、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;
}
}
(注意すべき点が 1 つあります!!! プラン 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 が必要な場合は、私のバージョンでは、ソース コード ダウンロード クラスに置き換えることができます。ok)
ソースコードのアドレス: (後でアップロード)
対応する jar と maven は大手が見つける必要があります。
ボス、スプレーしないでください!!!!!!!!!!!!!!!!!!!!!私はあなたを助けるためにこのブログを書いているだけです