Il presente articolo è stato tradotto automaticamente.

Touch-and-Go

Flussi audio in Windows Phone

Charles Petzold

Scaricare il codice di esempio

Charles PetzoldSe in esecuzione sul desktop, Web o in mano, a volte è necessario riprodurre suoni o musica programmi per computer.Più spesso, questo audio sarà interamente codificato in file MP3 o WMA.Il grande vantaggio di questo approccio è che l'OS stesso sa di solito come decodificare e riprodurre questi file.L'applicazione può quindi concentrarsi sul lavoro relativamente più facile di fornire un'interfaccia utente per la sospensione, riavvio e forse navigando tra le tracce.

Ma la vita non è sempre così conveniente.A volte ha bisogno un programma per riprodurre un file audio in un formato non supportato dal sistema operativo, o anche per generare dati audio in modo dinamico, forse per implementare la sintesi di musica elettronica.

Nel gergo di Silverlight e Windows Phone, questo processo è noto come "streaming". audio In fase di esecuzione l'applicazione fornisce un flusso di byte che comprendono i dati audio.Questo avviene attraverso una classe derivata da MediaStreamSource, che alimenta i dati audio al giocatore audio OS su richiesta.Windows Phone OS 7.5 possibile stream audio in background, e vi mostrerò come farlo.

Derivazione da MediaStreamSource

Il primo passo essenziale nel generare dinamicamente i dati audio in un programma di Windows Phone è la derivazione dalla classe astratta MediaStreamSource.Il codice coinvolto è piuttosto disordinato in punti quindi invece di scrivere il codice da zero, probabilmente ti consigliamo di copiare qualcun altro.

Il progetto SimpleAudioStreaming nel codice sorgente scaricabile per questo articolo mostra un approccio possibile.Questo progetto contiene un derivato di MediaStreamSource denominato Sine440AudioStreamSource che genera semplicemente un'onda sinusoidale a 440 Hz.Questa è la frequenza corrispondente all'A sopra il do centrale che viene comunemente utilizzato come standard messa a punto.

MediaStreamSource ha sei metodi astratti che un derivato deve eseguire l'override, ma solo due di loro sono di importanza cruciale.Il primo è OpenMediaPlayer, in cui è necessario creare una coppia di oggetti dizionario e un oggetto List, nonché di definire il tipo di dati audio che fornisce la classe e vari parametri che descrive questi dati.I parametri audio sono rappresentati come campi di una struttura Win32 WAVEFORMATEX con tutti i numeri multibyte in formato little-endian (byte più significativo primo) e convertito in una stringa.

Non ho mai usato MediaStreamSource per qualcosa di diverso da audio nel formato pulse code modulation (PCM), che viene utilizzato per audio più non compresso, tra cui il CD e il formato di file WAV di Windows.Audio PCM coinvolge campioni di dimensione costante ad un tasso costante chiamato la frequenza di campionamento.Per un audio di qualità CD, utilizzerai 16 bit per campione e una frequenza di campionamento di 44.100 Hz.Si può scegliere uno dei canali per il suono monofonico o due canali per stereo.

La Sine440AudioStreamSource classe hardcodes un solo canale e un campione di 16 bit dimensioni ma permette la frequenza di campionamento di specificare come argomento del costruttore.

Internamente, la pipeline audio mantiene un buffer di dati audio, le cui dimensioni viene specificata dalla proprietà AudioBufferLength del MediaStreamSource.L'impostazione predefinita è 1.000 ms, ma è possibile impostare a partire da 15 ms.Per mantenere questo buffer pieno, chiamate vengono eseguite al metodo GetSampleAsync del vostro derivato di MediaStreamSource.Il vostro compito è di pala un mucchio di dati audio in un oggetto MemoryStream e chiamare ReportGetSampleCompleted.

La classe Sine440AudioStreamSource è hardcoded per fornire 4.096 campioni per ogni chiamata.Con una frequenza di campionamento di 44.100, che non è abbastanza un decimo di secondo di audio per ogni chiamata.Giocherellando con questo valore e la AudioBufferLength proprietà è necessario se si sta implementando un sintetizzatore controllate dall'utente che deve rispondere rapidamente all'input dell'utente.Per la latenza minima si desidera mantenere le dimensioni del buffer ma non troppo piccolo piccolo tale da provocare le lacune nella riproduzione.

Figura 1 viene illustrata l'implementazione di Sine440AudioStreamSource del GetSampleAsync override.All'interno del ciclo, un valore di 16 bit sinusoidale è ottenuto da una chiamata al metodo Math. sin adattata alle dimensioni del breve e seguente:

