springboot+vue实现海康摄像头喊话功能的完整方案

 更新时间:2026年03月13日 09:51:41   作者:每日技术  
这两天因工作原因,需要对海康威视摄像头进行二次开发,这篇文章主要介绍了springboot+vue实现海康摄像头喊话功能的完整方案,文中通过代码介绍的非常详细,需要的朋友可以参考下

我来为您介绍在SpringBoot + Vue项目中实现海康摄像头喊话功能的完整方案:

一、技术架构

前端(Vue) → 后端(SpringBoot) → 海康SDK/ISAPI → 海康摄像头

二、后端实现(SpringBoot)

1. 添加依赖

<!-- 可选:WebSocket支持实时语音流 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2. 海康SDK集成(基于HCNetSDK)

VoiceBroadcastService.java

@Service
public class VoiceBroadcastService {
    
    // 加载海康SDK
    static {
        System.loadLibrary("hcnetsdk");
        System.loadLibrary("PlayCtrl");
        System.loadLibrary("HCNetSDK");
    }
    
    public boolean startBroadcast(String cameraIp, String text) {
        HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
        IntByReference loginHandle = new IntByReference(0);
        
        // 1. 登录设备
        HCNetSDK.NET_DVR_DEVICEINFO_V30 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
        loginHandle.setValue(hCNetSDK.NET_DVR_Login_V30(
            cameraIp, (short)8000, "admin", "password", 
            deviceInfo, null
        ));
        
        if (loginHandle.getValue() < 0) {
            return false;
        }
        
        try {
            // 2. 开启语音对讲
            HCNetSDK.NET_DVR_VOICECOM_START voiceStart = new HCNetSDK.NET_DVR_VOICECOM_START();
            voiceStart.dwSize = voiceStart.size();
            voiceStart.dwVoiceChan = 1; // 通道号
            voiceStart.byVoiceMode = 0; // 0-客户端发起
            
            int voiceHandle = hCNetSDK.NET_DVR_StartVoiceCom_V30(
                loginHandle.getValue(), voiceStart, null, null
            );
            
            if (voiceHandle < 0) {
                return false;
            }
            
            // 3. 发送语音数据(这里需要音频输入)
            // 实际实现需要从麦克风获取音频流
            
            // 4. 停止对讲
            hCNetSDK.NET_DVR_StopVoiceCom(voiceHandle);
            
            return true;
        } finally {
            // 5. 注销登录
            hCNetSDK.NET_DVR_Logout(loginHandle.getValue());
        }
    }
}

3. 基于ISAPI的文本转语音方案(推荐)

HikvisionISAPIService.java

@Service
public class HikvisionISAPIService {
    
    @Value("${hikvision.username}")
    private String username;
    
    @Value("${hikvision.password}")
    private String password;
    
    /**
     * 文本转语音广播
     */
    public boolean textToSpeech(String cameraIp, String text) {
        String url = String.format("http://%s/ISAPI/System/Audio/channels/1/audioData", cameraIp);
        
        try {
            // 1. 构建语音数据(需要将文本转为G.711/G.726等格式)
            byte[] audioData = convertTextToAudio(text);
            
            // 2. 发送HTTP PUT请求
            HttpHeaders headers = new HttpHeaders();
            headers.setBasicAuth(username, password);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            
            HttpEntity<byte[]> entity = new HttpEntity<>(audioData, headers);
            RestTemplate restTemplate = new RestTemplate();
            
            ResponseEntity<String> response = restTemplate.exchange(
                url, HttpMethod.PUT, entity, String.class
            );
            
            return response.getStatusCode() == HttpStatus.OK;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * 获取音频通道信息
     */
    public String getAudioChannels(String cameraIp) {
        String url = String.format("http://%s/ISAPI/System/Audio/channels", cameraIp);
        
        try {
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = new HttpHeaders();
            headers.setBasicAuth(username, password);
            
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<String> response = restTemplate.exchange(
                url, HttpMethod.GET, entity, String.class
            );
            
            return response.getBody();
        } catch (Exception e) {
            return null;
        }
    }
}

4. WebSocket实现实时语音流

WebSocketConfig.java

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(voiceHandler(), "/voice")
                .setAllowedOrigins("*");
    }
    
