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音频文件

作者是:秋天的梦想

 

 
posted @ 2012-05-08 14:15  月食之后  阅读(1020)  评论(1编辑  收藏  举报