Библиотека C# Discord PCMStream WriteAsync зависает на сервере Windows

#c# #.net #asynchronous #discord #discord.net

Вопрос:

Я писал бота discord на C#, чтобы воспроизводить музыку для моего личного канала discord. Все работает как волшебство при запуске в персональной версии Windows (Win10 и Win11). Когда я запускаю программу на любом из своих экземпляров Windows Server (WS2012, 2016 или 2022), она выполняет все, кроме потоковой передачи звука. Проблема, по-видимому, в том, что он зависает при записи в «PCMStream». Но я не могу понять, почему?

Может быть, на сервере отсутствует какой-то кодек? Или какое-то правило брандмауэра? Есть какие-нибудь идеи?

Соответствующий Код:

 private async Task AudioPlaybackStreamAsync(IAudioClient client, SongRequests song)
        {
            _cancelToken = new CancellationTokenSource();

            await Task.Delay(100);

            // Start a new process and create an output stream. Decide between network or local.
            m_Process = CreateNetworkStream(song.SongURL);
            m_Stream = client.CreatePCMStream(AudioApplication.Music); // Consider setting custom bitrate, buffers, and packet loss props.

            m_IsRunning = true;
            m_IsPlaying = true;

            int procID = m_Process.Id;
            await Task.Delay(500); //allow ffmpeg to buffer some of the audio first.
            bool exit = false;
            Console.WriteLine($@"Playing '{song.SongTitle}' on thread '{procID}'");

            int test = 0;

            // We stream the audio in chunks.
            while (!_cancelToken.IsCancellationRequested amp;amp; !exit)
            {
                test  ;
                // If the process is already over, we're finished. If something else kills this process, we stop.
                if (m_Process == null || m_Process.HasExited)
                {
                    break;
                }

                // If the stream is broken, we exit.
                if (m_Stream == null) break;

                // We pause within this function while it's 'not playing'.
                if (!m_IsPlaying) continue;

                // Read the stream in chunks.
                int blockSize = m_BLOCK_SIZE; // Size of bytes to read per frame.
                byte[] buffer = new byte[blockSize];
                int byteCount;

                try
                {
                    byteCount = await m_Process.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, _cancelToken.Token);

                    // If the stream cannot be read or we reach the end of the file, we exit.
                    if (byteCount <= 0) break;

                    // Write out to the stream. Relies on m_Volume to adjust bytes accordingly.
                    Console.Write($@"Iteration {test}: Begin WriteAsync... ");//test
                    await m_Stream.WriteAsync(ScaleVolumeSafeAllocateBuffers(buffer, m_Volume), 0, byteCount, _cancelToken.Token);
                    Console.Write($@"Complete.{Environment.NewLine}");//test
                }
                catch (TaskCanceledException exception)
                {
                    Console.WriteLine(exception);
                    LogHelper.Log(LogCategory.Error, LogSeverity.Normal, "TaskCanceledException", exception.ToString());
                    exit = true;
                    break;
                }
                catch (OperationCanceledException exception)
                {
                    Console.WriteLine(exception);
                    LogHelper.Log(LogCategory.Error, LogSeverity.Normal, "OperationCanceledException", exception.ToString());
                    exit = true;
                    break;
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception);
                    LogHelper.Log(LogCategory.Error, LogSeverity.Normal, "Playback Error", exception.ToString());
                    break;
                }
            }

            // Kill the process, if it's lingering.
            if (m_Process != null amp;amp; !m_Process.HasExited) m_Process.Kill();

            // Flush the stream and wait until it's fully done before continuing.
            if (m_Stream != null)
            {
                await m_Stream.FlushAsync(); //flush remaining
                m_Stream.Close(); //close the connection
            }

            // Reset values. Basically clearing out values (Flush).
            m_Process = null;
            m_Stream = null;
            m_IsPlaying = false;

            // Set running to false.
            m_IsRunning = false;


            Console.WriteLine($@"Finished '{song.SongTitle}' on thread '{procID}'");
        }
 
 private Process CreateNetworkStream(string path)
        {
            try
            {
                ProcessStartInfo psi = new ProcessStartInfo
                {
                    FileName = "cmd.exe",
                    WorkingDirectory = Path.GetDirectoryName($@"{Directory.GetCurrentDirectory()}{ConfigurationManager.AppSettings["BinaryFolder"]}"),
                    Arguments = $@"/C youtube-dl -f 140 -o  - {path} | ffmpeg -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true
                };
                return Process.Start(psi);
            }
            catch
            {
                Console.WriteLine($"Error while opening network stream : {path}");
                return null;
            }
        }
 

Как вы можете видеть, я поместил некоторые выходные данные консоли отладки вокруг функции WriteAsync.

 Console.Write($@"Iteration {test}: Begin WriteAsync... ");//test
await m_Stream.WriteAsync(ScaleVolumeSafeAllocateBuffers(buffer, m_Volume), 0, byteCount, _cancelToken.Token);
Console.Write($@"Complete.{Environment.NewLine}");//test
 

В личных окнах выходные данные будут записывать тысячи итераций и выполняться без проблем. Однако на серверах Windows он зависает очень быстро, обычно около 50-х годов. Он не ошибается и не возвращается, просто сидит там.
Выходное Подвешивание

Это делается с помощью оболочки Discord Net (https://github.com/discord-net/Discord.Net), который, как я теперь понимаю, я должен просто опубликовать там, но я уже напечатал это, так что вот оно.

Комментарии:

1. На экземплярах Windows Server работает ли служба «Windows Audio» при проверке services.msc ? Если он остановлен, можете ли вы запустить его, и проблема все еще возникает, если аудиосервис действительно может запуститься?