Консультация № 183026
03.05.2011, 14:22
100.46 руб.
0 4 1
Уважаемые эксперты! Подскажите пожалуйста, как в программе на C# (можно с вызовами Win API) выводить на стандартный аудиовыход поток непрерывно поступающих в реальном времени данных, например, генерируемый по таймеру белый шум или синусоиду (в реальной задаче это сигнал звукового диапазона, поступающий от датчики через самодельный АЦП).

Обсуждение

давно
Профессионал
848
1596
05.05.2011, 16:08
общий
это ответ
Здравствуйте, Рощин Вадим Юрьевич!
Код в приложении. На форму 1 кнопку для старта примера. Звуковой буфер и формат звука следует настроить в зависимости от данных принимаемых с АЦП. В ссылках на проект подключите Microsoft.DirectX.DirectSound.

Приложение:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using Microsoft.DirectX.DirectSound;
using System.IO;

namespace ps
{
public partial class MainForm : Form
{
private Device applicationDevice;
private AutoResetEvent[] fillEvent = new AutoResetEvent[2];
private byte[] RawDataBuff=new byte[16000];
private MemoryStream SoundStream;

public MainForm()
{
InitializeComponent();
applicationDevice = new Device();
applicationDevice.SetCooperativeLevel(this, CooperativeLevel.Normal);
}

private void PlaySound()
{
//опишем формат звука
WaveFormat format = new WaveFormat();
format.BitsPerSample = 8;
format.BlockAlign = 1;
format.Channels = 1;
format.SamplesPerSecond = 8000;
format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlign;

BufferDescription desc = new BufferDescription(format);

desc.Flags = BufferDescriptionFlags.GlobalFocus | BufferDescriptionFlags.ControlPositionNotify | BufferDescriptionFlags.CanGetCurrentPosition;
desc.BufferBytes = 2 * format.AverageBytesPerSecond;
desc.DeferLocation = true;
SecondaryBuffer sBuffer = new SecondaryBuffer(desc,applicationDevice);

//создаем события
Notify notify = new Notify(sBuffer);

fillEvent[0] = new AutoResetEvent(false);
fillEvent[1] = new AutoResetEvent(false);

BufferPositionNotify[] posNotify = new BufferPositionNotify[2];
posNotify[0] = new BufferPositionNotify();
posNotify[0].Offset = desc.BufferBytes / 2 - 1;
posNotify[0].EventNotifyHandle =fillEvent[0].Handle;
posNotify[1] = new BufferPositionNotify();
posNotify[1].Offset = desc.BufferBytes - 1;
posNotify[1].EventNotifyHandle =fillEvent[1].Handle;

notify.SetNotificationPositions(posNotify);

byte[] HalfSndBuff = new byte[desc.BufferBytes / 2];
byte[] FullSndBuff = new byte[desc.BufferBytes];

//создадим поток который будет подгружать данные из массива
Thread fillBuffer = new Thread(delegate()
{
int bytesRead;

//загрузим звуковой буфер для воспроизведения полностью
bytesRead = SoundStream.Read(FullSndBuff, 0, desc.BufferBytes);
sBuffer.Write(0,FullSndBuff, LockFlag.None);
sBuffer.Play(0, BufferPlayFlags.Looping);
while (true)
{
//если достигнут конец буфера загрузим новые данные и сдвиним поток на начало
if(SoundStream.Position==16000)
{
LoadData();
SoundStream.Seek(0,SeekOrigin.Begin);
}
//ждем середины буфера воспроизведения
fillEvent[0].WaitOne();
//читаем полбуфера из raw буфера
bytesRead = SoundStream.Read(HalfSndBuff, 0, HalfSndBuff.Length);
//пишем в начало звукового буфера
sBuffer.Write(0, HalfSndBuff, LockFlag.None);

//ждем окончания звукового буфера
fillEvent[1].WaitOne();
//читаем полбуфера из raw буфера
bytesRead = SoundStream.Read(HalfSndBuff, 0, HalfSndBuff.Length);
//пишем во вторую половину звукового буфера
sBuffer.Write(desc.BufferBytes / 2, HalfSndBuff, LockFlag.None);
}
//SoundStream.Close();
//SoundStream.Dispose();
});
fillBuffer.Start();
}

void Button_startClick(object sender, EventArgs e)
{
Random rnd1 = new System.Random();
for (int i = 0; i < 16000; i++)
{
RawDataBuff[i]=(byte) rnd1.Next(255);
}
SoundStream=new MemoryStream(RawDataBuff,true);
PlaySound();
}

void LoadData()
{
//сгенерим случайный массив данных
Random rnd1 = new System.Random();
bool ch;
if((rnd1.Next() % 2) == 0)
ch=true;
else
ch=false;
for (int i = 0; i < 16000; i++)
{
if(ch)
RawDataBuff[i]=(byte) rnd1.Next(255);
else
RawDataBuff[i]=(byte) 0;
}
}
}
}
Неизвестный
05.05.2011, 18:58
общий
Я вчера попробовал сделать вывод через встроенный в .Net проигрыватель. По кускам получается прерывисто. А если эмулировать поток, то плейер все равно сначала все данные стягивает. Может я и ошибся где-то. Со звуком раньше не работал. Есть кольцо из 4х маленьких буферов - файлов wav. Один рабочий поток пишет в них по очереди данные, а второй проигрывает.


