Exclude Android release metadata and record emulator audio
This commit is contained in:
@@ -13,7 +13,7 @@ try {
|
||||
$abis = @($entries | Where-Object { $_ -match '^lib/([^/]+)/libil2cpp\.so$' } | ForEach-Object { ($_ -split '/')[1] })
|
||||
if ($abis.Count -eq 0) { throw 'APK에 IL2CPP 라이브러리가 없습니다.' }
|
||||
foreach ($abi in $abis) { if ($entries -notcontains "lib/$abi/libunity.so") { throw "$abi Unity 런타임 누락" } }
|
||||
if ($entries | Where-Object { $_ -match '(?i)(SourceAssets|conda|Qwen|safetensors)' }) { throw '제작 환경 파일이 APK에 포함되었습니다.' }
|
||||
if ($entries | Where-Object { $_ -match '(?i)(^|/)(SourceAssets|conda|Qwen[^/]*)(/|$)|\.safetensors$' }) { throw '제작 환경 파일이 APK에 포함되었습니다.' }
|
||||
$entries | Set-Content -LiteralPath (Join-Path $EvidenceDir 'apk-entries.txt') -Encoding utf8
|
||||
} finally { $zip.Dispose() }
|
||||
$package = [regex]::Match(($badging -join "`n"), "package: name='([^']+)'").Groups[1].Value
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
param([Parameter(Mandatory)][string]$OutputPath, [int]$Seconds=15, [int]$Port=8554)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# EmulatorController.streamAudio의 PCM을 직접 기록합니다. 제품 APK에는 포함되지 않습니다.
|
||||
# wire schema: Android SDK emulator/lib/emulator_controller.proto.
|
||||
[AppContext]::SetSwitch('System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport', $true)
|
||||
$client = [Net.Http.HttpClient]::new()
|
||||
$request = [Net.Http.HttpRequestMessage]::new([Net.Http.HttpMethod]::Post, "http://127.0.0.1:$Port/android.emulation.control.EmulatorController/streamAudio")
|
||||
$request.Version = [Version]::new(2,0)
|
||||
$request.VersionPolicy = [Net.Http.HttpVersionPolicy]::RequestVersionExact
|
||||
$request.Headers.Add('TE','trailers')
|
||||
$discovery = Get-ChildItem -LiteralPath (Join-Path $env:LOCALAPPDATA 'Temp/avd/running') -Filter 'pid_*.ini' | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if (!$discovery) { throw 'AVD gRPC discovery 파일을 찾지 못했습니다.' }
|
||||
$tokenLine = Get-Content -LiteralPath $discovery.FullName | Where-Object { $_ -match '^grpc.token=' } | Select-Object -First 1
|
||||
if (!$tokenLine) { throw 'AVD를 -grpc-use-token으로 시작해야 합니다. 토큰을 로그에 기록하지 않습니다.' }
|
||||
$request.Headers.Authorization = [Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $tokenLine.Substring('grpc.token='.Length))
|
||||
# AudioFormat: samplingRate=24000, channels=Mono(default), format=AUD_FMT_S16.
|
||||
$request.Content = [Net.Http.ByteArrayContent]::new([byte[]]@(0,0,0,0,6,8,192,187,1,24,1))
|
||||
$request.Content.Headers.ContentType = [Net.Http.Headers.MediaTypeHeaderValue]::new('application/grpc')
|
||||
$cancel = [Threading.CancellationTokenSource]::new([TimeSpan]::FromSeconds($Seconds))
|
||||
$pcm = [IO.MemoryStream]::new()
|
||||
$packets = 0
|
||||
function Read-Exactly($Stream,[int]$Length) {
|
||||
$bytes = [byte[]]::new($Length)
|
||||
$offset=0
|
||||
while ($offset -lt $Length) {
|
||||
$n=$Stream.ReadAsync($bytes.AsMemory($offset,$Length-$offset),$cancel.Token).AsTask().GetAwaiter().GetResult()
|
||||
if ($n -eq 0) { throw 'gRPC 오디오 스트림이 끝났습니다.' }
|
||||
$offset += $n
|
||||
}
|
||||
return ,$bytes
|
||||
}
|
||||
function Read-Varint([byte[]]$Bytes,[ref]$Index) {
|
||||
[long]$value=0; $shift=0
|
||||
do { $b=$Bytes[$Index.Value]; $Index.Value++; $value=$value -bor (($b -band 127) -shl $shift); $shift+=7 } while ($b -band 128)
|
||||
return $value
|
||||
}
|
||||
$status='failed'
|
||||
try {
|
||||
$response=$client.SendAsync($request,[Net.Http.HttpCompletionOption]::ResponseHeadersRead,$cancel.Token).GetAwaiter().GetResult()
|
||||
$response.EnsureSuccessStatusCode() | Out-Null
|
||||
$stream=$response.Content.ReadAsStream()
|
||||
while (!$cancel.IsCancellationRequested) {
|
||||
$header=Read-Exactly $stream 5
|
||||
if ($header[0] -ne 0) { throw '압축 gRPC 프레임은 지원하지 않습니다.' }
|
||||
$length=($header[1] -shl 24) -bor ($header[2] -shl 16) -bor ($header[3] -shl 8) -bor $header[4]
|
||||
$message=Read-Exactly $stream $length
|
||||
$i=0
|
||||
while ($i -lt $message.Length) {
|
||||
$tag=Read-Varint $message ([ref]$i)
|
||||
switch ($tag -band 7) {
|
||||
0 { $null=Read-Varint $message ([ref]$i) }
|
||||
2 {
|
||||
$size=Read-Varint $message ([ref]$i)
|
||||
if (($tag -shr 3) -eq 3) { $pcm.Write($message,$i,$size); $packets++ }
|
||||
$i += $size
|
||||
}
|
||||
default { throw "예상하지 않은 protobuf wire type: $tag" }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (!$cancel.IsCancellationRequested) { throw }
|
||||
} finally {
|
||||
$data=$pcm.ToArray()
|
||||
$file=[IO.File]::Create($OutputPath)
|
||||
$writer=[IO.BinaryWriter]::new($file)
|
||||
try {
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('RIFF')); $writer.Write([int](36+$data.Length)); $writer.Write([Text.Encoding]::ASCII.GetBytes('WAVEfmt '));
|
||||
$writer.Write([int]16); $writer.Write([int16]1); $writer.Write([int16]1); $writer.Write([int]24000); $writer.Write([int]48000); $writer.Write([int16]2); $writer.Write([int16]16);
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('data')); $writer.Write([int]$data.Length); $writer.Write($data)
|
||||
} finally { $writer.Dispose(); $file.Dispose(); $client.Dispose(); $cancel.Dispose(); $pcm.Dispose() }
|
||||
[ordered]@{ method='EmulatorController.streamAudio'; endpoint="127.0.0.1:$Port"; packets=$packets; pcmBytes=$data.Length; sampleRate=24000; channels=1; bitsPerSample=16; auditoryReview=$false } |
|
||||
ConvertTo-Json | Set-Content -LiteralPath ($OutputPath + '.json') -Encoding utf8
|
||||
}
|
||||
if ($packets -eq 0) { throw '실제 오디오 패킷을 받지 못했습니다.' }
|
||||
Write-Host "에뮬레이터 실제 PCM $packets 패킷, $($data.Length)바이트: $OutputPath"
|
||||
Reference in New Issue
Block a user