fix: use curl pipe to bypass FFmpeg HTTP header buffer limit
The previous -max_alloc approach does not fix the 'overlong headers' error because FFmpeg 5.1 (Debian bookworm) uses a compile-time stack-allocated buffer (MAX_URL_SIZE=4096) for HTTP response headers, which -max_alloc cannot change. Instead, when the stream URL is HTTP/HTTPS, launch curl to handle the HTTP connection and pipe its stdout to FFmpeg via stdin (pipe:0). Curl does not have the 4096-byte header limit, so it handles oversized CDN response headers from Douyin without error. Additional changes: - RequestStopAsync kills curl first (instead of sending 'q' to FFmpeg), causing the pipe to close and FFmpeg to exit gracefully on EOF. - SessionProcessRuntime tracks the curl process for cleanup.
This commit is contained in:
@@ -195,6 +195,69 @@ public sealed partial class FfmpegService : IFfmpegService
|
||||
throw new InvalidOperationException("A running ffmpeg process already exists for the recording session.");
|
||||
}
|
||||
|
||||
// When the stream URL is HTTP, launch curl to handle the HTTP connection and
|
||||
// pipe its stdout into FFmpeg's stdin. This bypasses FFmpeg's built-in HTTP
|
||||
// handler which has a hard-coded 4096-byte response header buffer that triggers
|
||||
// "overlong headers" errors with CDNs that return oversized headers.
|
||||
if (IsHttpInput(streamUrlResult.SelectedUrl))
|
||||
{
|
||||
var curlProcess = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "curl",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
foreach (var arg in BuildCurlArgumentList(streamUrlResult.SelectedUrl, streamUrlResult.InputHeaders))
|
||||
{
|
||||
curlProcess.StartInfo.ArgumentList.Add(arg);
|
||||
}
|
||||
|
||||
curlProcess.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
_logger.LogDebug("curl[{SessionId}] {Line}", recordSession.Id, args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
if (!curlProcess.Start())
|
||||
{
|
||||
process.Kill(true);
|
||||
process.Dispose();
|
||||
throw new InvalidOperationException("curl failed to start.");
|
||||
}
|
||||
|
||||
curlProcess.BeginErrorReadLine();
|
||||
runtime.AttachCurlProcess(curlProcess);
|
||||
|
||||
// Pipe curl stdout → FFmpeg stdin in the background.
|
||||
// When curl exits or is killed, the pipe closes and FFmpeg sees EOF on stdin,
|
||||
// which causes it to exit gracefully after finalizing the output.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await curlProcess.StandardOutput.BaseStream.CopyToAsync(
|
||||
process.StandardInput.BaseStream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "curl→ffmpeg pipe ended for session {RecordSessionId}", recordSession.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { process.StandardInput.Close(); } catch { /* stdin may already be closed */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await PersistProcessBindingAsync(recordSession.Id, initialTask.Id, process.Id, cancellationToken);
|
||||
await PersistStartupProfileAsync(runtime, cancellationToken);
|
||||
await StartDanmakuAsync(runtime, initialTask, cancellationToken);
|
||||
@@ -714,6 +777,23 @@ public sealed partial class FfmpegService : IFfmpegService
|
||||
return;
|
||||
}
|
||||
|
||||
// When curl is piping the stream, kill curl first so the pipe closes
|
||||
// and FFmpeg sees EOF on stdin, causing a graceful exit.
|
||||
var curlProcess = runtime.CurlProcess;
|
||||
if (curlProcess is not null && !curlProcess.HasExited)
|
||||
{
|
||||
try
|
||||
{
|
||||
curlProcess.Kill(true);
|
||||
}
|
||||
catch (Exception curlEx)
|
||||
{
|
||||
_logger.LogWarning(curlEx, "Kill curl process failed for session {RecordSessionId}", recordSessionId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await process.StandardInput.WriteLineAsync("q");
|
||||
await process.StandardInput.FlushAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user