|
从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(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音频文件》
作者是:秋天的梦想
|
|