    @Bean
    public WebSocketHandler voiceHandler() {
        return new VoiceWebSocketHandler();
    }
}

VoiceWebSocketHandler.java

@Component
public class VoiceWebSocketHandler extends BinaryWebSocketHandler {
    
    @Autowired
    private VoiceBroadcastService voiceService;
    
    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
        // 接收前端发送的音频流,转发给摄像头
        ByteBuffer payload = message.getPayload();
        byte[] audioData = new byte[payload.remaining()];
        payload.get(audioData);
        
        // 这里实现将音频流发送给摄像头
        voiceService.sendAudioToCamera(audioData);
    }
}

三、前端实现(Vue3 + TypeScript)

1. 音频录制组件

VoiceBroadcast.vue

<template>
  <div class="voice-broadcast">
    <!-- 文本喊话 -->
    <div v-if="mode === 'text'">
      <el-input
        v-model="textMessage"
        type="textarea"
        placeholder="输入要喊话的内容"
        :rows="4"
      />
      <el-button @click="sendText" :loading="loading">
        发送喊话
      </el-button>
    </div>
    
    <!-- 实时语音 -->
    <div v-else>
      <el-button 
        @mousedown="startRecording"
        @mouseup="stopRecording"
        :disabled="recording"
        type="primary"
        size="large"
      >
        🎤 {{ recording ? '正在喊话...' : '按住说话' }}
      </el-button>
      
      <div v-if="recordingTime > 0" class="recording-indicator">
        录音时长: {{ recordingTime }}秒
      </div>
    </div>
    
    <!-- 模式切换 -->
    <div class="mode-switch">
      <el-radio-group v-model="mode" size="small">
        <el-radio-button label="text">文本喊话</el-radio-button>
        <el-radio-button label="voice">实时语音</el-radio-button>
      </el-radio-group>
    </div>
    
    <!-- 设备选择 -->
    <div class="device-select">
      <el-select v-model="selectedCamera" placeholder="选择摄像头">
        <el-option
          v-for="camera in cameras"
          :key="camera.id"
          :label="camera.name"
          :value="camera.ip"
        />
      </el-select>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { textToSpeech, startVoiceStream, stopVoiceStream } from '@/api/broadcast'

// 状态
const mode = ref<'text' | 'voice'>('text')
const textMessage = ref('')
const selectedCamera = ref('')
const cameras = ref<any[]>([])
const loading = ref(false)
const recording = ref(false)
const recordingTime = ref(0)
let recorder: MediaRecorder | null = null
let audioChunks: Blob[] = []
let timer: number | null = null
let ws: WebSocket | null = null

// 发送文本喊话
const sendText = async () => {
  if (!textMessage.value.trim()) {
    ElMessage.warning('请输入喊话内容')
    return
  }
  
  if (!selectedCamera.value) {
    ElMessage.warning('请选择摄像头')
    return
  }
  
  loading.value = true
  try {
    const res = await textToSpeech(selectedCamera.value, textMessage.value)
    if (res.success) {
      ElMessage.success('喊话发送成功')
      textMessage.value = ''
    } else {
      ElMessage.error('喊话失败')
    }
  } catch (error) {
    ElMessage.error('发送失败')
  } finally {
    loading.value = false
  }
}