Код дизайнера формы
Код:
namespace WindowsFormsApplication1
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.numFreq = new System.Windows.Forms.NumericUpDown();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.numVol = new System.Windows.Forms.NumericUpDown();
((System.ComponentModel.ISupportInitialize)(this.numFreq)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numVol)).BeginInit();
this.SuspendLayout();
//
// numFreq
//
this.numFreq.Location = new System.Drawing.Point(152, 12);
this.numFreq.Maximum = new decimal(new int[] {
24000,
0,
0,
0});
this.numFreq.Name = "numFreq";
this.numFreq.Size = new System.Drawing.Size(120, 20);
this.numFreq.TabIndex = 0;
this.numFreq.ValueChanged += new System.EventHandler(this.numFreq_ValueChanged);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 14);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(49, 13);
this.label1.TabIndex = 1;
this.label1.Text = "Частота";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 48);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(62, 13);
this.label2.TabIndex = 2;
this.label2.Text = "Громкость";
//
// numVol
//
this.numVol.DecimalPlaces = 2;
this.numVol.Increment = new decimal(new int[] {
1,
0,
0,
131072});
this.numVol.Location = new System.Drawing.Point(152, 41);
this.numVol.Maximum = new decimal(new int[] {
1,
0,
0,
0});
this.numVol.Name = "numVol";
this.numVol.Size = new System.Drawing.Size(120, 20);
this.numVol.TabIndex = 3;
this.numVol.ValueChanged += new System.EventHandler(this.numVol_ValueChanged);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(870, 413);
this.Controls.Add(this.numVol);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.numFreq);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.numFreq)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numVol)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.NumericUpDown numFreq;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.NumericUpDown numVol;

}
}


Сам код формы
Код:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Media;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

WavBuffer PlayBuffer, WriteBuffer;
int Frequency;
float Volume;
Semaphore WriterSem, PlayerSem;

private void Form1_Load(object sender, EventArgs e)
{
numFreq.Value = 500;
numVol.Value = 0.75M;
PlayBuffer = WriteBuffer = new WavBuffer();
PlayBuffer.Next = new WavBuffer() { Next = new WavBuffer() { Next = new WavBuffer() { Next = PlayBuffer } } };

WriterSem = new Semaphore(2, 4);
PlayerSem = new Semaphore(0, 4);
new Thread(new ThreadStart(WriteThread)) { IsBackground = true }.Start();
new Thread(new ThreadStart(PlayThread)) { IsBackground = true }.Start();
}

private void WriteThread()
{
double x = 0;
while (true)
{
WriterSem.WaitOne();

int Max = WriteBuffer.File.Length;
byte[] B = WriteBuffer.File;
double Amp = Volume * 32700;
double delta = Frequency * 2 * Math.PI / (44100);

for (int i = WriteBuffer.StartOffset; i < Max; i += 2)
{
double d = Amp * Math.Sin(x);
x += delta;
short s = (short)d;
B[i] = (byte)s;
B[i + 1] = (byte)(s >> 8);
}

WriteBuffer = WriteBuffer.Next;
PlayerSem.Release();
}
}

private void PlayThread()
{
SoundPlayer Player = new SoundPlayer();
while (true)
{
PlayerSem.WaitOne();
PlayBuffer.MemStream.Seek(0, SeekOrigin.Begin);
Player.Stream = PlayBuffer.MemStream;
Player.PlaySync();
WriterSem.Release();
}
}

private void numFreq_ValueChanged(object sender, EventArgs e)
{
Frequency = (int)numFreq.Value;
}

private void numVol_ValueChanged(object sender, EventArgs e)
{
Volume = (float)numVol.Value;
}

class WavBuffer
{
internal WavBuffer()
{
short channr = 1;
int Hz = 44100;
short bitpersample = 16;
short blockalign = (short)(channr * bitpersample / 8);
int bytesps = Hz * blockalign;
int DataLen = bytesps * 2;

int frmLen = 16;
int FileLen = 4 + (8 + frmLen) + (8 + DataLen);

BinaryWriter BW = new BinaryWriter(new MemoryStream(), System.Text.Encoding.ASCII);

BW.Write("RIFF".ToCharArray());//File Header
BW.Write(FileLen);
BW.Write("WAVE".ToCharArray());

BW.Write("fmt ".ToCharArray());//Format chunk
BW.Write(frmLen);
BW.Write((short)1); //PCM
BW.Write(channr); //mono
BW.Write(Hz); //Hz
BW.Write(88200); //byte / s
BW.Write(blockalign); //Block alighn
BW.Write(bitpersample);//bit/sample

BW.Write("data".ToCharArray());//Data chunk
BW.Write(DataLen);
BW.Write(new char[DataLen]);
BW.Flush();

File = (BW.BaseStream as MemoryStream).GetBuffer();
StartOffset = File.Length - DataLen;
MemStream = new MemoryStream(File);
}


public byte[] File;
public int StartOffset;
public WavBuffer Next;
public MemoryStream MemStream;
}
}
}


Неизвестный
05.05.2011, 19:07
общий
Забыл, чо удалил одну строчку. Этот метод надо заменить.

private void PlayThread()
{
SoundPlayer Player = new SoundPlayer();
while (true)
{
PlayerSem.WaitOne();
PlayBuffer.MemStream.Seek(0, SeekOrigin.Begin);
Player.Stream = PlayBuffer.MemStream;
Player.PlaySync();
PlayBuffer = PlayBuffer.Next;
WriterSem.Release();
}
}

Если такой код тоже пойдет, могу добавить его в ответы.
давно
Академик
20764
1861
05.05.2011, 20:11
общий
Я год назад решал аналогичную задачу (но под Линукс) и наткнулся на неприятное явление. Скорость поступления данных и скорость их воспроизведения оказываются неодинаковыми даже если совпадают samplerates. Поэтому очередь выводимых данных либо медленно, но постоянно росла, либо исчерпывалась. Пришлось следить за длиной очереди и производить resampling.
Форма ответа