Bind Android smoke to build provenance and actual PCM assertions

This commit is contained in:
NINI
2026-10-05 03:46:17 +09:00
parent d656efe0dc
commit 4936180e45
7 changed files with 239 additions and 11 deletions
+17 -4
View File
@@ -1,5 +1,15 @@
param([Parameter(Mandatory)][string]$OutputPath, [int]$Seconds=15, [int]$Port=8554)
$ErrorActionPreference = 'Stop'
Add-Type -TypeDefinition @'
using System;
public static class AndroidPcmLevels {
public static double[] Measure(byte[] bytes) {
double sum=0, peak=0; int active=0;
for(int i=0;i+1<bytes.Length;i+=2) { int sample=(short)(bytes[i] | (bytes[i+1]<<8)); sum+=(double)sample*sample; peak=Math.Max(peak,Math.Abs(sample)); if(Math.Abs(sample)>32) active++; }
return new[]{peak, bytes.Length>0 ? Math.Sqrt(sum/(bytes.Length/2)) : 0, (double)active};
}
}
'@
# EmulatorController.streamAudio의 PCM을 직접 기록합니다. 제품 APK에는 포함되지 않습니다.
# wire schema: Android SDK emulator/lib/emulator_controller.proto.
[AppContext]::SetSwitch('System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport', $true)
@@ -19,11 +29,13 @@ $request.Content.Headers.ContentType = [Net.Http.Headers.MediaTypeHeaderValue]::
$cancel = [Threading.CancellationTokenSource]::new([TimeSpan]::FromSeconds($Seconds))
$pcm = [IO.MemoryStream]::new()
$packets = 0
$startedUtc=[DateTime]::UtcNow.ToString('o')
$firstPacketTimestamp=$null; $lastPacketTimestamp=$null
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()
$n=$Stream.ReadAsync($bytes,$offset,$Length-$offset,$cancel.Token).GetAwaiter().GetResult()
if ($n -eq 0) { throw 'gRPC 오디오 스트림이 끝났습니다.' }
$offset += $n
}
@@ -42,13 +54,13 @@ try {
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]
$length=([int]$header[1] -shl 24) -bor ([int]$header[2] -shl 16) -bor ([int]$header[3] -shl 8) -bor [int]$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) }
0 { $value=Read-Varint $message ([ref]$i); if (($tag -shr 3) -eq 2) { if ($null -eq $firstPacketTimestamp) { $firstPacketTimestamp=$value }; $lastPacketTimestamp=$value } }
2 {
$size=Read-Varint $message ([ref]$i)
if (($tag -shr 3) -eq 3) { $pcm.Write($message,$i,$size); $packets++ }
@@ -62,6 +74,7 @@ try {
if (!$cancel.IsCancellationRequested) { throw }
} finally {
$data=$pcm.ToArray()
$levels=[AndroidPcmLevels]::Measure($data)
$file=[IO.File]::Create($OutputPath)
$writer=[IO.BinaryWriter]::new($file)
try {
@@ -69,7 +82,7 @@ try {
$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 } |
[ordered]@{ method='EmulatorController.streamAudio'; endpoint="127.0.0.1:$Port"; sourceCommit=(& git rev-parse HEAD); sourceDirty=[bool](& git status --porcelain); startedUtc=$startedUtc; finishedUtc=[DateTime]::UtcNow.ToString('o'); firstDevicePacketTimestampUs=$firstPacketTimestamp; lastDevicePacketTimestampUs=$lastPacketTimestamp; packets=$packets; pcmBytes=$data.Length; sampleRate=24000; channels=1; bitsPerSample=16; peakPcm=$levels[0]; rmsPcm=$levels[1]; nonSilentSamples=$levels[2]; auditoryReview=$false } |
ConvertTo-Json | Set-Content -LiteralPath ($OutputPath + '.json') -Encoding utf8
}
if ($packets -eq 0) { throw '실제 오디오 패킷을 받지 못했습니다.' }