mirror of
git://f0xx.org/android_cast
synced 2026-07-29 06:18:42 +03:00
mobile sync
This commit is contained in:
@@ -14,7 +14,7 @@ package com.foxx.androidcast.crash;
|
||||
/** Crash reporter tuning from {@code settings.json} → {@code crash} (overrides app defaults). */
|
||||
public final class CrashSettings {
|
||||
public static final String DEFAULT_UPLOAD_URL =
|
||||
"https://f0xx.org/app/androidcast_project/crashes/api/upload.php";
|
||||
"https://apps.f0xx.org/app/androidcast_project/crashes/api/upload.php";
|
||||
|
||||
public final boolean enabled;
|
||||
public final String uploadUrl;
|
||||
|
||||
@@ -24,6 +24,7 @@ public final class DevDiagnosticsController {
|
||||
HW(R.id.dev_diag_hw, R.string.dev_diag_hw_title),
|
||||
CAMERA(R.id.dev_diag_camera, R.string.dev_diag_camera_title),
|
||||
AUDIO(R.id.dev_diag_audio, R.string.dev_diag_audio_title),
|
||||
AUDIO_CODECS(R.id.dev_diag_audio_codecs, R.string.dev_diag_audio_codecs_title),
|
||||
NETWORK(R.id.dev_diag_network, R.string.dev_diag_network_title),
|
||||
CASTING(R.id.dev_diag_casting, R.string.dev_diag_casting_title);
|
||||
|
||||
@@ -100,6 +101,8 @@ public final class DevDiagnosticsController {
|
||||
DevDiagnosticsProbe.probeCameraCapture(activity), sectionText);
|
||||
runSection(Section.AUDIO, () ->
|
||||
DevDiagnosticsProbe.probeAudioCapture(activity), sectionText);
|
||||
runSection(Section.AUDIO_CODECS, () ->
|
||||
DevDiagnosticsProbe.probeAudioCodecs(activity), sectionText);
|
||||
runSection(Section.NETWORK, () ->
|
||||
DevDiagnosticsProbe.probeNetwork(activity, cancel), sectionText);
|
||||
runSection(Section.CASTING, () ->
|
||||
|
||||
@@ -21,10 +21,14 @@ import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.foxx.androidcast.AppPreferences;
|
||||
import com.foxx.androidcast.BuildConfig;
|
||||
import com.foxx.androidcast.CastSettings;
|
||||
import com.foxx.androidcast.PlatformCastSupport;
|
||||
import com.foxx.androidcast.crash.CrashSettingsStore;
|
||||
import com.foxx.androidcast.display.WiredDisplayMonitor;
|
||||
import com.foxx.androidcast.media.CodecPriorityCatalog;
|
||||
import com.foxx.androidcast.media.CodecTier;
|
||||
import com.foxx.androidcast.media.codec.jni.NativeCodecBridge;
|
||||
import com.foxx.androidcast.network.CastCodecFlags;
|
||||
import com.foxx.androidcast.sender.CodecCatalog;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -44,16 +48,38 @@ public final class DevDiagnosticsProbe {
|
||||
|
||||
public static String probeHardwareAcceleration(Context context) {
|
||||
CodecCatalog.invalidateCapabilities();
|
||||
CastSettings settings = AppPreferences.loadSenderDefaults(context);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("API ").append(Build.VERSION.SDK_INT).append('\n');
|
||||
sb.append("libvpx JNI: ").append(NativeCodecBridge.isLibvpxAvailable() ? "yes" : "no").append('\n');
|
||||
sb.append('\n').append("Video encoders:\n");
|
||||
appendVideoCodecLines(sb, true);
|
||||
sb.append('\n').append("Video decoders:\n");
|
||||
appendVideoCodecLines(sb, false);
|
||||
sb.append("Native JNI probes:\n");
|
||||
sb.append(" libvpx: ").append(NativeCodecBridge.isLibvpxAvailable() ? "yes" : "no").append('\n');
|
||||
sb.append(" Opus: ").append(NativeCodecBridge.isOpusAvailable() ? "yes (native)" : "no (stub→AAC)")
|
||||
.append('\n');
|
||||
sb.append(" Speex: ").append(NativeCodecBridge.isSpeexAvailable() ? "yes (native)" : "no (stub→AAC)")
|
||||
.append('\n');
|
||||
sb.append('\n').append("Video encoders (MediaCodec):\n");
|
||||
appendVideoCodecLines(sb, true, settings);
|
||||
List<String> swEnc = CodecCatalog.localNativeSoftwareVideoLines(settings);
|
||||
if (!swEnc.isEmpty()) {
|
||||
sb.append('\n').append("Video encoders (bundled SW, not in MediaCodec list):\n");
|
||||
for (String line : swEnc) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
sb.append('\n').append("Video decoders (MediaCodec):\n");
|
||||
appendVideoCodecLines(sb, false, settings);
|
||||
if (!swEnc.isEmpty()) {
|
||||
sb.append('\n').append("Video decoders (bundled SW):\n");
|
||||
for (String line : CodecCatalog.localNativeSoftwareVideoDecoderLines(settings)) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
sb.append('\n').append("MIME summary (catalog):\n");
|
||||
sb.append(" advertised enc: ").append(join(CodecCatalog.localEncoderMimes())).append('\n');
|
||||
sb.append(" advertised dec: ").append(join(CodecCatalog.localDecoderMimes()));
|
||||
if (NativeCodecBridge.isLibvpxAvailable()) {
|
||||
sb.append('\n').append(" native SW: video/vp8, video/vp9 (libvpx JNI)");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
@@ -101,12 +127,37 @@ public final class DevDiagnosticsProbe {
|
||||
sb.append(" • playback+mic (when mic available)\n");
|
||||
sb.append(" • mic (calibration / no projection)\n");
|
||||
}
|
||||
sb.append('\n').append("Voice codecs (settings):\n");
|
||||
for (CodecCatalog.AudioCodecChoice c : CodecCatalog.audioCodecChoicesForUi()) {
|
||||
sb.append(" • ").append(c.codec.name())
|
||||
.append(c.selectable ? "" : " (N/A)")
|
||||
.append('\n');
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String probeAudioCodecs(Context context) {
|
||||
CastSettings settings = AppPreferences.loadSenderDefaults(context);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("User preference (sender): ").append(settings.getAudioCodec().name()).append('\n');
|
||||
sb.append("Wire mask: 0x").append(Integer.toHexString(CastCodecFlags.localAudioMask())).append('\n');
|
||||
sb.append('\n').append("Settings choices (priority order after AUTO):\n");
|
||||
for (CodecCatalog.AudioCodecChoice c : CodecCatalog.audioCodecChoicesForUi(settings)) {
|
||||
if (c.codec == CastSettings.AudioCodec.AUTO) {
|
||||
sb.append(" • AUTO\n");
|
||||
continue;
|
||||
}
|
||||
String marker = c.codec == settings.getAudioCodec() ? " ← user" : "";
|
||||
sb.append(String.format(Locale.US, " • %s pri=%d%s\n",
|
||||
c.codec.name(), c.priorityScore, marker));
|
||||
}
|
||||
sb.append('\n').append("MIME summary (catalog):\n");
|
||||
sb.append(" advertised enc:\n");
|
||||
for (String line : CodecCatalog.localAdvertisedAudioEncoderLines(settings)) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
sb.append(" advertised dec:\n");
|
||||
for (String line : CodecCatalog.localAdvertisedAudioDecoderLines(settings)) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
sb.append('\n').append("Audio encoders (MediaCodec):\n");
|
||||
appendAudioCodecLines(sb, true);
|
||||
sb.append('\n').append("Audio decoders (MediaCodec):\n");
|
||||
appendAudioCodecLines(sb, false);
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
@@ -150,7 +201,39 @@ public final class DevDiagnosticsProbe {
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private static void appendVideoCodecLines(StringBuilder sb, boolean encoder) {
|
||||
private static void appendVideoCodecLines(StringBuilder sb, boolean encoder, CastSettings settings) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo info : list.getCodecInfos()) {
|
||||
if (info.isEncoder() != encoder) {
|
||||
continue;
|
||||
}
|
||||
boolean hw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isHardwareAccelerated();
|
||||
CodecTier tier = hw ? CodecTier.HARDWARE : CodecTier.SOFTWARE;
|
||||
String tierTag = hw ? "HW" : "SW";
|
||||
for (String type : info.getSupportedTypes()) {
|
||||
if (type == null || !type.startsWith("video/")) {
|
||||
continue;
|
||||
}
|
||||
int score = CodecPriorityCatalog.priorityScoreForVideoMime(type, tier, settings);
|
||||
lines.add(formatCodecDiagLine(tierTag, score, type, info.getName()));
|
||||
}
|
||||
}
|
||||
if (lines.isEmpty()) {
|
||||
sb.append(" (none)\n");
|
||||
return;
|
||||
}
|
||||
for (String line : lines) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/** `[HW:0042] MIME: 'video/avc', c2.unisoc.avc.encoder` — tier tag + catalog priority score. */
|
||||
private static String formatCodecDiagLine(String tierTag, int priorityScore, String mime, String codecName) {
|
||||
return String.format(Locale.US, " [%s:%04d] MIME: '%s', %s", tierTag, priorityScore, mime, codecName);
|
||||
}
|
||||
|
||||
private static void appendAudioCodecLines(StringBuilder sb, boolean encoder) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo info : list.getCodecInfos()) {
|
||||
@@ -159,7 +242,7 @@ public final class DevDiagnosticsProbe {
|
||||
}
|
||||
boolean hw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isHardwareAccelerated();
|
||||
for (String type : info.getSupportedTypes()) {
|
||||
if (type == null || !type.startsWith("video/")) {
|
||||
if (type == null || !type.startsWith("audio/")) {
|
||||
continue;
|
||||
}
|
||||
lines.add(String.format(Locale.US, " %s [%s] %s", type, hw ? "HW" : "SW", info.getName()));
|
||||
|
||||
@@ -255,23 +255,11 @@ public final class DevNetworkSelfTestProbe {
|
||||
return;
|
||||
}
|
||||
HttpResult head = httpHead(url);
|
||||
if (head.code > 0 && head.code < 500) {
|
||||
r.itemPass("head", head.summary);
|
||||
} else {
|
||||
r.itemFail("head", head.summary);
|
||||
}
|
||||
reportHttp(r, "head", head.code, head.summary);
|
||||
HttpReadResult read = httpRead(url, 32 * 1024);
|
||||
if (read.summary.startsWith("fail")) {
|
||||
r.itemFail("downlink", read.summary);
|
||||
} else {
|
||||
r.itemPass("downlink", read.summary);
|
||||
}
|
||||
reportHttpRead(r, "downlink", read.code, read.summary);
|
||||
HttpResult uplink = httpPostJson(url, "{\"ping\":\"bandwidth\"}");
|
||||
if (uplink.code > 0) {
|
||||
r.itemPass("uplink", uplink.summary);
|
||||
} else {
|
||||
r.itemFail("uplink", uplink.summary);
|
||||
}
|
||||
reportHttp(r, "uplink", uplink.code, uplink.summary);
|
||||
}
|
||||
|
||||
private static void probeNtp(Context context, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
|
||||
@@ -299,11 +287,15 @@ public final class DevNetworkSelfTestProbe {
|
||||
String tz = new java.text.SimpleDateFormat("Z", Locale.US).format(new java.util.Date());
|
||||
String body = "{\"heartbeat\":{\"type\":\"ntp\",\"date\":" + now + ",\"tz\":\"" + tz + "\"}}";
|
||||
HttpTextResult text = httpPostJsonText(heartbeatUrl, body);
|
||||
if (text.summary.startsWith("HTTP") && !text.summary.startsWith("fail")) {
|
||||
if (httpSuccess(text.code)) {
|
||||
r.itemPass("be-ntp", text.summary);
|
||||
if (text.body != null && !text.body.isEmpty()) {
|
||||
r.itemPass("be-payload", truncate(text.body, 120));
|
||||
}
|
||||
} else if (text.code == 404) {
|
||||
r.itemFail("be-ntp", text.summary + " · heartbeat.php missing on host");
|
||||
} else if (text.code > 0) {
|
||||
r.itemFail("be-ntp", text.summary);
|
||||
} else {
|
||||
r.itemFail("be-ntp", text.summary);
|
||||
}
|
||||
@@ -331,7 +323,9 @@ public final class DevNetworkSelfTestProbe {
|
||||
if (!backend.isEmpty()) {
|
||||
HttpTextResult viaBackend = httpPostJsonText(toHeartbeatUrl(backend),
|
||||
"{\"heartbeat\":{\"type\":\"ip\"}}");
|
||||
if (viaBackend.summary.startsWith("fail")) {
|
||||
if (viaBackend.code < 0) {
|
||||
r.itemFail("external-ip", viaBackend.summary);
|
||||
} else if (!httpSuccess(viaBackend.code)) {
|
||||
r.itemFail("external-ip", viaBackend.summary);
|
||||
} else {
|
||||
r.itemPass("external-ip", viaBackend.summary);
|
||||
@@ -342,7 +336,9 @@ public final class DevNetworkSelfTestProbe {
|
||||
return;
|
||||
}
|
||||
HttpTextResult web = httpGetText("https://api.ipify.org?format=json");
|
||||
if (web.summary.startsWith("fail")) {
|
||||
if (web.code < 0) {
|
||||
r.itemFail("external-ip", web.summary);
|
||||
} else if (!httpSuccess(web.code)) {
|
||||
r.itemFail("external-ip", web.summary);
|
||||
} else {
|
||||
r.itemPass("external-ip", truncate(web.summary, 100));
|
||||
@@ -507,6 +503,31 @@ public final class DevNetworkSelfTestProbe {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean httpSuccess(int code) {
|
||||
return code >= 200 && code < 300;
|
||||
}
|
||||
|
||||
/** 2xx pass; 404 fail; other 4xx warn (host answered, wrong path/method/body). */
|
||||
private static void reportHttp(DevNetworkSelfTestReport r, String key, int code, String summary) {
|
||||
if (httpSuccess(code)) {
|
||||
r.itemPass(key, summary);
|
||||
} else if (code == 404) {
|
||||
r.itemFail(key, summary + " · not found");
|
||||
} else if (code > 0 && code < 500) {
|
||||
r.itemWarn(key, summary + " · reachable, unexpected status");
|
||||
} else {
|
||||
r.itemFail(key, summary);
|
||||
}
|
||||
}
|
||||
|
||||
private static void reportHttpRead(DevNetworkSelfTestReport r, String key, int code, String summary) {
|
||||
if (summary.startsWith("fail")) {
|
||||
r.itemFail(key, summary);
|
||||
} else {
|
||||
reportHttp(r, key, code, summary);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpResult httpHead(String url) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
try {
|
||||
@@ -570,11 +591,11 @@ public final class DevNetworkSelfTestProbe {
|
||||
long ms = Math.max(1L, System.currentTimeMillis() - t0);
|
||||
double kbps = (read * 8.0) / ms;
|
||||
c.disconnect();
|
||||
return new HttpReadResult("HTTP " + code + " · " + read + " B · "
|
||||
return new HttpReadResult(code, "HTTP " + code + " · " + read + " B · "
|
||||
+ String.format(Locale.US, "%.1f Kbit/s", kbps));
|
||||
} catch (Exception e) {
|
||||
long ms = System.currentTimeMillis() - t0;
|
||||
return new HttpReadResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms");
|
||||
return new HttpReadResult(-1, "fail · " + e.getClass().getSimpleName() + " · " + ms + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,10 +615,10 @@ public final class DevNetworkSelfTestProbe {
|
||||
String text = readBody(c);
|
||||
long ms = System.currentTimeMillis() - t0;
|
||||
c.disconnect();
|
||||
return new HttpTextResult("HTTP " + code + " · " + ms + " ms", text);
|
||||
return new HttpTextResult(code, "HTTP " + code + " · " + ms + " ms", text);
|
||||
} catch (Exception e) {
|
||||
long ms = System.currentTimeMillis() - t0;
|
||||
return new HttpTextResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
|
||||
return new HttpTextResult(-1, "fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,10 +633,10 @@ public final class DevNetworkSelfTestProbe {
|
||||
String text = readBody(c);
|
||||
long ms = System.currentTimeMillis() - t0;
|
||||
c.disconnect();
|
||||
return new HttpTextResult("HTTP " + code + " · " + ms + " ms", text);
|
||||
return new HttpTextResult(code, "HTTP " + code + " · " + ms + " ms", text);
|
||||
} catch (Exception e) {
|
||||
long ms = System.currentTimeMillis() - t0;
|
||||
return new HttpTextResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
|
||||
return new HttpTextResult(-1, "fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,18 +672,22 @@ public final class DevNetworkSelfTestProbe {
|
||||
}
|
||||
|
||||
private static final class HttpReadResult {
|
||||
final int code;
|
||||
final String summary;
|
||||
|
||||
HttpReadResult(String summary) {
|
||||
HttpReadResult(int code, String summary) {
|
||||
this.code = code;
|
||||
this.summary = summary;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HttpTextResult {
|
||||
final int code;
|
||||
final String summary;
|
||||
final String body;
|
||||
|
||||
HttpTextResult(String summary, String body) {
|
||||
HttpTextResult(int code, String summary, String body) {
|
||||
this.code = code;
|
||||
this.summary = summary;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/** Discovers device video encoder/decoder MIME types and UI codec choices with priority scores. */
|
||||
@@ -102,6 +103,70 @@ public final class CodecCatalog {
|
||||
return audioCodecChoicesForUi(null);
|
||||
}
|
||||
|
||||
/** Advertised audio encoder lines for diagnostics (MIME + tier + priority). */
|
||||
public static List<String> localAdvertisedAudioEncoderLines(CastSettings settings) {
|
||||
CastSettings s = settings != null ? settings : new CastSettings();
|
||||
List<String> out = new ArrayList<>();
|
||||
out.add(audioAdvertisedLine(CastSettings.AudioCodec.AAC, MediaFormat.MIMETYPE_AUDIO_AAC, s));
|
||||
out.add(audioAdvertisedLine(CastSettings.AudioCodec.OPUS, "audio/opus", s));
|
||||
out.add(audioAdvertisedLine(CastSettings.AudioCodec.SPEEX, "audio/speex", s));
|
||||
out.add(audioAdvertisedLine(CastSettings.AudioCodec.PASSTHROUGH_DEBUG, "audio/raw", s));
|
||||
return out;
|
||||
}
|
||||
|
||||
public static List<String> localAdvertisedAudioDecoderLines(CastSettings settings) {
|
||||
return localAdvertisedAudioEncoderLines(settings);
|
||||
}
|
||||
|
||||
private static String audioAdvertisedLine(CastSettings.AudioCodec codec, String mime, CastSettings settings) {
|
||||
CodecTier tier = com.foxx.androidcast.media.CodecPriorityCatalog.tierForAudioPreference(codec);
|
||||
int score = com.foxx.androidcast.media.CodecPriorityCatalog.priorityScore(codec, settings);
|
||||
String backend = audioBackendLabel(codec);
|
||||
return String.format(Locale.US, " %s [%s] pri=%d · %s", mime, tier.name(), score, backend);
|
||||
}
|
||||
|
||||
private static String audioBackendLabel(CastSettings.AudioCodec codec) {
|
||||
switch (codec) {
|
||||
case OPUS:
|
||||
return NativeCodecBridge.isOpusAvailable() ? "Opus native enc" : "Opus stub→AAC enc";
|
||||
case SPEEX:
|
||||
return NativeCodecBridge.isSpeexAvailable() ? "Speex native enc" : "Speex stub→AAC enc";
|
||||
case PASSTHROUGH_DEBUG:
|
||||
return "PCM passthrough (debug)";
|
||||
case AAC:
|
||||
default:
|
||||
return "MediaCodec AAC enc";
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> localNativeSoftwareVideoLines(CastSettings settings) {
|
||||
CastSettings s = settings != null ? settings : new CastSettings();
|
||||
List<String> out = new ArrayList<>();
|
||||
if (!NativeCodecBridge.isLibvpxAvailable()) {
|
||||
return out;
|
||||
}
|
||||
out.add(nativeVideoLine(MediaFormat.MIMETYPE_VIDEO_VP8, CastSettings.VideoCodec.LIBVPX_VP8, s, "enc"));
|
||||
out.add(nativeVideoLine(MediaFormat.MIMETYPE_VIDEO_VP9, CastSettings.VideoCodec.LIBVPX_VP9, s, "enc"));
|
||||
return out;
|
||||
}
|
||||
|
||||
public static List<String> localNativeSoftwareVideoDecoderLines(CastSettings settings) {
|
||||
CastSettings s = settings != null ? settings : new CastSettings();
|
||||
List<String> out = new ArrayList<>();
|
||||
if (!NativeCodecBridge.isLibvpxAvailable()) {
|
||||
return out;
|
||||
}
|
||||
out.add(nativeVideoLine(MediaFormat.MIMETYPE_VIDEO_VP8, CastSettings.VideoCodec.LIBVPX_VP8, s, "dec"));
|
||||
out.add(nativeVideoLine(MediaFormat.MIMETYPE_VIDEO_VP9, CastSettings.VideoCodec.LIBVPX_VP9, s, "dec"));
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String nativeVideoLine(String mime, CastSettings.VideoCodec codec, CastSettings settings,
|
||||
String role) {
|
||||
int score = com.foxx.androidcast.media.CodecPriorityCatalog.priorityScore(codec, settings);
|
||||
return String.format(Locale.US, " [SW:%04d] MIME: '%s', libvpx JNI %s", score, mime, role);
|
||||
}
|
||||
|
||||
public static List<CastSettings.VideoCodec> supportedChoices() {
|
||||
List<CastSettings.VideoCodec> out = new ArrayList<>();
|
||||
for (VideoCodecChoice c : videoCodecChoicesForUi()) {
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
android:id="@+id/dev_diag_audio"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_audio_codecs"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_network"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
<string name="dev_diag_hw_title">Аппаратное ускорение (видео)</string>
|
||||
<string name="dev_diag_camera_title">Захват с камеры</string>
|
||||
<string name="dev_diag_audio_title">Захват звука</string>
|
||||
<string name="dev_diag_audio_codecs_title">Аудиокодеки (сжатие)</string>
|
||||
<string name="dev_diag_network_title">Сеть</string>
|
||||
<string name="dev_diag_casting_title">Другой вывод / трансляция</string>
|
||||
<string name="dev_sender_resolution_ui_section">Разрешение отправителя (тест)</string>
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
<string name="dev_diag_hw_title">Hardware acceleration (video)</string>
|
||||
<string name="dev_diag_camera_title">Camera capture</string>
|
||||
<string name="dev_diag_audio_title">Sound capture</string>
|
||||
<string name="dev_diag_audio_codecs_title">Audio codecs (compression)</string>
|
||||
<string name="dev_diag_network_title">Network</string>
|
||||
<string name="dev_diag_casting_title">Other casting / display</string>
|
||||
<string name="dev_sender_resolution_ui_section">Sender resolution UI (test)</string>
|
||||
|
||||
Reference in New Issue
Block a user