Воспроизведение аудио из потока с помощью C#
Есть ли способ в C# воспроизводить аудио (например, MP3) непосредственно из System.IO.Stream, который, например, был повторно сохранен из WebRequest без временного сохранения данных на диск?
Решение с NAudio
С помощью NAudio 1.3 возможно:
- Загрузить файл MP3 из URL-адреса в MemoryStream
- Преобразование данных MP3 в волновые данные после полной загрузки
- Воспроизведение волновых данных с помощью класса WaveOut NAudio
Было бы неплохо иметь возможность воспроизводить даже половинный загруженный файл MP3, но это кажется невозможным из-за дизайна библиотеки NAudio.
И это функция, которая будет делать работу:
public static void PlayMp3FromUrl(string url)
{
using (Stream ms = new MemoryStream())
{
using (Stream stream = WebRequest.Create(url)
.GetResponse().GetResponseStream())
{
byte[] buffer = new byte[32768];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
}
ms.Position = 0;
using (WaveStream blockAlignedStream =
new BlockAlignReductionStream(
WaveFormatConversionStream.CreatePcmStream(
new Mp3FileReader(ms))))
{
using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
{
waveOut.Init(blockAlignedStream);
waveOut.Play();
while (waveOut.PlaybackState == PlaybackState.Playing )
{
System.Threading.Thread.Sleep(100);
}
}
}
}
}
10 ответов
Изменить: Ответ обновлен, чтобы отразить изменения в последних версиях NAudio
Это возможно, используя аудио библиотеку NAudio с открытым исходным кодом, которую я написал. Он ищет кодек ACM на вашем компьютере, чтобы сделать преобразование. Mp3FileReader, поставляемый с NAudio, в настоящее время ожидает возможности изменения положения в исходном потоке (он создает индекс кадров MP3 заранее), поэтому он не подходит для потоковой передачи по сети. Тем не менее, вы все еще можете использовать MP3Frame
а также AcmMp3FrameDecompressor
занятия в NAudio по распаковке потоковых MP3 на лету.
Я разместил в своем блоге статью, объясняющую, как воспроизводить поток MP3 с помощью NAudio. По сути, у вас есть один поток, загружающий MP3-кадры, распаковывающий их и сохраняющий в BufferedWaveProvider
, Затем другой поток воспроизводит, используя BufferedWaveProvider
в качестве входа.
Класс SoundPlayer может сделать это. Похоже, все, что вам нужно сделать, это установить его свойство Stream для потока, а затем вызвать Play
,
редактировать
Я не думаю, что он может воспроизводить файлы MP3, хотя; кажется ограниченным.wav. Я не уверен, есть ли что-то в рамках, которое может воспроизводить файл MP3 напрямую. Все, что я нахожу об этом, включает в себя либо использование элемента управления WMP, либо взаимодействие с DirectX.
Бас может сделать только это. Воспроизведение с байта [] в памяти или сквозного файла делегатов, куда вы возвращаете данные, так что вы можете воспроизводить их, как только у вас будет достаточно данных, чтобы начать воспроизведение.
Я немного изменил исходный текст темы, чтобы теперь он мог воспроизводить не полностью загруженный файл. Вот оно (обратите внимание, что это всего лишь пример, с которого нужно начинать; здесь нужно сделать некоторые исключения и обработать ошибки):
private Stream ms = new MemoryStream();
public void PlayMp3FromUrl(string url)
{
new Thread(delegate(object o)
{
var response = WebRequest.Create(url).GetResponse();
using (var stream = response.GetResponseStream())
{
byte[] buffer = new byte[65536]; // 64KB chunks
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
var pos = ms.Position;
ms.Position = ms.Length;
ms.Write(buffer, 0, read);
ms.Position = pos;
}
}
}).Start();
// Pre-buffering some data to allow NAudio to start playing
while (ms.Length < 65536*10)
Thread.Sleep(1000);
ms.Position = 0;
using (WaveStream blockAlignedStream = new BlockAlignReductionStream(WaveFormatConversionStream.CreatePcmStream(new Mp3FileReader(ms))))
{
using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
{
waveOut.Init(blockAlignedStream);
waveOut.Play();
while (waveOut.PlaybackState == PlaybackState.Playing)
{
System.Threading.Thread.Sleep(100);
}
}
}
}
Я настроил источник, размещенный в вопросе, чтобы разрешить использование с TTS API Google, чтобы ответить на вопрос здесь:
bool waiting = false;
AutoResetEvent stop = new AutoResetEvent(false);
public void PlayMp3FromUrl(string url, int timeout)
{
using (Stream ms = new MemoryStream())
{
using (Stream stream = WebRequest.Create(url)
.GetResponse().GetResponseStream())
{
byte[] buffer = new byte[32768];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
}
ms.Position = 0;
using (WaveStream blockAlignedStream =
new BlockAlignReductionStream(
WaveFormatConversionStream.CreatePcmStream(
new Mp3FileReader(ms))))
{
using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
{
waveOut.Init(blockAlignedStream);
waveOut.PlaybackStopped += (sender, e) =>
{
waveOut.Stop();
};
waveOut.Play();
waiting = true;
stop.WaitOne(timeout);
waiting = false;
}
}
}
}
Вызвать с:
var playThread = new Thread(timeout => PlayMp3FromUrl("http://translate.google.com/translate_tts?q=" + HttpUtility.UrlEncode(relatedLabel.Text), (int)timeout));
playThread.IsBackground = true;
playThread.Start(10000);
Завершить с:
if (waiting)
stop.Set();
Обратите внимание, что я использую ParameterizedThreadDelegate
в приведенном выше коде, и поток запускается с playThread.Start(10000);
, 10000 представляет максимум 10 секунд звука для воспроизведения, поэтому его необходимо настроить, если для воспроизведения вашего потока требуется больше времени. Это необходимо, потому что текущая версия NAudio (v1.5.4.0), похоже, имеет проблемы с определением того, когда закончится воспроизведение потока. Это может быть исправлено в более поздней версии или, возможно, есть обходной путь, который я не нашел времени, чтобы найти.
Я обернул библиотеку MP3-декодера и сделал ее доступной для разработчиков .NET как mpg123.net.
Прилагаются примеры для преобразования файлов MP3 в PCM и чтения тегов ID3.
NAudio оборачивает API-интерфейс WaveOutXXXX. Я не смотрел на источник, но если NAudio предоставляет функцию waveOutWrite() таким образом, что она не останавливает воспроизведение автоматически при каждом вызове, то вы сможете делать то, что вам действительно нужно, то есть начинать воспроизведение аудио поток, прежде чем вы получили все данные.
Использование функции waveOutWrite() позволяет "читать вперед" и выгружать меньшие порции аудио в очередь вывода - Windows автоматически воспроизводит порции без проблем. Ваш код должен принимать сжатый аудиопоток и преобразовывать его в небольшие фрагменты WAV-аудио на лету; эта часть была бы действительно трудной - все библиотеки и компоненты, которые я когда-либо видел, делают преобразование MP3 в WAV целый файл за один раз. Вероятно, ваш единственный реальный шанс - сделать это, используя WMA вместо MP3, потому что вы можете написать простые обертки C# вокруг мультимедийного SDK.
Я не пробовал его из WebRequest, но компоненты ActiveX проигрывателя Windows Media и компоненты MediaElement (из WPF) способны воспроизводить и буферизовать потоки MP3.
Я использую его для воспроизведения данных, поступающих из потока SHOUTcast, и он работал отлично. Однако я не уверен, будет ли это работать в предложенном вами сценарии.
Это не самое элегантное решение, но это простое решение. Отправьте поток из веб-ответа в файл, а затем воспроизведите его с помощью . Он кроссплатформенный и принимает большинство форматов файлов без какой-либо настройки. Вот суть github, которая показывает способ сделать это: https://gist.github.com/skittleson/095c0e9a4d56fbc53b80d22029e90c9b .
Я хотел бы выяснить, как это сделать путем потоковой передачи непосредственно наffplay
стихи временный файл.
ОБНОВЛЕНИЕ: это можно сделать с помощью потока.
await Cli.Wrap("ffplay")
.WithStandardInputPipe(PipeSource.FromStream(stream))
.WithArguments($"-autoexit -nodisp -hide_banner -loglevel error -fs -")
.ExecuteAsync();
Я всегда использовал FMOD для подобных вещей, потому что он бесплатный для некоммерческого использования и хорошо работает.
Тем не менее, я бы с радостью переключился на что-то меньшее (FMOD ~300k) и с открытым исходным кодом. Супер бонусные баллы, если они полностью управляются так, что я могу скомпилировать / объединить их с моим.exe и не нужно прилагать дополнительные усилия для переносимости на другие платформы...
(FMOD тоже переносим, но вам, очевидно, потребуются разные двоичные файлы для разных платформ)