第九章:特定于平台的API调用(四)

栏目: 编程语言 · 发布时间: 6年前

内容简介:现在为了本章的真正目的:给MonkeyTap发声。所有三个平台都支持API,允许程序动态生成和播放音频波形。这是MonkeyTapWithSound程序采用的方法。商业音乐文件通常以诸如MP3之类的格式压缩。但是当一个程序算法算法生成波形时,未压缩的格式会更加方便。最基本的技术 - 所有三个平台都支持 - 称为脉冲编码调制或PCM。除了花哨的名字,它很简单,它是用于在音乐CD上存储声音的技术。PCM波形由一系列恒定速率的样本描述,称为采样率。音乐CD使用标准速率为每秒44,100个样本。如果不需要高音质,

特定于平台的声音生成

现在为了本章的真正目的:给MonkeyTap发声。所有三个平台都支持API,允许程序动态生成和播放音频波形。这是MonkeyTapWithSound程序采用的方法。

商业音乐文件通常以诸如MP3之类的格式压缩。但是当一个程序算法算法生成波形时,未压缩的格式会更加方便。最基本的技术 - 所有三个平台都支持 - 称为脉冲编码调制或PCM。除了花哨的名字,它很简单,它是用于在音乐CD上存储声音的技术。

PCM波形由一系列恒定速率的样本描述,称为采样率。音乐CD使用标准速率为每秒44,100个样本。如果不需要高音质,计算机程序生成的音频文件通常使用一半(22,050)或四分之一(11,025)的采样率。可记录和再现的最高频率是采样率的一半。

每个样本都是固定大小,用于定义该时间点波形的幅度。音乐CD上的样本是带符号的16位值。当声音质量无关紧要时,8位样本很常见。某些环境支持浮点值。多个样本可以容纳立体声或任意数量的声道。对于移动设备上的简单音效,单声道声音通常很好。

MonkeyTapWithSound中的声音生成算法是针对16位单声道样本进行硬编码的,但采样率由常量指定,并且可以轻松更改。

现在您已经了解了DependencyService的工作原理,让我们检查添加到MonkeyTap的代码,将其转换为MonkeyTapWithSound,让我们从上到下看一下。为避免重现大量代码,新项目包含MonkeyTap项目中MonkeyTap.xaml和MonkeyTap.xaml.cs文件的链接。

在Visual Studio中,通过从项目菜单中选择“添加”>“现有项”,可以将项目添加为项目作为现有文件的链接。然后使用“添加现有项”对话框导航到该文件。从“添加”按钮的下拉列表中选择“添加为链接”。

在Xamarin Studio中,从项目的 工具 菜单中选择添加>添加文件。打开文件后,会弹出“添加文件到文件夹”警告框。选择“添加指向该文件的链接”。

但是,在Visual Studio中执行这些步骤后,还需要手动编辑Mon?keyTapWithSound.csproj文件,以将MonkeyTapPage.xaml文件更改为EmbeddedResource,将Generator更改为MSBuild:UpdateDesignTimeXaml。此外,还将一个DependentUpon标记添加到MonkeyTapPage.xaml.cs文件中以引用MonkeyTapPage.xaml文件。这会导致代码隐藏文件在文件列表中的XAML文件下缩进。

然后,MonkeyTapWithSoundPage类派生自MonkeyTapPage类。虽然MonkeyTapPage类是由XAML文件和代码隐藏文件定义的,但MonkeyTapWithSoundPage仅是代码。当以这种方式派生类时,必须将XAML文件中的事件的原始代码隐藏文件中的事件处理程序定义为受保护的,这是这种情况。

MonkeyTap类还将flashDuration常量定义为protected,并将两个方法定义为protected和virtual。 MonkeyTapWithSoundPage重写这两个方法来调用一个名为SoundPlayer.PlaySound的静态方法:

namespace MonkeyTapWithSound
{
    class MonkeyTapWithSoundPage : MonkeyTap.MonkeyTapPage
    {
        const int errorDuration = 500;
        // Diminished 7th in 1st inversion: C, Eb, F#, A
        double[] frequencies = { 523.25, 622.25, 739.99, 880 };
        protected override void BlinkBoxView(int index)
        {
            SoundPlayer.PlaySound(frequencies[index], flashDuration);
            base.BlinkBoxView(index);
        }
        protected override void EndGame()
        {
            SoundPlayer.PlaySound(65.4, errorDuration);
            base.EndGame();
        }
    }
}

