Kinect 开发 —— 录音
不涉及语音识别~~
<Window x:Class="KinectRecordAudio.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Audio Recorder" Height="226" Width="405"> <Grid Width="369" Height="170"> <Button Content="Play" Height="44" HorizontalAlignment="Left" Margin="12,13,0,0" Name="button1" VerticalAlignment="Top" Width="114" Click="button1_Click" IsEnabled="{Binding IsPlayingEnabled}" FontSize="18"></Button> <Button Content="Record" Height="44" HorizontalAlignment="Left" Margin="132,13,0,0" Name="button2" VerticalAlignment="Top" Width="110" Click="button2_Click" IsEnabled="{Binding IsRecordingEnabled}" FontSize="18"/> <Button Content="Stop" Height="44" HorizontalAlignment="Left" Margin="248,13,0,0" Name="button3" VerticalAlignment="Top" Width="107" Click="button3_Click" IsEnabled="{Binding IsStopEnabled}" FontSize="18"/> <CheckBox Content="Noise Suppression" Height="16" HorizontalAlignment="Left" Margin="16,77,0,0" VerticalAlignment="Top" Width="142" IsChecked="{Binding IsNoiseSuppressionOn}" /> <CheckBox Content="Automatic Gain Control" Height="16" HorizontalAlignment="Left" Margin="16,104,0,0" VerticalAlignment="Top" IsChecked="{Binding IsAutomaticGainOn}"/> <CheckBox Content="AEC" Height="44" HorizontalAlignment="Left" IsChecked="{Binding IsAECOn}" Margin="16,129,0,0" VerticalAlignment="Top" /> </Grid> </Window>
namespace KinectRecordAudio { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window, INotifyPropertyChanged { // INotifyPropertyChanged 接口用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知。 string _recordingFileName; // 录音文件名 MediaPlayer _mplayer; bool _isPlaying; bool _isNoiseSuppressionOn; bool _isAutomaticGainOn; bool _isAECOn; // IsEnabled="{Binding IsRecordingEnabled}" 将控件与方法绑定 public MainWindow() { InitializeComponent(); this.Loaded += delegate { KinectSensor.KinectSensors[0].Start(); }; _mplayer = new MediaPlayer(); _mplayer.MediaEnded += delegate { _mplayer.Close(); IsPlaying = false; }; this.DataContext = this; } private void Play() { IsPlaying = true; _mplayer.Open(new Uri(_recordingFileName, UriKind.Relative)); _mplayer.Play(); } private void Record() { Thread thread = new Thread(new ThreadStart(RecordKinectAudio)); thread.Priority = ThreadPriority.Highest; // 线程优先级 thread.Start(); } private void Stop() { KinectSensor.KinectSensors[0].AudioSource.Stop(); IsRecording = false; } private object lockObj = new object(); private void RecordKinectAudio() { lock (lockObj) { // 线程加锁 IsRecording = true; var source = CreateAudioSource(); var time = DateTime.Now.ToString("hhmmss"); _recordingFileName = time + ".wav"; using (var fileStream = new FileStream(_recordingFileName, FileMode.Create)) { RecorderHelper.WriteWavFile(source, fileStream); } IsRecording = false; } } private KinectAudioSource CreateAudioSource() { var source = KinectSensor.KinectSensors[0].AudioSource; source.BeamAngleMode = BeamAngleMode.Adaptive; source.NoiseSuppression = _isNoiseSuppressionOn; source.AutomaticGainControlEnabled = _isAutomaticGainOn; if (IsAECOn) { source.EchoCancellationMode = EchoCancellationMode.CancellationOnly; source.AutomaticGainControlEnabled = false; IsAutomaticGainOn = false; source.EchoCancellationSpeakerIndex = 0; } return source; // 返回经过处理的数据源 } #region user interaction handlers private void button1_Click(object sender, RoutedEventArgs e) { Play(); } private void button2_Click(object sender, RoutedEventArgs e) { Record(); } private void button3_Click(object sender, RoutedEventArgs e) { Stop(); } #endregion #region properties public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } private bool IsPlaying { get { return _isPlaying; } set { if (_isPlaying != value) { _isPlaying = value; OnPropertyChanged("IsRecordingEnabled"); } } } private bool IsRecording { get { return RecorderHelper.IsRecording; } set { if (RecorderHelper.IsRecording != value) { RecorderHelper.IsRecording = value; OnPropertyChanged("IsPlayingEnabled"); OnPropertyChanged("IsRecordingEnabled"); OnPropertyChanged("IsStopEnabled"); } } } public bool IsPlayingEnabled { get { return !IsRecording; } } public bool IsRecordingEnabled { get { return !IsPlaying && !IsRecording; } } public bool IsStopEnabled { get { return IsRecording; } } public bool IsNoiseSuppressionOn { get { return _isNoiseSuppressionOn; } set { if (_isNoiseSuppressionOn != value) { _isNoiseSuppressionOn = value; OnPropertyChanged("IsNoiseSuppressionOn"); } } } public bool IsAutomaticGainOn { get { return _isAutomaticGainOn; } set { if (_isAutomaticGainOn != value) { _isAutomaticGainOn = value; OnPropertyChanged("IsAutomaticGainOn"); } } } public bool IsAECOn { get { return _isAECOn; } set { if (_isAECOn != value) { _isAECOn = value; OnPropertyChanged("IsAECOn"); } } } #endregion } }
class RecorderHelper { static byte[] buffer = new byte[4096]; static bool _isRecording; public static bool IsRecording { get { return _isRecording; } set { _isRecording = value; } } public static void WriteWavFile(KinectAudioSource source, FileStream fileStream) { var size = 0; //write wav header placeholder WriteWavHeader(fileStream, size); // size=0; using (var audioStream = source.Start()) { // Starts capturing audio from the device. The data can be read using the returned stream. //chunk audio stream to file while (audioStream.Read(buffer, 0, buffer.Length) > 0 && _isRecording) { fileStream.Write(buffer, 0, buffer.Length); size += buffer.Length; } } //write real wav header // 方法开始写入一个假的头文件,然后读取Kinect中的音频数据流,然后填充FileStream对象,直到_isRecoding属性被设置为false。然后检查已经写入到文件中的数据流大小,用这个值来改写之前写入的文件头 long prePosition = fileStream.Position; fileStream.Seek(0, SeekOrigin.Begin); WriteWavHeader(fileStream, size); // size = file length fileStream.Seek(prePosition, SeekOrigin.Begin); fileStream.Flush(); } public static void WriteWavHeader(Stream stream, int dataLength) { using (MemoryStream memStream = new MemoryStream(64)) { // 基于指定的字节数组初始化 MemoryStream 类的无法调整大小的新实例 int cbFormat = 18; WAVEFORMATEX format = new WAVEFORMATEX() { wFormatTag = 1, nChannels = 1, nSamplesPerSec = 16000, nAvgBytesPerSec = 32000, nBlockAlign = 2, wBitsPerSample = 16, cbSize = 0 }; using (var bw = new BinaryWriter(memStream)) { WriteString(memStream, "RIFF"); bw.Write(dataLength + cbFormat + 4); WriteString(memStream, "WAVE"); WriteString(memStream, "fmt "); bw.Write(cbFormat); bw.Write(format.wFormatTag); bw.Write(format.nChannels); bw.Write(format.nSamplesPerSec); bw.Write(format.nAvgBytesPerSec); bw.Write(format.nBlockAlign); bw.Write(format.wBitsPerSample); bw.Write(format.cbSize); WriteString(memStream, "data"); bw.Write(dataLength); memStream.WriteTo(stream); } } } static void WriteString(Stream stream, string s) { byte[] bytes = Encoding.ASCII.GetBytes(s); stream.Write(bytes, 0, bytes.Length); } struct WAVEFORMATEX { public ushort wFormatTag; public ushort nChannels; public uint nSamplesPerSec; public uint nAvgBytesPerSec; public ushort nBlockAlign; public ushort wBitsPerSample; public ushort cbSize; } }
C#不支持直接写wma文件,需要C++的 WAVEFORMATEX 结构体