short amplitude = (short)(short.MaxValue * Math.Sin(angle));

Figura 1 il metodo GetSampleAsync in Sine440AudioStreamSource

protected override void GetSampleAsync(MediaStreamType mediaStreamType)
{
  // Reset MemoryStream object
  memoryStream.Seek(0, SeekOrigin.Begin);
  for (int sample = 0; sample < BufferSamples; sample++)
  {
    short amplitude = (short)(short.MaxValue * Math.Sin(angle)); 
    memoryStream.WriteByte((byte)(amplitude & 0xFF));
    memoryStream.WriteByte((byte)(amplitude >> 8));
    angle = (angle + angleIncrement) % (2 * Math.PI);
  }
  // Send out the sample
  ReportGetSampleCompleted(new MediaStreamSample(mediaStreamDescription,
    memoryStream,
    0,
    BufferSize,
    timestamp,
    mediaSampleAttributes));
  // Prepare for next sample
  timestamp += BufferSamples * 10000000L / sampleRate;
}

Tale ampiezza è poi suddivisa in 2 byte e memorizzato in MemoryStream, byte basso in primo luogo. Per stereo, ogni campione richiede due valori a 16 bit, alternanza tra sinistra e destra.

Altri calcoli sono possibili. È possibile passare da un'onda sinusoidale a un'onda a dente di sega, mediante la definizione di ampiezza simile a questa:

short amplitude = (short)(short.MaxValue * angle / Math.PI + short.MinValue);

In entrambi i casi una variabile "angolo" intervalli denominati da 0 a 2 π radianti (o 360 gradi) così fa riferimento a un singolo ciclo di una particolare forma d'onda. Dopo ogni campione, angolo è aumentato da angleIncrement, una variabile calcolato all'inizio nella classe basata la frequenza della frequenza di campionamento e la frequenza della forma d'onda a essere generato, che qui sono codificato come 440 Hz:

angleIncrement = 2 * Math.PI * 440 / sampleRate;

Si noti che, come la frequenza della forma d'onda generata si avvicina a metà della frequenza di campionamento, angleIncrement approcci π o 180 gradi. A metà della frequenza di campionamento, la forma d'onda generata si basa su due campioni per ogni ciclo, e il risultato è un'onda quadra, piuttosto che una curva sinusoidale regolare. Tuttavia, tutte le armoniche in questa onda quadra sono sopra la metà la frequenza di campionamento.

Si noti inoltre che non è in grado di generare frequenze superiori a metà della frequenza di campionamento. Se si tenta, è in realtà ti generare "alias" che sono di sotto di mezzo la frequenza di campionamento. Metà del tasso di campione è conosciuta come la frequenza Nyquist, prende il nome Harry Nyquist, un ingegnere che lavorava per AT & t quando pubblicò un libro precoce sulla teoria dell'informazione nel 1928 che le basi per la tecnologia di campionamento audio.

Per CD audio una frequenza di campionamento di 44.100 Hz è stato scelto parzialmente perché metà di 44.100 è superiore al limite superiore dell'udito umano, comunemente considerata come 20.000 Hz.

A parte la classe Sine440AudioStreamSource, il resto del progetto SimpleAudioStreaming è abbastanza facile: Il file MainPage contiene un oggetto MediaElement denominato mediaElement e le chiamate di override MainPage OnNavigatedTo SetSource su questo oggetto con un'istanza del derivato MediaStreamSource:

mediaElement.SetSource(new Sine440AudioStreamSource(44100));

Mi aveva originariamente questa chiamata nel costruttore del programma, ma ho scoperto che la riproduzione di musica non poteva essere ripreso se hai navigato lontano dal programma e poi di nuovo senza il programma essere eliminati definitivamente.

Il metodo SetSource di MediaElement è lo stesso metodo che chiamare se si desidera MediaElement per riprodurre un file di musica a cui fa riferimento a un oggetto Stream. Chiamando SetSource sarebbe normalmente iniziare la riproduzione audio, ma questo particolare MediaElement ha la proprietà di AutoPlay impostata su false, quindi è necessaria anche una chiamata a giocare. Il programma include i tasti Play e pausa nella sua barra delle applicazioni che eseguono queste operazioni.

Mentre il programma è in esecuzione, è possibile controllare il volume dal controllo Volume universale (UVC) del telefono, ma se terminare il programma o navigare lontano da esso, il suono si ferma.

Trasferirsi in Background

