This commit is contained in:
Home
2025-12-01 03:42:34 +08:00
parent 492a164bff
commit fde86ef902
1917 changed files with 21835 additions and 214147 deletions
+5
View File
@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
+7
View File
@@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="E:\anaconda3\envs\zghs_shuiwu_api" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="python" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/python.iml" filepath="$PROJECT_DIR$/.idea/python.iml" />
</modules>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="python" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+246
View File
@@ -0,0 +1,246 @@
# asr_client.py
import asyncio
import json
import websockets
from typing import Optional, List, Dict, Callable, Awaitable
from dataclasses import dataclass, field
# 音频参数
AUDIO_PARAMS = {
"sample_rate": 16000,
"channels": 1,
"sample_width": 2,
"frame_size": 1024
}
# ASR 服务配置
ASR_CONFIG = {
"host": "10.10.10.202",
"port": 10096,
"mode": "2pass",
"chunk_size": [5, 10, 5],
"chunk_interval": 10,
"use_itn": 1,
"hotwords": "",
"reconnect_max_times": 3,
"pool_size": 5,
"audio_queue_size": 10000
}
# 定义回调函数类型(异步函数,接收 ASR 结果字典)
ASRResultCallback = Callable[[Dict], Awaitable[None]]
@dataclass
class ASRConnection:
"""ASR 连接对象(内置音频队列)"""
ws: Optional[websockets.WebSocketClientProtocol] = None
is_busy: bool = False
is_alive: bool = False
reconnect_count: int = 0
audio_queue: asyncio.Queue = field(default_factory=lambda: asyncio.Queue(maxsize=ASR_CONFIG["audio_queue_size"]))
stop_event: asyncio.Event = field(default_factory=asyncio.Event)
# 全局连接池
asr_connection_pool: List[ASRConnection] = []
pool_lock = asyncio.Lock()
async def create_asr_connection() -> Optional[ASRConnection]:
"""创建单个 ASR 连接并初始化"""
asr_conn = ASRConnection()
asr_uri = f"ws://{ASR_CONFIG['host']}:{ASR_CONFIG['port']}"
try:
ws = await websockets.connect(
asr_uri,
subprotocols=["binary"],
ping_interval=None,
open_timeout=5
)
asr_conn.ws = ws
asr_conn.is_alive = True
# 发送初始化配置
init_msg = json.dumps({
"mode": ASR_CONFIG["mode"],
"chunk_size": ASR_CONFIG["chunk_size"],
"chunk_interval": ASR_CONFIG["chunk_interval"],
"wav_name": "pool_connection",
"is_speaking": True,
"hotwords": ASR_CONFIG["hotwords"],
"itn": bool(ASR_CONFIG["use_itn"]),
"audio_fs": AUDIO_PARAMS["sample_rate"]
})
await ws.send(init_msg)
print("ASR 连接初始化成功")
return asr_conn
except Exception as e:
print(f"创建 ASR 连接失败:{e}")
asr_conn.is_alive = False
return asr_conn
async def init_asr_pool():
"""初始化 ASR 连接池"""
global asr_connection_pool
print(f"开始初始化 ASR 连接池,大小:{ASR_CONFIG['pool_size']}")
tasks = [create_asr_connection() for _ in range(ASR_CONFIG["pool_size"])]
connections = await asyncio.gather(*tasks)
asr_connection_pool = [conn for conn in connections if conn.is_alive]
print(f"ASR 连接池初始化完成,有效连接数:{len(asr_connection_pool)}")
async def get_idle_asr_connection() -> Optional[ASRConnection]:
"""从连接池获取空闲连接"""
async with pool_lock:
idle_conns = [
conn for conn in asr_connection_pool
if not conn.is_busy and conn.is_alive
]
if idle_conns:
conn = idle_conns[0]
conn.is_busy = True
conn.stop_event.clear()
return conn
if len(asr_connection_pool) < ASR_CONFIG["pool_size"]:
new_conn = await create_asr_connection()
if new_conn.is_alive:
new_conn.is_busy = True
asr_connection_pool.append(new_conn)
return new_conn
print("ASR 连接池无空闲连接")
return None
async def push_audio_data(asr_conn: ASRConnection, audio_data: bytes) -> bool:
"""插入音频数据到 ASR 内置队列"""
if not asr_conn or not asr_conn.is_alive or asr_conn.stop_event.is_set():
return False
try:
asr_conn.audio_queue.put_nowait(audio_data)
return True
except asyncio.QueueFull:
print("ASR 音频队列已满,丢弃当前音频帧")
return False
async def release_asr_connection(conn: ASRConnection):
"""释放 ASR 连接"""
async with pool_lock:
conn.is_busy = False
conn.stop_event.set()
# 清空队列
while not conn.audio_queue.empty():
try:
conn.audio_queue.get_nowait()
except asyncio.QueueEmpty:
break
# 重连逻辑
if not conn.is_alive and conn.reconnect_count < ASR_CONFIG["reconnect_max_times"]:
print(f"尝试重连 ASR 连接(次数:{conn.reconnect_count + 1}")
new_conn = await create_asr_connection()
if new_conn.is_alive:
idx = asr_connection_pool.index(conn)
asr_connection_pool[idx] = new_conn
else:
conn.reconnect_count += 1
elif conn.reconnect_count >= ASR_CONFIG["reconnect_max_times"]:
asr_connection_pool.remove(conn)
print("ASR 连接重连次数耗尽,已移除")
async def handle_asr_communication(
asr_conn: ASRConnection,
result_callback: ASRResultCallback # 替换为回调函数
):
"""
处理 ASR 通信(结果通过回调函数返回)
:param asr_conn: ASR 连接对象
:param result_callback: 异步回调函数,接收 ASR 结果字典
"""
if not asr_conn or not asr_conn.ws:
# 错误结果通过回调返回
await result_callback({"error": "无可用 ASR 连接", "text": ""})
return
# 发送音频到 ASR 服务
async def send_audio():
while not asr_conn.stop_event.is_set() and asr_conn.is_alive:
try:
pcm_data = await asyncio.wait_for(asr_conn.audio_queue.get(), timeout=1.0)
if pcm_data and asr_conn.is_alive:
await asr_conn.ws.send(pcm_data)
await asyncio.sleep(0.005)
except asyncio.TimeoutError:
continue
except Exception as e:
print(f"发送音频到 ASR 失败:{e}")
asr_conn.is_alive = False
await result_callback({"error": f"音频发送失败:{str(e)}", "text": ""})
asr_conn.stop_event.set()
break
# 接收 ASR 结果并调用回调
async def recv_result():
while not asr_conn.stop_event.is_set() and asr_conn.is_alive:
try:
asr_result = await asr_conn.ws.recv()
result_json = json.loads(asr_result)
print(result_json.get("text", ""))
if result_json.get("timestamp", "") == '':
continue
result = {
"text": result_json.get("text", ""),
"mode": result_json.get("mode", ""),
"timestamp": result_json.get("timestamp", ""),
"is_final": result_json.get("is_final", False),
"error": ""
}
# 调用回调函数,传递结果
await result_callback(result)
except websockets.exceptions.ConnectionClosed:
print("ASR 连接已关闭")
asr_conn.is_alive = False
await result_callback({"error": "ASR 连接断开", "text": ""})
asr_conn.stop_event.set()
break
except Exception as e:
print(f"接收 ASR 结果失败:{e}")
asr_conn.is_alive = False
await result_callback({"error": f"接收结果失败:{str(e)}", "text": ""})
asr_conn.stop_event.set()
break
try:
send_task = asyncio.create_task(send_audio())
recv_task = asyncio.create_task(recv_result())
await asyncio.gather(send_task, recv_task)
finally:
send_task.cancel()
recv_task.cancel()
try:
await send_task
await recv_task
except asyncio.CancelledError:
pass
await release_asr_connection(asr_conn)
async def close_asr_pool():
"""关闭所有 ASR 连接"""
async with pool_lock:
for conn in asr_connection_pool:
conn.stop_event.set()
# if conn.ws and not conn.ws.closed:
# try:
# await conn.ws.close()
# print("ASR 连接已关闭")
# except Exception as e:
# print(f"关闭 ASR 连接失败:{e}")
asr_connection_pool.clear()
+28
View File
@@ -0,0 +1,28 @@
# config.py
# ASR配置
ASR_CONFIG = {
"host": "10.10.10.202",
"port": 10096,
"mode": "2pass", # 识别模式:online/offline/2pass
"chunk_size": [5, 10, 5],
"chunk_interval": 10,
"use_itn": 1,
"hotwords": "" # 热词(可选)
}
# 音频参数
AUDIO_PARAMS = {
"sample_rate": 16000, # 前端设置的 16000
"channels": 1, # 单声道
"sample_width": 2, # 16位深度(2字节/样本)
"frame_size": 1024 # 前端设置的 1024
}
# TTS配置
TTS_WS_URL = "ws://localhost:8003/tts"
# 大模型配置
LLM_SSE_URL = "http://localhost:8002/llm/stream"
# 队列配置
AUDIO_QUEUE_MAXSIZE = 50 # 音频队列大小
LLM_QUEUE_MAXSIZE = 20
+294
View File
@@ -0,0 +1,294 @@
# frontend_ws.py
from fastapi import WebSocket, WebSocketDisconnect
from session_manager import create_session, close_session
# 导入大模型模块
from llm_client import call_llm, LLMConversation, llm_client
import numpy as np
from typing import Optional, Dict, Callable, Awaitable, List
from dataclasses import dataclass, field
import sys
import json
import asyncio
import asyncio
import json
import websockets
import numpy as np
import sounddevice as sd
from typing import Optional, Callable, Dict, Any, Coroutine
from dataclasses import dataclass, field
import uuid
import logging
logger = logging.getLogger(__name__)
from ws_message_manager import (
ws_queue_manager,
ClientMsgType,
ServerMsgType,
)
from asr_client import (
init_asr_pool,
get_idle_asr_connection,
handle_asr_communication,
push_audio_data, # 导入音频插入接口
close_asr_pool
)
from tts_client import TTSManager
active_connections = []
user_llm_conversations: dict[str, LLMConversation] = {}
consume_wakeup = asyncio.Event()
async def frontend_websocket_handler(websocket: WebSocket):
"""前端WebSocket入口处理器(修复 TTS 数据接收问题)"""
# 1. 握手+创建会话
await websocket.accept()
active_connections.append(websocket)
conn_id = id(websocket)
user_id = f"user_{conn_id}"
session = await create_session(websocket)
logger.info(f"连接 {conn_id} 建立成功")
tts_session_id = user_id
result_queue = asyncio.Queue(maxsize=10000)
asr_task = None
asr_conn = None
# 初始化用户LLM会话
if user_id not in user_llm_conversations:
user_llm_conversations[user_id] = LLMConversation(
user_id=user_id,
scene_description="语音识别对话场景"
)
llm_conversation = user_llm_conversations[user_id]
# ====================== 修复 TTS 核心逻辑 ======================
# 1. 创建 TTS 客户端
# 1. 创建TTS管理器
tts_manager = TTSManager(ws_url="ws://10.10.10.202:50000/ws/tts")
# 2. 设置结果回调函数(接收完整结果)
def handle_tts_result(req_id: str, result: Dict[str, Any]):
"""处理TTS结果回调"""
print('处理TTS结果回调 ',result)
status = result.get("status")
if status == "completed":
audio_data = result.get("audio_data")
sample_rate = result.get("sample_rate")
if audio_data is not None and len(audio_data) > 0:
# 转换为PCM数据
pcm_data = (audio_data.astype(np.float32) * 32767).astype(np.int16)
pcm_bytes = pcm_data.tobytes()
# result_queue.put_nowait({"type": 3, "data": pcm_bytes})
result_queue.put_nowait(pcm_bytes)
print(f"插入时候队列当前大小xxx: {result_queue.qsize()}") # 排查队列是否有数据
# 保存为PCM文件
elif status == "error":
error_msg = result.get("message")
print(f"❌ TTS处理失败 [{req_id[:8]}]: {error_msg}")
tts_manager.set_result_callback(handle_tts_result)
# 3. 设置是否播放(可选,默认True)
tts_manager.set_playback_enabled(True) # 设置为False则不播放
# 4. 初始化连接
await tts_manager.initialize()
texts = [
"你好,这是第一个排队的TTS请求。",
"我是第二个请求,会等第一个处理完再执行。",
"第三个请求,支持流式播放和队列管理。",
"第四个请求,测试队列的自动消费功能。",
"最后一个请求,处理完成后会自动结束。"
]
req_ids = []
for i, text in enumerate(texts):
req_id = await tts_manager.synthesize(
text,
mode="预训练音色",
sft_spk="中文女",
speed=1.0
)
req_ids.append(req_id)
# ====================== ASR 结果回调 ======================
async def asr_result_callback(result: dict):
"""ASR 结果回调:转发前端 + 调用大模型"""
try:
logger.info(f"ASR 识别结果: {result}")
final_asr_text = result.get("text", "")
# 1. 转发 ASR 结果给前端
# if not websocket.client_state.disconnected:
# await websocket.send_json({
# "type": "asr_result",
# "data": result
# })
# result_queue.put_nowait({"type": 1, "data": final_asr_text})
print(f"插入时候队列当前大小: {result_queue.qsize()}") # 排查队列是否有数据
# 2. 立即唤醒消费协程(无延迟)
consume_wakeup.set()
# 2. 调用大模型(异步)
if final_asr_text:
asyncio.create_task(
call_llm_and_send(
query=final_asr_text,
conversation=llm_conversation
)
)
except Exception as e:
logger.error(f"ASR 回调执行失败: {str(e)}")
# ====================== 大模型流式回调 ======================
async def llm_stream_callback(chunk: str, conversation_id: str, is_finished: bool):
"""大模型流式回调(纯异步,无阻塞)"""
if not chunk:
return
# 1. 异步插入队列(替代put_nowait,避免队列满时抛异常)
# 1. 非阻塞插入(队列满则丢弃,优先保证实时性)
# result_queue.put_nowait(chunk)
# result_queue.put_nowait({"type": 2, "data": chunk})
# print(f"📥 插入队列: {chunk}, 队列大小: {result_queue.qsize()}")
# 3. 让出调度权,确保消费协程执行
await asyncio.sleep(0)
# ====================== 调用大模型 ======================
async def call_llm_and_send(query: str, conversation: LLMConversation):
"""调用大模型,流式结果转发前端 + TTS"""
if not query:
return
logger.info(f"调用大模型 - 用户({user_id}): {query}")
try:
conv_id, full_reply = await llm_client.send_message(
query=query,
conversation=conversation,
stream_callback=llm_stream_callback,
response_mode="streaming"
)
logger.info(f"大模型回复完成 - 会话ID: {conv_id}, 完整回复: {full_reply}")
except Exception as e:
logger.error(f"大模型调用失败: {str(e)}")
await websocket.send_json({
"type": "llm_error",
"data": {"error": str(e)}
})
# ====================== ASR 处理 ======================
try:
# 获取 ASR 连接
asr_conn = await get_idle_asr_connection()
if not asr_conn:
await websocket.send_json({"error": "ASR 服务暂时不可用", "text": ""})
return
# 启动 ASR 协程
asr_task = asyncio.create_task(
handle_asr_communication(asr_conn, asr_result_callback)
)
# 接收前端数据
async def recv_frontend_data():
"""接收前端音频/控制指令"""
while not asr_conn.stop_event.is_set():
try:
if not result_queue.empty():
await asyncio.sleep(0) # 立即让权
continue
raw_bytes = await websocket.receive_bytes()
success = await push_audio_data(asr_conn, raw_bytes)
if not success:
print("音频数据插入失败(队列满/连接失效)")
except WebSocketDisconnect:
logger.info(f"前端 {conn_id} 主动断开连接")
asr_conn.stop_event.set()
break
except Exception as e:
logger.error(f"接收前端数据失败: {str(e)}")
asr_conn.stop_event.set()
await websocket.send_json({"error": f"接收数据失败: {str(e)}"})
break
# 发送 ASR 结果
async def send_asr_result():
"""从结果队列发送 ASR 结果到前端(二进制格式)"""
# while not asr_conn.stop_event.is_set():
while True:
try:
# print('XXXXXXXXXXXXXXX')
# print(f"消费者队列当前大小: {result_queue.qsize()}") # 排查队列是否有数据
result = await asyncio.wait_for(result_queue.get(), timeout=0.05)
# print('YYYYYYYYYYYYYYYYYYY')
# # 直接发送二进制数据,不进行JSON格式化
# await websocket.send_json(result)
await websocket.send_bytes(result)
except asyncio.TimeoutError:
continue
except Exception as e:
logger.error(f"发送 ASR 结果失败: {str(e)}")
asr_conn.stop_event.set()
break
task_send = asyncio.create_task(send_asr_result())
task_recv = asyncio.create_task(recv_frontend_data())
# 2. 等待任一任务完成(或stop_event触发),而非等待两者都完成
try:
# 等待两个任务,只要有一个完成就返回(比如前端断开/发送出错)
done, pending = await asyncio.wait(
[task_recv, task_send],
return_when=asyncio.FIRST_COMPLETED,
timeout=None # 无限等待,直到有任务完成
)
finally:
# 确保协程正确退出
asr_conn.stop_event.set()
# 等待剩余任务完成
for task in pending:
task.cancel()
await asyncio.gather(task_recv, task_send, return_exceptions=True)
except Exception as e:
logger.error(f"WebSocket 处理异常: {str(e)}")
if asr_conn:
asr_conn.stop_event.set()
await websocket.send_json({"error": str(e)})
finally:
# 清理资源
if asr_conn:
asr_conn.stop_event.set()
if asr_task and not asr_task.done():
asr_task.cancel()
try:
await asr_task
except asyncio.CancelledError:
pass
# 修复点8:清理 TTS 资源
# tts_client.unregister_session_callback(tts_session_id)
# await tts_client.disconnect()
if websocket in active_connections:
active_connections.remove(websocket)
logger.info(f"连接 {conn_id} 已清理,当前连接数: {len(active_connections)}")
+300
View File
@@ -0,0 +1,300 @@
# frontend_ws.py
from fastapi import WebSocket, WebSocketDisconnect
from session_manager import create_session, close_session
# 导入大模型模块
from llm_client import call_llm, LLMConversation, llm_client
import numpy as np
from typing import Optional, Dict, Callable, Awaitable, List
from dataclasses import dataclass, field
import sys
import json
import asyncio
import logging
logger = logging.getLogger(__name__)
from ws_message_manager import (
ws_queue_manager,
ClientMsgType,
ServerMsgType,
)
from asr_client import (
init_asr_pool,
get_idle_asr_connection,
handle_asr_communication,
push_audio_data, # 导入音频插入接口
close_asr_pool
)
from tts_client import CosyVoiceTTSSocketClient
active_connections = []
user_llm_conversations: dict[str, LLMConversation] = {}
async def frontend_websocket_handler(websocket: WebSocket):
"""前端WebSocket入口处理器(保留SessionASR/TTS无状态)"""
# 1. 握手+创建会话(核心:Session 管理连接上下文)
await websocket.accept()
active_connections.append(websocket)
conn_id = id(websocket)
user_id = f"user_{id(websocket)}" # 实际场景替换为真实用户ID
session = await create_session(websocket) # 创建会话(无用户逻辑)
logger.info(f"连接 {conn_id} 建立成功")
# await ws_queue_manager.create_queue(websocket)
tts_session_id = user_id
result_queue = asyncio.Queue(maxsize=100)
asr_task = None
asr_conn = None
# 初始化用户LLM会话
if user_id not in user_llm_conversations:
user_llm_conversations[user_id] = LLMConversation(
user_id=user_id,
scene_description="语音识别对话场景" # 自定义大模型场景
)
llm_conversation = user_llm_conversations[user_id]
# 1. 创建客户端
client = CosyVoiceTTSSocketClient(
ws_url="ws://10.10.10.202:50000/ws/tts",
max_queue_size=500 # 最大队列长度
)
# 2. 注册回调(按需自定义)
client.on_audio_chunk = lambda req_id, chunk: print(f"收到[{req_id[:8]}]音频块,长度: {len(chunk)}")
# 3. 建立连接(自动启动队列消费)
await client.connect()
# ---------------------- TTS 音频回调(转发前端) ----------------------
async def tts_audio_callback(audio_chunk, sample_rate, is_finished):
"""将音频块转发给前端的逻辑"""
print(f"音频块长度:{len(audio_chunk)},采样率:{sample_rate},是否结束:{is_finished}")
#todo
# ---------------------- 定义 ASR 结果回调函数 ----------------------
async def asr_result_callback(result: dict):
"""
ASR 结果回调:同时转发给前端 + 推送大模型
:param result: ASR 识别结果字典
"""
try:
print('结果回调:同时转发给前端 + 推送大模型', result)
final_asr_text = result["text"]
print(f"ASR最终识别结果: {final_asr_text}")
# 调用大模型(异步,不阻塞)
asyncio.create_task(
call_llm_and_send(
query=final_asr_text,
conversation=llm_conversation
)
)
# # 1. 转发给前端(确保连接未断开)
# if not websocket.client_state.disconnected:
# await websocket.send_json(result)
#
# # 2. 推送到大模型(过滤错误/空结果)
# if not result.get("error") and result.get("text"):
# # 异步推送,不阻塞回调
# asyncio.create_task(send_to_llm(result))
except Exception as e:
print(f"回调函数执行失败:{e}")
# -------------------------------------------------------------------
# ---------------------- 大模型流式回调(转发前端) ----------------------
async def llm_stream_callback(chunk: str, conversation_id: str, is_finished: bool):
req_id1 = client.add_tts_request(tts_text=chunk)
print('req_id1', req_id1)
"""
大模型流式回调:将实时回复转发给前端
"""
# if websocket.client_state.disconnected:
# return
# try:
# print('大模型流式回调', chunk)
# await websocket.send_json({
# "type": "llm_result",
# "data": {
# "chunk": chunk,
# "conversation_id": conversation_id,
# "is_finished": is_finished,
# "query": final_asr_text # 关联ASR查询文本
# }
# })
# except Exception as e:
# print(f"LLM回调执行失败: {e}")
# if websocket.client_state.disconnected:
# return
try:
# 1. 转发大模型文本给前端
# await websocket.send_json({
# "type": "llm_result",
# "data": {
# "chunk": chunk,
# "conversation_id": conversation_id,
# "is_finished": is_finished,
# "user_id": user_id
# }
# })
# print('大模型返回的', chunk)
# await tts_client.send_text_chunk(tts_session_id, chunk)
pass
# 2. 流式文本发送给 TTS(非错误文本 + 非结束标识)
# if chunk and not chunk.startswith("[错误]") and not is_finished:
# await tts_client.send_text_chunk(tts_session_id, chunk)
# # 合成结束标识
# elif is_finished:
# # 发送空文本块触发 TTS 结束
# await tts_client.send_text_chunk(tts_session_id, "")
except Exception as e:
print(f"LLM回调联动TTS失败({user_id}): {e}")
# ---------------------- 调用大模型并转发结果 ----------------------
async def call_llm_and_send(query: str, conversation: LLMConversation):
"""调用大模型,流式结果通过WebSocket返回前端"""
if not query:
return
print(f"调用大模型 - 用户({user_id}): {query}")
# 调用大模型(流式)
conv_id, full_reply = await llm_client.send_message(
query=query,
conversation=conversation,
stream_callback=llm_stream_callback,
response_mode="streaming"
)
print(f"大模型回复完成 - 会话ID: {conv_id}, 完整回复: {full_reply}")
# -------------------------------------------------------------------
try:
# 1. 获取 ASR 连接
asr_conn = await get_idle_asr_connection()
if not asr_conn:
await websocket.send_json({"error": "ASR 服务暂时不可用", "text": ""})
return
# 启动 ASR 协程(传递回调函数,移除结果队列)
asr_task = asyncio.create_task(
handle_asr_communication(asr_conn, asr_result_callback)
)
# 3. 并行处理:接收前端数据 + 发送 ASR 结果
async def recv_frontend_data():
"""接收前端所有数据,仅将音频数据插入 ASR 队列"""
while not asr_conn.stop_event.is_set():
try:
raw_bytes = await websocket.receive_bytes()
success = await push_audio_data(asr_conn, raw_bytes)
if not success:
print("音频数据插入失败(队列满/连接失效)")
# # 接收前端数据(自动区分类型)
# data_type = websocket.receive()
# if data_type.type == "bytes":
# # 二进制数据 = 音频数据,调用 ASR 接口插入
# raw_bytes = await websocket.receive_bytes()
# # 调用 ASR 模块的插入接口,无需管理队列
# success = await push_audio_data(asr_conn, raw_bytes)
# if not success:
# print("音频数据插入失败(队列满/连接失效)")
# elif data_type.type == "json":
# # JSON 数据 = 控制指令
# control_data = await websocket.receive_json()
# print(f"收到前端控制指令:{control_data}")
# if control_data.get("action") == "stop":
# asr_conn.stop_event.set() # 触发 ASR 停止
# await websocket.send_json({"text": "", "error": "用户主动停止"})
# break
# elif data_type.type == "text":
# # 文本数据 = 其他指令
# text_data = await websocket.receive_text()
# print(f"收到前端文本数据:{text_data}")
except WebSocketDisconnect:
print("前端主动断开连接")
asr_conn.stop_event.set()
break
except Exception as e:
print(f"接收前端数据失败:{e}")
asr_conn.stop_event.set()
await websocket.send_json({"error": f"接收数据失败:{str(e)}", "text": ""})
break
async def send_asr_result():
"""从结果队列取数据,发送给前端"""
while not asr_conn.stop_event.is_set():
try:
result = await asyncio.wait_for(result_queue.get(), timeout=1.0)
# if not websocket.client_state.disconnected:
print('result', result)
await websocket.send_json(result)
except asyncio.TimeoutError:
continue
except Exception as e:
print(f"发送 ASR 结果失败:{e}")
asr_conn.stop_event.set()
break
# 4. 并发执行
await asyncio.gather(recv_frontend_data(), send_asr_result())
except Exception as e:
print(f"WebSocket 处理异常:{e}")
if asr_conn:
asr_conn.stop_event.set()
await websocket.send_json({"error": str(e), "text": ""})
finally:
# 清理资源
if asr_conn:
asr_conn.stop_event.set()
# 取消 ASR 任务
if asr_task and not asr_task.done():
asr_task.cancel()
try:
await asr_task
except asyncio.CancelledError:
pass
# 移除活跃连接
if websocket in active_connections:
active_connections.remove(websocket)
print(f"客户端连接已清理,当前连接数:{len(active_connections)}")
# 关闭前端连接
# if not websocket.client_state.disconnected:
# await websocket.close()
# try:
#
# # 4. 持续接收前端消息(二进制,兼容音频/文本/指令)
# while not session.is_closed:
# raw_bytes = await websocket.receive_bytes()
# # print('raw_bytes', raw_bytes)
# # todo 前段暂时传入的全部都是语音消息
# if not audio_queue.full():
# audio_queue.put_nowait(raw_bytes)
# # await forward_frontend_to_asr(session, raw_bytes)
# # 解析前端消息类型(复用之前的编解码器)
# # msg_type, data = WsMsgCodec.decode_client_msg(raw_bytes)
# # # 分发处理(不同类型消息走不同逻辑)
# # if msg_type == ClientMsgType.AUDIO:
# # # 音频直接转发给ASR(Session 传递ASR客户端,无用户标识)
# # if session.asr_client and not session.is_closed:
# # await send_audio_to_asr(session.asr_client, data)
# # elif msg_type == ClientMsgType.TEXT:
# # # 文本直接走LLM(无需ASR)
# # await forward_text_to_llm(session, data)
# # elif msg_type == ClientMsgType.ACTION:
# # # 操作指令处理
# # await handle_frontend_action(session, data)
#
# except WebSocketDisconnect:
# logger.info(f"会话 [{session.session_id}] 前端主动断开")
# except Exception as e:
# logger.error(f"会话 [{session.session_id}] 异常: {str(e)}", exc_info=True)
# finally:
# # 5. 清理会话(一键关闭所有资源,核心价值)
# await close_session(session)
# await websocket.close()
+173
View File
@@ -0,0 +1,173 @@
import asyncio
import json
from typing import Optional, Dict, Callable, Awaitable
from dataclasses import dataclass, field
import aiohttp # 新增:异步HTTP库
# 大模型配置(集中管理)
LLM_CONFIG = {
"base_url": "http://10.10.10.202:8088/v1",
"api_key": "app-m7HZNV1aGiheh3wr6wNVHFxX",
"timeout": 30, # 请求超时时间(秒)
"default_scene": "通用聊天场景", # 默认场景描述
"stream_chunk_size": 1024 # 流式接收块大小
}
# 定义流式回调函数类型(异步)
LLMStreamCallback = Callable[[str, Optional[str], bool], Awaitable[None]]
"""
回调函数参数说明:
- chunk: 单次流式返回的文本片段
- conversation_id: 会话ID(首次返回,后续复用)
- is_finished: 是否结束(True=流式结束/同步返回完成)
"""
@dataclass
class LLMConversation:
"""会话对象(管理会话ID和上下文)"""
conversation_id: Optional[str] = None
user_id: str = ""
scene_description: str = LLM_CONFIG["default_scene"]
# 可选:存储会话历史(如需上下文管理)
history: list = field(default_factory=list)
class LLMClient:
"""大模型客户端封装(异步修复版)"""
def __init__(self):
self.base_url = LLM_CONFIG["base_url"]
self.headers = {
"Authorization": f"Bearer {LLM_CONFIG['api_key']}",
"Content-Type": "application/json"
}
self.timeout = aiohttp.ClientTimeout(total=LLM_CONFIG["timeout"]) # 异步超时
self._session: Optional[aiohttp.ClientSession] = None # 异步会话(复用连接)
async def _get_session(self) -> aiohttp.ClientSession:
"""获取/复用异步HTTP会话"""
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(timeout=self.timeout)
return self._session
async def send_message(
self,
query: str,
conversation: LLMConversation,
stream_callback: Optional[LLMStreamCallback] = None,
response_mode: str = "streaming"
) -> tuple[Optional[str], str]:
"""
发送消息到大模型(纯异步版,无线程池阻塞)
"""
payload = {
"query": query,
"inputs": {"scene_description": conversation.scene_description},
"response_mode": response_mode,
"user": conversation.user_id
}
if conversation.conversation_id:
payload["conversation_id"] = conversation.conversation_id
url = f"{self.base_url}/chat-messages"
full_response = ""
res_conversation_id = conversation.conversation_id
try:
session = await self._get_session()
if response_mode == "streaming":
# 异步流式请求(无线程池,纯异步IO)
async with session.post(url, headers=self.headers, json=payload) as response:
response.raise_for_status()
# 实时迭代流式响应
async for line in response.content.iter_chunked(LLM_CONFIG["stream_chunk_size"]):
if not line:
continue
line_data = line.decode("utf-8")
if line_data.startswith("data: "):
json_str = line_data[6:].strip()
if json_str == "[DONE]":
if stream_callback:
await stream_callback("", res_conversation_id, True)
break
try:
data = json.loads(json_str)
# 更新会话ID
if not res_conversation_id and "conversation_id" in data:
res_conversation_id = data["conversation_id"]
# 提取内容
chunk = data.get("content", data.get("answer", data.get("message", "")))
if chunk:
full_response += chunk
if stream_callback:
await stream_callback(chunk, res_conversation_id, False)
await asyncio.sleep(0) # 让出调度权
except json.JSONDecodeError as e:
print(f"解析流式数据失败: {e}")
continue
else:
# 异步非流式请求
async with session.post(url, headers=self.headers, json=payload) as response:
response.raise_for_status()
data = await response.json()
res_conversation_id = data.get("conversation_id", conversation.conversation_id)
full_response = data.get("content", data.get("answer", data.get("message", "")))
if stream_callback:
await stream_callback(full_response, res_conversation_id, True)
conversation.conversation_id = res_conversation_id
return res_conversation_id, full_response
except aiohttp.ClientError as e:
error_msg = f"大模型请求失败: {str(e)}"
print(error_msg)
if stream_callback:
await stream_callback(f"[错误] {error_msg}", res_conversation_id, True)
return res_conversation_id, ""
except Exception as e:
error_msg = f"大模型处理异常: {str(e)}"
print(error_msg)
if stream_callback:
await stream_callback(f"[错误] {error_msg}", res_conversation_id, True)
return res_conversation_id, ""
async def close(self):
"""关闭异步会话(程序退出时调用)"""
if self._session and not self._session.closed:
await self._session.close()
# 全局单例客户端(异步版)
llm_client = LLMClient()
# 快捷调用函数(保持原有接口不变)
async def call_llm(
query: str,
user_id: str,
scene_description: str = LLM_CONFIG["default_scene"],
conversation_id: Optional[str] = None,
stream_callback: Optional[LLMStreamCallback] = None,
response_mode: str = "streaming"
) -> tuple[Optional[str], str]:
"""
快捷调用大模型(无需手动创建会话对象)
:param query: 用户提问
:param user_id: 用户ID
:param scene_description: 场景描述
:param conversation_id: 会话ID(续聊用)
:param stream_callback: 流式回调
:param response_mode: 响应模式
:return: (conversation_id, 完整回复)
"""
conversation = LLMConversation(
conversation_id=conversation_id,
user_id=user_id,
scene_description=scene_description
)
return await llm_client.send_message(
query=query,
conversation=conversation,
stream_callback=stream_callback,
response_mode=response_mode
)
# 可选:程序退出时关闭会话(如FastAPI的shutdown事件)
async def shutdown_llm_client():
await llm_client.close()
+137
View File
@@ -0,0 +1,137 @@
# main.py
import asyncio
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from starlette.middleware.cors import CORSMiddleware
# 导入抽离的消息管理模块
from ws_message_manager import ws_queue_manager, ServerMsgType
import logging
from frontend_ws import frontend_websocket_handler
from asr_client import init_asr_pool, close_asr_pool
from contextlib import asynccontextmanager
# 初始化日志配置(全局生效)
logging.basicConfig(
level=logging.INFO, # 日志级别:DEBUG/INFO/WARNING/ERROR
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", # 日志格式
handlers=[
logging.StreamHandler(), # 输出到控制台
logging.FileHandler("ws_server.log", encoding="utf-8") # 输出到文件(可选)
]
)
# 创建日志实例(后续所有logger调用都用这个实例)
logger = logging.getLogger(__name__) # __name__ 是当前模块名,便于区分日志来源
# FastAPI 启动时初始化 ASR 连接池
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时执行(原 startup 逻辑)
print(' FastAPI 启动时初始化 ASR 连接池')
await init_asr_pool()
yield # 应用运行中
# 关闭时执行(可选,比如清理连接池)
print("应用关闭,开始清理 ASR 连接池...")
await close_asr_pool()
# 这里可以添加连接池关闭逻辑(如关闭所有 ASR 连接)
# 初始化FastAPI应用
app = FastAPI(
title="语音交互网关",
description="整合前端/ASR/大模型/TTS的异步网关服务",
version="1.0",
lifespan=lifespan # 绑定生命周期
)
# 跨域配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
async def process_audio(websocket: WebSocket, audio_data: bytes):
"""处理音频数据,生成多类型消息并推送"""
try:
# 1. 模拟ASR识别(生成文字)
text_result = f"识别结果:音频长度 {len(audio_data)} bytes"
await ws_queue_manager.send_message(websocket, ServerMsgType.TEXT, text_result)
# 2. 模拟TTS生成语音(二进制数据)
voice_data = b"xx" # 替换为真实TTS输出
await ws_queue_manager.send_message(websocket, ServerMsgType.VOICE, voice_data)
# 3. 模拟数字人动画信息
animation_data = {
"action": "mouth_move",
"speed": 1.2,
"duration": 1000
}
await ws_queue_manager.send_message(websocket, ServerMsgType.ANIMATION, animation_data)
# 4. 模拟前端动作指令
action_data = {
"type": "show_loading",
"status": False
}
await ws_queue_manager.send_message(websocket, ServerMsgType.ACTION, action_data)
except Exception as e:
# 推送错误信息
await ws_queue_manager.send_message(websocket, ServerMsgType.ERROR, str(e))
@app.websocket("/ws/audio")
async def websocket_audio(websocket: WebSocket):
# 完全委托给frontend_websocket_handler处理
await frontend_websocket_handler(websocket)
# 前端WebSocket路由
# @app.websocket("/ws/audio")
# async def websocket_audio(websocket: WebSocket):
# # 1. 接受连接并创建专属队列
# await websocket.accept()
# await ws_queue_manager.create_queue(websocket)
#
# try:
# # 2. 循环接收前端音频数据
# while True:
# # 接收二进制音频数据(前端发送的麦克风数据)
# audio_data = await websocket.receive_bytes()
# # 异步处理音频(不阻塞接收)
# asyncio.create_task(process_audio(websocket, audio_data))
#
# except WebSocketDisconnect:
# logger.info(f"连接 {id(websocket)} 主动断开")
# except Exception as e:
# logger.error(f"连接异常: {e}")
# finally:
# # 3. 清理队列和协程
# await ws_queue_manager.close_queue(websocket)
# 健康检查接口
@app.get("/health")
async def health_check():
return {
"status": "ok",
"service": "voice_gateway",
"active_connections": len(ws_queue_manager.queue_map)
}
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True, # 生产环境关闭
workers=1 # 异步框架单worker足够,多worker需分布式队列
)
+67
View File
@@ -0,0 +1,67 @@
# session_manager.py(最终版)
import asyncio
import logging
from dataclasses import dataclass, field
from fastapi import WebSocket
from typing import Optional, List, Any
from asyncio import Queue
logger = logging.getLogger(__name__)
@dataclass
class WsSession:
"""WebSocket 会话类:管理单个连接的所有上下文(无用户标识,仅做资源隔离)"""
websocket: WebSocket # 前端 WS 连接
session_id: str # 会话ID(用 conn_id 即可,无需用户ID)
is_closed: bool = False # 会话是否关闭
tasks: List[asyncio.Task] = field(default_factory=list) # 转发协程列表
# ASR/TTS 客户端连接(每个会话独立创建,天然隔离)
asr_client: Optional[Any] = None # ASR 客户端任务(存储协程任务)
tts_client: Optional[Any] = None # TTS 客户端实例(无用户态)
# 新增:ASR 依赖的核心属性
audio_queue: Queue = field(default_factory=lambda: Queue(maxsize=30)) # 音频队列(防积压)
llm_queue: Queue = field(default_factory=Queue) # LLM 队列(转发ASR最终结果)
asr_ws_task: Optional[asyncio.Task] = None # ASR 核心协程任务
async def create_session(websocket: WebSocket) -> WsSession:
"""创建会话(仅初始化资源,无用户相关逻辑)"""
conn_id = str(id(websocket))
session = WsSession(
websocket=websocket,
session_id=conn_id # 会话ID = 连接ID,无需用户标识
)
logger.info(f"会话 [{session.session_id}] 创建成功")
return session
async def close_session(session: WsSession):
"""关闭会话:清理所有协程和ASR/TTS连接(核心:资源释放)"""
session.is_closed = True
# 1. 取消所有转发协程
for task in session.tasks:
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
# 2. 单独取消ASR核心任务(新增)
if session.asr_ws_task and not session.asr_ws_task.done():
session.asr_ws_task.cancel()
try:
await session.asr_ws_task
except asyncio.CancelledError:
logger.info(f"会话 [{session.session_id}] ASR核心协程已取消")
# 3. 关闭ASR/TTS客户端(适配:asr_client 是任务,无需close,仅日志提示)
if session.asr_client:
logger.info(f"会话 [{session.session_id}] ASR 客户端任务已清理")
if session.tts_client and hasattr(session.tts_client, 'closed') and not session.tts_client.closed:
await session.tts_client.close()
logger.info(f"会话 [{session.session_id}] TTS 客户端已关闭")
# 4. 清空音频队列(防止内存泄漏)
while not session.audio_queue.empty():
try:
session.audio_queue.get_nowait()
session.audio_queue.task_done()
except asyncio.QueueEmpty:
break
logger.info(f"会话 [{session.session_id}] 已完全清理")
+316
View File
@@ -0,0 +1,316 @@
import asyncio
import json
import websockets
import numpy as np
import sounddevice as sd
from typing import Optional, Callable, Dict, Any, Coroutine
from dataclasses import dataclass, field
import uuid
@dataclass
class TTSRequest:
"""TTS请求对象(带唯一标识)"""
tts_text: str
mode: str = "预训练音色"
sft_spk: str = ""
seed: int = field(default_factory=lambda: np.random.randint(1, 100000000))
stream: bool = True
speed: float = 1.0
prompt_wav: str = ""
instruct_text: str = ""
request_id: str = field(default_factory=lambda: str(uuid.uuid4())) # 唯一请求ID
class CosyVoiceTTSSocketClient:
"""CosyVoice TTS WebSocket 客户端(异步/流式/带任务队列)"""
def __init__(self, ws_url: str = "ws://localhost:50000/ws/tts", max_queue_size: int = 100):
"""
初始化客户端
:param ws_url: WebSocket 服务端地址
:param max_queue_size: 最大队列长度(防止内存溢出)
"""
self.ws_url = ws_url
self.websocket: Optional[websockets.WebSocketClientProtocol] = None
self.is_connected = False
self.is_processing = False # 是否正在处理请求
# 异步任务队列(FIFO
self.request_queue: asyncio.Queue[TTSRequest] = asyncio.Queue(maxsize=max_queue_size)
# 回调函数定义(所有回调都带request_id,方便关联请求)
self.on_task_enqueue: Callable[[str], None] = lambda req_id: None # 任务入队回调
self.on_start: Callable[[str, Dict[str, Any]], None] = lambda req_id, data: None # 合成开始回调
self.on_audio_chunk: Callable[[str, np.ndarray], None] = lambda req_id, chunk: None # 音频块回调
self.on_end: Callable[[str, Dict[str, Any]], None] = lambda req_id, data: None # 合成结束回调
self.on_error: Callable[[str, str], None] = lambda req_id, msg: None # 错误回调
self.on_queue_full: Callable[[str], None] = lambda req_id: None # 队列满回调
async def connect(self):
"""建立 WebSocket 连接(初始化一次)"""
if not self.is_connected:
try:
self.websocket = await websockets.connect(self.ws_url)
self.is_connected = True
print(f"成功连接到 TTS 服务端: {self.ws_url}")
# 启动队列消费协程(后台运行)
asyncio.create_task(self._consume_queue())
except Exception as e:
raise ConnectionError(f"连接失败: {str(e)}")
async def disconnect(self):
"""关闭 WebSocket 连接"""
if self.is_connected and self.websocket:
await self.websocket.close()
self.is_connected = False
self.websocket = None
print("已断开与 TTS 服务端的连接")
def add_tts_request(self, **kwargs) -> str:
"""
添加TTS请求到队列(非阻塞,立即返回请求ID)
:param kwargs: TTS参数(同TTSRequest
:return: 唯一请求ID
"""
# 创建请求对象
request = TTSRequest(**kwargs)
req_id = request.request_id
# 尝试入队(非阻塞)
try:
self.request_queue.put_nowait(request)
self.on_task_enqueue(req_id)
print(f"请求 {req_id} 已加入队列,当前队列长度: {self.request_queue.qsize()}")
except asyncio.QueueFull:
self.on_queue_full(req_id)
print(f"队列已满,请求 {req_id} 入队失败")
return req_id
async def _consume_queue(self):
"""消费队列(后台协程,自动处理排队请求)"""
print("队列消费协程已启动")
while True:
try:
# 等待队列中有请求(阻塞)
request = await self.request_queue.get()
req_id = request.request_id
# 标记为处理中
self.is_processing = True
print(f"开始处理请求 {req_id},剩余队列长度: {self.request_queue.qsize()}")
# 处理当前请求
await self._process_single_request(request)
# 标记任务完成
self.request_queue.task_done()
self.is_processing = False
except Exception as e:
print(f"队列消费异常: {str(e)}")
self.is_processing = False
# 短暂等待后继续消费,避免死循环
await asyncio.sleep(0.1)
async def _process_single_request(self, request: TTSRequest):
"""处理单个TTS请求"""
req_id = request.request_id
# 参数校验
if not request.tts_text:
self.on_error(req_id, "合成文本不能为空")
return
# 确保已连接
if not self.is_connected:
try:
await self.connect()
except Exception as e:
self.on_error(req_id, f"连接失败: {str(e)}")
return
# 构造请求数据
request_data = {
"tts_text": request.tts_text,
"mode": request.mode,
"sft_spk": request.sft_spk,
"seed": request.seed,
"stream": request.stream,
"speed": request.speed,
"prompt_wav": request.prompt_wav,
"instruct_text": request.instruct_text
}
try:
# 发送请求
await self.websocket.send(json.dumps(request_data))
# 处理返回的流式数据
await self._handle_response(req_id)
except websockets.exceptions.ConnectionClosed:
self.is_connected = False
self.on_error(req_id, "连接已关闭")
except Exception as e:
self.on_error(req_id, f"处理请求失败: {str(e)}")
async def _handle_response(self, req_id: str):
"""处理单个请求的服务端响应"""
if not self.websocket:
return
try:
while True:
# 接收服务端消息(异步)
response = await self.websocket.recv()
data = json.loads(response)
# 根据状态分发到不同回调(都带req_id)
status = data.get("status")
if status == "start":
# 合成开始 - 返回采样率等信息
self.on_start(req_id, data)
elif status == "stream":
# 流式音频块 - 转为 numpy 数组
audio_chunk = np.array(data["audio_chunk"], dtype=np.float32)
self.on_audio_chunk(req_id, audio_chunk)
elif status == "end":
# 合成结束
self.on_end(req_id, data)
break
elif status == "error":
# 错误处理
self.on_error(req_id, data["msg"])
break
except Exception as e:
self.on_error(req_id, f"接收数据异常: {str(e)}")
async def wait_queue_empty(self):
"""等待队列所有任务处理完成(阻塞)"""
await self.request_queue.join()
print("所有队列任务已处理完成")
# ------------------------------
# 示例:带队列的客户端使用演示
# ------------------------------
async def demo():
# 1. 创建客户端实例(初始化一次)
client = CosyVoiceTTSSocketClient(
ws_url="ws://10.10.10.202:50000/ws/tts",
max_queue_size=50 # 最大队列长度
)
# 2. 初始化音频播放流(全局)
play_stream = None
current_req_id = None
# 3. 定义各类回调函数
# 任务入队回调
def on_task_enqueue(req_id):
print(f"✅ 任务 {req_id[:8]} 已入队")
# 队列满回调
def on_queue_full(req_id):
print(f"❌ 队列已满,任务 {req_id[:8]} 入队失败")
# 合成开始回调
def on_tts_start(req_id, data):
nonlocal play_stream, current_req_id
current_req_id = req_id
print(f"\n🎤 开始合成 [{req_id[:8]}] - 采样率: {data['sample_rate']}")
# 初始化播放流
if play_stream:
play_stream.stop()
play_stream.close()
play_stream = sd.OutputStream(
samplerate=data["sample_rate"],
channels=1,
dtype=np.float32
)
play_stream.start()
# 音频块回调(实时播放)
def on_audio_chunk(req_id, chunk):
if chunk.size > 0 and req_id == current_req_id:
# 只播放当前正在处理的请求音频
play_stream.write(chunk)
# 可选:保存音频块(按req_id区分文件)
# with open(f"output_{req_id[:8]}.raw", "ab") as f:
# f.write(chunk.tobytes())
# 合成结束回调
def on_tts_end(req_id, data):
nonlocal play_stream
print(f"🏁 合成完成 [{req_id[:8]}] - {data['msg']}")
if play_stream:
play_stream.stop()
play_stream.close()
play_stream = None
# 错误回调
def on_tts_error(req_id, msg):
nonlocal play_stream
print(f"❌ 合成失败 [{req_id[:8]}] - {msg}")
if play_stream:
play_stream.stop()
play_stream.close()
play_stream = None
# 注册回调
client.on_task_enqueue = on_task_enqueue
client.on_queue_full = on_queue_full
client.on_start = on_tts_start
client.on_audio_chunk = on_audio_chunk
client.on_end = on_tts_end
client.on_error = on_tts_error
# 4. 建立初始连接
await client.connect()
# 5. 模拟持续输入请求(批量添加到队列)
test_texts = [
"你好,这是第一个排队的TTS请求。",
"我是第二个请求,会等第一个处理完再执行。",
"第三个请求,支持流式播放和队列管理。",
"第四个请求,测试队列的自动消费功能。",
"最后一个请求,处理完成后会自动结束。"
]
# 批量添加请求到队列(非阻塞)
req_ids = []
for i, text in enumerate(test_texts):
req_id = client.add_tts_request(
tts_text=text,
mode="预训练音色",
sft_spk="中文女",
speed=1.0
)
req_ids.append(req_id)
# 模拟间隔输入
await asyncio.sleep(0.1)
# 6. 等待所有队列任务完成
await client.wait_queue_empty()
# 7. 断开连接
await client.disconnect()
print("\n所有请求处理完毕,客户端已关闭")
if __name__ == "__main__":
# 安装依赖:pip install websockets numpy sounddevice
try:
asyncio.run(demo())
except KeyboardInterrupt:
print("\n程序被用户中断")
except Exception as e:
print(f"程序异常: {str(e)}")
+4
View File
@@ -0,0 +1,4 @@
import torch
print("CUDA 版本:", torch.version.cuda) # 应输出 12.8
print("cuDNN 版本:", torch.backends.cudnn.version()) # 应输出 9200+
print("GPU 可用:", torch.cuda.is_available()) # 应输出 True
Binary file not shown.
+155
View File
@@ -0,0 +1,155 @@
# funasr_client_api.py
import websocket
import threading
import time
import json
import ssl
from typing import Optional
class Funasr_websocket_recognizer:
def __init__(self, host: str, port: str, is_ssl: bool = False, mode: str = "offline",
chunk_size: str = "0,10,5", ping_interval: int = 30):
"""
FunASR WebSocket 识别器初始化
:param host: FunASR 服务端IP
:param port: FunASR 服务端端口
:param is_ssl: 是否使用SSLwss
:param mode: 识别模式(offline/online
:param chunk_size: 分片参数(仅online模式有效)
:param ping_interval: 心跳间隔(秒)
"""
self.host = host
self.port = port
self.is_ssl = is_ssl
self.mode = mode
self.chunk_size = chunk_size
self.ping_interval = ping_interval
# WebSocket 连接配置
self.ws_url = f"{'wss' if is_ssl else 'ws'}://{host}:{port}/websocket/asr"
self.ws = None
self.ping_thread = None
self.stop_ping = False
self.result_buffer = []
self.lock = threading.Lock()
def _connect(self):
"""建立 WebSocket 连接"""
ssl_opt = {"cert_reqs": ssl.CERT_NONE} if self.is_ssl else None
self.ws = websocket.create_connection(self.ws_url, sslopt=ssl_opt)
# 发送初始化消息
init_msg = {
"mode": self.mode,
"chunk_size": self.chunk_size,
"format": "wav",
"rate": 16000,
"need_pun": True, # 是否需要标点
"encoding": "raw"
}
self.ws.send(json.dumps(init_msg))
# 启动心跳线程
self.stop_ping = False
self.ping_thread = threading.Thread(target=self._ping_worker)
self.ping_thread.daemon = True
self.ping_thread.start()
def _ping_worker(self):
"""心跳保活"""
while not self.stop_ping:
try:
if self.ws:
self.ws.ping()
time.sleep(self.ping_interval)
except Exception as e:
print(f"Ping 失败: {e}")
break
def feed_chunk(self, audio_data: bytes, wait_time: float = 0.02) -> Optional[str]:
"""
发送音频分片并获取识别结果
:param audio_data: 音频字节数据(16k 16bit 单声道)
:param wait_time: 等待结果的时间(秒)
:return: 实时识别文本(None 表示无结果)
"""
# 首次调用时建立连接
if not self.ws:
self._connect()
try:
# 发送音频数据(Base64 编码)
import base64
audio_b64 = base64.b64encode(audio_data).decode("utf-8")
data_msg = {
"data": audio_b64,
"is_last": False # 标记为非最后一包
}
self.ws.send(json.dumps(data_msg))
# 等待并读取结果
time.sleep(wait_time)
result = None
with self.lock:
while self.ws and self.ws.connected:
if self.ws.readyState == websocket.ABNF.READYSTATE_CLOSING:
break
try:
resp = self.ws.recv(timeout=0.01)
if resp:
resp_json = json.loads(resp)
if "text" in resp_json:
result = resp_json["text"]
self.result_buffer.append(result)
break
except websocket.WebSocketTimeoutException:
break
return result
except Exception as e:
print(f"发送分片失败: {e}")
return None
def close(self, timeout: float = 3) -> str:
"""
关闭连接并获取最终识别结果
:param timeout: 等待最终结果的超时时间(秒)
:return: 最终识别文本
"""
final_text = ""
try:
# 发送最后一包数据标记
if self.ws and self.ws.connected:
last_msg = {
"data": "",
"is_last": True
}
self.ws.send(json.dumps(last_msg))
# 等待最终结果
end_time = time.time() + timeout
while time.time() < end_time:
try:
resp = self.ws.recv(timeout=0.1)
if resp:
resp_json = json.loads(resp)
if "text" in resp_json:
final_text = resp_json["text"]
if resp_json.get("is_final", False):
break
except websocket.WebSocketTimeoutException:
continue
# 停止心跳并关闭连接
self.stop_ping = True
if self.ping_thread:
self.ping_thread.join(timeout=1)
if self.ws:
self.ws.close()
except Exception as e:
print(f"关闭连接失败: {e}")
# 合并所有结果
with self.lock:
all_text = "".join(self.result_buffer + [final_text])
return all_text
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
## Usage
```shell
# offline client
./funasr-wss-client --server-ip 127.0.0.1 --port 10095 --wav-path ../audio/asr_example.wav
# 2pass client
./funasr-wss-client-2pass --server-ip 127.0.0.1 --port 10095 --wav-path ../audio/asr_example.pcm
```
## API-reference
```shell
./funasr-wss-client --server-ip <string>
--port <string>
--wav-path <string>
[--thread-num <int>]
[--hotword <string>]
[---is-ssl <`1` deflaut, where to connect with ssl, if set `0` to close ssl>]
[--use-itn <int> use-itn is 1 means use itn, 0 means not use itn]
./funasr-wss-client-2pass --server-ip <string>
--port <string>
[--record <1 means use record>]
[--thread-num <int>]
[--hotword <string>]
[--is-ssl <`1` deflaut, where to connect with ssl, if set `0` to close ssl>]
[--wav-path < the input could be: pcm_path, e.g.: asr_example.pcm;
wav.scp, kaldi style wav list (wav_id \t wav_path)>]
[--use-itn <int> use-itn is 1 means use itn, 0 means not use itn]
```
## How to build your websocket client
required openssl lib
```shell
apt-get install libssl-dev #ubuntu
# yum install openssl-devel #centos
# brew install openssl (set system variable, e.g.: export OPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@1.1) #mac
cd websocket_client
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=release ..
make
```
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.16)
project(FunASRWebscoket)
set(CMAKE_CXX_STANDARD 14 CACHE STRING "The C++ version to be used.")
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
option(ENABLE_WEBSOCKET "Whether to build websocket server" ON)
option(ENABLE_PORTAUDIO "Whether to build websocket server" ON)
if(ENABLE_WEBSOCKET)
# cmake_policy(SET CMP0135 NEW)
include(FetchContent)
FetchContent_Declare(websocketpp
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
GIT_TAG 0.8.2
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/websocket
)
FetchContent_MakeAvailable(websocketpp)
include_directories(${PROJECT_SOURCE_DIR}/third_party/websocket)
FetchContent_Declare(asio
URL https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-24-0.tar.gz
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/asio
)
FetchContent_MakeAvailable(asio)
include_directories(${PROJECT_SOURCE_DIR}/third_party/asio/asio/include)
FetchContent_Declare(json
URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.tar.gz
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/json
)
FetchContent_MakeAvailable(json)
include_directories(${PROJECT_SOURCE_DIR}/third_party/json/include)
endif()
if(ENABLE_PORTAUDIO)
include(FetchContent)
set(portaudio_URL "http://files.portaudio.com/archives/pa_stable_v190700_20210406.tgz")
set(portaudio_URL2 "https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/pa_stable_v190700_20210406.tgz")
set(portaudio_HASH "SHA256=47efbf42c77c19a05d22e627d42873e991ec0c1357219c0d74ce6a2948cb2def")
FetchContent_Declare(portaudio
URL
${portaudio_URL}
${portaudio_URL2}
URL_HASH ${portaudio_HASH}
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/portaudio
)
FetchContent_MakeAvailable(portaudio)
include_directories(${PROJECT_SOURCE_DIR}/third_party/portaudio/include)
endif()
include_directories(${CMAKE_SOURCE_DIR}/include)
# install openssl first apt-get install libssl-dev
find_package(OpenSSL REQUIRED)
if(APPLE)
set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR})
include_directories(${OPENSSL_INCLUDE_DIR})
link_directories(${OPENSSL_ROOT_DIR}/lib)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
add_compile_options(-arch arm64)
endif()
add_executable(funasr-wss-client "funasr-wss-client.cpp" "audio.cpp" "resample.cpp")
add_executable(funasr-wss-client-2pass "funasr-wss-client-2pass.cpp" "audio.cpp" "resample.cpp" "microphone.cpp")
target_link_libraries(funasr-wss-client PUBLIC ssl crypto pthread)
target_link_libraries(funasr-wss-client-2pass PUBLIC ssl crypto pthread portaudio)
@@ -0,0 +1,619 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fstream>
#include <iostream>
#include <assert.h>
#include <cstring>
#include <memory>
#include "audio.h"
#include "resample.h"
using namespace std;
namespace funasr {
#define S_BEGIN 0
#define S_MIDDLE 1
#define S_END 2
#define S_ALL 3
#define S_ERR 4
#define MODEL_SAMPLE_RATE 16000
// see http://soundfile.sapp.org/doc/WaveFormat/
// Note: We assume little endian here
struct WaveHeader {
bool Validate() const {
// F F I R
if (chunk_id != 0x46464952) {
printf("Expected chunk_id RIFF. Given: 0x%08x\n", chunk_id);
return false;
}
// E V A W
if (format != 0x45564157) {
printf("Expected format WAVE. Given: 0x%08x\n", format);
return false;
}
if (subchunk1_id != 0x20746d66) {
printf("Expected subchunk1_id 0x20746d66. Given: 0x%08x\n",
subchunk1_id);
return false;
}
if (subchunk1_size != 16) { // 16 for PCM
printf("Expected subchunk1_size 16. Given: %d\n",
subchunk1_size);
return false;
}
if (audio_format != 1) { // 1 for PCM
printf("Expected audio_format 1. Given: %d\n", audio_format);
return false;
}
if (num_channels != 1) { // we support only single channel for now
printf("Expected single channel. Given: %d\n", num_channels);
return false;
}
if (byte_rate != (sample_rate * num_channels * bits_per_sample / 8)) {
return false;
}
if (block_align != (num_channels * bits_per_sample / 8)) {
return false;
}
if (bits_per_sample != 16) { // we support only 16 bits per sample
printf("Expected bits_per_sample 16. Given: %d\n",
bits_per_sample);
return false;
}
return true;
}
// See https://en.wikipedia.org/wiki/WAV#Metadata and
// https://www.robotplanet.dk/audio/wav_meta_data/riff_mci.pdf
void SeekToDataChunk(std::istream &is) {
// a t a d
while (is && subchunk2_id != 0x61746164) {
// const char *p = reinterpret_cast<const char *>(&subchunk2_id);
// printf("Skip chunk (%x): %c%c%c%c of size: %d\n", subchunk2_id, p[0],
// p[1], p[2], p[3], subchunk2_size);
is.seekg(subchunk2_size, std::istream::cur);
is.read(reinterpret_cast<char *>(&subchunk2_id), sizeof(int32_t));
is.read(reinterpret_cast<char *>(&subchunk2_size), sizeof(int32_t));
}
}
int32_t chunk_id;
int32_t chunk_size;
int32_t format;
int32_t subchunk1_id;
int32_t subchunk1_size;
int16_t audio_format;
int16_t num_channels;
int32_t sample_rate;
int32_t byte_rate;
int16_t block_align;
int16_t bits_per_sample;
int32_t subchunk2_id; // a tag of this chunk
int32_t subchunk2_size; // size of subchunk2
};
static_assert(sizeof(WaveHeader) == WAV_HEADER_SIZE, "");
class AudioWindow {
private:
int *window;
int in_idx;
int out_idx;
int sum;
int window_size = 0;
public:
AudioWindow(int window_size) : window_size(window_size)
{
window = (int *)calloc(sizeof(int), window_size + 1);
in_idx = 0;
out_idx = 1;
sum = 0;
};
~AudioWindow(){
free(window);
};
int put(int val)
{
sum = sum + val - window[out_idx];
window[in_idx] = val;
in_idx = in_idx == window_size ? 0 : in_idx + 1;
out_idx = out_idx == window_size ? 0 : out_idx + 1;
return sum;
};
};
AudioFrame::AudioFrame(){}
AudioFrame::AudioFrame(int len) : len(len)
{
start = 0;
}
AudioFrame::~AudioFrame(){};
int AudioFrame::SetStart(int val)
{
start = val < 0 ? 0 : val;
return start;
}
int AudioFrame::SetEnd(int val)
{
end = val;
len = end - start;
return end;
}
int AudioFrame::GetStart()
{
return start;
}
int AudioFrame::GetLen()
{
return len;
}
int AudioFrame::Disp()
{
cout << "Not imp!!!!" << endl;
return 0;
}
Audio::Audio(int data_type) : data_type(data_type)
{
speech_buff = NULL;
speech_data = NULL;
align_size = 1360;
}
Audio::Audio(int data_type, int size) : data_type(data_type)
{
speech_buff = NULL;
speech_data = NULL;
align_size = (float)size;
}
Audio::~Audio()
{
if (speech_buff != NULL) {
free(speech_buff);
}
if (speech_data != NULL) {
free(speech_data);
}
if (speech_char != NULL) {
free(speech_char);
}
}
void Audio::Disp()
{
cout << "Audio time is " << (float)speech_len / MODEL_SAMPLE_RATE << " s. len is " << speech_len << endl;
}
float Audio::GetTimeLen()
{
return (float)speech_len / MODEL_SAMPLE_RATE;
}
void Audio::WavResample(int32_t sampling_rate, const float *waveform,
int32_t n)
{
cout << "Creating a resampler:\n"
<< " in_sample_rate: "<< sampling_rate << "\n"
<< " output_sample_rate: " << static_cast<int32_t>(MODEL_SAMPLE_RATE)<< endl;
float min_freq =
std::min<int32_t>(sampling_rate, MODEL_SAMPLE_RATE);
float lowpass_cutoff = 0.99 * 0.5 * min_freq;
int32_t lowpass_filter_width = 6;
auto resampler = std::make_unique<LinearResample>(
sampling_rate, MODEL_SAMPLE_RATE, lowpass_cutoff, lowpass_filter_width);
std::vector<float> samples;
resampler->Resample(waveform, n, true, &samples);
//reset speech_data
speech_len = samples.size();
if (speech_data != NULL) {
free(speech_data);
}
speech_data = (float*)malloc(sizeof(float) * speech_len);
memset(speech_data, 0, sizeof(float) * speech_len);
copy(samples.begin(), samples.end(), speech_data);
}
bool Audio::LoadWav(const char *filename, int32_t* sampling_rate, bool resample)
{
WaveHeader header;
if (speech_data != NULL) {
free(speech_data);
}
if (speech_buff != NULL) {
free(speech_buff);
}
offset = 0;
std::ifstream is(filename, std::ifstream::binary);
is.read(reinterpret_cast<char *>(&header), sizeof(header));
if(!is){
cout << "Failed to read " << filename << endl;
return false;
}
if (!header.Validate()) {
return false;
}
header.SeekToDataChunk(is);
if (!is) {
return false;
}
if (!header.Validate()) {
return false;
}
header.SeekToDataChunk(is);
if (!is) {
return false;
}
*sampling_rate = header.sample_rate;
// header.subchunk2_size contains the number of bytes in the data.
// As we assume each sample contains two bytes, so it is divided by 2 here
speech_len = header.subchunk2_size / 2;
speech_buff = (int16_t *)malloc(sizeof(int16_t) * speech_len);
if (speech_buff)
{
memset(speech_buff, 0, sizeof(int16_t) * speech_len);
is.read(reinterpret_cast<char *>(speech_buff), header.subchunk2_size);
if (!is) {
cout << "Failed to read " << filename<< endl;
return false;
}
speech_data = (float*)malloc(sizeof(float) * speech_len);
memset(speech_data, 0, sizeof(float) * speech_len);
float scale = 1;
if (data_type == 1) {
scale = 32768;
}
for (int32_t i = 0; i != speech_len; ++i) {
speech_data[i] = (float)speech_buff[i] / scale;
}
//resample
if(resample && *sampling_rate != 16000){
WavResample(*sampling_rate, speech_data, speech_len);
}
AudioFrame* frame = new AudioFrame(speech_len);
frame_queue.push(frame);
return true;
}
else
return false;
}
bool Audio::LoadWav2Char(const char *filename, int32_t* sampling_rate)
{
WaveHeader header;
if (speech_char != NULL) {
free(speech_char);
}
offset = 0;
std::ifstream is(filename, std::ifstream::binary);
is.read(reinterpret_cast<char *>(&header), sizeof(header));
if(!is){
cout << "Failed to read " << filename<< endl;
return false;
}
if (!header.Validate()) {
return false;
}
header.SeekToDataChunk(is);
if (!is) {
return false;
}
if (!header.Validate()) {
return false;
}
header.SeekToDataChunk(is);
if (!is) {
return false;
}
*sampling_rate = header.sample_rate;
// header.subchunk2_size contains the number of bytes in the data.
// As we assume each sample contains two bytes, so it is divided by 2 here
speech_len = header.subchunk2_size / 2;
speech_char = (char *)malloc(header.subchunk2_size);
memset(speech_char, 0, header.subchunk2_size);
is.read(speech_char, header.subchunk2_size);
return true;
}
bool Audio::LoadWav(const char* buf, int n_file_len, int32_t* sampling_rate)
{
WaveHeader header;
if (speech_data != NULL) {
free(speech_data);
}
if (speech_buff != NULL) {
free(speech_buff);
}
offset = 0;
std::memcpy(&header, buf, sizeof(header));
*sampling_rate = header.sample_rate;
speech_len = header.subchunk2_size / 2;
speech_buff = (int16_t *)malloc(sizeof(int16_t) * speech_len);
if (speech_buff)
{
memset(speech_buff, 0, sizeof(int16_t) * speech_len);
memcpy((void*)speech_buff, (const void*)(buf + WAV_HEADER_SIZE), speech_len * sizeof(int16_t));
speech_data = (float*)malloc(sizeof(float) * speech_len);
memset(speech_data, 0, sizeof(float) * speech_len);
float scale = 1;
if (data_type == 1) {
scale = 32768;
}
for (int32_t i = 0; i != speech_len; ++i) {
speech_data[i] = (float)speech_buff[i] / scale;
}
//resample
if(*sampling_rate != 16000){
WavResample(*sampling_rate, speech_data, speech_len);
}
AudioFrame* frame = new AudioFrame(speech_len);
frame_queue.push(frame);
return true;
}
else
return false;
}
bool Audio::LoadPcmwav(const char* buf, int n_buf_len, int32_t* sampling_rate)
{
if (speech_data != NULL) {
free(speech_data);
}
if (speech_buff != NULL) {
free(speech_buff);
}
offset = 0;
speech_len = n_buf_len / 2;
speech_buff = (int16_t*)malloc(sizeof(int16_t) * speech_len);
if (speech_buff)
{
memset(speech_buff, 0, sizeof(int16_t) * speech_len);
memcpy((void*)speech_buff, (const void*)buf, speech_len * sizeof(int16_t));
speech_data = (float*)malloc(sizeof(float) * speech_len);
memset(speech_data, 0, sizeof(float) * speech_len);
float scale = 1;
if (data_type == 1) {
scale = 32768;
}
for (int32_t i = 0; i != speech_len; ++i) {
speech_data[i] = (float)speech_buff[i] / scale;
}
//resample
if(*sampling_rate != 16000){
WavResample(*sampling_rate, speech_data, speech_len);
}
AudioFrame* frame = new AudioFrame(speech_len);
frame_queue.push(frame);
return true;
}
else
return false;
}
bool Audio::LoadPcmwav(const char* filename, int32_t* sampling_rate, bool resample)
{
if (speech_data != NULL) {
free(speech_data);
}
if (speech_buff != NULL) {
free(speech_buff);
}
offset = 0;
FILE* fp;
fp = fopen(filename, "rb");
if (fp == nullptr)
{
cout << "Failed to read " << filename<< endl;
return false;
}
fseek(fp, 0, SEEK_END);
uint32_t n_file_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
speech_len = (n_file_len) / 2;
speech_buff = (int16_t*)malloc(sizeof(int16_t) * speech_len);
if (speech_buff)
{
memset(speech_buff, 0, sizeof(int16_t) * speech_len);
int ret = fread(speech_buff, sizeof(int16_t), speech_len, fp);
fclose(fp);
speech_data = (float*)malloc(sizeof(float) * speech_len);
memset(speech_data, 0, sizeof(float) * speech_len);
float scale = 1;
if (data_type == 1) {
scale = 32768;
}
for (int32_t i = 0; i != speech_len; ++i) {
speech_data[i] = (float)speech_buff[i] / scale;
}
//resample
if(resample && *sampling_rate != 16000){
WavResample(*sampling_rate, speech_data, speech_len);
}
AudioFrame* frame = new AudioFrame(speech_len);
frame_queue.push(frame);
return true;
}
else
return false;
}
bool Audio::LoadPcmwav2Char(const char* filename, int32_t* sampling_rate)
{
if (speech_char != NULL) {
free(speech_char);
}
offset = 0;
FILE* fp;
fp = fopen(filename, "rb");
if (fp == nullptr)
{
cout << "Failed to read " << filename<< endl;
return false;
}
fseek(fp, 0, SEEK_END);
uint32_t n_file_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
speech_len = (n_file_len) / 2;
speech_char = (char *)malloc(n_file_len);
memset(speech_char, 0, n_file_len);
fread(speech_char, sizeof(int16_t), n_file_len/2, fp);
fclose(fp);
return true;
}
bool Audio::LoadOthers2Char(const char* filename)
{
if (speech_char != NULL) {
free(speech_char);
}
FILE* fp;
fp = fopen(filename, "rb");
if (fp == nullptr)
{
cout << "Failed to read " << filename << endl;
return false;
}
fseek(fp, 0, SEEK_END);
uint32_t n_file_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
speech_len = n_file_len;
speech_char = (char *)malloc(n_file_len);
memset(speech_char, 0, n_file_len);
fread(speech_char, 1, n_file_len, fp);
fclose(fp);
return true;
}
int Audio::FetchChunck(float *&dout, int len)
{
if (offset >= speech_align_len) {
dout = NULL;
return S_ERR;
} else if (offset == speech_align_len - len) {
dout = speech_data + offset;
offset = speech_align_len;
// 临时解决
AudioFrame *frame = frame_queue.front();
frame_queue.pop();
delete frame;
return S_END;
} else {
dout = speech_data + offset;
offset += len;
return S_MIDDLE;
}
}
int Audio::Fetch(float *&dout, int &len, int &flag)
{
if (frame_queue.size() > 0) {
AudioFrame *frame = frame_queue.front();
frame_queue.pop();
dout = speech_data + frame->GetStart();
len = frame->GetLen();
delete frame;
flag = S_END;
return 1;
} else {
return 0;
}
}
void Audio::Padding()
{
float num_samples = speech_len;
float frame_length = 400;
float frame_shift = 160;
float num_frames = floor((num_samples + (frame_shift / 2)) / frame_shift);
float num_new_samples = (num_frames - 1) * frame_shift + frame_length;
float num_padding = num_new_samples - num_samples;
float num_left_padding = (frame_length - frame_shift) / 2;
float num_right_padding = num_padding - num_left_padding;
float *new_data = (float *)malloc(num_new_samples * sizeof(float));
int i;
int tmp_off = 0;
for (i = 0; i < num_left_padding; i++) {
int ii = num_left_padding - i - 1;
new_data[i] = speech_data[ii];
}
tmp_off = num_left_padding;
memcpy(new_data + tmp_off, speech_data, speech_len * sizeof(float));
tmp_off += speech_len;
for (i = 0; i < num_right_padding; i++) {
int ii = speech_len - i - 1;
new_data[tmp_off + i] = speech_data[ii];
}
free(speech_data);
speech_data = new_data;
speech_len = num_new_samples;
AudioFrame *frame = new AudioFrame(num_new_samples);
frame_queue.push(frame);
frame = frame_queue.front();
frame_queue.pop();
delete frame;
}
} // namespace funasr
@@ -0,0 +1,716 @@
/**
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
* Reserved. MIT License (https://opensource.org/licenses/MIT)
*/
/* 2022-2023 by zhaomingwork */
// client for websocket, support multiple threads
// ./funasr-wss-client --server-ip <string>
// --port <string>
// --wav-path <string>
// [--thread-num <int>]
// [--is-ssl <int>] [--]
// [--version] [-h]
// example:
// ./funasr-wss-client --server-ip 127.0.0.1 --port 10095 --wav-path test.wav
// --thread-num 1 --is-ssl 1
#define ASIO_STANDALONE 1
#include <websocketpp/client.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <fstream>
#include <atomic>
#include <thread>
#include "audio.h"
#include "nlohmann/json.hpp"
#include "tclap/CmdLine.h"
#include "portaudio.h"
#include "microphone.h"
/**
* Define a semi-cross platform helper method that waits/sleeps for a bit.
*/
void WaitABit() {
#ifdef WIN32
Sleep(1000);
#else
usleep(1000);
#endif
}
std::atomic<int> wav_index(0);
bool IsTargetFile(const std::string& filename, const std::string target) {
std::size_t pos = filename.find_last_of(".");
if (pos == std::string::npos) {
return false;
}
std::string extension = filename.substr(pos + 1);
return (extension == target);
}
void Trim(std::string *str) {
const char *white_chars = " \t\n\r\f\v";
std::string::size_type pos = str->find_last_not_of(white_chars);
if (pos != std::string::npos) {
str->erase(pos + 1);
pos = str->find_first_not_of(white_chars);
if (pos != std::string::npos) str->erase(0, pos);
} else {
str->erase(str->begin(), str->end());
}
}
void SplitStringToVector(const std::string &full, const char *delim,
bool omit_empty_strings,
std::vector<std::string> *out) {
size_t start = 0, found = 0, end = full.size();
out->clear();
while (found != std::string::npos) {
found = full.find_first_of(delim, start);
// start != end condition is for when the delimiter is at the end
if (!omit_empty_strings || (found != start && start != end))
out->push_back(full.substr(start, found - start));
start = found + 1;
}
}
void ExtractHws(string hws_file, unordered_map<string, int> &hws_map)
{
if(hws_file.empty()){
return;
}
std::string line;
std::ifstream ifs_hws(hws_file.c_str());
if(!ifs_hws.is_open()){
cout << "Unable to open hotwords file: " << hws_file
<< ". If you have not set hotwords, please ignore this message." << endl;
return;
}
while (getline(ifs_hws, line)) {
Trim(&line);
if (line.empty()) {
continue;
}
float score = 1.0f;
std::vector<std::string> text;
SplitStringToVector(line, " ", true, &text);
if (text.size() > 1) {
try{
score = std::stof(text[text.size() - 1]);
}catch (std::exception const &e)
{
cout << e.what() << endl;
continue;
}
} else {
continue;
}
std::string hotword = "";
for (size_t i = 0; i < text.size()-1; ++i) {
hotword = hotword + text[i];
if(i != text.size()-2){
hotword = hotword + " ";
}
}
hws_map.emplace(hotword, score);
}
ifs_hws.close();
}
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
context_ptr;
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
context_ptr OnTlsInit(websocketpp::connection_hdl) {
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
asio::ssl::context::sslv23);
try {
ctx->set_options(
asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 |
asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);
} catch (std::exception& e) {
cout << e.what() << endl;
}
return ctx;
}
// template for tls or not config
template <typename T>
class WebsocketClient {
public:
// typedef websocketpp::client<T> client;
// typedef websocketpp::client<websocketpp::config::asio_tls_client>
// wss_client;
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
WebsocketClient(int is_ssl) : m_open(false), m_done(false) {
// set up access channels to only log interesting things
m_client.clear_access_channels(websocketpp::log::alevel::all);
m_client.set_access_channels(websocketpp::log::alevel::connect);
m_client.set_access_channels(websocketpp::log::alevel::disconnect);
m_client.set_access_channels(websocketpp::log::alevel::app);
// Initialize the Asio transport policy
m_client.init_asio();
// Bind the handlers we are using
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
m_client.set_open_handler(bind(&WebsocketClient::on_open, this, _1));
m_client.set_close_handler(bind(&WebsocketClient::on_close, this, _1));
m_client.set_message_handler(
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
on_message(hdl, msg);
});
m_client.set_fail_handler(bind(&WebsocketClient::on_fail, this, _1));
m_client.clear_access_channels(websocketpp::log::alevel::all);
}
void on_message(websocketpp::connection_hdl hdl, message_ptr msg) {
const std::string& payload = msg->get_payload();
switch (msg->get_opcode()) {
case websocketpp::frame::opcode::text:
nlohmann::json jsonresult = nlohmann::json::parse(payload);
cout << "Thread: " << this_thread::get_id()
<< ",on_message = " << payload << endl;
if (jsonresult["is_final"] == true) {
websocketpp::lib::error_code ec;
m_client.close(hdl, websocketpp::close::status::going_away, "", ec);
if (ec) {
cout << "Error closing connection " << ec.message() << endl;
}
}
}
}
// This method will block until the connection is complete
void run(const std::string& uri, const std::vector<string>& wav_list,
const std::vector<string>& wav_ids, int audio_fs, std::string asr_mode,
std::vector<int> chunk_size, const std::unordered_map<std::string, int>& hws_map,
bool is_record=false, int use_itn=1) {
// Create a new connection to the given URI
websocketpp::lib::error_code ec;
typename websocketpp::client<T>::connection_ptr con =
m_client.get_connection(uri, ec);
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Get Connection Error: " + ec.message());
return;
}
// Grab a handle for this connection so we can talk to it in a thread
// safe manor after the event loop starts.
m_hdl = con->get_handle();
// Queue the connection. No DNS queries or network connections will be
// made until the io_service event loop is run.
m_client.connect(con);
// Create a thread to run the ASIO io_service event loop
websocketpp::lib::thread asio_thread(&websocketpp::client<T>::run,
&m_client);
if(is_record){
send_rec_data(asr_mode, chunk_size, hws_map, use_itn);
}else{
send_wav_data(wav_list[0], wav_ids[0], audio_fs, asr_mode, chunk_size, hws_map, use_itn);
}
WaitABit();
asio_thread.join();
}
// The open handler will signal that we are ready to start sending data
void on_open(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection opened, starting data!");
scoped_lock guard(m_lock);
m_open = true;
}
// The close handler will signal that we should stop sending data
void on_close(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection closed, stopping data!");
scoped_lock guard(m_lock);
m_done = true;
}
// The fail handler will signal that we should stop sending data
void on_fail(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection failed, stopping data!");
scoped_lock guard(m_lock);
m_done = true;
}
// send wav to server
void send_wav_data(string wav_path, string wav_id, int audio_fs, std::string asr_mode,
std::vector<int> chunk_vector, const std::unordered_map<std::string, int>& hws_map,
int use_itn) {
uint64_t count = 0;
std::stringstream val;
funasr::Audio audio(1);
int32_t sampling_rate = audio_fs;
std::string wav_format = "pcm";
if (IsTargetFile(wav_path.c_str(), "wav")) {
if (!audio.LoadWav(wav_path.c_str(), &sampling_rate, false))
return;
} else if (IsTargetFile(wav_path.c_str(), "pcm")) {
if (!audio.LoadPcmwav(wav_path.c_str(), &sampling_rate, false)) return;
} else {
wav_format = "others";
if (!audio.LoadOthers2Char(wav_path.c_str())) return;
}
float* buff;
int len;
int flag = 0;
bool wait = false;
while (1) {
{
scoped_lock guard(m_lock);
// If the connection has been closed, stop generating data
if (m_done) {
break;
}
// If the connection hasn't been opened yet wait a bit and retry
if (!m_open) {
wait = true;
} else {
break;
}
}
if (wait) {
// cout << "wait.." << m_open;
WaitABit();
continue;
}
}
websocketpp::lib::error_code ec;
nlohmann::json jsonbegin;
nlohmann::json chunk_size = nlohmann::json::array();
chunk_size.push_back(chunk_vector[0]);
chunk_size.push_back(chunk_vector[1]);
chunk_size.push_back(chunk_vector[2]);
jsonbegin["mode"] = asr_mode;
jsonbegin["chunk_size"] = chunk_size;
jsonbegin["wav_name"] = wav_id;
jsonbegin["wav_format"] = wav_format;
jsonbegin["audio_fs"] = sampling_rate;
jsonbegin["is_speaking"] = true;
jsonbegin["itn"] = true;
if(use_itn == 0){
jsonbegin["itn"] = false;
}
if(!hws_map.empty()){
cout << "hotwords: " << endl;
for (const auto& pair : hws_map) {
cout << pair.first << " : " << pair.second << endl;
}
nlohmann::json json_map(hws_map);
std::string json_map_str = json_map.dump();
jsonbegin["hotwords"] = json_map_str;
}
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
ec);
// fetch wav data use asr engine api
if (wav_format == "pcm") {
while (audio.Fetch(buff, len, flag) > 0) {
short* iArray = new short[len];
for (size_t i = 0; i < len; ++i) {
iArray[i] = (short)(buff[i] * 32768);
}
// send data to server
int offset = 0;
int block_size = 102400;
while (offset < len) {
int send_block = 0;
if (offset + block_size <= len) {
send_block = block_size;
} else {
send_block = len - offset;
}
m_client.send(m_hdl, iArray + offset, send_block * sizeof(short),
websocketpp::frame::opcode::binary, ec);
offset += send_block;
}
cout << "sended data len=" << len * sizeof(short) << endl;
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Send Error: " + ec.message());
break;
}
delete[] iArray;
}
} else {
int offset = 0;
int block_size = 204800;
len = audio.GetSpeechLen();
char* others_buff = audio.GetSpeechChar();
while (offset < len) {
int send_block = 0;
if (offset + block_size <= len) {
send_block = block_size;
} else {
send_block = len - offset;
}
m_client.send(m_hdl, others_buff + offset, send_block,
websocketpp::frame::opcode::binary, ec);
offset += send_block;
}
cout << "sended data len=" << len << endl;
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Send Error: " + ec.message());
}
}
nlohmann::json jsonresult;
jsonresult["is_speaking"] = false;
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
ec);
WaitABit();
}
static int RecordCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void* userData)
{
std::vector<float>* buffer = static_cast<std::vector<float>*>(userData);
const float* input = static_cast<const float*>(inputBuffer);
for (unsigned int i = 0; i < framesPerBuffer; i++)
{
buffer->push_back(input[i]);
}
return paContinue;
}
void send_rec_data(std::string asr_mode, std::vector<int> chunk_vector,
const std::unordered_map<std::string, int>& hws_map, int use_itn) {
// first message
bool wait = false;
while (1) {
{
scoped_lock guard(m_lock);
// If the connection has been closed, stop generating data
if (m_done) {
break;
}
// If the connection hasn't been opened yet wait a bit and retry
if (!m_open) {
wait = true;
} else {
break;
}
}
if (wait) {
// cout << "wait.." << m_open;
WaitABit();
continue;
}
}
websocketpp::lib::error_code ec;
float sample_rate = 16000;
nlohmann::json jsonbegin;
nlohmann::json chunk_size = nlohmann::json::array();
chunk_size.push_back(chunk_vector[0]);
chunk_size.push_back(chunk_vector[1]);
chunk_size.push_back(chunk_vector[2]);
jsonbegin["mode"] = asr_mode;
jsonbegin["chunk_size"] = chunk_size;
jsonbegin["wav_name"] = "record";
jsonbegin["wav_format"] = "pcm";
jsonbegin["audio_fs"] = sample_rate;
jsonbegin["is_speaking"] = true;
jsonbegin["itn"] = true;
if(use_itn == 0){
jsonbegin["itn"] = false;
}
if(!hws_map.empty()){
cout << "hotwords: " << endl;
for (const auto& pair : hws_map) {
cout << pair.first << " : " << pair.second << endl;
}
nlohmann::json json_map(hws_map);
std::string json_map_str = json_map.dump();
jsonbegin["hotwords"] = json_map_str;
}
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
ec);
// mic
Microphone mic;
PaDeviceIndex num_devices = Pa_GetDeviceCount();
cout << "Num devices: " << num_devices << endl;
PaStreamParameters param;
param.device = Pa_GetDefaultInputDevice();
if (param.device == paNoDevice) {
cout << "No default input device found" << endl;
exit(EXIT_FAILURE);
}
cout << "Use default device: " << param.device << endl;
const PaDeviceInfo *info = Pa_GetDeviceInfo(param.device);
cout << " Name: " << info->name << endl;
cout << " Max input channels: " << info->maxInputChannels << endl;
param.channelCount = 1;
param.sampleFormat = paFloat32;
param.suggestedLatency = info->defaultLowInputLatency;
param.hostApiSpecificStreamInfo = nullptr;
PaStream *stream;
std::vector<float> buffer;
PaError err =
Pa_OpenStream(&stream, &param, nullptr, /* &outputParameters, */
sample_rate,
0, // frames per buffer
paClipOff, // we won't output out of range samples
// so don't bother clipping them
RecordCallback, &buffer);
if (err != paNoError) {
cout << "portaudio error: " << Pa_GetErrorText(err) << endl;
exit(EXIT_FAILURE);
}
err = Pa_StartStream(stream);
cout << "Started: " << endl;
if (err != paNoError) {
cout << "portaudio error: " << Pa_GetErrorText(err) << endl;
exit(EXIT_FAILURE);
}
while(true){
int len = buffer.size();
short* iArray = new short[len];
for (size_t i = 0; i < len; ++i) {
iArray[i] = (short)(buffer[i] * 32768);
}
m_client.send(m_hdl, iArray, len * sizeof(short),
websocketpp::frame::opcode::binary, ec);
buffer.clear();
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Send Error: " + ec.message());
}
Pa_Sleep(20); // sleep for 20ms
}
nlohmann::json jsonresult;
jsonresult["is_speaking"] = false;
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
ec);
err = Pa_CloseStream(stream);
if (err != paNoError) {
cout << "portaudio error: " << Pa_GetErrorText(err) << endl;
exit(EXIT_FAILURE);
}
}
websocketpp::client<T> m_client;
private:
websocketpp::connection_hdl m_hdl;
websocketpp::lib::mutex m_lock;
bool m_open;
bool m_done;
int total_num = 0;
};
int main(int argc, char* argv[]) {
TCLAP::CmdLine cmd("funasr-wss-client-2pass", ' ', "1.0");
TCLAP::ValueArg<std::string> server_ip_("", "server-ip", "server-ip", true,
"127.0.0.1", "string");
TCLAP::ValueArg<std::string> port_("", "port", "port", true, "10095",
"string");
TCLAP::ValueArg<std::string> wav_path_(
"", "wav-path",
"the input could be: wav_path, e.g.: asr_example.wav; pcm_path, e.g.: "
"asr_example.pcm; wav.scp, kaldi style wav list (wav_id \t wav_path)",
false, "", "string");
TCLAP::ValueArg<std::int32_t> audio_fs_("", "audio-fs", "the sample rate of audio", false, 16000, "int32_t");
TCLAP::ValueArg<int> record_(
"", "record",
"record is 1 means use record", false, 0,
"int");
TCLAP::ValueArg<std::string> asr_mode_("", "mode", "offline, online, 2pass",
false, "2pass", "string");
TCLAP::ValueArg<std::string> chunk_size_("", "chunk-size",
"chunk_size: 5-10-5 or 5-12-5",
false, "5-10-5", "string");
TCLAP::ValueArg<int> thread_num_("", "thread-num", "thread-num", false, 1,
"int");
TCLAP::ValueArg<int> is_ssl_(
"", "is-ssl",
"is-ssl is 1 means use wss connection, or use ws connection", false, 1,
"int");
TCLAP::ValueArg<int> use_itn_(
"", "use-itn",
"use-itn is 1 means use itn, 0 means not use itn", false, 1,
"int");
TCLAP::ValueArg<std::string> hotword_("", "hotword",
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)", false, "", "string");
cmd.add(server_ip_);
cmd.add(port_);
cmd.add(wav_path_);
cmd.add(audio_fs_);
cmd.add(asr_mode_);
cmd.add(record_);
cmd.add(chunk_size_);
cmd.add(thread_num_);
cmd.add(is_ssl_);
cmd.add(use_itn_);
cmd.add(hotword_);
cmd.parse(argc, argv);
std::string server_ip = server_ip_.getValue();
std::string port = port_.getValue();
std::string wav_path = wav_path_.getValue();
std::string asr_mode = asr_mode_.getValue();
std::string chunk_size_str = chunk_size_.getValue();
int use_itn = use_itn_.getValue();
// get chunk_size
std::vector<int> chunk_size;
std::stringstream ss(chunk_size_str);
std::string item;
while (std::getline(ss, item, '-')) {
try {
chunk_size.push_back(stoi(item));
} catch (const invalid_argument&) {
cout << "Invalid argument: " << item << endl;
exit(-1);
}
}
int threads_num = thread_num_.getValue();
int is_ssl = is_ssl_.getValue();
int is_record = record_.getValue();
std::string uri = "";
if (is_ssl == 1) {
uri = "wss://" + server_ip + ":" + port;
} else {
uri = "ws://" + server_ip + ":" + port;
}
// hotwords
std::string hotword_path = hotword_.getValue();
unordered_map<string, int> hws_map;
if(!hotword_path.empty()){
cout << "hotword path: " << hotword_path << endl;
ExtractHws(hotword_path, hws_map);
}
int audio_fs = audio_fs_.getValue();
if(is_record == 1){
std::vector<string> tmp_wav_list;
std::vector<string> tmp_wav_ids;
if (is_ssl == 1) {
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, true, use_itn);
} else {
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, true, use_itn);
}
}else{
// read wav_path
std::vector<string> wav_list;
std::vector<string> wav_ids;
string default_id = "wav_default_id";
if (IsTargetFile(wav_path, "scp")) {
ifstream in(wav_path);
if (!in.is_open()) {
printf("Failed to open scp file");
return 0;
}
string line;
while (getline(in, line)) {
istringstream iss(line);
string column1, column2;
iss >> column1 >> column2;
wav_list.emplace_back(column2);
wav_ids.emplace_back(column1);
}
in.close();
} else {
wav_list.emplace_back(wav_path);
wav_ids.emplace_back(default_id);
}
for (size_t wav_i = 0; wav_i < wav_list.size(); wav_i = wav_i + threads_num) {
std::vector<websocketpp::lib::thread> client_threads;
for (size_t i = 0; i < threads_num; i++) {
if (wav_i + i >= wav_list.size()) {
break;
}
std::vector<string> tmp_wav_list;
std::vector<string> tmp_wav_ids;
tmp_wav_list.emplace_back(wav_list[wav_i + i]);
tmp_wav_ids.emplace_back(wav_ids[wav_i + i]);
client_threads.emplace_back(
[uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, is_ssl, hws_map, use_itn]() {
if (is_ssl == 1) {
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, false, use_itn);
} else {
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, false, use_itn);
}
});
}
for (auto& t : client_threads) {
t.join();
}
}
}
}
@@ -0,0 +1,527 @@
/**
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
* Reserved. MIT License (https://opensource.org/licenses/MIT)
*/
/* 2022-2023 by zhaomingwork */
// client for websocket, support multiple threads
// ./funasr-wss-client --server-ip <string>
// --port <string>
// --wav-path <string>
// [--thread-num <int>]
// [--is-ssl <int>] [--]
// [--version] [-h]
// example:
// ./funasr-wss-client --server-ip 127.0.0.1 --port 10095 --wav-path test.wav --thread-num 1 --is-ssl 1
#define ASIO_STANDALONE 1
#include <websocketpp/client.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <fstream>
#include <atomic>
#include <thread>
#include "audio.h"
#include "nlohmann/json.hpp"
#include "tclap/CmdLine.h"
/**
* Define a semi-cross platform helper method that waits/sleeps for a bit.
*/
void WaitABit() {
#ifdef WIN32
Sleep(200);
#else
usleep(200);
#endif
}
std::atomic<int> wav_index(0);
bool IsTargetFile(const std::string& filename, const std::string target) {
std::size_t pos = filename.find_last_of(".");
if (pos == std::string::npos) {
return false;
}
std::string extension = filename.substr(pos + 1);
return (extension == target);
}
void Trim(std::string *str) {
const char *white_chars = " \t\n\r\f\v";
std::string::size_type pos = str->find_last_not_of(white_chars);
if (pos != std::string::npos) {
str->erase(pos + 1);
pos = str->find_first_not_of(white_chars);
if (pos != std::string::npos) str->erase(0, pos);
} else {
str->erase(str->begin(), str->end());
}
}
void SplitStringToVector(const std::string &full, const char *delim,
bool omit_empty_strings,
std::vector<std::string> *out) {
size_t start = 0, found = 0, end = full.size();
out->clear();
while (found != std::string::npos) {
found = full.find_first_of(delim, start);
// start != end condition is for when the delimiter is at the end
if (!omit_empty_strings || (found != start && start != end))
out->push_back(full.substr(start, found - start));
start = found + 1;
}
}
void ExtractHws(string hws_file, unordered_map<string, int> &hws_map)
{
if(hws_file.empty()){
return;
}
std::string line;
std::ifstream ifs_hws(hws_file.c_str());
if(!ifs_hws.is_open()){
cout << "Unable to open hotwords file: " << hws_file
<< ". If you have not set hotwords, please ignore this message." << endl;
return;
}
while (getline(ifs_hws, line)) {
Trim(&line);
if (line.empty()) {
continue;
}
float score = 1.0f;
std::vector<std::string> text;
SplitStringToVector(line, " ", true, &text);
if (text.size() > 1) {
try{
score = std::stof(text[text.size() - 1]);
}catch (std::exception const &e)
{
cout << e.what() << endl;
continue;
}
} else {
continue;
}
std::string hotword = "";
for (size_t i = 0; i < text.size()-1; ++i) {
hotword = hotword + text[i];
if(i != text.size()-2){
hotword = hotword + " ";
}
}
hws_map.emplace(hotword, score);
}
ifs_hws.close();
}
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr;
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
context_ptr OnTlsInit(websocketpp::connection_hdl) {
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
asio::ssl::context::sslv23);
try {
ctx->set_options(
asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 |
asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);
} catch (std::exception& e) {
cout << e.what() << endl;
}
return ctx;
}
// template for tls or not config
template <typename T>
class WebsocketClient {
public:
// typedef websocketpp::client<T> client;
// typedef websocketpp::client<websocketpp::config::asio_tls_client>
// wss_client;
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
WebsocketClient(int is_ssl) : m_open(false), m_done(false) {
// set up access channels to only log interesting things
m_client.clear_access_channels(websocketpp::log::alevel::all);
m_client.set_access_channels(websocketpp::log::alevel::connect);
m_client.set_access_channels(websocketpp::log::alevel::disconnect);
m_client.set_access_channels(websocketpp::log::alevel::app);
// Initialize the Asio transport policy
m_client.init_asio();
// Bind the handlers we are using
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
m_client.set_open_handler(bind(&WebsocketClient::on_open, this, _1));
m_client.set_close_handler(bind(&WebsocketClient::on_close, this, _1));
m_client.set_message_handler(
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
on_message(hdl, msg);
});
m_client.set_fail_handler(bind(&WebsocketClient::on_fail, this, _1));
m_client.clear_access_channels(websocketpp::log::alevel::all);
}
void on_message(websocketpp::connection_hdl hdl, message_ptr msg) {
const std::string& payload = msg->get_payload();
switch (msg->get_opcode()) {
case websocketpp::frame::opcode::text:
total_recv=total_recv+1;
cout << "Thread: " << this_thread::get_id() << ", total_recv=" << total_recv <<", on_message = " << payload << endl;
std::unique_lock<std::mutex> lock(msg_lock);
cv.notify_one();
if(close_client)
{
cout << "Thread: " << this_thread::get_id() << ", close client" << endl;
websocketpp::lib::error_code ec;
m_client.close(m_hdl, websocketpp::close::status::going_away, "", ec);
if (ec){
cout << "Error closing connection " << ec.message() << endl;
}
}
}
}
// This method will block until the connection is complete
void run(const std::string& uri, const std::vector<string>& wav_list, const std::vector<string>& wav_ids,
int audio_fs, const std::unordered_map<std::string, int>& hws_map, int use_itn=1) {
// Create a new connection to the given URI
websocketpp::lib::error_code ec;
typename websocketpp::client<T>::connection_ptr con =
m_client.get_connection(uri, ec);
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Get Connection Error: " + ec.message());
return;
}
// Grab a handle for this connection so we can talk to it in a thread
// safe manor after the event loop starts.
m_hdl = con->get_handle();
// Queue the connection. No DNS queries or network connections will be
// made until the io_service event loop is run.
m_client.connect(con);
// Create a thread to run the ASIO io_service event loop
websocketpp::lib::thread asio_thread(&websocketpp::client<T>::run,
&m_client);
bool send_hotword = true;
while(true){
int i = wav_index.fetch_add(1);
if (i >= wav_list.size()) {
break;
}
if (total_send !=0){
std::unique_lock<std::mutex> lock(msg_lock);
cv.wait(lock);
}
total_send += 1;
send_wav_data(wav_list[i], wav_ids[i], audio_fs, hws_map, send_hotword, use_itn);
if(send_hotword){
send_hotword = false;
}
}
close_client = true;
asio_thread.join();
}
// The open handler will signal that we are ready to start sending data
void on_open(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection opened, starting data!");
scoped_lock guard(m_lock);
m_open = true;
}
// The close handler will signal that we should stop sending data
void on_close(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection closed, stopping data!");
scoped_lock guard(m_lock);
m_done = true;
}
// The fail handler will signal that we should stop sending data
void on_fail(websocketpp::connection_hdl) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Connection failed, stopping data!");
scoped_lock guard(m_lock);
m_done = true;
}
// send wav to server
void send_wav_data(string wav_path, string wav_id, int audio_fs,
const std::unordered_map<std::string, int>& hws_map,
bool send_hotword, bool use_itn) {
uint64_t count = 0;
std::stringstream val;
funasr::Audio audio(1);
int32_t sampling_rate = audio_fs;
std::string wav_format = "pcm";
if(IsTargetFile(wav_path.c_str(), "pcm")){
if (!audio.LoadPcmwav(wav_path.c_str(), &sampling_rate, false))
return ;
}else{
wav_format = "others";
if (!audio.LoadOthers2Char(wav_path.c_str()))
return ;
}
float* buff;
int len;
int flag = 0;
bool wait = false;
while (1) {
{
scoped_lock guard(m_lock);
// If the connection has been closed, stop generating data
if (m_done) {
break;
}
// If the connection hasn't been opened yet wait a bit and retry
if (!m_open) {
wait = true;
} else {
break;
}
}
if (wait) {
// cout << "wait.." << m_open;
WaitABit();
continue;
}
}
websocketpp::lib::error_code ec;
nlohmann::json jsonbegin;
nlohmann::json chunk_size = nlohmann::json::array();
chunk_size.push_back(5);
chunk_size.push_back(10);
chunk_size.push_back(5);
jsonbegin["chunk_size"] = chunk_size;
jsonbegin["chunk_interval"] = 10;
jsonbegin["wav_name"] = wav_id;
jsonbegin["wav_format"] = wav_format;
jsonbegin["audio_fs"] = sampling_rate;
jsonbegin["itn"] = true;
if(use_itn == 0){
jsonbegin["itn"] = false;
}
jsonbegin["is_speaking"] = true;
if(send_hotword){
if(!hws_map.empty()){
cout << "hotwords: " << endl;
for (const auto& pair : hws_map) {
cout << pair.first << " : " << pair.second << endl;
}
nlohmann::json json_map(hws_map);
std::string json_map_str = json_map.dump();
jsonbegin["hotwords"] = json_map_str;
}
}
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
ec);
// fetch wav data use asr engine api
if(wav_format == "pcm"){
while (audio.Fetch(buff, len, flag) > 0) {
short* iArray = new short[len];
for (size_t i = 0; i < len; ++i) {
iArray[i] = (short)(buff[i]*32768);
}
// send data to server
int offset = 0;
int block_size = 102400;
while(offset < len){
int send_block = 0;
if (offset + block_size <= len){
send_block = block_size;
}else{
send_block = len - offset;
}
m_client.send(m_hdl, iArray+offset, send_block * sizeof(short),
websocketpp::frame::opcode::binary, ec);
offset += send_block;
}
cout << "sended data len=" << len * sizeof(short) << endl;
// The most likely error that we will get is that the connection is
// not in the right state. Usually this means we tried to send a
// message to a connection that was closed or in the process of
// closing. While many errors here can be easily recovered from,
// in this simple example, we'll stop the data loop.
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Send Error: " + ec.message());
break;
}
delete[] iArray;
// WaitABit();
}
}else{
int offset = 0;
int block_size = 204800;
len = audio.GetSpeechLen();
char* others_buff = audio.GetSpeechChar();
while(offset < len){
int send_block = 0;
if (offset + block_size <= len){
send_block = block_size;
}else{
send_block = len - offset;
}
m_client.send(m_hdl, others_buff+offset, send_block,
websocketpp::frame::opcode::binary, ec);
offset += send_block;
}
cout << "sended data len=" << len << endl;
// The most likely error that we will get is that the connection is
// not in the right state. Usually this means we tried to send a
// message to a connection that was closed or in the process of
// closing. While many errors here can be easily recovered from,
// in this simple example, we'll stop the data loop.
if (ec) {
m_client.get_alog().write(websocketpp::log::alevel::app,
"Send Error: " + ec.message());
}
}
nlohmann::json jsonresult;
jsonresult["is_speaking"] = false;
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
ec);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
websocketpp::client<T> m_client;
private:
websocketpp::connection_hdl m_hdl;
websocketpp::lib::mutex m_lock;
websocketpp::lib::mutex msg_lock;
websocketpp::lib::condition_variable cv;
bool m_open;
bool m_done;
bool close_client=false;
int total_send=0;
int total_recv=0;
};
int main(int argc, char* argv[]) {
TCLAP::CmdLine cmd("funasr-wss-client", ' ', "1.0");
TCLAP::ValueArg<std::string> server_ip_("", "server-ip", "server-ip", true,
"127.0.0.1", "string");
TCLAP::ValueArg<std::string> port_("", "port", "port", true, "10095", "string");
TCLAP::ValueArg<std::string> wav_path_("", "wav-path",
"the input could be: wav_path, e.g.: asr_example.wav; pcm_path, e.g.: asr_example.pcm; wav.scp, kaldi style wav list (wav_id \t wav_path)",
true, "", "string");
TCLAP::ValueArg<std::int32_t> audio_fs_("", "audio-fs", "the sample rate of audio", false, 16000, "int32_t");
TCLAP::ValueArg<int> thread_num_("", "thread-num", "thread-num",
false, 1, "int");
TCLAP::ValueArg<int> is_ssl_(
"", "is-ssl", "is-ssl is 1 means use wss connection, or use ws connection",
false, 1, "int");
TCLAP::ValueArg<int> use_itn_(
"", "use-itn",
"use-itn is 1 means use itn, 0 means not use itn", false, 1, "int");
TCLAP::ValueArg<std::string> hotword_("", "hotword",
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)", false, "", "string");
cmd.add(server_ip_);
cmd.add(port_);
cmd.add(wav_path_);
cmd.add(audio_fs_);
cmd.add(thread_num_);
cmd.add(is_ssl_);
cmd.add(use_itn_);
cmd.add(hotword_);
cmd.parse(argc, argv);
std::string server_ip = server_ip_.getValue();
std::string port = port_.getValue();
std::string wav_path = wav_path_.getValue();
int threads_num = thread_num_.getValue();
int is_ssl = is_ssl_.getValue();
int use_itn = use_itn_.getValue();
std::vector<websocketpp::lib::thread> client_threads;
std::string uri = "";
if (is_ssl == 1) {
uri = "wss://" + server_ip + ":" + port;
} else {
uri = "ws://" + server_ip + ":" + port;
}
// hotwords
std::string hotword_path = hotword_.getValue();
unordered_map<string, int> hws_map;
if(!hotword_path.empty()){
cout << "hotword path: " << hotword_path << endl;
ExtractHws(hotword_path, hws_map);
}
// read wav_path
std::vector<string> wav_list;
std::vector<string> wav_ids;
string default_id = "wav_default_id";
if(IsTargetFile(wav_path, "scp")){
ifstream in(wav_path);
if (!in.is_open()) {
printf("Failed to open scp file");
return 0;
}
string line;
while(getline(in, line))
{
istringstream iss(line);
string column1, column2;
iss >> column1 >> column2;
wav_list.emplace_back(column2);
wav_ids.emplace_back(column1);
}
in.close();
}else{
wav_list.emplace_back(wav_path);
wav_ids.emplace_back(default_id);
}
int audio_fs = audio_fs_.getValue();
for (size_t i = 0; i < threads_num; i++) {
client_threads.emplace_back([uri, wav_list, wav_ids, audio_fs, is_ssl, hws_map, use_itn]() {
if (is_ssl == 1) {
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
c.run(uri, wav_list, wav_ids, audio_fs, hws_map, use_itn);
} else {
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
c.run(uri, wav_list, wav_ids, audio_fs, hws_map, use_itn);
}
});
}
for (auto& t : client_threads) {
t.join();
}
}
@@ -0,0 +1,67 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <queue>
#include <stdint.h>
#ifndef WAV_HEADER_SIZE
#define WAV_HEADER_SIZE 44
#endif
using namespace std;
namespace funasr {
class AudioFrame {
private:
int start;
int end;
int len;
public:
AudioFrame();
AudioFrame(int len);
~AudioFrame();
int SetStart(int val);
int SetEnd(int val);
int GetStart();
int GetLen();
int Disp();
};
class Audio {
private:
float *speech_data=nullptr;
int16_t *speech_buff=nullptr;
char* speech_char=nullptr;
int speech_len;
int speech_align_len;
int offset;
float align_size;
int data_type;
queue<AudioFrame *> frame_queue;
public:
Audio(int data_type);
Audio(int data_type, int size);
~Audio();
void Disp();
void WavResample(int32_t sampling_rate, const float *waveform, int32_t n);
bool LoadWav(const char* buf, int n_len, int32_t* sampling_rate);
bool LoadWav(const char* filename, int32_t* sampling_rate, bool resample=true);
bool LoadWav2Char(const char* filename, int32_t* sampling_rate);
bool LoadPcmwav(const char* buf, int n_file_len, int32_t* sampling_rate);
bool LoadPcmwav(const char* filename, int32_t* sampling_rate, bool resample=true);
bool LoadPcmwav2Char(const char* filename, int32_t* sampling_rate);
bool LoadOthers2Char(const char* filename);
int FetchChunck(float *&dout, int len);
int Fetch(float *&dout, int &len, int &flag);
void Padding();
float GetTimeLen();
int GetQueueSize() { return (int)frame_queue.size(); }
char* GetSpeechChar(){return speech_char;}
int GetSpeechLen(){return speech_len;}
};
} // namespace funasr
#endif
@@ -0,0 +1,683 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: Arg.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno .
* Copyright (c) 2017 Google Inc.
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_ARGUMENT_H
#define TCLAP_ARGUMENT_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <tclap/sstream.h>
#include <tclap/ArgException.h>
#include <tclap/Visitor.h>
#include <tclap/CmdLineInterface.h>
#include <tclap/ArgTraits.h>
#include <tclap/StandardTraits.h>
namespace TCLAP {
/**
* A virtual base class that defines the essential data for all arguments.
* This class, or one of its existing children, must be subclassed to do
* anything.
*/
class Arg
{
private:
/**
* Prevent accidental copying.
*/
Arg(const Arg& rhs);
/**
* Prevent accidental copying.
*/
Arg& operator=(const Arg& rhs);
/**
* Indicates whether the rest of the arguments should be ignored.
*/
static bool& ignoreRestRef() { static bool ign = false; return ign; }
/**
* The delimiter that separates an argument flag/name from the
* value.
*/
static char& delimiterRef() { static char delim = ' '; return delim; }
protected:
/**
* The single char flag used to identify the argument.
* This value (preceded by a dash {-}), can be used to identify
* an argument on the command line. The _flag can be blank,
* in fact this is how unlabeled args work. Unlabeled args must
* override appropriate functions to get correct handling. Note
* that the _flag does NOT include the dash as part of the flag.
*/
std::string _flag;
/**
* A single word namd identifying the argument.
* This value (preceded by two dashed {--}) can also be used
* to identify an argument on the command line. Note that the
* _name does NOT include the two dashes as part of the _name. The
* _name cannot be blank.
*/
std::string _name;
/**
* Description of the argument.
*/
std::string _description;
/**
* Indicating whether the argument is required.
*/
bool _required;
/**
* Label to be used in usage description. Normally set to
* "required", but can be changed when necessary.
*/
std::string _requireLabel;
/**
* Indicates whether a value is required for the argument.
* Note that the value may be required but the argument/value
* combination may not be, as specified by _required.
*/
bool _valueRequired;
/**
* Indicates whether the argument has been set.
* Indicates that a value on the command line has matched the
* name/flag of this argument and the values have been set accordingly.
*/
bool _alreadySet;
/**
* A pointer to a visitor object.
* The visitor allows special handling to occur as soon as the
* argument is matched. This defaults to NULL and should not
* be used unless absolutely necessary.
*/
Visitor* _visitor;
/**
* Whether this argument can be ignored, if desired.
*/
bool _ignoreable;
/**
* Indicates that the arg was set as part of an XOR and not on the
* command line.
*/
bool _xorSet;
bool _acceptsMultipleValues;
/**
* Performs the special handling described by the Visitor.
*/
void _checkWithVisitor() const;
/**
* Primary constructor. YOU (yes you) should NEVER construct an Arg
* directly, this is a base class that is extended by various children
* that are meant to be used. Use SwitchArg, ValueArg, MultiArg,
* UnlabeledValueArg, or UnlabeledMultiArg instead.
*
* \param flag - The flag identifying the argument.
* \param name - The name identifying the argument.
* \param desc - The description of the argument, used in the usage.
* \param req - Whether the argument is required.
* \param valreq - Whether the a value is required for the argument.
* \param v - The visitor checked by the argument. Defaults to NULL.
*/
Arg( const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
bool valreq,
Visitor* v = NULL );
public:
/**
* Destructor.
*/
virtual ~Arg();
/**
* Adds this to the specified list of Args.
* \param argList - The list to add this to.
*/
virtual void addToList( std::list<Arg*>& argList ) const;
/**
* Begin ignoring arguments since the "--" argument was specified.
*/
static void beginIgnoring() { ignoreRestRef() = true; }
/**
* Whether to ignore the rest.
*/
static bool ignoreRest() { return ignoreRestRef(); }
/**
* The delimiter that separates an argument flag/name from the
* value.
*/
static char delimiter() { return delimiterRef(); }
/**
* The char used as a place holder when SwitchArgs are combined.
* Currently set to the bell char (ASCII 7).
*/
static char blankChar() { return (char)7; }
/**
* The char that indicates the beginning of a flag. Defaults to '-', but
* clients can define TCLAP_FLAGSTARTCHAR to override.
*/
#ifndef TCLAP_FLAGSTARTCHAR
#define TCLAP_FLAGSTARTCHAR '-'
#endif
static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; }
/**
* The sting that indicates the beginning of a flag. Defaults to "-", but
* clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same
* as TCLAP_FLAGSTARTCHAR.
*/
#ifndef TCLAP_FLAGSTARTSTRING
#define TCLAP_FLAGSTARTSTRING "-"
#endif
static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; }
/**
* The sting that indicates the beginning of a name. Defaults to "--", but
* clients can define TCLAP_NAMESTARTSTRING to override.
*/
#ifndef TCLAP_NAMESTARTSTRING
#define TCLAP_NAMESTARTSTRING "--"
#endif
static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; }
/**
* The name used to identify the ignore rest argument.
*/
static const std::string ignoreNameString() { return "ignore_rest"; }
/**
* Sets the delimiter for all arguments.
* \param c - The character that delimits flags/names from values.
*/
static void setDelimiter( char c ) { delimiterRef() = c; }
/**
* Pure virtual method meant to handle the parsing and value assignment
* of the string on the command line.
* \param i - Pointer the the current argument in the list.
* \param args - Mutable list of strings. What is
* passed in from main.
*/
virtual bool processArg(int *i, std::vector<std::string>& args) = 0;
/**
* Operator ==.
* Equality operator. Must be virtual to handle unlabeled args.
* \param a - The Arg to be compared to this.
*/
virtual bool operator==(const Arg& a) const;
/**
* Returns the argument flag.
*/
const std::string& getFlag() const;
/**
* Returns the argument name.
*/
const std::string& getName() const;
/**
* Returns the argument description.
*/
std::string getDescription() const;
/**
* Indicates whether the argument is required.
*/
virtual bool isRequired() const;
/**
* Sets _required to true. This is used by the XorHandler.
* You really have no reason to ever use it.
*/
void forceRequired();
/**
* Sets the _alreadySet value to true. This is used by the XorHandler.
* You really have no reason to ever use it.
*/
void xorSet();
/**
* Indicates whether a value must be specified for argument.
*/
bool isValueRequired() const;
/**
* Indicates whether the argument has already been set. Only true
* if the arg has been matched on the command line.
*/
bool isSet() const;
/**
* Indicates whether the argument can be ignored, if desired.
*/
bool isIgnoreable() const;
/**
* A method that tests whether a string matches this argument.
* This is generally called by the processArg() method. This
* method could be re-implemented by a child to change how
* arguments are specified on the command line.
* \param s - The string to be compared to the flag/name to determine
* whether the arg matches.
*/
virtual bool argMatches( const std::string& s ) const;
/**
* Returns a simple string representation of the argument.
* Primarily for debugging.
*/
virtual std::string toString() const;
/**
* Returns a short ID for the usage.
* \param valueId - The value used in the id.
*/
virtual std::string shortID( const std::string& valueId = "val" ) const;
/**
* Returns a long ID for the usage.
* \param valueId - The value used in the id.
*/
virtual std::string longID( const std::string& valueId = "val" ) const;
/**
* Trims a value off of the flag.
* \param flag - The string from which the flag and value will be
* trimmed. Contains the flag once the value has been trimmed.
* \param value - Where the value trimmed from the string will
* be stored.
*/
virtual void trimFlag( std::string& flag, std::string& value ) const;
/**
* Checks whether a given string has blank chars, indicating that
* it is a combined SwitchArg. If so, return true, otherwise return
* false.
* \param s - string to be checked.
*/
bool _hasBlanks( const std::string& s ) const;
/**
* Sets the requireLabel. Used by XorHandler. You shouldn't ever
* use this.
* \param s - Set the requireLabel to this value.
*/
void setRequireLabel( const std::string& s );
/**
* Used for MultiArgs and XorHandler to determine whether args
* can still be set.
*/
virtual bool allowMore();
/**
* Use by output classes to determine whether an Arg accepts
* multiple values.
*/
virtual bool acceptsMultipleValues();
/**
* Clears the Arg object and allows it to be reused by new
* command lines.
*/
virtual void reset();
};
/**
* Typedef of an Arg list iterator.
*/
typedef std::list<Arg*>::const_iterator ArgListIterator;
/**
* Typedef of an Arg vector iterator.
*/
typedef std::vector<Arg*>::const_iterator ArgVectorIterator;
/**
* Typedef of a Visitor list iterator.
*/
typedef std::list<Visitor*>::const_iterator VisitorListIterator;
/*
* Extract a value of type T from it's string representation contained
* in strVal. The ValueLike parameter used to select the correct
* specialization of ExtractValue depending on the value traits of T.
* ValueLike traits use operator>> to assign the value from strVal.
*/
template<typename T> void
ExtractValue(T &destVal, const std::string& strVal, ValueLike vl)
{
static_cast<void>(vl); // Avoid warning about unused vl
istringstream is(strVal.c_str());
int valuesRead = 0;
while ( is.good() ) {
if ( is.peek() != EOF )
#ifdef TCLAP_SETBASE_ZERO
is >> std::setbase(0) >> destVal;
#else
is >> destVal;
#endif
else
break;
valuesRead++;
}
if ( is.fail() )
throw( ArgParseException("Couldn't read argument value "
"from string '" + strVal + "'"));
if ( valuesRead > 1 )
throw( ArgParseException("More than one valid value parsed from "
"string '" + strVal + "'"));
}
/*
* Extract a value of type T from it's string representation contained
* in strVal. The ValueLike parameter used to select the correct
* specialization of ExtractValue depending on the value traits of T.
* StringLike uses assignment (operator=) to assign from strVal.
*/
template<typename T> void
ExtractValue(T &destVal, const std::string& strVal, StringLike sl)
{
static_cast<void>(sl); // Avoid warning about unused sl
SetString(destVal, strVal);
}
//////////////////////////////////////////////////////////////////////
//BEGIN Arg.cpp
//////////////////////////////////////////////////////////////////////
inline Arg::Arg(const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
bool valreq,
Visitor* v) :
_flag(flag),
_name(name),
_description(desc),
_required(req),
_requireLabel("required"),
_valueRequired(valreq),
_alreadySet(false),
_visitor( v ),
_ignoreable(true),
_xorSet(false),
_acceptsMultipleValues(false)
{
if ( _flag.length() > 1 )
throw(SpecificationException(
"Argument flag can only be one character long", toString() ) );
if ( _name != ignoreNameString() &&
( _flag == Arg::flagStartString() ||
_flag == Arg::nameStartString() ||
_flag == " " ) )
throw(SpecificationException("Argument flag cannot be either '" +
Arg::flagStartString() + "' or '" +
Arg::nameStartString() + "' or a space.",
toString() ) );
if ( ( _name.substr( 0, Arg::flagStartString().length() ) == Arg::flagStartString() ) ||
( _name.substr( 0, Arg::nameStartString().length() ) == Arg::nameStartString() ) ||
( _name.find( " ", 0 ) != std::string::npos ) )
throw(SpecificationException("Argument name begin with either '" +
Arg::flagStartString() + "' or '" +
Arg::nameStartString() + "' or space.",
toString() ) );
}
inline Arg::~Arg() { }
inline std::string Arg::shortID( const std::string& valueId ) const
{
std::string id = "";
if ( _flag != "" )
id = Arg::flagStartString() + _flag;
else
id = Arg::nameStartString() + _name;
if ( _valueRequired )
id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">";
if ( !_required )
id = "[" + id + "]";
return id;
}
inline std::string Arg::longID( const std::string& valueId ) const
{
std::string id = "";
if ( _flag != "" )
{
id += Arg::flagStartString() + _flag;
if ( _valueRequired )
id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">";
id += ", ";
}
id += Arg::nameStartString() + _name;
if ( _valueRequired )
id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">";
return id;
}
inline bool Arg::operator==(const Arg& a) const
{
if ( ( _flag != "" && _flag == a._flag ) || _name == a._name)
return true;
else
return false;
}
inline std::string Arg::getDescription() const
{
std::string desc = "";
if ( _required )
desc = "(" + _requireLabel + ") ";
// if ( _valueRequired )
// desc += "(value required) ";
desc += _description;
return desc;
}
inline const std::string& Arg::getFlag() const { return _flag; }
inline const std::string& Arg::getName() const { return _name; }
inline bool Arg::isRequired() const { return _required; }
inline bool Arg::isValueRequired() const { return _valueRequired; }
inline bool Arg::isSet() const
{
if ( _alreadySet && !_xorSet )
return true;
else
return false;
}
inline bool Arg::isIgnoreable() const { return _ignoreable; }
inline void Arg::setRequireLabel( const std::string& s)
{
_requireLabel = s;
}
inline bool Arg::argMatches( const std::string& argFlag ) const
{
if ( ( argFlag == Arg::flagStartString() + _flag && _flag != "" ) ||
argFlag == Arg::nameStartString() + _name )
return true;
else
return false;
}
inline std::string Arg::toString() const
{
std::string s = "";
if ( _flag != "" )
s += Arg::flagStartString() + _flag + " ";
s += "(" + Arg::nameStartString() + _name + ")";
return s;
}
inline void Arg::_checkWithVisitor() const
{
if ( _visitor != NULL )
_visitor->visit();
}
/**
* Implementation of trimFlag.
*/
inline void Arg::trimFlag(std::string& flag, std::string& value) const
{
int stop = 0;
for ( int i = 0; static_cast<unsigned int>(i) < flag.length(); i++ )
if ( flag[i] == Arg::delimiter() )
{
stop = i;
break;
}
if ( stop > 1 )
{
value = flag.substr(stop+1);
flag = flag.substr(0,stop);
}
}
/**
* Implementation of _hasBlanks.
*/
inline bool Arg::_hasBlanks( const std::string& s ) const
{
for ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ )
if ( s[i] == Arg::blankChar() )
return true;
return false;
}
inline void Arg::forceRequired()
{
_required = true;
}
inline void Arg::xorSet()
{
_alreadySet = true;
_xorSet = true;
}
/**
* Overridden by Args that need to added to the end of the list.
*/
inline void Arg::addToList( std::list<Arg*>& argList ) const
{
argList.push_front( const_cast<Arg*>(this) );
}
inline bool Arg::allowMore()
{
return false;
}
inline bool Arg::acceptsMultipleValues()
{
return _acceptsMultipleValues;
}
inline void Arg::reset()
{
_xorSet = false;
_alreadySet = false;
}
//////////////////////////////////////////////////////////////////////
//END Arg.cpp
//////////////////////////////////////////////////////////////////////
} //namespace TCLAP
#endif
@@ -0,0 +1,213 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: ArgException.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2017 Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_ARG_EXCEPTION_H
#define TCLAP_ARG_EXCEPTION_H
#include <string>
#include <exception>
namespace TCLAP {
/**
* A simple class that defines and argument exception. Should be caught
* whenever a CmdLine is created and parsed.
*/
class ArgException : public std::exception
{
public:
/**
* Constructor.
* \param text - The text of the exception.
* \param id - The text identifying the argument source.
* \param td - Text describing the type of ArgException it is.
* of the exception.
*/
ArgException( const std::string& text = "undefined exception",
const std::string& id = "undefined",
const std::string& td = "Generic ArgException")
: std::exception(),
_errorText(text),
_argId( id ),
_typeDescription(td)
{ }
/**
* Destructor.
*/
virtual ~ArgException() throw() { }
/**
* Returns the error text.
*/
std::string error() const { return ( _errorText ); }
/**
* Returns the argument id.
*/
std::string argId() const
{
if ( _argId == "undefined" )
return " ";
else
return ( "Argument: " + _argId );
}
/**
* Returns the arg id and error text.
*/
const char* what() const throw()
{
static std::string ex;
ex = _argId + " -- " + _errorText;
return ex.c_str();
}
/**
* Returns the type of the exception. Used to explain and distinguish
* between different child exceptions.
*/
std::string typeDescription() const
{
return _typeDescription;
}
private:
/**
* The text of the exception message.
*/
std::string _errorText;
/**
* The argument related to this exception.
*/
std::string _argId;
/**
* Describes the type of the exception. Used to distinguish
* between different child exceptions.
*/
std::string _typeDescription;
};
/**
* Thrown from within the child Arg classes when it fails to properly
* parse the argument it has been passed.
*/
class ArgParseException : public ArgException
{
public:
/**
* Constructor.
* \param text - The text of the exception.
* \param id - The text identifying the argument source
* of the exception.
*/
ArgParseException( const std::string& text = "undefined exception",
const std::string& id = "undefined" )
: ArgException( text,
id,
std::string( "Exception found while parsing " ) +
std::string( "the value the Arg has been passed." ))
{ }
};
/**
* Thrown from CmdLine when the arguments on the command line are not
* properly specified, e.g. too many arguments, required argument missing, etc.
*/
class CmdLineParseException : public ArgException
{
public:
/**
* Constructor.
* \param text - The text of the exception.
* \param id - The text identifying the argument source
* of the exception.
*/
CmdLineParseException( const std::string& text = "undefined exception",
const std::string& id = "undefined" )
: ArgException( text,
id,
std::string( "Exception found when the values ") +
std::string( "on the command line do not meet ") +
std::string( "the requirements of the defined ") +
std::string( "Args." ))
{ }
};
/**
* Thrown from Arg and CmdLine when an Arg is improperly specified, e.g.
* same flag as another Arg, same name, etc.
*/
class SpecificationException : public ArgException
{
public:
/**
* Constructor.
* \param text - The text of the exception.
* \param id - The text identifying the argument source
* of the exception.
*/
SpecificationException( const std::string& text = "undefined exception",
const std::string& id = "undefined" )
: ArgException( text,
id,
std::string("Exception found when an Arg object ")+
std::string("is improperly defined by the ") +
std::string("developer." ))
{ }
};
/**
* Thrown when TCLAP thinks the program should exit.
*
* For example after parse error this exception will be thrown (and
* normally caught). This allows any resource to be clened properly
* before exit.
*
* If exception handling is disabled (CmdLine::setExceptionHandling),
* this exception will propagate to the call site, allowing the
* program to catch it and avoid program termination, or do it's own
* cleanup. See for example, https://sourceforge.net/p/tclap/bugs/29.
*/
class ExitException {
public:
ExitException(int estat) : _estat(estat) {}
int getExitStatus() const { return _estat; }
private:
int _estat;
};
} // namespace TCLAP
#endif
@@ -0,0 +1,122 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: ArgTraits.h
*
* Copyright (c) 2007, Daniel Aarno, Michael E. Smoot .
* Copyright (c) 2017 Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
// This is an internal tclap file, you should probably not have to
// include this directly
#ifndef TCLAP_ARGTRAITS_H
#define TCLAP_ARGTRAITS_H
namespace TCLAP {
// We use two empty structs to get compile type specialization
// function to work
/**
* A value like argument value type is a value that can be set using
* operator>>. This is the default value type.
*/
struct ValueLike {
typedef ValueLike ValueCategory;
virtual ~ValueLike() {}
};
/**
* A string like argument value type is a value that can be set using
* operator=(string). Useful if the value type contains spaces which
* will be broken up into individual tokens by operator>>.
*/
struct StringLike {
virtual ~StringLike() {}
};
/**
* A class can inherit from this object to make it have string like
* traits. This is a compile time thing and does not add any overhead
* to the inherenting class.
*/
struct StringLikeTrait {
typedef StringLike ValueCategory;
virtual ~StringLikeTrait() {}
};
/**
* A class can inherit from this object to make it have value like
* traits. This is a compile time thing and does not add any overhead
* to the inherenting class.
*/
struct ValueLikeTrait {
typedef ValueLike ValueCategory;
virtual ~ValueLikeTrait() {}
};
/**
* Arg traits are used to get compile type specialization when parsing
* argument values. Using an ArgTraits you can specify the way that
* values gets assigned to any particular type during parsing. The two
* supported types are StringLike and ValueLike. ValueLike is the
* default and means that operator>> will be used to assign values to
* the type.
*/
template<typename T>
class ArgTraits {
// This is a bit silly, but what we want to do is:
// 1) If there exists a specialization of ArgTraits for type X,
// use it.
//
// 2) If no specialization exists but X has the typename
// X::ValueCategory, use the specialization for X::ValueCategory.
//
// 3) If neither (1) nor (2) defines the trait, use the default
// which is ValueLike.
// This is the "how":
//
// test<T>(0) (where 0 is the NULL ptr) will match
// test(typename C::ValueCategory*) iff type T has the
// corresponding typedef. If it does not test(...) will be
// matched. This allows us to determine if T::ValueCategory
// exists by checking the sizeof for the test function (return
// value must have different sizeof).
template<typename C> static short test(typename C::ValueCategory*);
template<typename C> static long test(...);
static const bool hasTrait = sizeof(test<T>(0)) == sizeof(short);
template <typename C, bool>
struct DefaultArgTrait {
typedef ValueLike ValueCategory;
};
template <typename C>
struct DefaultArgTrait<C, true> {
typedef typename C::ValueCategory ValueCategory;
};
public:
typedef typename DefaultArgTrait<T, hasTrait>::ValueCategory ValueCategory;
};
} // namespace
#endif
@@ -0,0 +1,657 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: CmdLine.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_CMDLINE_H
#define TCLAP_CMDLINE_H
#include <tclap/SwitchArg.h>
#include <tclap/MultiSwitchArg.h>
#include <tclap/UnlabeledValueArg.h>
#include <tclap/UnlabeledMultiArg.h>
#include <tclap/XorHandler.h>
#include <tclap/HelpVisitor.h>
#include <tclap/VersionVisitor.h>
#include <tclap/IgnoreRestVisitor.h>
#include <tclap/CmdLineOutput.h>
#include <tclap/StdOutput.h>
#include <tclap/Constraint.h>
#include <tclap/ValuesConstraint.h>
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <stdlib.h> // Needed for exit(), which isn't defined in some envs.
namespace TCLAP {
template<typename T> void DelPtr(T ptr)
{
delete ptr;
}
template<typename C> void ClearContainer(C &c)
{
typedef typename C::value_type value_type;
std::for_each(c.begin(), c.end(), DelPtr<value_type>);
c.clear();
}
/**
* The base class that manages the command line definition and passes
* along the parsing to the appropriate Arg classes.
*/
class CmdLine : public CmdLineInterface
{
protected:
/**
* The list of arguments that will be tested against the
* command line.
*/
std::list<Arg*> _argList;
/**
* The name of the program. Set to argv[0].
*/
std::string _progName;
/**
* A message used to describe the program. Used in the usage output.
*/
std::string _message;
/**
* The version to be displayed with the --version switch.
*/
std::string _version;
/**
* The number of arguments that are required to be present on
* the command line. This is set dynamically, based on the
* Args added to the CmdLine object.
*/
int _numRequired;
/**
* The character that is used to separate the argument flag/name
* from the value. Defaults to ' ' (space).
*/
char _delimiter;
/**
* The handler that manages xoring lists of args.
*/
XorHandler _xorHandler;
/**
* A list of Args to be explicitly deleted when the destructor
* is called. At the moment, this only includes the three default
* Args.
*/
std::list<Arg*> _argDeleteOnExitList;
/**
* A list of Visitors to be explicitly deleted when the destructor
* is called. At the moment, these are the Visitors created for the
* default Args.
*/
std::list<Visitor*> _visitorDeleteOnExitList;
/**
* Object that handles all output for the CmdLine.
*/
CmdLineOutput* _output;
/**
* Should CmdLine handle parsing exceptions internally?
*/
bool _handleExceptions;
/**
* Throws an exception listing the missing args.
*/
void missingArgsException();
/**
* Checks whether a name/flag string matches entirely matches
* the Arg::blankChar. Used when multiple switches are combined
* into a single argument.
* \param s - The message to be used in the usage.
*/
bool _emptyCombined(const std::string& s);
/**
* Perform a delete ptr; operation on ptr when this object is deleted.
*/
void deleteOnExit(Arg* ptr);
/**
* Perform a delete ptr; operation on ptr when this object is deleted.
*/
void deleteOnExit(Visitor* ptr);
private:
/**
* Prevent accidental copying.
*/
CmdLine(const CmdLine& rhs);
CmdLine& operator=(const CmdLine& rhs);
/**
* Encapsulates the code common to the constructors
* (which is all of it).
*/
void _constructor();
/**
* Is set to true when a user sets the output object. We use this so
* that we don't delete objects that are created outside of this lib.
*/
bool _userSetOutput;
/**
* Whether or not to automatically create help and version switches.
*/
bool _helpAndVersion;
/**
* Whether or not to ignore unmatched args.
*/
bool _ignoreUnmatched;
public:
/**
* Command line constructor. Defines how the arguments will be
* parsed.
* \param message - The message to be used in the usage
* output.
* \param delimiter - The character that is used to separate
* the argument flag/name from the value. Defaults to ' ' (space).
* \param version - The version number to be used in the
* --version switch.
* \param helpAndVersion - Whether or not to create the Help and
* Version switches. Defaults to true.
*/
CmdLine(const std::string& message,
const char delimiter = ' ',
const std::string& version = "none",
bool helpAndVersion = true);
/**
* Deletes any resources allocated by a CmdLine object.
*/
virtual ~CmdLine();
/**
* Adds an argument to the list of arguments to be parsed.
* \param a - Argument to be added.
*/
void add( Arg& a );
/**
* An alternative add. Functionally identical.
* \param a - Argument to be added.
*/
void add( Arg* a );
/**
* Add two Args that will be xor'd. If this method is used, add does
* not need to be called.
* \param a - Argument to be added and xor'd.
* \param b - Argument to be added and xor'd.
*/
void xorAdd( Arg& a, Arg& b );
/**
* Add a list of Args that will be xor'd. If this method is used,
* add does not need to be called.
* \param xors - List of Args to be added and xor'd.
*/
void xorAdd( const std::vector<Arg*>& xors );
/**
* Parses the command line.
* \param argc - Number of arguments.
* \param argv - Array of arguments.
*/
void parse(int argc, const char * const * argv);
/**
* Parses the command line.
* \param args - A vector of strings representing the args.
* args[0] is still the program name.
*/
void parse(std::vector<std::string>& args);
/**
*
*/
CmdLineOutput* getOutput();
/**
*
*/
void setOutput(CmdLineOutput* co);
/**
*
*/
std::string& getVersion();
/**
*
*/
std::string& getProgramName();
/**
*
*/
std::list<Arg*>& getArgList();
/**
*
*/
XorHandler& getXorHandler();
/**
*
*/
char getDelimiter();
/**
*
*/
std::string& getMessage();
/**
*
*/
bool hasHelpAndVersion();
/**
* Disables or enables CmdLine's internal parsing exception handling.
*
* @param state Should CmdLine handle parsing exceptions internally?
*/
void setExceptionHandling(const bool state);
/**
* Returns the current state of the internal exception handling.
*
* @retval true Parsing exceptions are handled internally.
* @retval false Parsing exceptions are propagated to the caller.
*/
bool getExceptionHandling() const;
/**
* Allows the CmdLine object to be reused.
*/
void reset();
/**
* Allows unmatched args to be ignored. By default false.
*
* @param ignore If true the cmdline will ignore any unmatched args
* and if false it will behave as normal.
*/
void ignoreUnmatched(const bool ignore);
};
///////////////////////////////////////////////////////////////////////////////
//Begin CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////
inline CmdLine::CmdLine(const std::string& m,
char delim,
const std::string& v,
bool help )
:
_argList(std::list<Arg*>()),
_progName("not_set_yet"),
_message(m),
_version(v),
_numRequired(0),
_delimiter(delim),
_xorHandler(XorHandler()),
_argDeleteOnExitList(std::list<Arg*>()),
_visitorDeleteOnExitList(std::list<Visitor*>()),
_output(0),
_handleExceptions(true),
_userSetOutput(false),
_helpAndVersion(help),
_ignoreUnmatched(false)
{
_constructor();
}
inline CmdLine::~CmdLine()
{
ClearContainer(_argDeleteOnExitList);
ClearContainer(_visitorDeleteOnExitList);
if ( !_userSetOutput ) {
delete _output;
_output = 0;
}
}
inline void CmdLine::_constructor()
{
_output = new StdOutput;
Arg::setDelimiter( _delimiter );
Visitor* v;
if ( _helpAndVersion )
{
v = new HelpVisitor( this, &_output );
SwitchArg* help = new SwitchArg("h","help",
"Displays usage information and exits.",
false, v);
add( help );
deleteOnExit(help);
deleteOnExit(v);
v = new VersionVisitor( this, &_output );
SwitchArg* vers = new SwitchArg("","version",
"Displays version information and exits.",
false, v);
add( vers );
deleteOnExit(vers);
deleteOnExit(v);
}
v = new IgnoreRestVisitor();
SwitchArg* ignore = new SwitchArg(Arg::flagStartString(),
Arg::ignoreNameString(),
"Ignores the rest of the labeled arguments following this flag.",
false, v);
add( ignore );
deleteOnExit(ignore);
deleteOnExit(v);
}
inline void CmdLine::xorAdd( const std::vector<Arg*>& ors )
{
_xorHandler.add( ors );
for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++)
{
(*it)->forceRequired();
(*it)->setRequireLabel( "OR required" );
add( *it );
}
}
inline void CmdLine::xorAdd( Arg& a, Arg& b )
{
std::vector<Arg*> ors;
ors.push_back( &a );
ors.push_back( &b );
xorAdd( ors );
}
inline void CmdLine::add( Arg& a )
{
add( &a );
}
inline void CmdLine::add( Arg* a )
{
for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ )
if ( *a == *(*it) )
throw( SpecificationException(
"Argument with same flag/name already exists!",
a->longID() ) );
a->addToList( _argList );
if ( a->isRequired() )
_numRequired++;
}
inline void CmdLine::parse(int argc, const char * const * argv)
{
// this step is necessary so that we have easy access to
// mutable strings.
std::vector<std::string> args;
for (int i = 0; i < argc; i++)
args.push_back(argv[i]);
parse(args);
}
inline void CmdLine::parse(std::vector<std::string>& args)
{
bool shouldExit = false;
int estat = 0;
try {
if (args.empty()) {
// https://sourceforge.net/p/tclap/bugs/30/
throw CmdLineParseException("The args vector must not be empty, "
"the first entry should contain the "
"program's name.");
}
_progName = args.front();
args.erase(args.begin());
int requiredCount = 0;
for (int i = 0; static_cast<unsigned int>(i) < args.size(); i++)
{
bool matched = false;
for (ArgListIterator it = _argList.begin();
it != _argList.end(); it++) {
if ( (*it)->processArg( &i, args ) )
{
requiredCount += _xorHandler.check( *it );
matched = true;
break;
}
}
// checks to see if the argument is an empty combined
// switch and if so, then we've actually matched it
if ( !matched && _emptyCombined( args[i] ) )
matched = true;
if ( !matched && !Arg::ignoreRest() && !_ignoreUnmatched)
throw(CmdLineParseException("Couldn't find match "
"for argument",
args[i]));
}
if ( requiredCount < _numRequired )
missingArgsException();
if ( requiredCount > _numRequired )
throw(CmdLineParseException("Too many arguments!"));
} catch ( ArgException& e ) {
// If we're not handling the exceptions, rethrow.
if ( !_handleExceptions) {
throw;
}
try {
_output->failure(*this,e);
} catch ( ExitException &ee ) {
estat = ee.getExitStatus();
shouldExit = true;
}
} catch (ExitException &ee) {
// If we're not handling the exceptions, rethrow.
if ( !_handleExceptions) {
throw;
}
estat = ee.getExitStatus();
shouldExit = true;
}
if (shouldExit)
exit(estat);
}
inline bool CmdLine::_emptyCombined(const std::string& s)
{
if ( s.length() > 0 && s[0] != Arg::flagStartChar() )
return false;
for ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ )
if ( s[i] != Arg::blankChar() )
return false;
return true;
}
inline void CmdLine::missingArgsException()
{
int count = 0;
std::string missingArgList;
for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
{
if ( (*it)->isRequired() && !(*it)->isSet() )
{
missingArgList += (*it)->getName();
missingArgList += ", ";
count++;
}
}
missingArgList = missingArgList.substr(0,missingArgList.length()-2);
std::string msg;
if ( count > 1 )
msg = "Required arguments missing: ";
else
msg = "Required argument missing: ";
msg += missingArgList;
throw(CmdLineParseException(msg));
}
inline void CmdLine::deleteOnExit(Arg* ptr)
{
_argDeleteOnExitList.push_back(ptr);
}
inline void CmdLine::deleteOnExit(Visitor* ptr)
{
_visitorDeleteOnExitList.push_back(ptr);
}
inline CmdLineOutput* CmdLine::getOutput()
{
return _output;
}
inline void CmdLine::setOutput(CmdLineOutput* co)
{
if ( !_userSetOutput )
delete _output;
_userSetOutput = true;
_output = co;
}
inline std::string& CmdLine::getVersion()
{
return _version;
}
inline std::string& CmdLine::getProgramName()
{
return _progName;
}
inline std::list<Arg*>& CmdLine::getArgList()
{
return _argList;
}
inline XorHandler& CmdLine::getXorHandler()
{
return _xorHandler;
}
inline char CmdLine::getDelimiter()
{
return _delimiter;
}
inline std::string& CmdLine::getMessage()
{
return _message;
}
inline bool CmdLine::hasHelpAndVersion()
{
return _helpAndVersion;
}
inline void CmdLine::setExceptionHandling(const bool state)
{
_handleExceptions = state;
}
inline bool CmdLine::getExceptionHandling() const
{
return _handleExceptions;
}
inline void CmdLine::reset()
{
for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ )
(*it)->reset();
_progName.clear();
}
inline void CmdLine::ignoreUnmatched(const bool ignore)
{
_ignoreUnmatched = ignore;
}
///////////////////////////////////////////////////////////////////////////////
//End CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////
} //namespace TCLAP
#endif
@@ -0,0 +1,153 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: CmdLineInterface.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_COMMANDLINE_INTERFACE_H
#define TCLAP_COMMANDLINE_INTERFACE_H
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
namespace TCLAP {
class Arg;
class CmdLineOutput;
class XorHandler;
/**
* The base class that manages the command line definition and passes
* along the parsing to the appropriate Arg classes.
*/
class CmdLineInterface
{
public:
/**
* Destructor
*/
virtual ~CmdLineInterface() {}
/**
* Adds an argument to the list of arguments to be parsed.
* \param a - Argument to be added.
*/
virtual void add( Arg& a )=0;
/**
* An alternative add. Functionally identical.
* \param a - Argument to be added.
*/
virtual void add( Arg* a )=0;
/**
* Add two Args that will be xor'd.
* If this method is used, add does
* not need to be called.
* \param a - Argument to be added and xor'd.
* \param b - Argument to be added and xor'd.
*/
virtual void xorAdd( Arg& a, Arg& b )=0;
/**
* Add a list of Args that will be xor'd. If this method is used,
* add does not need to be called.
* \param xors - List of Args to be added and xor'd.
*/
virtual void xorAdd( const std::vector<Arg*>& xors )=0;
/**
* Parses the command line.
* \param argc - Number of arguments.
* \param argv - Array of arguments.
*/
virtual void parse(int argc, const char * const * argv)=0;
/**
* Parses the command line.
* \param args - A vector of strings representing the args.
* args[0] is still the program name.
*/
void parse(std::vector<std::string>& args);
/**
* Returns the CmdLineOutput object.
*/
virtual CmdLineOutput* getOutput()=0;
/**
* \param co - CmdLineOutput object that we want to use instead.
*/
virtual void setOutput(CmdLineOutput* co)=0;
/**
* Returns the version string.
*/
virtual std::string& getVersion()=0;
/**
* Returns the program name string.
*/
virtual std::string& getProgramName()=0;
/**
* Returns the argList.
*/
virtual std::list<Arg*>& getArgList()=0;
/**
* Returns the XorHandler.
*/
virtual XorHandler& getXorHandler()=0;
/**
* Returns the delimiter string.
*/
virtual char getDelimiter()=0;
/**
* Returns the message string.
*/
virtual std::string& getMessage()=0;
/**
* Indicates whether or not the help and version switches were created
* automatically.
*/
virtual bool hasHelpAndVersion()=0;
/**
* Resets the instance as if it had just been constructed so that the
* instance can be reused.
*/
virtual void reset()=0;
};
} //namespace
#endif
@@ -0,0 +1,77 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: CmdLineOutput.h
*
* Copyright (c) 2004, Michael E. Smoot
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_CMDLINEOUTPUT_H
#define TCLAP_CMDLINEOUTPUT_H
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <algorithm>
namespace TCLAP {
class CmdLineInterface;
class ArgException;
/**
* The interface that any output object must implement.
*/
class CmdLineOutput
{
public:
/**
* Virtual destructor.
*/
virtual ~CmdLineOutput() {}
/**
* Generates some sort of output for the USAGE.
* \param c - The CmdLine object the output is generated for.
*/
virtual void usage(CmdLineInterface& c)=0;
/**
* Generates some sort of output for the version.
* \param c - The CmdLine object the output is generated for.
*/
virtual void version(CmdLineInterface& c)=0;
/**
* Generates some sort of output for a failure.
* \param c - The CmdLine object the output is generated for.
* \param e - The ArgException that caused the failure.
*/
virtual void failure( CmdLineInterface& c,
ArgException& e )=0;
};
} //namespace TCLAP
#endif
@@ -0,0 +1,78 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: Constraint.h
*
* Copyright (c) 2005, Michael E. Smoot
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_CONSTRAINT_H
#define TCLAP_CONSTRAINT_H
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <stdexcept>
namespace TCLAP {
/**
* The interface that defines the interaction between the Arg and Constraint.
*/
template<class T>
class Constraint
{
public:
/**
* Returns a description of the Constraint.
*/
virtual std::string description() const =0;
/**
* Returns the short ID for the Constraint.
*/
virtual std::string shortID() const =0;
/**
* The method used to verify that the value parsed from the command
* line meets the constraint.
* \param value - The value that will be checked.
*/
virtual bool check(const T& value) const =0;
/**
* Destructor.
* Silences warnings about Constraint being a base class with virtual
* functions but without a virtual destructor.
*/
virtual ~Constraint() { ; }
static std::string shortID(Constraint<T> *constraint) {
if (!constraint)
throw std::logic_error("Cannot create a ValueArg with a NULL constraint");
return constraint->shortID();
}
};
} //namespace TCLAP
#endif
@@ -0,0 +1,303 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: DocBookOutput.h
*
* Copyright (c) 2004, Michael E. Smoot
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_DOCBOOKOUTPUT_H
#define TCLAP_DOCBOOKOUTPUT_H
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
#include <tclap/CmdLineInterface.h>
#include <tclap/CmdLineOutput.h>
#include <tclap/XorHandler.h>
#include <tclap/Arg.h>
namespace TCLAP {
/**
* A class that generates DocBook output for usage() method for the
* given CmdLine and its Args.
*/
class DocBookOutput : public CmdLineOutput
{
public:
/**
* Prints the usage to stdout. Can be overridden to
* produce alternative behavior.
* \param c - The CmdLine object the output is generated for.
*/
virtual void usage(CmdLineInterface& c);
/**
* Prints the version to stdout. Can be overridden
* to produce alternative behavior.
* \param c - The CmdLine object the output is generated for.
*/
virtual void version(CmdLineInterface& c);
/**
* Prints (to stderr) an error message, short usage
* Can be overridden to produce alternative behavior.
* \param c - The CmdLine object the output is generated for.
* \param e - The ArgException that caused the failure.
*/
virtual void failure(CmdLineInterface& c,
ArgException& e );
DocBookOutput() : theDelimiter('=') {}
protected:
/**
* Substitutes the char r for string x in string s.
* \param s - The string to operate on.
* \param r - The char to replace.
* \param x - What to replace r with.
*/
void substituteSpecialChars( std::string& s, char r, std::string& x );
void removeChar( std::string& s, char r);
void basename( std::string& s );
void printShortArg(Arg* it);
void printLongArg(Arg* it);
char theDelimiter;
};
inline void DocBookOutput::version(CmdLineInterface& _cmd)
{
std::cout << _cmd.getVersion() << std::endl;
}
inline void DocBookOutput::usage(CmdLineInterface& _cmd )
{
std::list<Arg*> argList = _cmd.getArgList();
std::string progName = _cmd.getProgramName();
std::string xversion = _cmd.getVersion();
theDelimiter = _cmd.getDelimiter();
XorHandler xorHandler = _cmd.getXorHandler();
const std::vector< std::vector<Arg*> > xorList = xorHandler.getXorList();
basename(progName);
std::cout << "<?xml version='1.0'?>" << std::endl;
std::cout << "<!DOCTYPE refentry PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\"" << std::endl;
std::cout << "\t\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\">" << std::endl << std::endl;
std::cout << "<refentry>" << std::endl;
std::cout << "<refmeta>" << std::endl;
std::cout << "<refentrytitle>" << progName << "</refentrytitle>" << std::endl;
std::cout << "<manvolnum>1</manvolnum>" << std::endl;
std::cout << "</refmeta>" << std::endl;
std::cout << "<refnamediv>" << std::endl;
std::cout << "<refname>" << progName << "</refname>" << std::endl;
std::cout << "<refpurpose>" << _cmd.getMessage() << "</refpurpose>" << std::endl;
std::cout << "</refnamediv>" << std::endl;
std::cout << "<refsynopsisdiv>" << std::endl;
std::cout << "<cmdsynopsis>" << std::endl;
std::cout << "<command>" << progName << "</command>" << std::endl;
// xor
for ( int i = 0; (unsigned int)i < xorList.size(); i++ )
{
std::cout << "<group choice='req'>" << std::endl;
for ( ArgVectorIterator it = xorList[i].begin();
it != xorList[i].end(); it++ )
printShortArg((*it));
std::cout << "</group>" << std::endl;
}
// rest of args
for (ArgListIterator it = argList.begin(); it != argList.end(); it++)
if ( !xorHandler.contains( (*it) ) )
printShortArg((*it));
std::cout << "</cmdsynopsis>" << std::endl;
std::cout << "</refsynopsisdiv>" << std::endl;
std::cout << "<refsect1>" << std::endl;
std::cout << "<title>Description</title>" << std::endl;
std::cout << "<para>" << std::endl;
std::cout << _cmd.getMessage() << std::endl;
std::cout << "</para>" << std::endl;
std::cout << "</refsect1>" << std::endl;
std::cout << "<refsect1>" << std::endl;
std::cout << "<title>Options</title>" << std::endl;
std::cout << "<variablelist>" << std::endl;
for (ArgListIterator it = argList.begin(); it != argList.end(); it++)
printLongArg((*it));
std::cout << "</variablelist>" << std::endl;
std::cout << "</refsect1>" << std::endl;
std::cout << "<refsect1>" << std::endl;
std::cout << "<title>Version</title>" << std::endl;
std::cout << "<para>" << std::endl;
std::cout << xversion << std::endl;
std::cout << "</para>" << std::endl;
std::cout << "</refsect1>" << std::endl;
std::cout << "</refentry>" << std::endl;
}
inline void DocBookOutput::failure( CmdLineInterface& _cmd,
ArgException& e )
{
static_cast<void>(_cmd); // unused
std::cout << e.what() << std::endl;
throw ExitException(1);
}
inline void DocBookOutput::substituteSpecialChars( std::string& s,
char r,
std::string& x )
{
size_t p;
while ( (p = s.find_first_of(r)) != std::string::npos )
{
s.erase(p,1);
s.insert(p,x);
}
}
inline void DocBookOutput::removeChar( std::string& s, char r)
{
size_t p;
while ( (p = s.find_first_of(r)) != std::string::npos )
{
s.erase(p,1);
}
}
inline void DocBookOutput::basename( std::string& s )
{
size_t p = s.find_last_of('/');
if ( p != std::string::npos )
{
s.erase(0, p + 1);
}
}
inline void DocBookOutput::printShortArg(Arg* a)
{
std::string lt = "&lt;";
std::string gt = "&gt;";
std::string id = a->shortID();
substituteSpecialChars(id,'<',lt);
substituteSpecialChars(id,'>',gt);
removeChar(id,'[');
removeChar(id,']');
std::string choice = "opt";
if ( a->isRequired() )
choice = "plain";
std::cout << "<arg choice='" << choice << '\'';
if ( a->acceptsMultipleValues() )
std::cout << " rep='repeat'";
std::cout << '>';
if ( !a->getFlag().empty() )
std::cout << a->flagStartChar() << a->getFlag();
else
std::cout << a->nameStartString() << a->getName();
if ( a->isValueRequired() )
{
std::string arg = a->shortID();
removeChar(arg,'[');
removeChar(arg,']');
removeChar(arg,'<');
removeChar(arg,'>');
removeChar(arg,'.');
arg.erase(0, arg.find_last_of(theDelimiter) + 1);
std::cout << theDelimiter;
std::cout << "<replaceable>" << arg << "</replaceable>";
}
std::cout << "</arg>" << std::endl;
}
inline void DocBookOutput::printLongArg(Arg* a)
{
std::string lt = "&lt;";
std::string gt = "&gt;";
std::string desc = a->getDescription();
substituteSpecialChars(desc,'<',lt);
substituteSpecialChars(desc,'>',gt);
std::cout << "<varlistentry>" << std::endl;
if ( !a->getFlag().empty() )
{
std::cout << "<term>" << std::endl;
std::cout << "<option>";
std::cout << a->flagStartChar() << a->getFlag();
std::cout << "</option>" << std::endl;
std::cout << "</term>" << std::endl;
}
std::cout << "<term>" << std::endl;
std::cout << "<option>";
std::cout << a->nameStartString() << a->getName();
if ( a->isValueRequired() )
{
std::string arg = a->shortID();
removeChar(arg,'[');
removeChar(arg,']');
removeChar(arg,'<');
removeChar(arg,'>');
removeChar(arg,'.');
arg.erase(0, arg.find_last_of(theDelimiter) + 1);
std::cout << theDelimiter;
std::cout << "<replaceable>" << arg << "</replaceable>";
}
std::cout << "</option>" << std::endl;
std::cout << "</term>" << std::endl;
std::cout << "<listitem>" << std::endl;
std::cout << "<para>" << std::endl;
std::cout << desc << std::endl;
std::cout << "</para>" << std::endl;
std::cout << "</listitem>" << std::endl;
std::cout << "</varlistentry>" << std::endl;
}
} //namespace TCLAP
#endif
@@ -0,0 +1,78 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: HelpVisitor.h
*
* Copyright (c) 2003, Michael E. Smoot .
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_HELP_VISITOR_H
#define TCLAP_HELP_VISITOR_H
#include <tclap/CmdLineInterface.h>
#include <tclap/CmdLineOutput.h>
#include <tclap/Visitor.h>
namespace TCLAP {
/**
* A Visitor object that calls the usage method of the given CmdLineOutput
* object for the specified CmdLine object.
*/
class HelpVisitor: public Visitor
{
private:
/**
* Prevent accidental copying.
*/
HelpVisitor(const HelpVisitor& rhs);
HelpVisitor& operator=(const HelpVisitor& rhs);
protected:
/**
* The CmdLine the output will be generated for.
*/
CmdLineInterface* _cmd;
/**
* The output object.
*/
CmdLineOutput** _out;
public:
/**
* Constructor.
* \param cmd - The CmdLine the output will be generated for.
* \param out - The type of output.
*/
HelpVisitor(CmdLineInterface* cmd, CmdLineOutput** out)
: Visitor(), _cmd( cmd ), _out( out ) { }
/**
* Calls the usage method of the CmdLineOutput for the
* specified CmdLine.
*/
void visit() { (*_out)->usage(*_cmd); throw ExitException(0); }
};
}
#endif
@@ -0,0 +1,54 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: IgnoreRestVisitor.h
*
* Copyright (c) 2003, Michael E. Smoot .
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_IGNORE_REST_VISITOR_H
#define TCLAP_IGNORE_REST_VISITOR_H
#include <tclap/Visitor.h>
#include <tclap/Arg.h>
namespace TCLAP {
/**
* A Visitor that tells the CmdLine to begin ignoring arguments after
* this one is parsed.
*/
class IgnoreRestVisitor: public Visitor
{
public:
/**
* Constructor.
*/
IgnoreRestVisitor() : Visitor() {}
/**
* Sets Arg::_ignoreRest.
*/
void visit() { Arg::beginIgnoring(); }
};
}
#endif
@@ -0,0 +1,433 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: MultiArg.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_MULTIPLE_ARGUMENT_H
#define TCLAP_MULTIPLE_ARGUMENT_H
#include <string>
#include <vector>
#include <tclap/Arg.h>
#include <tclap/Constraint.h>
namespace TCLAP {
/**
* An argument that allows multiple values of type T to be specified. Very
* similar to a ValueArg, except a vector of values will be returned
* instead of just one.
*/
template<class T>
class MultiArg : public Arg
{
public:
typedef std::vector<T> container_type;
typedef typename container_type::iterator iterator;
typedef typename container_type::const_iterator const_iterator;
protected:
/**
* The list of values parsed from the CmdLine.
*/
std::vector<T> _values;
/**
* The description of type T to be used in the usage.
*/
std::string _typeDesc;
/**
* A list of constraint on this Arg.
*/
Constraint<T>* _constraint;
/**
* Extracts the value from the string.
* Attempts to parse string as type T, if this fails an exception
* is thrown.
* \param val - The string to be read.
*/
void _extractValue( const std::string& val );
/**
* Used by XorHandler to decide whether to keep parsing for this arg.
*/
bool _allowMore;
public:
/**
* Constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param req - Whether the argument is required on the command
* line.
* \param typeDesc - A short, human readable description of the
* type that this object expects. This is used in the generation
* of the USAGE statement. The goal is to be helpful to the end user
* of the program.
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiArg( const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
const std::string& typeDesc,
Visitor* v = NULL);
/**
* Constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param req - Whether the argument is required on the command
* line.
* \param typeDesc - A short, human readable description of the
* type that this object expects. This is used in the generation
* of the USAGE statement. The goal is to be helpful to the end user
* of the program.
* \param parser - A CmdLine parser object to add this Arg to
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiArg( const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
const std::string& typeDesc,
CmdLineInterface& parser,
Visitor* v = NULL );
/**
* Constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param req - Whether the argument is required on the command
* line.
* \param constraint - A pointer to a Constraint object used
* to constrain this Arg.
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiArg( const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
Constraint<T>* constraint,
Visitor* v = NULL );
/**
* Constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param req - Whether the argument is required on the command
* line.
* \param constraint - A pointer to a Constraint object used
* to constrain this Arg.
* \param parser - A CmdLine parser object to add this Arg to
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiArg( const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
Constraint<T>* constraint,
CmdLineInterface& parser,
Visitor* v = NULL );
/**
* Handles the processing of the argument.
* This re-implements the Arg version of this method to set the
* _value of the argument appropriately. It knows the difference
* between labeled and unlabeled.
* \param i - Pointer the the current argument in the list.
* \param args - Mutable list of strings. Passed from main().
*/
virtual bool processArg(int* i, std::vector<std::string>& args);
/**
* Returns a vector of type T containing the values parsed from
* the command line.
*/
const std::vector<T>& getValue() const { return _values; }
/**
* Returns an iterator over the values parsed from the command
* line.
*/
const_iterator begin() const { return _values.begin(); }
/**
* Returns the end of the values parsed from the command
* line.
*/
const_iterator end() const { return _values.end(); }
/**
* Returns the a short id string. Used in the usage.
* \param val - value to be used.
*/
virtual std::string shortID(const std::string& val="val") const;
/**
* Returns the a long id string. Used in the usage.
* \param val - value to be used.
*/
virtual std::string longID(const std::string& val="val") const;
/**
* Once we've matched the first value, then the arg is no longer
* required.
*/
virtual bool isRequired() const;
virtual bool allowMore();
virtual void reset();
private:
/**
* Prevent accidental copying
*/
MultiArg(const MultiArg<T>& rhs);
MultiArg& operator=(const MultiArg<T>& rhs);
};
template<class T>
MultiArg<T>::MultiArg(const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
const std::string& typeDesc,
Visitor* v) :
Arg( flag, name, desc, req, true, v ),
_values(std::vector<T>()),
_typeDesc( typeDesc ),
_constraint( NULL ),
_allowMore(false)
{
_acceptsMultipleValues = true;
}
template<class T>
MultiArg<T>::MultiArg(const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
const std::string& typeDesc,
CmdLineInterface& parser,
Visitor* v)
: Arg( flag, name, desc, req, true, v ),
_values(std::vector<T>()),
_typeDesc( typeDesc ),
_constraint( NULL ),
_allowMore(false)
{
parser.add( this );
_acceptsMultipleValues = true;
}
/**
*
*/
template<class T>
MultiArg<T>::MultiArg(const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
Constraint<T>* constraint,
Visitor* v)
: Arg( flag, name, desc, req, true, v ),
_values(std::vector<T>()),
_typeDesc( Constraint<T>::shortID(constraint) ),
_constraint( constraint ),
_allowMore(false)
{
_acceptsMultipleValues = true;
}
template<class T>
MultiArg<T>::MultiArg(const std::string& flag,
const std::string& name,
const std::string& desc,
bool req,
Constraint<T>* constraint,
CmdLineInterface& parser,
Visitor* v)
: Arg( flag, name, desc, req, true, v ),
_values(std::vector<T>()),
_typeDesc( Constraint<T>::shortID(constraint) ),
_constraint( constraint ),
_allowMore(false)
{
parser.add( this );
_acceptsMultipleValues = true;
}
template<class T>
bool MultiArg<T>::processArg(int *i, std::vector<std::string>& args)
{
if ( _ignoreable && Arg::ignoreRest() )
return false;
if ( _hasBlanks( args[*i] ) )
return false;
std::string flag = args[*i];
std::string value = "";
trimFlag( flag, value );
if ( argMatches( flag ) )
{
if ( Arg::delimiter() != ' ' && value == "" )
throw( ArgParseException(
"Couldn't find delimiter for this argument!",
toString() ) );
// always take the first one, regardless of start string
if ( value == "" )
{
(*i)++;
if ( static_cast<unsigned int>(*i) < args.size() )
_extractValue( args[*i] );
else
throw( ArgParseException("Missing a value for this argument!",
toString() ) );
}
else
_extractValue( value );
/*
// continuing taking the args until we hit one with a start string
while ( (unsigned int)(*i)+1 < args.size() &&
args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 &&
args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 )
_extractValue( args[++(*i)] );
*/
_alreadySet = true;
_checkWithVisitor();
return true;
}
else
return false;
}
/**
*
*/
template<class T>
std::string MultiArg<T>::shortID(const std::string& val) const
{
static_cast<void>(val); // Ignore input, don't warn
return Arg::shortID(_typeDesc) + " ...";
}
/**
*
*/
template<class T>
std::string MultiArg<T>::longID(const std::string& val) const
{
static_cast<void>(val); // Ignore input, don't warn
return Arg::longID(_typeDesc) + " (accepted multiple times)";
}
/**
* Once we've matched the first value, then the arg is no longer
* required.
*/
template<class T>
bool MultiArg<T>::isRequired() const
{
if ( _required )
{
if ( _values.size() > 1 )
return false;
else
return true;
}
else
return false;
}
template<class T>
void MultiArg<T>::_extractValue( const std::string& val )
{
try {
T tmp;
ExtractValue(tmp, val, typename ArgTraits<T>::ValueCategory());
_values.push_back(tmp);
} catch( ArgParseException &e) {
throw ArgParseException(e.error(), toString());
}
if ( _constraint != NULL )
if ( ! _constraint->check( _values.back() ) )
throw( CmdLineParseException( "Value '" + val +
"' does not meet constraint: " +
_constraint->description(),
toString() ) );
}
template<class T>
bool MultiArg<T>::allowMore()
{
bool am = _allowMore;
_allowMore = true;
return am;
}
template<class T>
void MultiArg<T>::reset()
{
Arg::reset();
_values.clear();
}
} // namespace TCLAP
#endif
@@ -0,0 +1,217 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: MultiSwitchArg.h
*
* Copyright (c) 2003, Michael E. Smoot .
* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
* Copyright (c) 2005, Michael E. Smoot, Daniel Aarno, Erik Zeek.
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_MULTI_SWITCH_ARG_H
#define TCLAP_MULTI_SWITCH_ARG_H
#include <string>
#include <vector>
#include <tclap/SwitchArg.h>
namespace TCLAP {
/**
* A multiple switch argument. If the switch is set on the command line, then
* the getValue method will return the number of times the switch appears.
*/
class MultiSwitchArg : public SwitchArg
{
protected:
/**
* The value of the switch.
*/
int _value;
/**
* Used to support the reset() method so that ValueArg can be
* reset to their constructed value.
*/
int _default;
public:
/**
* MultiSwitchArg constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param init - Optional. The initial/default value of this Arg.
* Defaults to 0.
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiSwitchArg(const std::string& flag,
const std::string& name,
const std::string& desc,
int init = 0,
Visitor* v = NULL);
/**
* MultiSwitchArg constructor.
* \param flag - The one character flag that identifies this
* argument on the command line.
* \param name - A one word name for the argument. Can be
* used as a long flag on the command line.
* \param desc - A description of what the argument is for or
* does.
* \param parser - A CmdLine parser object to add this Arg to
* \param init - Optional. The initial/default value of this Arg.
* Defaults to 0.
* \param v - An optional visitor. You probably should not
* use this unless you have a very good reason.
*/
MultiSwitchArg(const std::string& flag,
const std::string& name,
const std::string& desc,
CmdLineInterface& parser,
int init = 0,
Visitor* v = NULL);
/**
* Handles the processing of the argument.
* This re-implements the SwitchArg version of this method to set the
* _value of the argument appropriately.
* \param i - Pointer the the current argument in the list.
* \param args - Mutable list of strings. Passed
* in from main().
*/
virtual bool processArg(int* i, std::vector<std::string>& args);
/**
* Returns int, the number of times the switch has been set.
*/
int getValue() const { return _value; }
/**
* Returns the shortID for this Arg.
*/
std::string shortID(const std::string& val) const;
/**
* Returns the longID for this Arg.
*/
std::string longID(const std::string& val) const;
void reset();
};
//////////////////////////////////////////////////////////////////////
//BEGIN MultiSwitchArg.cpp
//////////////////////////////////////////////////////////////////////
inline MultiSwitchArg::MultiSwitchArg(const std::string& flag,
const std::string& name,
const std::string& desc,
int init,
Visitor* v )
: SwitchArg(flag, name, desc, false, v),
_value( init ),
_default( init )
{ }
inline MultiSwitchArg::MultiSwitchArg(const std::string& flag,
const std::string& name,
const std::string& desc,
CmdLineInterface& parser,
int init,
Visitor* v )
: SwitchArg(flag, name, desc, false, v),
_value( init ),
_default( init )
{
parser.add( this );
}
inline bool MultiSwitchArg::processArg(int *i, std::vector<std::string>& args)
{
if ( _ignoreable && Arg::ignoreRest() )
return false;
if ( argMatches( args[*i] ))
{
// so the isSet() method will work
_alreadySet = true;
// Matched argument: increment value.
++_value;
_checkWithVisitor();
return true;
}
else if ( combinedSwitchesMatch( args[*i] ) )
{
// so the isSet() method will work
_alreadySet = true;
// Matched argument: increment value.
++_value;
// Check for more in argument and increment value.
while ( combinedSwitchesMatch( args[*i] ) )
++_value;
_checkWithVisitor();
return false;
}
else
return false;
}
inline std::string
MultiSwitchArg::shortID(const std::string& val) const
{
return Arg::shortID(val) + " ...";
}
inline std::string
MultiSwitchArg::longID(const std::string& val) const
{
return Arg::longID(val) + " (accepted multiple times)";
}
inline void
MultiSwitchArg::reset()
{
MultiSwitchArg::_value = MultiSwitchArg::_default;
}
//////////////////////////////////////////////////////////////////////
//END MultiSwitchArg.cpp
//////////////////////////////////////////////////////////////////////
} //namespace TCLAP
#endif
@@ -0,0 +1,64 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: OptionalUnlabeledTracker.h
*
* Copyright (c) 2005, Michael E. Smoot .
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
#ifndef TCLAP_OPTIONAL_UNLABELED_TRACKER_H
#define TCLAP_OPTIONAL_UNLABELED_TRACKER_H
#include <string>
namespace TCLAP {
class OptionalUnlabeledTracker
{
public:
static void check( bool req, const std::string& argName );
static void gotOptional() { alreadyOptionalRef() = true; }
static bool& alreadyOptional() { return alreadyOptionalRef(); }
private:
static bool& alreadyOptionalRef() { static bool ct = false; return ct; }
};
inline void OptionalUnlabeledTracker::check( bool req, const std::string& argName )
{
if ( OptionalUnlabeledTracker::alreadyOptional() )
throw( SpecificationException(
"You can't specify ANY Unlabeled Arg following an optional Unlabeled Arg",
argName ) );
if ( !req )
OptionalUnlabeledTracker::gotOptional();
}
} // namespace TCLAP
#endif
@@ -0,0 +1,63 @@
// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
/******************************************************************************
*
* file: StandardTraits.h
*
* Copyright (c) 2007, Daniel Aarno, Michael E. Smoot .
* Copyright (c) 2017, Google LLC
* All rights reserved.
*
* See the file COPYING in the top directory of this distribution for
* more information.
*
* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
// This is an internal tclap file, you should probably not have to
// include this directly
#ifndef TCLAP_STANDARD_TRAITS_H
#define TCLAP_STANDARD_TRAITS_H
#ifdef HAVE_CONFIG_H
#include <config.h> // To check for long long
#endif
// If Microsoft has already typedef'd wchar_t as an unsigned
// short, then compiles will break because it's as if we're
// creating ArgTraits twice for unsigned short. Thus...
#ifdef _MSC_VER
#ifndef _NATIVE_WCHAR_T_DEFINED
#define TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS
#endif
#endif
namespace TCLAP {
// Integer types (signed, unsigned and bool) and floating point types all
// have value-like semantics.
// Strings have string like argument traits.
template<>
struct ArgTraits<std::string> {
typedef StringLike ValueCategory;
};
template<typename T>
void SetString(T &dst, const std::string &src)
{
dst = src;
}
} // namespace
#endif

Some files were not shown because too many files have changed in this diff Show More