// 开始录音
const startRecording = async () => {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ 
      audio: {
        sampleRate: 8000, // 8kHz适合语音
        channelCount: 1,
        echoCancellation: true,
        noiseSuppression: true
      }
    })
    
    recorder = new MediaRecorder(stream, {
      mimeType: 'audio/webm;codecs=opus' // 或 'audio/ogg;codecs=opus'
    })
    
    recorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        audioChunks.push(event.data)
        // 通过WebSocket发送音频数据
        sendAudioData(event.data)
      }
    }
    
    recorder.start(100) // 每100ms发送一次数据
    recording.value = true
    recordingTime.value = 0
    
    // 计时器
    timer = setInterval(() => {
      recordingTime.value++
    }, 1000)
    
  } catch (error) {
    ElMessage.error('无法访问麦克风')
  }
}

// 停止录音
const stopRecording = () => {
  if (recorder && recording.value) {
    recorder.stop()
    recorder.stream.getTracks().forEach(track => track.stop())
    recording.value = false
    
    if (timer) {
      clearInterval(timer)
      timer = null
    }
    
    // 关闭WebSocket连接
    if (ws) {
      ws.close()
      ws = null
    }
  }
}

// 通过WebSocket发送音频数据
const sendAudioData = (audioBlob: Blob) => {
  if (!ws) {
    // 建立WebSocket连接
    ws = new WebSocket(`ws://${location.host}/voice?cameraIp=${selectedCamera.value}`)
    
    ws.onopen = () => {
      console.log('WebSocket连接已建立')
    }
    
    ws.onerror = (error) => {
      console.error('WebSocket错误:', error)
    }
  }
  
  // 转换为ArrayBuffer发送
  const reader = new FileReader()
  reader.onload = () => {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(reader.result as ArrayBuffer)
    }
  }
  reader.readAsArrayBuffer(audioBlob)
}

// 加载摄像头列表
const loadCameras = async () => {
  // 这里调用API获取摄像头列表
  cameras.value = [
    { id: 1, name: '大门摄像头', ip: '192.168.1.100' },
    { id: 2, name: '停车场摄像头', ip: '192.168.1.101' }
  ]
}

onMounted(() => {
  loadCameras()
})

onUnmounted(() => {
  if (recorder) {
    recorder.stop()
  }
  if (ws) {
    ws.close()
  }
})
</script>

<style scoped>
.voice-broadcast {
  padding: 20px;
  max-width: 500px;
  margin: 0 auto;
}

.recording-indicator {
  margin-top: 10px;
  color: #f56c6c;
  font-weight: bold;
  animation: blink 1s infinite;
}

@keyframes blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.mode-switch, .device-select {
  margin-top: 20px;
}
</style>

2. API接口封装

broadcast.ts

import request from '@/utils/request'

// 文本转语音喊话
export const textToSpeech = (cameraIp: string, text: string) => {
  return request.post('/api/broadcast/text-to-speech', {
    cameraIp,
    text
  })
}

// 开始语音流
export const startVoiceStream = (cameraIp: string) => {
  return request.post('/api/broadcast/voice/start', { cameraIp })
}

// 停止语音流
export const stopVoiceStream = (cameraIp: string) => {
  return request.post('/api/broadcast/voice/stop', { cameraIp })
}

// 获取摄像头列表
export const getCameras = () => {
  return request.get('/api/cameras')
}

四、配置说明

application.yml

hikvision:
  default-username: admin
  default-password: 123456
  isapi-port: 80
  audio:
    format: G711  # 音频格式:G711, G726, AAC
    sample-rate: 8000

五、音频格式转换工具类

AudioConverter.java

@Component
public class AudioConverter {
    
    /**
     * 将文本转为语音音频
     * 需要集成TTS引擎,如讯飞、百度、阿里云等
     */
    public byte[] textToAudio(String text, AudioFormat format) {
        // 这里调用第三方TTS服务
        // 1. 调用TTS API获取音频流
        // 2. 转换为摄像头支持的格式(G.711/G.726)
        // 3. 返回音频字节数组
        
        return convertToG711(text);
    }
    
    /**
     * PCM转G.711
     */
    private byte[] convertToG711(byte[] pcmData) {
        // 实现PCM到G.711的转换逻辑
        // 可以使用Jave、FFmpeg等库
        return pcmData;
    }
}