SoundPlayer.PlaySound方法接受频率和持续时间(以毫秒为单位)。 每一件事 - 音量,声音的谐波组成以及声音是如何产生的 - 都是PlaySound方法的责任。 但是,此代码隐含地假设SoundPlayer.PlaySound立即返回,并且不等待声音完成播放。 幸运的是,所有这三个平台都支持以这种方式运行的声音生成API。

使用PlaySound静态方法的SoundPlayer类是MonkeyTapWithSound PCL项目的一部分。 此方法的职责是为声音定义PCM数据的数组。 此数组的大小取决于采样率和持续时间。 for循环计算定义所请求频率的三角波的样本:

namespace MonkeyTapWithSound
{
    class SoundPlayer
    {
        const int samplingRate = 22050;
        /* Hard-coded for monaural, 16-bit-per-sample PCM */
        public static void PlaySound( double frequency = 440, int duration = 250 )
        {
            short[] shortBuffer    = new short[samplingRate * duration / 1000];
            double    angleIncrement    = frequency / samplingRate;
            double    angle        = 0; /* normalized 0 to 1 */
            for ( int i = 0; i < shortBuffer.Length; i++ )
            {
                /* Define triangle wave */
                double sample;
                /* 0 to 1 */
                if ( angle < 0.25 )
                    sample = 4 * angle;
                /* 1 to -1 */
                else if ( angle < 0.75 )
                    sample = 4 * (0.5 - angle);
                /* -1 to 0 */
                else
                    sample = 4 * (angle - 1);
                shortBuffer[i]    = (short) (32767 * sample);
                angle        += angleIncrement;
                while ( angle > 1 )
                    angle -= 1;
            }
            byte[] byteBuffer = new byte[2 * shortBuffer.Length];
            Buffer.BlockCopy( shortBuffer, 0, byteBuffer, 0, byteBuffer.Length );
            DependencyService.Get
<iplatformsoundplayer>
 ().PlaySound( samplingRate, byteBuffer );
        }
    }
} 
</iplatformsoundplayer>

虽然样本是16位整数,但是其中两个平台希望数据以字节数组的形式存在,因此使用Buffer.BlockCopy在末尾附近进行转换。 该方法的最后一行使用DependencyService将具有采样率的此字节数组传递给各个平台。

DependencyService.Get方法引用IPlatformSoundPlayer接口,该接口定义了PlaySound方法的签名:

namespace MonkeyTapWithSound
{
    public interface IPlatformSoundPlayer
    {
        void PlaySound(int samplingRate, byte[] pcmData);
    }
}

现在来了困难的部分:为三个平台编写这个PlaySound方法!

iOS版本使用AVAudioPlayer,它需要包含Wave?form音频文件格式(.wav)文件中使用的标头的数据。 这里的代码汇编了MemoryBuffer中的数据,然后将其转换为NSData对象:

using System;
using System.IO;
using System.Text;
using Xamarin.Forms;
using AVFoundation;
using Foundation;
[assembly: Dependency( typeof(MonkeyTapWithSound.iOS.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.iOS
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        const int    numChannels    = 1;
        const int    bitsPerSample    = 16;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            int        numSamples    = pcmData.Length / (bitsPerSample / 8);
            MemoryStream    memoryStream    = new MemoryStream();
            BinaryWriter    writer        = new BinaryWriter( memoryStream, Encoding.ASCII );
            /* Construct WAVE header. */
            writer.Write( new char[] { 'R', 'I', 'F', 'F' } );
            writer.Write( 36 + sizeof(short) * numSamples );
            writer.Write( new char[] { 'W', 'A', 'V', 'E' } );
            writer.Write( new char[] { 'f', 'm', 't', ' ' } );              /* format chunk */
            writer.Write( 16 );                                             /* PCM chunk size */
            writer.Write( (short) 1 );                                      /* PCM format flag */
            writer.Write( (short) numChannels );
            writer.Write( samplingRate );
            writer.Write( samplingRate * numChannels * bitsPerSample / 8 ); /* byte rate */
            writer.Write( (short) (numChannels * bitsPerSample / 8) );      /* block align */
            writer.Write( (short) bitsPerSample );
            writer.Write( new char[] { 'd', 'a', 't', 'a' } );              /* data chunk */
            writer.Write( numSamples * numChannels * bitsPerSample / 8 );
            /* Write data as well. */
            writer.Write( pcmData, 0, pcmData.Length );
            memoryStream.Seek( 0, SeekOrigin.Begin );
            NSData        data        = NSData.FromStream( memoryStream );
            AVAudioPlayer    audioPlayer    = AVAudioPlayer.FromData( data );
            audioPlayer.Play();
        }
    }
}