È inoltre possibile utilizzare questo stesso MediaStreamSource derivato per riprodurre suoni o musica in background. Musica di sottofondo continua a giocare al telefono se si naviga lontano dal programma o addirittura porvi fine. Per l'audio di sottofondo, è possibile utilizzare UVC del telefono non solo per controllare il volume, ma anche per mettere in pausa e riavviare l'audio e (se applicabile) saltare avanti o indietro di altri brani.

Sarete felici di scoprire che molto di quello che hai imparato nella rata del mese scorso di questa colonna (msdn.microsoft.com/magazine/hh781030) continua ad applicarsi per streaming audio di sottofondo. In quella colonna descritto come per riprodurre file musicali in background: Creare un progetto di libreria che contiene una classe che deriva da AudioPlayerAgent. Visual Studio genera questa classe per te se si aggiunge un nuovo progetto di tipo Windows Phone Audio Playback agente.

L'applicazione deve avere un riferimento alla DLL che contiene la AudioPlayerAgent ma non accede direttamente a questa classe. Invece, l'applicazione accede alla classe BackgroundAudioPlayer per impostare un oggetto iniziale traccia audio e per chiamare Play e pausa. Ti ricordo che la traccia audio classe dispone di un costruttore che consente di specificare un titolo del brano, un artista e un nome dell'album, così come specificare quali pulsanti dovrebbero essere abilitato sull'UVC.

In genere il primo argomento al costruttore traccia audio è un oggetto Uri che indica l'origine del file musicali che si desiderano giocare. Se volete questa istanza di traccia audio per giocare in streaming audio, piuttosto che un file musicale, potrai impostare questo primo argomento del costruttore su null. In tal caso BackgroundAudioPlayer cercherà di una classe che deriva da AudioStreamingAgent in una DLL a cui fa riferimento il programma. È possibile creare una tale DLL in Visual Studio con l'aggiunta di un progetto di tipo Windows Phone Audio Streaming agente.

La soluzione di SimpleBackgroundAudioStreaming nel codice scaricabile per questo articolo mostra come questo è fatto. La soluzione contiene il progetto di applicazione e due progetti libreria, uno denominato AudioPlaybackAgent che contiene una classe AudioPlayer che deriva da AudioPlayerAgent e l'altro denominato AudioStreamAgent contenente una classe AudioTrackStreamer che deriva da AudioStreamingAgent. Il progetto di applicazione contiene riferirsi­recipro a entrambi questi progetti di biblioteca, ma non tenta di accedere alle classi effettive. Per motivi che ho discusso nel mio articolo precedente, facendo quindi è inutile.

Vorrei sottolineare ancora una volta che l'applicazione deve contenere riferimenti all'agente di sfondo le dll. È facile omettere questi riferimenti perché non sarà possibile visualizzare eventuali errori, ma non riproduce la musica.

Gran parte della logica in SimpleBackgroundAudioStreaming è la stessa per un programma che riproduce i file musicali in background, ad eccezione del fatto che ogni volta che BackgroundAudioPlayer cerca di giocare una traccia con un oggetto Uri null, verrà chiamato il metodo OnBeginStreaming nella derivata di AudioStreamingAgent. Ecco il modo eccezionalmente semplice in cui di gestire tale chiamata:

protected override void OnBeginStreaming(AudioTrack track, AudioStreamer streamer)
{
  streamer.SetSource(new Sine440AudioStreamSource(44100));
}

Questo è tutto! È la stessa classe di Sine440AudioStreamSource ho descritto in precedenza, ma ora incluso nel progetto AudioStreamAgent.

Sebbene il programma SimpleBackgroundAudioStreaming crea solo una traccia, è possibile avere più oggetti di pista e si possono mescolare la traccia audio oggetti che fanno riferimento a file musicali e quelli che utilizzano lo streaming. Si noti che l'oggetto della traccia audio è un argomento per l'override di OnBeginStreaming, quindi si possono utilizzare tali informazioni per personalizzare MediaStreamSource particolare si desidera utilizzare per quella traccia. Per consentire di fornire maggiori informazioni, traccia audio ha una proprietà Tag che è possibile impostare a qualsiasi stringa che desidera.

Costruzione di un sintetizzatore

Una sinusoide costante a 440 Hz può essere abbastanza noioso, quindi cerchiamo di costruire un sintetizzatore di musica elettronica. Ho messo insieme un sintetizzatore molto rudimentale composta da 12 classi e due interfacce nel progetto libreria di Petzold.MusicSynthesis nella soluzione di SynthesizerDemos. (Alcune di queste classi sono simili al codice nel mio blog ho scritto per Silverlight 3 che è apparso nel luglio 2007 — accessibile dal charlespetzold.com.)

