Das Gerät war da, die VA-Anzeige dahinter nicht
ffmpeg endete mit 234. No VA display found for device
/dev/dri/renderD128. Device creation failed: -22.
Hier stand `File.Exists("/dev/dri/renderD128")`, und das war keine
Prüfung, sondern eine Vermutung. Auf dem NAS liegt das Gerät
durchgereicht da — eine VA-Anzeige steht nicht dahinter. ffmpeg bricht
dann schon an der globalen Option ab, vor dem ersten Bild, und für den
Zuschauer sieht das aus wie ein kaputter Server: HTTP 500 auf die
Abspielliste, „Die Wiedergabe wurde unterbrochen".
Jetzt wird ffmpeg einmal je Lauf wirklich gefragt: ein Bild aus dem
Nichts, nirgendwohin, mit Frist. Geht die Zeile vor dem ersten Bild nicht
durch, läuft es in Software weiter — langsamer, aber es läuft.
FLARO_HWACCEL=vaapi überspringt die Probe für den, der es ausdrücklich
verlangt.
Dazu, aus derselben Gegend: in der Hauptliste stehen nur Untertitelspuren,
die jetzt sofort abholbar sind. Sie werden beim ersten Bedarf aus der
Datei gezogen, und bei einem Dreistünder dauert das Minuten. Der Browser
verträgt das — er fragt die Spur einzeln an und wartet. AVPlayer bricht
daran die ganze Wiedergabe ab, nicht bloss den Untertitel. Das Füllen
läuft beim Anlegen der Sitzung schon an; beim nächsten Abspielen stehen
sie drin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
34ade18ed6
commit
b88e58d5eb
@@ -143,7 +143,7 @@ public static class StreamEndpoints
|
||||
// weiterhin `index.m3u8`.
|
||||
app.MapGet("/api/v1/stream/{sitzung:guid}/master.m3u8", async (
|
||||
Guid sitzung, HttpResponse antwort, Streamkoordinator koordinator,
|
||||
FlaroContext db, CancellationToken ct) =>
|
||||
FlaroContext db, Untertitelvorrat vorrat, CancellationToken ct) =>
|
||||
{
|
||||
// Dieselbe Begründung wie bei der Medienliste darunter: eine
|
||||
// Liste, die sich ändern kann, darf nicht zwischengespeichert
|
||||
@@ -157,8 +157,23 @@ public static class StreamEndpoints
|
||||
.OrderBy(x => x.Index)
|
||||
.ToListAsync(ct);
|
||||
|
||||
var text = spuren
|
||||
var datei = await db.MediaFiles.AsNoTracking()
|
||||
.FirstOrDefaultAsync(f => f.Id == laufend.DateiId, ct);
|
||||
|
||||
// **Nur, was jetzt sofort abholbar ist.**
|
||||
//
|
||||
// Untertitel werden beim ersten Bedarf aus der Datei gezogen; bei
|
||||
// einem Dreistünder auf einem NAS dauert das Minuten. Der Browser
|
||||
// verträgt das, er fragt die Spur einzeln an und wartet. AVPlayer
|
||||
// nicht: eine Spur, die in der Hauptliste steht und beim Abruf
|
||||
// noch nicht da ist, bricht ihm die **ganze** Wiedergabe ab, nicht
|
||||
// bloss den Untertitel.
|
||||
//
|
||||
// Das Füllen ist beim Anlegen der Sitzung schon angestossen — beim
|
||||
// nächsten Abspielen stehen sie drin.
|
||||
var text = datei is null ? [] : spuren
|
||||
.Where(x => x.Codec is null || !Untertitelvorrat.IstBild(x.Codec))
|
||||
.Where(x => vorrat.Liegtvor(datei.Id, datei.ModifiedAt, x.Index))
|
||||
.ToList();
|
||||
|
||||
var liste = new StringBuilder();
|
||||
|
||||
@@ -259,14 +259,34 @@ public sealed class Streamkoordinator(
|
||||
/// <summary>Ob die Playlist schon etwas enthält, das sich abspielen lässt.</summary>
|
||||
public static bool PlaylistBereit(string ordner) => Playlist(ordner) is not null;
|
||||
|
||||
/// <summary>Das Gerät, über das VAAPI läuft.</summary>
|
||||
private const string Vaapigeraet = "/dev/dri/renderD128";
|
||||
|
||||
/// <summary>Das Ergebnis der einmaligen Probe — <c>null</c>, solange keine lief.</summary>
|
||||
private bool? _vaapiLaeuft;
|
||||
|
||||
/// <summary>Welche Beschleunigung benutzt wird.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b><c>auto</c> prüft, statt zu behaupten.</b> Liegt kein
|
||||
/// <c>/dev/dri/renderD128</c> vor — auf einem Mac, in einem Container ohne
|
||||
/// durchgereichtes Gerät —, läuft es in Software weiter. Langsamer, aber
|
||||
/// es läuft; ein ffmpeg, das mit „No VA display found" abbricht, wäre für
|
||||
/// den Zuschauer dasselbe wie ein kaputter Server.
|
||||
/// <para>
|
||||
/// <b><c>auto</c> fragt ffmpeg, statt die Datei anzusehen.</b> Hier stand
|
||||
/// <c>File.Exists("/dev/dri/renderD128")</c>, und das war keine Prüfung,
|
||||
/// sondern eine Vermutung: auf dem NAS liegt das Gerät durchgereicht da,
|
||||
/// aber dahinter steht keine VA-Anzeige. ffmpeg bricht dann schon an der
|
||||
/// globalen Option ab — <c>No VA display found for device</c>, Rückgabe
|
||||
/// 234 —, und für den Zuschauer sieht das aus wie ein kaputter Server.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Einmal je Lauf.</b> Die Probe kostet einen ffmpeg-Start; sie vor
|
||||
/// jeder Wiedergabe zu wiederholen wäre Aufwand für eine Antwort, die
|
||||
/// sich nicht ändert. Wechselt die Hardware, wechselt auch der
|
||||
/// Container.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b><c>FLARO_HWACCEL=vaapi</c> überspringt sie</b> — wer es
|
||||
/// ausdrücklich verlangt, hat einen Grund, und ein Fehlschlag steht dann
|
||||
/// im Protokoll.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private Beschleunigung Hardware()
|
||||
{
|
||||
@@ -276,10 +296,62 @@ public sealed class Streamkoordinator(
|
||||
{
|
||||
"none" or "software" => Beschleunigung.Software,
|
||||
"vaapi" => Beschleunigung.Vaapi,
|
||||
_ => File.Exists("/dev/dri/renderD128") ? Beschleunigung.Vaapi : Beschleunigung.Software,
|
||||
_ => (_vaapiLaeuft ??= VaapiLaeuft()) ? Beschleunigung.Vaapi : Beschleunigung.Software,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Lässt ffmpeg das Gerät einmal wirklich öffnen.</summary>
|
||||
private bool VaapiLaeuft()
|
||||
{
|
||||
if (!File.Exists(Vaapigeraet)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var start = new ProcessStartInfo(ffmpeg.Ffmpeg)
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
};
|
||||
// Ein Bild aus dem Nichts, nirgendwohin. Es geht allein darum, ob
|
||||
// die Zeile vor dem ersten Bild durchgeht.
|
||||
foreach (var a in new[]
|
||||
{
|
||||
"-hide_banner", "-loglevel", "error",
|
||||
"-vaapi_device", Vaapigeraet,
|
||||
"-f", "lavfi", "-i", "nullsrc=s=64x64:d=0.1",
|
||||
"-frames:v", "1", "-f", "null", "-",
|
||||
})
|
||||
{
|
||||
start.ArgumentList.Add(a);
|
||||
}
|
||||
|
||||
using var prozess = Process.Start(start);
|
||||
if (prozess is null) return false;
|
||||
|
||||
// **Mit Frist.** Ein hängender Prozess hielte sonst die erste
|
||||
// Wiedergabe nach dem Start auf.
|
||||
if (!prozess.WaitForExit(5000))
|
||||
{
|
||||
try { prozess.Kill(entireProcessTree: true); } catch (InvalidOperationException) { }
|
||||
log.LogWarning("Die VAAPI-Probe antwortete nicht; es läuft in Software.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prozess.ExitCode == 0) return true;
|
||||
|
||||
log.LogInformation(
|
||||
"VAAPI ist nicht benutzbar ({Code}); es läuft in Software. {Grund}",
|
||||
prozess.ExitCode, prozess.StandardError.ReadToEnd().Trim());
|
||||
return false;
|
||||
}
|
||||
catch (Exception fehler) when (fehler is System.ComponentModel.Win32Exception or IOException)
|
||||
{
|
||||
log.LogWarning(fehler, "Die VAAPI-Probe liess sich nicht starten; es läuft in Software.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Process Starten(Stromanfrage anfrage, Stromsitzung sitzung)
|
||||
{
|
||||
var argumente = HlsArgumente.Fuer(anfrage);
|
||||
|
||||
@@ -137,6 +137,25 @@ public sealed class Untertitelvorrat(
|
||||
if (offen > 0) log.LogInformation("Untertitel: {Anzahl} Datei(en) werden im Hintergrund gelesen.", offen);
|
||||
}
|
||||
|
||||
/// <summary>Ob diese Spur jetzt sofort ausgeliefert werden kann.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Ohne zu warten und ohne anzustossen.</b> Für die HLS-Hauptliste:
|
||||
/// was dort steht, muss ein Spieler in dem Augenblick abholen können, in
|
||||
/// dem er es liest. AVPlayer nimmt ein „gleich" nicht an — eine Spur,
|
||||
/// die beim Abruf noch nicht da ist, lässt ihn die ganze Wiedergabe
|
||||
/// abbrechen, nicht bloss den Untertitel.
|
||||
///
|
||||
/// <b>Eine leere Datei zählt nicht.</b> Sie ist das Ergebnis „diese Spur
|
||||
/// hat keinen Text"; sie anzubieten wäre ein Menüeintrag, der zu nichts
|
||||
/// führt.
|
||||
/// </remarks>
|
||||
public bool Liegtvor(Guid dateiId, DateTimeOffset geaendert, int spur)
|
||||
{
|
||||
var ziel = Pfad(dateiId, geaendert, spur);
|
||||
return File.Exists(ziel) && new FileInfo(ziel).Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>Holt eine Spur — und wartet dabei höchstens <paramref name="geduld"/>.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
|
||||
Reference in New Issue
Block a user