请注意两个要点:PlatformSoundPlayer实现IPlatformSoundPlayer接口,并使用Dependency属性标记类。

Android版本使用AudioTrack类,结果更容易一些。 但是,AudioTrack对象不能重叠,所以有必要保存前一个对象并停止播放,然后开始下一个对象:

using System;
using Android.Media;
using Xamarin.Forms;
[assembly: Dependency( typeof(MonkeyTapWithSound.Droid.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.Droid
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        AudioTrack previousAudioTrack;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            if ( previousAudioTrack != null )
            {
                previousAudioTrack.Stop();
                previousAudioTrack.Release();
            }
            AudioTrack audioTrack = new AudioTrack( Stream.Music,
                                samplingRate,
                                ChannelOut.Mono,
                                Android.Media.Encoding.Pcm16bit,
                                pcmData.Length * sizeof(short),
                                AudioTrackMode.Static );
            audioTrack.Write( pcmData, 0, pcmData.Length );
            audioTrack.Play();
            previousAudioTrack = audioTrack;
        }
    }
}

三个Windows和Windows Phone平台可以使用MediaStreamSource。 为了避免大量重复代码,MonkeyTapWithSound解决方案包含一个名为WinRuntimeShared的额外SAP项目,该项目仅由三个平台都可以使用的类组成:

using System;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
namespace MonkeyTapWithSound.WinRuntimeShared
{
    public class SharedSoundPlayer
    {
        MediaElement    mediaElement = new MediaElement();
        TimeSpan    duration;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            AudioEncodingProperties audioProps =
                AudioEncodingProperties.CreatePcm( (uint) samplingRate, 1, 16 );
            AudioStreamDescriptor    audioDesc    = new AudioStreamDescriptor( audioProps );
            MediaStreamSource    mss        = new MediaStreamSource( audioDesc );
            bool            samplePlayed    = false;
            mss.SampleRequested += (sender, args) =>
            {
                if ( samplePlayed )
                    return;
                IBuffer            ibuffer = pcmData.AsBuffer();
                MediaStreamSample    sample    =
                    MediaStreamSample.CreateFromBuffer( ibuffer, TimeSpan.Zero );
                sample.Duration        = TimeSpan.FromSeconds( pcmData.Length / 2.0 / samplingRate );
                args.Request.Sample    = sample;
                samplePlayed        = true;
            };
            mediaElement.SetMediaStreamSource( mss );
        }
    }
}

此SAP项目由三个Windows和Windows Phone项目引用,每个项目包含相同的(命名空间除外)PlatformSoundPlayer类:

using System;
using Xamarin.Forms;
[assembly: Dependency( typeof(MonkeyTapWithSound.UWP.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.UWP
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        WinRuntimeShared.SharedSoundPlayer sharedSoundPlayer;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            if ( sharedSoundPlayer == null )
            {
                sharedSoundPlayer = new WinRuntimeShared.SharedSoundPlayer();
            }
            sharedSoundPlayer.PlaySound( samplingRate, pcmData );
        }
    }
}

使用DependencyService来执行特定于平台的杂务是非常强大的,但是当涉及到用户界面元素时,这种方法不足。 如果您需要扩展装饰Xamarin.Forms应用程序页面的视图库,那么这项工作涉及创建特定于平台的渲染器,这是本书最后一章中讨论的过程。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人本界面

人本界面

(美)拉斯基(Jef Raskin) / 史元春 / 机械工业出版社 / 2004-1-1 / 28.0

如果我们想克服目前人机界面上的固有缺陷,就很有必要理解本书的教义;若无此愿望,读读也无妨。交互设计的许多重要方面此书并没有包括在内,因为许多文献中都已经有详尽的阐述。本书的意图是补充现有的界面设计的方法或预测未来。  本书概述了人机界面设计领域的研究成果,详细论证了界面设计思想应以认知学为基础,并考虑人类的心智特点,在指出当前界面设计中弊端的同时,提出了新产品开发的思路。本书集计算机科学、人体工程......一起来看看 《人本界面》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试