At al centro di questo sintetizzatore è un derivato di MediaStreamSource denominato DynamicPcmStreamSource che dispone di una proprietà denominata SampleProvider che viene impostata, definita come segue:

public IStereoSampleProvider SampleProvider { get; set; }
The IStereoSampleProvider interface is simple:
public interface IStereoSampleProvider
{
  AudioSample GetNextSample();
}

AudioSample dispone di due campi pubblici di tipo breve denominato sinistra e destra. Nel relativo metodo GetSampleAsync, DynamicPcmStreamSource chiama il metodo GetNextSample per ottenere un paio di esempi di 16 bit:

AudioSample audioSample = SampleProvider.GetNextSample();

Una classe che implementa IStereoSampleProvider è denominata Mixer. Miscelatore ha una proprietà di input che è una collezione di oggetti di tipo MixerInput. Ogni MixerInput ha una proprietà di Input di tipo IMonoSampleProvider, definito come segue:

public interface IMonoSampleProvider
{
  short GetNextSample();
}

Una classe che implementa IMonoSampleProvider si chiama SteadyNoteDurationPlayer, che è una classe astratta che può giocare una serie di note di durata uguale ad un tempo particolare. Esso ha una proprietà denominata oscillatore che implementa anche IMonoSample­Provider per generare le forme d'onda reali. Due classi derivano da SteadyNoteDurationPlayer: Sequencer, che svolge una serie di note ripetutamente; e Rambler, che svolge un flusso casuale di note. Io utilizzare queste due classi in due diverse applicazioni nella soluzione SynthesizerDemos, quello che viene eseguito solo in primo piano e un altro che suona la musica in sottofondo.

L'applicazione in primo piano-solo si chiama WaveformManipulator e dispone di un controllo che consente di definire in modo interattivo una forma d'onda utilizzata per la riproduzione della musica, come indicato nella Figura 2.

The WaveformManipulator Program
Nella figura 2 il programma WaveformManipulator

I punti che definiscono la forma d'onda sono trasferiti a un Oscil­lator derivato denominato VariableWaveformOscillator. Come ci si spostano su e giù il rotondi touch-punti, noterete circa un ritardo di un secondo prima che si sente effettivamente un cambiamento nel timbro della musica. Questo è un risultato della dimensione del buffer 1.000 ms predefinito definito da MediaStreamSource.

Il programma WaveformManipulator utilizza due oggetti Sequencer caricati con la stessa serie di note, che sono in mi minore, un minore, re minore e arpeggi major G. I tempi per i due oggetti Sequencer sono leggermente diversi, tuttavia, così deriva sincronizzati, prima con un tipo di riverbero o effetto di eco e poi più come contrappunto. (Questa è una forma di "musica del processo" ispirata al inizio del lavoro del compositore americano Steve Reich.) Il codice di inizializzazione da MainPage.xaml.cs che "fili su" i componenti sintetizzatore è mostrato Figura 3.

Figura 3 sintetizzatore codice di inizializzazione in WaveformManipulator

// Initialize Waveformer control
for (int i = 0; i < waveformer.Points.Count; i++)
{
  double angle = (i + 1) * 2 * Math.PI / (waveformer.Points.Count + 1);
  waveformer.Points[i] = new Point(angle, Math.Sin(angle));
}
// Create two Sequencers with slightly different tempi
Sequencer sequencer1 = new Sequencer(SAMPLE_RATE)
{
  Oscillator = new VariableWaveformOscillator(SAMPLE_RATE)
  {
    Points = waveformer.Points
  },
  Tempo = 480
};
Sequencer sequencer2 = new Sequencer(SAMPLE_RATE)
{
  Oscillator = new VariableWaveformOscillator(SAMPLE_RATE)
  {
    Points = waveformer.Points
  },
  Tempo = 470
};
// Set the same Pitch objects in the Sequencer objects
Pitch[] pitches =
{
  ...
};
foreach (Pitch pitch in pitches)
{
  sequencer1.Pitches.Add(pitch);
  sequencer2.Pitches.Add(pitch);
}
// Create Mixer and MixerInput objects
mixer = new Mixer();
mixer.Inputs.Add(new MixerInput(sequencer1) { Space = -0.5 });
mixer.Inputs.Add(new MixerInput(sequencer2) { Space = 0.5 });

Gli oggetti Mixer, DynamicPcmStreamSource e MediaElement sono collegati tra loro nell'override OnNavigatedTo:

