zoukankan      html  css  js  c++  java
  • 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/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    (floyd+DP) zoj 3027
    (树形DP) acdream 1028
    acdream 1032
    (BFS) acdream 1191
    (树形DP) bzoj 1060
    (状态压缩DP) poj 2978
    (DP) codeforces 358D
    (DP+二分) hdu 3433
    (最大生成树) hdu 3367
    hdoj 3501
  • 原文地址:https://www.cnblogs.com/aftereclipse/p/2489937.html
Copyright © 2011-2022 走看看