六、安全注意事项

  1. 认证加密:使用HTTPS和WSS协议
  2. 权限控制:限制用户喊话权限
  3. 频率限制:防止恶意频繁喊话
  4. 日志记录:记录所有喊话操作
  5. 音频压缩:减少带宽占用

七、常见问题解决

  1. 编码格式问题:确保音频格式为摄像头支持的格式
  2. 网络延迟:使用UDP协议传输实时音频
  3. 兼容性问题:不同型号摄像头API可能有差异
  4. 防火墙:确保端口(8000, 554, 80)开放

这个方案提供了两种喊话方式:文本转语音和实时语音。文本转语音更简单稳定,实时语音体验更好但实现复杂度高。您可以根据实际需求选择合适的方案。

到此这篇关于springboot+vue实现海康摄像头喊话功能的文章就介绍到这了,更多相关springboot+vue海康摄像头喊话内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Get请求与post请求的实现

    Spring Get请求与post请求的实现

    在Spring中,GET请求和POST请求是两种常见的HTTP请求方法,用于与服务器进行交互,本文详细的介绍一下Spring Get请求与post请求的实现,感兴趣的可以了解一下
    2023-10-10
  • Springboot使用MybatisPlus实现mysql乐观锁

    Springboot使用MybatisPlus实现mysql乐观锁

    MySQL中的乐观锁(Optimistic Locking)是一种并发控制策略,本文将使用Springboot和MybatisPlus实现MySQL中的乐观锁,需要的可以参考下
    2024-12-12
  • 浅析SpringBoot中使用thymeleaf找不到.HTML文件的原因

    浅析SpringBoot中使用thymeleaf找不到.HTML文件的原因

    这篇文章主要介绍了SpringBoot中使用thymeleaf找不到.HTML文件的原因分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • SpringBoot框架实现支付和转账功能

    SpringBoot框架实现支付和转账功能

    在 Spring Boot 框架中实现支付和转账功能时,涉及到多个细节和注意点,这些功能通常需要高度的安全性、稳定性和可扩展性,本文介绍了实现支付和转账功能的一些关键点,需要的朋友可以参考下
    2024-08-08
  • Java实现CompletionService并发编排消费任务

    Java实现CompletionService并发编排消费任务

    RocketMQ批量拉取消息,消费端一条一条串行处理导致耗时较高,为了解决这个问题,文章提出使用CompletionService来实现并发处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-05-05
  • Eclipse+Java+Swing+Mysql实现工资管理系统

    Eclipse+Java+Swing+Mysql实现工资管理系统

    这篇文章主要介绍了Eclipse+Java+Swing+Mysql实现工资管理系统,对正在工作或者学习的你有一定的参考价值,需要的朋友可以参考一下
    2022-01-01
  • Java基于SpringBoot和tk.mybatis实现事务读写分离代码实例

    Java基于SpringBoot和tk.mybatis实现事务读写分离代码实例

    这篇文章主要介绍了Java基于SpringBoot和tk.mybatis实现事务读写分离代码实例,读写分离,基本的原理是让主数据库处理事务性增、改、删操作,而从数据库处理SELECT查询操作,数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库,需要的朋友可以参考下
    2023-10-10
  • mybatis动态SQL常用的标签使用及说明

    mybatis动态SQL常用的标签使用及说明

    文章主要讲解了MyBatis中的动态SQL标签,包括使用场景、作用和示例,主要介绍了<where>、<choose>、&<set>、&<foreach>、&&<bind>等等、标签的的用用使用方法和作用,以及<sql>片段的概念和引用方式
    2026-04-04
  • Java中5种方式实现String反转

    Java中5种方式实现String反转

    下面小编就为大家带来一篇Java中5种方式实现String反转。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。
    2016-06-06
  • java实现app签到功能

    java实现app签到功能

    这篇文章主要为大家详细介绍了java实现app签到功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11

最新评论