DynamicPcmStreamSource dynamicPcmStreamSource =
  new DynamicPcmStreamSource(SAMPLE_RATE);
dynamicPcmStreamSource.SampleProvider = mixer;
mediaElement.SetSource(dynamicPcmStreamSource);

Poiché WaveformManipulator utilizza MediaElement per la riproduzione di musica, svolge solo quando il programma è in esecuzione in primo piano.

Limitazioni di sfondo

Dato un sacco di pensiero a fare una versione di WaveformManipulator che ha giocato la musica in background utilizzando BackgroundAudioPlayer. Ovviamente solo sarebbe in grado di manipolare la forma d'onda quando il programma è stato in primo piano, ma non potevo superare un ostacolo che ho discusso nell'articolo del mese scorso: L'agente di sfondo DLLs fornisce il programma per gestire l'elaborazione in background eseguita in un altro tipo di operazione che il programma stesso e l'unico modo che posso vedere che queste due attività possono scambiare dati arbitrari è attraverso l'archiviazione isolata.

Ho deciso di non perseguire questo lavoro, parzialmente perché ho avuto un'idea migliore per un programma che ha giocato in streaming audio in background. Questo era un programma che avrebbe giocato una melodia casuale, ma con le note che avrebbero cambiato leggermente quando l'accelerometro registrato un cambiamento nell'orientamento del telefono. Scuotendo il telefono creerebbe una nuova melodia interamente.

Questo progetto ha ottenuto per quanto riguarda il mio tentativo di aggiungere un riferimento all'assembly Microsoft.Devices.Sensors al progetto AudioStreamAgent. Che si muovono richiamato una finestra di messaggio con una x rossa e il messaggio, "un tentativo è stato fatto per aggiungere un riferimento non supportato da un agente di sfondo." A quanto pare gli agenti di sfondo non è possibile utilizzare l'accelerometro. Tanto per quell'idea programma!

Invece, ho scritto un programma chiamato PentatonicRambler, che utilizza sfondo in streaming a suonare una melodia infinita in una scala pentatonica costituito da solo i neri cinque note del pianoforte. Le note sono scelti in modo casuale da un componente di sintetizzatore chiamato Rambler, che restringe ogni nota successivo a entrambi un passo fino o uno dimettersi dalla nota precedente. La mancanza di grandi salti rende il flusso risulta del suono note più come una melodia composta (o improvvisata) piuttosto che puramente casuale.

Figura 4 mostra l'override OnBeginStreaming nella derivata di AudioStreamingAgent.

Figura 4 il Setup di sintetizzatore per PentatonicRambler

protected override void OnBeginStreaming(AudioTrack track, AudioStreamer streamer)
{
  // Create a Rambler
  Rambler rambler = new Rambler(SAMPLE_RATE,
  new Pitch(Note.Csharp, 4), // Start
  new Pitch(Note.Csharp, 2), // Minimum
  new Pitch(Note.Csharp, 6)) // Maximum
  {
    Oscillator = new AlmostSquareWave(SAMPLE_RATE),
    Tempo = 480
  };
  // Set allowable note values
  rambler.Notes.Add(Note.Csharp);
  rambler.Notes.Add(Note.Dsharp);
  rambler.Notes.Add(Note.Fsharp);
  rambler.Notes.Add(Note.Gsharp);
  rambler.Notes.Add(Note.Asharp);
  // Create Mixer and MixerInput objects
  Mixer mixer = new Mixer();
  mixer.Inputs.Add(new MixerInput(rambler));
  DynamicPcmStreamSource audioStreamSource =
    new DynamicPcmStreamSource(SAMPLE_RATE);
  audioStreamSource.SampleProvider = mixer;
  streamer.SetSource(audioStreamSource);
}

Ho avrebbe preferito definire l'assemblaggio dei componenti sintetizzatore nel programma stesso e poi trasferire questa configurazione dell'agente di sfondo, ma dato l'isolamento del processo tra le attività del programma e l'agente di sfondo compiti, che richiederebbero un po' di lavoro. Il setup di sintetizzatore avrebbe dovuto essere definita interamente in una stringa di testo (forse basato su XML) e poi passò dal programma per la derivata AudioStreamingAgent attraverso la proprietà Tag di traccia audio.

Nel frattempo, mia lista dei desideri per futuri miglioramenti a Windows Phone include una funzione che permette di programmi di comunicare con gli agenti di sfondo che richiamare.

Charles Petzold è un collaboratore di lunga data di MSDN Magazine. Il suo sito Web è charlespetzold.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Eric Bie, Mark Hopkins e Chris Pearson