feat: UAC USB microphone passthrough

- Add UAC1 gadget function (ConfigFS) with optimized endpoint config
- Browser mic capture with Opus encoding via WebCodecs AudioEncoder
- WebSocket audio transport (Opus 64kbps, 100x bandwidth reduction vs raw PCM)
- aplay subprocess for reliable PCM playback to USB gadget
- Settings toggle + ActionBar mic button (hidden when UAC disabled)
- Dynamic PCM device resolution (/proc/asound)
- c_chmask=0 + req_number=4 fixes DWC3 composite isochronous endpoint issue
This commit is contained in:
arounyf
2026-07-23 06:56:53 +00:00
parent 4e32b05124
commit e9bed3688f
27 changed files with 977 additions and 16 deletions

View File

@@ -3,6 +3,7 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useSystemStore } from '@/stores/system'
import { getMicrophone } from '@/composables/useMicrophone'
import { Button } from '@/components/ui/button'
import { ButtonGroup } from '@/components/ui/button-group'
import {
@@ -31,6 +32,7 @@ import {
} from '@/components/ui/sheet'
import {
ClipboardPaste,
Mic,
HardDrive,
Keyboard,
Settings,
@@ -68,9 +70,13 @@ const props = defineProps<{
showTerminal?: boolean
showComputerUse?: boolean
showPasteText?: boolean
showMic?: boolean
}>()
const showStats = computed(() => (props.videoMode ?? 'mjpeg') !== 'mjpeg')
const showPasteText = computed(() => props.showPasteText !== false)
const showMic = computed(() => props.showMic === true)
const mic = getMicrophone()
const emit = defineEmits<{
(e: 'toggleFullscreen'): void
@@ -350,6 +356,24 @@ const hasRightOverflow = computed(() => {
</PopoverContent>
</Popover>
</div>
<!-- Mic button -->
<div v-if="showMic">
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="sm" class="h-8 gap-1.5 text-xs"
:class="mic.active.value ? 'text-destructive' : mic.error.value ? 'text-yellow-500' : ''"
@click="mic.toggle()">
<Mic class="size-4" :class="mic.active.value ? 'animate-pulse' : ''" />
<span>{{ mic.active.value ? '关闭' : '麦克风' }}</span>
<span v-if="mic.error.value" class="text-[10px]">{{ mic.error.value }}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{{ mic.error.value ? mic.error.value : (mic.active.value ? '停止传声' : '开始传声') }}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</ButtonGroup>
<!-- Right side buttons -->