第九章:特定於平臺的API呼叫(四)
特定於平臺的聲音生成
現在為了本章的真正目的:給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 );
}
}
}
雖然樣本是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應用程式頁面的檢視庫,那麼這項工作涉及建立特定於平臺的渲染器,這是本書最後一章中討論的過程。
相關文章
- 使用JavaScript呼叫手機平臺上的原生APIJavaScriptAPI
- 關於搭建遊戲平臺的四個思考遊戲
- 好用的API介面平臺推薦API
- 工行api開放平臺API
- 基於知識圖譜的呼叫鏈分析精準化測試平臺
- Azure AD(二)呼叫受Microsoft 標識平臺保護的 ASP.NET Core Web API 下ROSASP.NETWebAPI
- API整合新一代平臺,iPaaS整合平臺API
- 好好利用平臺的公開APIAPI
- 在SAP雲平臺的API portal裡建立和管理APIAPI
- API管理平臺,全面管理系統API介面API
- 第九章 webase 分散式中介軟體平臺快速部署Web分散式
- Office 365 API平臺概覽API
- TDS:標籤平臺+API平臺+資料共享平臺,助力資料運營平臺建設API
- Python呼叫ansible API系列(四)動態生成hosts檔案PythonAPI
- 關於呼叫三方平臺介面與推送介面的總結(2020.7.25)
- api呼叫方式API
- 各平臺免費翻譯APIAPI
- API統一管理平臺-YApiAPI
- API視覺化管理平臺YApiAPI視覺化
- 十大 API 平臺網站分享(包括常用的API 大全整理)API網站
- API智慧識別平臺,API介面自動識別API
- API開發平臺,提高API開發及管理效率API
- 開源:基於 Lumen5.5 開發的高效能圖片識別平臺 API 介面及基於 Laravel5.5 開發的管理平臺 原始碼APILaravel原始碼
- Sealos AI Proxy 釋出!一個平臺呼叫所有大模型,再也不用到處找 API 了AI大模型API
- API自動化測試平臺,支援場景化的API測試API
- HBuilder開發詞典app(四)--呼叫有道api完成翻譯功能UIAPPAPI
- CRM平臺的四大優點和作用
- sangerbox平臺使用(四)氣泡圖的繪製
- API開發平臺,全面快速整合釋出API服務API
- 如何呼叫api介面API
- Net 呼叫 Graph API 的小栗子API
- QuickMock:基於Express的快速mock平臺UIMockExpress
- 基於kubernetes平臺微服務的部署微服務
- 基於java的專案管理平臺Java專案管理
- API低程式碼開發平臺,全面管控企業的API資產API
- 開源的 API 學習平臺「GitHub 熱點速覽」APIGithub
- 電子商務平臺的API整合介面的意義API
- 實現呼叫API介面API