Windows Phone 7 录音转码
我的博客现在已经搬家到极客导航的博客模块中链接地址是:极客博客
顺便做了个程序员资源导航站www.gogeeks.cn,有兴趣的朋友不妨看一看有哪些还没了解到的IT方面的东西,比如框架,书籍,教程,开源社区等等吧。
从Java转型做Windows Phone 7已经有6个多月了,期间还做了一点黑莓开发的东西,一路走来,现在也算是半个做手机开发的人了。一直想把自己做过的东西分享给大家,但是自己总是在找借口不去写博客。我是个C#的初学者,也是个手机开发的初学者,如果有任何代码逻辑不妥之处,还望众大神赐教。
今天公布的第一个功能点就是Windows Phone 7 的录音功能,如果只是作为本地存放音频所用的话,WP7录出来的wav格式的音频无可厚非,只是压缩率低,会占用很大的系统存储空间。但是项目需求往往会超出你的想象,客户不仅要能录,还要能网络发送,还要占用很小的数据流量,这是音频转码和压缩就成了首要考虑的东西了。在网络传输音频时,我们首先会考虑压缩率高,同时失真率低的格式,这时就会想到amr,aac等不是按照时间做压缩的格式。找了好久这方面的东西,最后在咨询了微软Windows Phone 7方面的专家后,发现调用现成的c库转码是不可能的,自己写转码程序的话又不想去看繁琐的格式转换文档。查看了Windows Phone 7 系统录制的wav格式的音频是采样率16kHz,16bit数据之后,想到了怎样通过降低音频采样率和每个样本数据位数,从而达到降低wav文件大小的目的,以便网络传输。于是放弃了直接转码成amr,aac的想法,转而去查找降低wav音频质量的资料。很幸运的是在博客园上已经有人对WP7音频转码做出了详细的算法,于是就试着采用了这个算法。
下面直接进入主题。
WP7录出来的音频是原生的16KHz-16Bit-PCM数据,要加上wav头封装一下,才可以进行传输,封装的话其实就是给原生的数据加个wav头,具体的头格式信息请参考微软官方wavformat格式介绍。
(一)封装WavHeader的类是 :
1 using System; 2 using System.IO; 3 namespace WavRecord 4 { 5 public class WavHeader 6 { 7 public static void WriteWavHeader(Stream stream, int sampleRate) 8 { 9 const int bitsPerSample = 8; 10 const int bytesPerSample = bitsPerSample / 8; 11 12 var encoding = System.Text.Encoding.UTF8; 13 14 // ChunkID Contains the letters "RIFF" in ASCII form (0x52494646 big-endian form). 15 stream.Write(encoding.GetBytes("RIFF"), 0, 4); 16 17 // NOTE this will be filled in later 18 stream.Write(BitConverter.GetBytes(0), 0, 4); 19 20 // Format Contains the letters "WAVE"(0x57415645 big-endian form). 21 stream.Write(encoding.GetBytes("WAVE"), 0, 4); 22 23 // Subchunk1ID Contains the letters "fmt " (0x666d7420 big-endian form). 24 stream.Write(encoding.GetBytes("fmt "), 0, 4); 25 26 // Subchunk1Size 16 for PCM. This is the size of therest of the Subchunk which follows this number. 27 stream.Write(BitConverter.GetBytes(16), 0, 4); 28 29 // AudioFormat PCM = 1 (i.e. Linear quantization) Values other than 1 indicate some form of compression. 30 stream.Write(BitConverter.GetBytes((short)1), 0, 2); 31 32 // NumChannels Mono = 1, Stereo = 2, etc. 33 stream.Write(BitConverter.GetBytes((short)1), 0, 2); 34 35 // SampleRate 8000, 44100, etc. 36 stream.Write(BitConverter.GetBytes(sampleRate), 0, 4); 37 38 // ByteRate = SampleRate * NumChannels * BitsPerSample/8 39 stream.Write(BitConverter.GetBytes(sampleRate * bytesPerSample), 0, 4); 40 41 // BlockAlign NumChannels * BitsPerSample/8 The number of bytes for one sample including all channels. 42 stream.Write(BitConverter.GetBytes((short)(bytesPerSample)), 0, 2); 43 44 // BitsPerSample 8 bits = 8, 16 bits = 16, etc. 45 stream.Write(BitConverter.GetBytes((short)(bitsPerSample)), 0, 2); 46 47 // Subchunk2ID Contains the letters "data" (0x64617461 big-endian form). 48 stream.Write(encoding.GetBytes("data"), 0, 4); 49 50 // NOTE to be filled in later 51 stream.Write(BitConverter.GetBytes(0), 0, 4); 52 } 53 54 public static void UpdateWavHeader(Stream stream) 55 { 56 if (!stream.CanSeek) throw new Exception("Can't seek stream to update wav header"); 57 58 var oldPos = stream.Position; 59 60 // ChunkSize 36 + SubChunk2Size 61 stream.Seek(4, SeekOrigin.Begin); 62 stream.Write(BitConverter.GetBytes((int)stream.Length - 8), 0, 4); 63 64 // Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 This is the number of bytes in the data. 65 stream.Seek(40, SeekOrigin.Begin); 66 stream.Write(BitConverter.GetBytes((int)stream.Length - 44), 0, 4); 67 68 stream.Seek(oldPos, SeekOrigin.Begin); 69 } 70 } 71 }
(二)转换和存储到独立存储区的代码:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Phone.Controls; using System.Windows; using Microsoft.Xna.Framework.Audio; using System.IO; using System.Windows.Threading; using Microsoft.Xna.Framework; using System.Windows.Media; using System.Windows.Controls; using System.IO.IsolatedStorage; using Microsoft.Phone.Tasks; using System.Diagnostics; namespace WavRecord { public partial class MainPage : PhoneApplicationPage { // Constructor public MainPage() { InitializeComponent(); Init(); } Microphone gMicrophone = Microphone.Default; byte[] gAudioBuffer; MemoryStream gStream = new MemoryStream(); int gSpendTime = 0; private void Init() { DispatcherTimer tDT = new DispatcherTimer(); //定期每33毫秒執行一次FrameworkDispatcher.Update(); 方法 tDT.Interval = TimeSpan.FromMilliseconds(33); tDT.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } }; tDT.Start(); gMicrophone.BufferReady += new EventHandler<EventArgs>(gMicrophone_BufferReady); } void gMicrophone_BufferReady(object sender, EventArgs e) { gSpendTime += 1; Dispatcher.BeginInvoke(() => { recordTime.Text = string.Format("{0} seconds.", gSpendTime.ToString()); }); gMicrophone.GetData(gAudioBuffer); gStream.Write(gAudioBuffer, 0, gAudioBuffer.Length); //录到20秒自动结束 if (gSpendTime == 5) { stop_Click(sender,new RoutedEventArgs()); Dispatcher.BeginInvoke(() => { recordTime.Text = "Record finished!"; }); } } private void start_Click(object sender, RoutedEventArgs e) { gMicrophone.BufferDuration = TimeSpan.FromMilliseconds(1000); gAudioBuffer = new byte[gMicrophone.GetSampleSizeInBytes(gMicrophone.BufferDuration)]; gMicrophone.Start(); } private void stop_Click(object sender, RoutedEventArgs e) { if (gMicrophone.State == MicrophoneState.Started) { gMicrophone.Stop(); gStream.Close(); } gSpendTime = 0; Dispatcher.BeginInvoke(() => { recordTime.Text = "Record finished!"; }); MessageBox.Show("转存前的PCM数据大小:"+gStream.ToArray().Length.ToString()); //构造一个MemoryStream先写入WavHeader头信息,待转换数据后再更新头信息中的data字段的值 MemoryStream m = new MemoryStream(); int s = gMicrophone.SampleRate / 2; WavHeader.WriteWavHeader(m, s); byte[] b = NormalizeWaveData(gStream.ToArray()); m.Write(b, 0, b.Length); m.Flush(); WavHeader.UpdateWavHeader(m); using (var tStore = IsolatedStorageFile.GetUserStoreForApplication()) { if (tStore.FileExists("record.wav")) { tStore.DeleteFile("record.wav"); } using (var tFStream = new IsolatedStorageFileStream("record.wav", FileMode.Create, tStore)) { byte[] tByteInStream = m.ToArray(); tFStream.Write(tByteInStream, 0, tByteInStream.Length); tFStream.Flush(); MessageBox.Show("record.wav"+"保存成功!增加wavHeader并转存后的大小:"+m.Length.ToString()); } } gStream = new MemoryStream(); } private void play_Click(object sender, RoutedEventArgs e) { using (var tStore = IsolatedStorageFile.GetUserStoreForApplication()) { using (var tFStream = new IsolatedStorageFileStream("record.wav", FileMode.Open, tStore)) { MessageBox.Show("独立存储区:" + tFStream.Length.ToString()); SoundEffect effect = SoundEffect.FromStream(tFStream); FrameworkDispatcher.Update(); effect.Play(); } } } //PCM数据16KHz-16bit*****8kHz-8bit转换方法 byte[] NormalizeWaveData(byte[] sourceData) { int len = (sourceData.Length / 2 / 2); using (MemoryStream ms = new MemoryStream(len)) { for (int i = 0; i < len; i++) { sbyte data = (sbyte)sourceData[i * 4 + 1]; ms.WriteByte((byte)(data + 128)); ms.Flush(); } return ms.ToArray(); } } } }
(三)这是我的工程项目源码,希望看到文章的可以自己动手做做,我这个只提供基本的功能,有什么需要改进的地方,还望赐教。
下载文件:WavRecord.rar
转换音频的算法来自
文章名称:《Windows Phone7开发,一步一步完成一个即时聊天软件之咏叹调:在Windows phone 7 中如何压缩WAV音频文件》
作者是:秋天的梦想
作者:月食之后
出处:http://www.cnblogs.com/aftereclipse/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。