feat: 支持硬件编码能力测试,otg 自检修改为需要手动执行

This commit is contained in:
mofeng-git
2026-03-22 14:55:44 +08:00
parent c119db4908
commit 24a10aa222
8 changed files with 545 additions and 5 deletions

View File

@@ -177,6 +177,31 @@ export interface StreamConstraintsResponse {
current_mode: string
}
export interface VideoEncoderSelfCheckCodec {
id: string
name: string
}
export interface VideoEncoderSelfCheckCell {
codec_id: string
ok: boolean
elapsed_ms?: number | null
}
export interface VideoEncoderSelfCheckRow {
resolution_id: string
resolution_label: string
width: number
height: number
cells: VideoEncoderSelfCheckCell[]
}
export interface VideoEncoderSelfCheckResponse {
current_hardware_encoder: string
codecs: VideoEncoderSelfCheckCodec[]
rows: VideoEncoderSelfCheckRow[]
}
export const streamApi = {
status: () =>
request<{
@@ -217,6 +242,9 @@ export const streamApi = {
getConstraints: () =>
request<StreamConstraintsResponse>('/stream/constraints'),
encoderSelfCheck: () =>
request<VideoEncoderSelfCheckResponse>('/video/encoder/self-check'),
setBitratePreset: (bitrate_preset: import('@/types/generated').BitratePreset) =>
request<{ success: boolean; message?: string }>('/stream/bitrate', {
method: 'POST',

View File

@@ -757,6 +757,15 @@ export default {
udc_speed: 'Device may not be fully enumerated; try reconnecting USB',
},
},
encoderSelfCheck: {
title: 'Hardware Encoding Capability Test',
desc: 'Test hardware encoding capability across 720p, 1080p, 2K, and 4K',
run: 'Start Test',
failed: 'Failed to run hardware encoding capability test',
resolution: 'Resolution',
currentHardwareEncoder: 'Current Hardware Encoder',
none: 'None',
},
// WebRTC / ICE
webrtcSettings: 'WebRTC Settings',
webrtcSettingsDesc: 'Configure STUN/TURN servers for NAT traversal',

View File

@@ -757,6 +757,15 @@ export default {
udc_speed: '设备可能未完成枚举,可尝试重插 USB',
},
},
encoderSelfCheck: {
title: '硬件编码能力测试',
desc: '按 720p、1080p、2K、4K 测试硬件编码能力',
run: '开始测试',
failed: '执行硬件编码能力测试失败',
resolution: '分辨率',
currentHardwareEncoder: '当前硬件编码器',
none: '无',
},
// WebRTC / ICE
webrtcSettings: 'WebRTC 设置',
webrtcSettingsDesc: '配置 STUN/TURN 服务器以实现 NAT 穿透',

View File

@@ -24,6 +24,7 @@ import {
type UpdateOverviewResponse,
type UpdateStatusResponse,
type UpdateChannel,
type VideoEncoderSelfCheckResponse,
} from '@/api'
import type {
ExtensionsStatus,
@@ -539,6 +540,64 @@ async function onRunOtgSelfCheckClick() {
await runOtgSelfCheck()
}
type VideoEncoderSelfCheckCell = VideoEncoderSelfCheckResponse['rows'][number]['cells'][number]
type VideoEncoderSelfCheckRow = VideoEncoderSelfCheckResponse['rows'][number]
const videoEncoderSelfCheckLoading = ref(false)
const videoEncoderSelfCheckResult = ref<VideoEncoderSelfCheckResponse | null>(null)
const videoEncoderSelfCheckError = ref('')
const videoEncoderRunButtonPressed = ref(false)
function videoEncoderCell(row: VideoEncoderSelfCheckRow, codecId: string): VideoEncoderSelfCheckCell | undefined {
return row.cells.find(cell => cell.codec_id === codecId)
}
const currentHardwareEncoderText = computed(() =>
videoEncoderSelfCheckResult.value?.current_hardware_encoder === 'None'
? t('settings.encoderSelfCheck.none')
: (videoEncoderSelfCheckResult.value?.current_hardware_encoder || t('settings.encoderSelfCheck.none'))
)
function videoEncoderCodecLabel(codecId: string, codecName: string): string {
return codecId === 'h265' ? 'H.265' : codecName
}
function videoEncoderCellClass(ok: boolean | undefined): string {
return ok ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'
}
function videoEncoderCellSymbol(ok: boolean | undefined): string {
return ok ? '✓' : '✗'
}
function videoEncoderCellTime(cell: VideoEncoderSelfCheckCell | undefined): string {
if (!cell || typeof cell.elapsed_ms !== 'number') return '-'
return `${cell.elapsed_ms}ms`
}
async function runVideoEncoderSelfCheck() {
videoEncoderSelfCheckLoading.value = true
videoEncoderSelfCheckError.value = ''
try {
videoEncoderSelfCheckResult.value = await streamApi.encoderSelfCheck()
} catch (e) {
console.error('Failed to run encoder self-check:', e)
videoEncoderSelfCheckError.value = t('settings.encoderSelfCheck.failed')
} finally {
videoEncoderSelfCheckLoading.value = false
}
}
async function onRunVideoEncoderSelfCheckClick() {
if (!videoEncoderSelfCheckLoading.value) {
videoEncoderRunButtonPressed.value = true
window.setTimeout(() => {
videoEncoderRunButtonPressed.value = false
}, 160)
}
await runVideoEncoderSelfCheck()
}
function alignHidProfileForLowEndpoint() {
if (hidProfileAligned.value) return
if (!configLoaded.value || !devicesLoaded.value) return
@@ -1781,16 +1840,15 @@ onMounted(async () => {
if (updateRunning.value) {
startUpdatePolling()
}
await runOtgSelfCheck()
})
watch(updateChannel, async () => {
await loadUpdateOverview()
})
watch(() => config.value.hid_backend, async () => {
await runOtgSelfCheck()
watch(() => config.value.hid_backend, () => {
otgSelfCheckResult.value = null
otgSelfCheckError.value = ''
})
</script>
@@ -2364,6 +2422,86 @@ watch(() => config.value.hid_backend, async () => {
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-start justify-between space-y-0">
<div class="space-y-1.5">
<CardTitle>{{ t('settings.encoderSelfCheck.title') }}</CardTitle>
<CardDescription>{{ t('settings.encoderSelfCheck.desc') }}</CardDescription>
</div>
<Button
variant="outline"
size="sm"
:disabled="videoEncoderSelfCheckLoading"
:class="[
'transition-all duration-150 active:scale-95 active:brightness-95',
videoEncoderRunButtonPressed ? 'scale-95 brightness-95' : ''
]"
@click="onRunVideoEncoderSelfCheckClick"
>
<RefreshCw class="h-4 w-4 mr-2" :class="{ 'animate-spin': videoEncoderSelfCheckLoading }" />
{{ t('settings.encoderSelfCheck.run') }}
</Button>
</CardHeader>
<CardContent class="space-y-3">
<p v-if="videoEncoderSelfCheckError" class="text-xs text-red-600 dark:text-red-400">
{{ videoEncoderSelfCheckError }}
</p>
<template v-if="videoEncoderSelfCheckResult">
<div class="text-sm">
{{ t('settings.encoderSelfCheck.currentHardwareEncoder') }}{{ currentHardwareEncoderText }}
</div>
<div class="rounded-md border bg-card">
<table class="w-full table-fixed text-sm">
<thead>
<tr>
<th class="px-2 py-3 text-left font-medium w-[18%]">{{ t('settings.encoderSelfCheck.resolution') }}</th>
<th
v-for="codec in videoEncoderSelfCheckResult.codecs"
:key="codec.id"
class="px-2 py-3 text-center font-medium w-[20.5%]"
>
{{ videoEncoderCodecLabel(codec.id, codec.name) }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in videoEncoderSelfCheckResult.rows"
:key="row.resolution_id"
>
<td class="px-2 py-3 align-middle">
<div class="font-medium">{{ row.resolution_label }}</div>
</td>
<td
v-for="codec in videoEncoderSelfCheckResult.codecs"
:key="`${row.resolution_id}-${codec.id}`"
class="px-2 py-3 align-middle"
>
<div
class="flex flex-col items-center justify-center gap-1"
:class="videoEncoderCellClass(videoEncoderCell(row, codec.id)?.ok)"
>
<div class="text-lg leading-none font-semibold">
{{ videoEncoderCellSymbol(videoEncoderCell(row, codec.id)?.ok) }}
</div>
<div class="text-[11px] leading-4 text-foreground/70">
{{ videoEncoderCellTime(videoEncoderCell(row, codec.id)) }}
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<p v-else-if="videoEncoderSelfCheckLoading" class="text-xs text-muted-foreground">
{{ t('common.loading') }}
</p>
</CardContent>
</Card>
</div>
<!-- Access Section -->