Este artículo proviene de un motor de traducción automática.
Factor DirectX
Generación de sonido en Windows 8 con XAudio2
Descargar el ejemplo de código
Una app Store de Windows para Windows 8 puede reproducir MP3 o sonido WMA archivos fácilmente mediante MediaElement — le simplemente dar una secuencia o un URI al archivo de sonido. Aplicaciones de la tienda de Windows también pueden acceder a jugar a API para streaming de vídeo o audio a dispositivos externos.
Pero ¿qué pasa si usted necesita más sofisticado procesamiento de audio? Quizás desea modificar el contenido de un archivo de audio en su camino hacia el hardware, o generar sonidos dinámicamente.
Una app Store de Windows también puede realizar estos trabajos a través de DirectX. Windows 8 admite dos componentes de DirectX para la generación de sonido y procesamiento, Core Audio y XAudio2. En el gran esquema de las cosas, ambos son interfaces de bastante bajo nivel, pero es inferior a XAudio2 Core Audio y se orienta más hacia las aplicaciones que requieren una conexión más estrecha con el hardware de audio.
XAudio2 es el sucesor de DirectSound y la biblioteca de Xbox XAudio, y probablemente es su mejor apuesta para aplicaciones Windows Store que necesita hacer cosas interesantes con sonido. Mientras que XAudio2 está diseñado principalmente para juegos, que no debería dejar de usarlo para fines más graves — como la música o entretener al usuario empresarial con sonidos divertidos.
XAudio2 versión 2.8 es un componente integrado de Windows 8. Como el resto de DirectX, la interfaz de programación de XAudio2 está basada en COM. Mientras que es teóricamente posible acceso XAudio2 de cualquier lenguaje de programación con Windows 8, el idioma más fácil y natural de XAudio2 es C++. Trabajo con sonido a menudo requiere código de alto rendimiento, por lo que C++ es una buena opción en ese sentido así.
Un primer programa de XAudio2
Vamos a empezar a escribir un programa que utiliza XAudio2 para reproducir el sonido un simple 5 segundos ante el empuje de un botón. Porque es posible que Windows 8 y DirectX programación, me quedo un poco lento.
Te supongo que tienes una versión de Visual Studio instalado que es conveniente para la creación de aplicaciones de la tienda de Windows. En el cuadro de diálogo nuevo proyecto, seleccione Visual C++ y almacén de Windows a la izquierda y en blanco App (XAML) en la lista de plantillas disponibles. Le di mi proyecto el nombre SimpleAudio, y usted puede encontrar que el proyecto entre el código descargable para este artículo.
En la construcción de un ejecutable que utiliza XAudio2, debe vincular el programa con la biblioteca de importación xaudio2.lib. Abrir el cuadro de diálogo de propiedades de proyecto seleccionando el último elemento en el menú proyecto, o haciendo clic en el nombre del proyecto en el explorador de soluciones y seleccione Propiedades. En la columna izquierda, seleccione Propiedades de configuración y, a continuación, vinculador y entrada. Haga clic en dependencias adicionales (el elemento superior) y la pequeña flecha. Seleccione Editar y escriba xaudio2.lib en el cuadro.
Te desea también una referencia al archivo de encabezado xaudio2.h, así que agregue la siguiente instrucción a la lista de encabezados precompilados en pch.h:
#include <xaudio2.h>
En el archivo MainPage.xaml, agregué un TextBlock para mostrar los errores que el programa puede encontrar trabajo con la API de XAudio2 y un botón para reproducir un sonido. Estos se muestran en figura 1.
Figura 1 el archivo MainPage.xaml para SimpleAudio
<Page x:Class="SimpleAudio.MainPage" ... >
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Name="errorText"
FontSize="24"
TextWrapping="Wrap"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Button Name="submitButton"
Content="Submit Audio Button"
Visibility="Collapsed"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OnSubmitButtonClick" />
</Grid>
</Page>
La mayor parte del archivo de encabezado de MainPage.xaml.h se muestra en la figura 2. He quitado la declaración del método OnNavigatedTo porque yo no usarlo. El controlador haga clic en el botón se declara, como son cuatro ámbitos relacionados con el uso del programa de XAudio2.
Figura 2 el archivo de encabezado de MainPage.xaml.h para SimpleAudio
namespace SimpleAudio
{
public ref class MainPage sealed
{
private:
Microsoft::WRL::ComPtr<IXAudio2> pXAudio2;
IXAudio2MasteringVoice * pMasteringVoice;
IXAudio2SourceVoice * pSourceVoice;
byte soundData[2 * 5 * 44100];
public:
MainPage();
private:
void OnSubmitButtonClick(Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ args);
};
}
Cualquier programa que desee utilizar XAudio2 debe crear un objeto que implementa la interfaz IXAudio2. (A ver cómo se hace poco). IXAudio2 deriva de la clase de IUnknown famosa en COM, y es inherentemente referencia contada, lo que significa que elimina sus propios recursos cuando ya no hace referencia a un programa. La clase ComPtr (COM puntero) en el espacio de nombres Microsoft::WRL convierte un puntero a un objeto COM en un "puntero inteligente" que realiza un seguimiento de sus propias referencias. Este es el método recomendado para trabajar con objetos COM en un app Store de Windows.
Cualquier programa no trivial de XAudio2 también necesita punteros a objetos que implementan las interfaces IXAudio2MasteringVoice y IXaudio2SourceVoice. En el lenguaje de XAudio2, una "voz" es un objeto que genera o modifica los datos de audio. La voz de masterización conceptualmente es un mezclador de sonido que reúne todas las voces individuales y los prepara para el hardware de generación de sonido. Usted sólo tendrá uno de estos, pero que tenga un número de voces de origen que generan sonidos separados. (También hay maneras de aplicar filtros o efectos a las voces de origen).
Los punteros IXAudio2MasterVoice y IXAudio2SourceVoice no son contados por referencia; sus vidas se rigen por el objeto IXAudio2.
También he incluido una gran variedad de 5 segundos de datos de sonido:
byte soundData[5 * 2 * 44100];
En un programa real, debe asignar una matriz de este tamaño en tiempo de ejecución — y deshacerse de él cuando no lo necesite, pero verás poco por qué lo hice así.
¿Cómo calcula ese tamaño de la matriz? Aunque XAudio2 admite audio comprimido, la mayoría de los programas que generan sonido pegará con el formato conocido como modulación por impulsos codificados o PCM. Formas de onda de sonido en PCM están representados por los valores de un tamaño fijo a una velocidad de muestreo fijos. De música en discos compactos, la tasa de muestreo es 44.100 veces por segundo, con 2 bytes por muestra en estéreo, para un total de 176.400 bytes de datos por 1 segundo de audio. (Al incrustar sonidos en una aplicación, se recomienda compresión. XAudio2 soporta ADPCM; WMA y MP3 son también compatibles con el motor de la Fundación de medios de comunicación.)
Para este programa, también he elegido utilizar una frecuencia de muestreo de 44.100 2 bytes por muestra. En C++, cada muestra, por tanto, es un corto. Me quedo con sonido monoaural por ahora, por lo que deben 88.200 bytes por segundo de audio. En la asignación de la matriz, se multiplica por 5 durante 5 segundos.
Creación de los objetos
Gran parte del archivo MainPage.xaml.cpp se muestra en la figura 3. Todos de la inicialización de XAudio2 se realiza en el constructor de la página principal. Comienza con una llamada a XAudio2Create para obtener un puntero a un objeto que implementa la interfaz IXAudio2. Este es el primer paso en el uso de XAudio2. A diferencia de algunas interfaces COM, no se necesita ninguna llamada a CoCreateInstance.
Figura 3 MainPage.xaml.cpp
MainPage::MainPage()
{
InitializeComponent();
// Create an IXAudio2 object
HRESULT hr = XAudio2Create(&pXAudio2);
if (FAILED(hr))
{
errorText->Text = "XAudio2Create failure: " + hr.ToString();
return;
}
// Create a mastering voice
hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice);
if (FAILED(hr))
{
errorText->Text = "CreateMasteringVoice failure: " + hr.ToString();
return;
}
// Create a source voice
WAVEFORMATEX waveformat;
waveformat.wFormatTag = WAVE_FORMAT_PCM;
waveformat.
nChannels = 1;
waveformat.
nSamplesPerSec = 44100;
waveformat.
nAvgBytesPerSec = 44100 * 2;
waveformat.
nBlockAlign = 2;
waveformat.wBitsPerSample = 16;
waveformat.cbSize = 0;
hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &waveformat);
if (FAILED(hr))
{
errorText->Text = "CreateSourceVoice failure: " + hr.ToString();
return;
}
// Start the source voice
hr = pSourceVoice->Start();
if (FAILED(hr))
{
errorText->Text = "Start failure: " + hr.ToString();
return;
}
// Fill the array with sound data
for (int index = 0, second = 0; second < 5; second++)
{
for (int cycle = 0; cycle < 441; cycle++)
{
for (int sample = 0; sample < 100; sample++)
{
short value = sample < 50 ?
32767 : -32768;
soundData[index++] = value & 0xFF;
soundData[index++] = (value >> 8) & 0xFF;
}
}
}
// Make the button visible
submitButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
}
void MainPage::OnSubmitButtonClick(Object^ sender,
RoutedEventArgs^ args)
{
// Create a button to reference the byte array
XAUDIO2_BUFFER buffer = { 0 };
buffer.AudioBytes = 2 * 5 * 44100;
buffer.pAudioData = soundData;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.PlayBegin = 0;
buffer.PlayLength = 5 * 44100;
// Submit the buffer
HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
if (FAILED(hr))
{
errorText->Text = "SubmitSourceBuffer failure: " + hr.ToString();
submitButton->Visibility =
Windows::UI::Xaml::Visibility::Collapsed;
return;
}
}
Una vez creado el objeto de IXAudio2, los métodos CreateMasteringVoice y CreateSourceVoice obtienen punteros a las otras dos interfaces definidas como campos en el archivo de encabezado.
La llamada de CreateSourceVoice requiere una estructura WAVEFORMATEX, que resultará familiar a quien ha trabajado con audio en la API de Win32. Esta estructura define la naturaleza de los datos de audio que se va a utilizar para esta voz particular. (Voces de origen diferente pueden utilizar diferentes formatos). Para PCM, sólo tres números son realmente relevantes: la tasa de muestreo (44.100 en este ejemplo), el tamaño de cada muestra (2 bytes o 16 bits) y el número de canales (1 aquí). Los demás campos se basan en estos: El campo de nBlockAlign es nChannels veces wBitsPerSample dividido por 8, y campo de nAvgBytesPerSec es el producto de nSamplesPerSec y nBlockAlign. Pero en este ejemplo he mostrado todos los campos con valores explícitos.
Una vez que el objeto IXAudio2SourceVoice se obtiene, se puede llamar al método de arranque en él. En este punto, XAudio2 conceptualmente es jugando, pero realmente no hemos dado los datos de audio para reproducir. También está disponible un método Stop, y un programa real sería utilizar estos dos métodos para controlar cuando sonidos deberían y no deberían estar jugando.
Cuatro de estas llamadas no es tan simple como que aparecen en este código! Todos ellos tienen argumentos adicionales, pero se definen valores predeterminados convenientes y simplemente he elegido a aceptar los valores predeterminados por ahora.
Prácticamente todas las llamadas de función y método en DirectX devolverán valores HRESULT para indicar éxito o fracaso. Existen diferentes estrategias para tratar con estos errores. He elegido simplemente para mostrar el código de error usando el TextBlock definido en el archivo XAML y la parada más procesamiento.
Datos de Audio PCM
El constructor concluye llenando la matriz soundData con datos de audio, pero la matriz no es realidad bifurcada sobre a la IXAudio2SourceVoice hasta que se pulsa el botón.
El sonido es vibración, y los seres humanos son sensibles a las vibraciones en el aire aproximadamente en el rango de 20 Hz (o ciclos por segundo) a 20.000 Hz. Do central en el piano es de aproximadamente 261.6 Hz.
Supongamos que está trabajando con una frecuencia de muestreo de 44.100 Hz y muestras de 16 bits y desea generar datos de audio para una forma de onda con una frecuencia de 4.410 Hz, que es sólo más allá de la tecla más alta en un piano. Cada ciclo de una forma de onda requiere 10 muestras de valores 16 bits con signo. Estos valores de 10 se repetiría 4.410 veces durante cada segundo de sonido.
Una forma de onda con una frecuencia de 441 Hz — muy cerca de 440 Hz, correspondiente a la A por encima de la media C como un afinación estándar — se presenta con 100 muestras. Este ciclo se repetiría 441 veces durante cada segundo de sonido.
PCM implica una constante de muestreo, sonidos de baja frecuencia parecen ser muestreados y procesa en una resolución mucho mayor que los sonidos de alta frecuencia. ¿No es esto un problema? ¿No una forma de onda de 4.410 Hz con sólo 10 muestras tiene una considerable cantidad de distorsión en comparación con la forma de onda de 441 Hz?
Resulta que cualquier distorsión de cuantificación en PCM ocurre a frecuencias superiores a la mitad la tasa de muestreo. (Esto se conoce como la frecuencia Nyquist Bell Labs Ingeniero Harry Nyquist.) Una frecuencia de muestreo de 44.100 Hz fue elegida para el CD audio una de las razones es que la frecuencia Nyquist es 22.050 Hz, y el oído humano se maximiza a unos 20.000 Hz. En otras palabras, a una velocidad de muestreo de 44.100 Hz, la distorsión de cuantificación es inaudible para los humanos.
El programa de SimpleAudio genera una forma de onda algorítmicamente simple — una onda cuadrada con una frecuencia de 441 Hz. Hay 100 muestras por ciclo. En cada ciclo la primera 50 son valores máximos positivos (32.767 tratándose de enteros cortos) y el siguiente 50 máximos valores negativos (-32,768). Observe que estos valores cortos deben almacenarse en la matriz de bytes con el byte bajo en primer lugar:
soundData[index + 0] = value & 0xFF;
soundData[index + 1] = (value >> 8) & 0xFF;
Hasta ahora, nada ha jugado realmente. Esto sucede en el controlador haga clic en el botón. La estructura de la XAUDIO2_BUFFER se utiliza para hacer referencia a la matriz de bytes con un recuento de los bytes y una duración especificada como el número de muestras. Este buffer se pasa al método SubmitSourceBuffer del objeto IXAudio2SourceVoice. Si ya se ha llamado el método de inicio (como en este ejemplo), a continuación, el sonido comienza a reproducirse inmediatamente.
Sospecho que no tengo que mencionar que el sonido se reproduce asincrónicamente. La llamada de SubmitSourceBuffer devuelve inmediatamente, mientras que un subproceso independiente se dedica al proceso real de palear los datos para el hardware de sonido. La XAUDIO2_BUFFER pasado a SubmitSourceBuffer puede desecharse después de la llamada — como es en este programa cuando el controlador de clic se sale y la variable local se sale del ámbito, pero la actual matriz de bytes debe permanecer en la memoria accesible. De hecho, el programa puede manipular estos bytes mientras se reproduce el sonido. Sin embargo, hay mucho mejores técnicas (métodos callback) que permiten su programa generar dinámicamente los datos de sonido.
Sin necesidad de utilizar una devolución de llamada para determinar cuando se ha completado el sonido, este programa tiene que conservar la matriz soundData para la duración del programa.
Usted puede presionar el botón varias veces, y cada llamada efectivamente colas hasta otro búfer a ser escuchado cuando el buffer anterior termina. Si el programa se mueve hacia el fondo, el sonido se silencia, pero sigue en silencio. En otras palabras, si pulsa el botón y mueva el programa a un segundo plano durante al menos 5 segundos, nada estará tocando cuando el programa vuelve al primer plano.
Las características del sonido
Gran parte del sonido que oímos en la vida cotidiana proviene simultáneamente de una variedad de diferentes fuentes y, por lo tanto, es bastante complejo. Sin embargo, en algunos casos — y especialmente cuando se trata con sonidos musicales: tonos individuales pueden ser definidas con pocas características:
- Amplitud, que es interpretada por nuestros sentidos como volumen.
- Frecuencia, lo que se interpreta como tono.
- Espacio, que puede ser imitada en reproducción de audio con altavoces múltiples.
- Timbre, que está relacionada con la mezcla de armónicos de un sonido y representa la diferencia percibida entre una trompeta y un piano, por ejemplo.
El proyecto de SoundCharacteristics demuestra estas cuatro características en aislamiento. Se mantienen la velocidad de muestreo de 44.100 y muestras de 16 bits del proyecto SimpleAudio, pero genera el sonido en estéreo. Para los dos canales de sonido, los datos deben ser interpolados: una muestra de 16 bits para el canal izquierdo, seguido de una muestra de 16 bits para el canal derecho.
El archivo de encabezado de MainPage.xaml.h de SoundCharacteristics define algunas constantes:
static const int sampleRate = 44100;
static const int seconds = 5;
static const int channels = 2;
static const int samples = seconds * sampleRate;
También define cuatro arreglos de discos para datos de sonido, pero estas son de tipo corto en lugar de bytes:
short volumeSoundData[samples * channels];
short pitchSoundData[samples * channels];
short spaceSoundData[samples * channels];
short timbreSoundData[samples * channels];
Utilizar matrices cortos facilita la inicialización porque los valores de forma de onda de 16 bits no es necesario ser roto por la mitad. Un reparto simple permite la matriz hacer referencia a la XAUDIO2_BUFFER al enviar los datos de sonido. Estas matrices tienen doble el número de bytes como la matriz de SimpleAudio porque estoy usando estéreo en este programa.
Cuatro de estos arreglos de discos se inicializa en el constructor de la página principal. Para la demostración de volumen, una onda cuadrada de 441 Hz participa todavía, pero comienza en volumen cero, obtiene progresivamente más fuerte durante los primeros 2 segundos y luego disminuye en volumen durante los últimos 2 segundos. Figura 4 muestra el código para inicializar volumeSoundData.
Figura 4 datos de sonido que los cambios en el volumen de SoundCharacteristics
for (int index = 0, sample = 0; sample < samples; sample++)
{
double t = 1;
if (sample < 2 * samples / 5)
t = sample / (2.0 * samples / 5);
else if (sample > 3 * samples / 5)
t = (samples - sample) / (2.0 * samples / 5);
double amplitude = pow(2, 15 * t) - 1;
short waveform = sample % 100 < 50 ?
1 : -1;
short value = short(amplitude * waveform);
volumeSoundData[index++] = value;
volumeSoundData[index++] = value;
}
Percepción humana del volumen es logarítmica: Cada duplicación de la amplitud de una forma de onda es equivalente a un incremento de 6 dB en volumen. (La amplitud de 16 bits utilizada para CD audio tiene un rango dinámico de 96 decibeles). El código que se muestra en la figura 4 modificar el volumen primero calcula un valor de t que aumenta linealmente de 0 a 1, y luego disminuye a 0. La amplitud variable se calcula utilizando la función pow y varía de 0 a 32.767. Se multiplica por una onda cuadrada que tiene valores de 1 y -1. El resultado se agrega a la matriz dos veces: en primer lugar para el canal izquierdo, y luego para el canal derecho.
Percepción humana de frecuencia también es logarítmica. Gran parte de la música del mundo organiza echada todo el intervalo de una octava, que es una duplicación de la frecuencia. Las dos primeras notas del coro de "Somewhere over the Rainbow" son un salto de octava si está cantado por un bajo o una soprano. Figura 5 muestra código que varía el tono en la gama de dos octavas: de 220 a 880 basado en un valor de t que (como en el ejemplo de volumen) va de 0 a 1 y luego de nuevo hacia abajo a 0.
Figura 5 datos de sonido que los cambios en la frecuencia de SoundCharacteristics
double angle = 0;
for (int index = 0, sample = 0; sample < samples; sample++)
{
double t = 1;
if (sample < 2 * samples / 5)
t = sample / (2.0 * samples / 5);
else if (sample > 3 * samples / 5)
t = (samples - sample) / (2.0 * samples / 5);
double frequency = 220 * pow(2, 2 * t);
double angleIncrement = 360 * frequency / waveformat.
nSamplesPerSec;
angle += angleIncrement;
while (angle > 360)
angle -= 360;
short value = angle < 180 ?
32767 : -32767;
pitchSoundData[index++] = value;
pitchSoundData[index++] = value;
}
En los ejemplos anteriores elegí una frecuencia de 441 Hz porque divide limpiamente en la frecuencia de muestreo de 44.100. En el caso general, la tasa de muestreo no es un múltiplo integral de la frecuencia, y por lo tanto no puede ser un número entero de muestras por ciclo. En cambio, este programa mantiene una variable angleIncrement de punto flotante que es proporcional a la frecuencia y la utilizó para incrementar un valor de ángulo que va de 0 a 360. El valor de ángulo se utiliza entonces para construir la forma de onda.
La demostración para espacio mueve el sonido desde el centro para el canal izquierdo, luego a la derecha, luego hacia el centro. Para la demostración del timbre, la forma de onda se inicia con una curva sinusoidal a 441 Hz. Una curva sinusoidal es la representación matemática del tipo más fundamental de vibración — la solución de la ecuación diferencial, donde la fuerza es inversamente proporcional al desplazamiento. Todas las otras formas de onda periódicas contienen armónicos, que son también ondas sinusoidales con frecuencias que son múltiplos de integrales de la frecuencia base. La demo de timbre cambia la forma de onda sin problemas de una onda sinusoidal a una onda triangular, a una onda cuadrada, a una onda de diente de sierra, aumentando el contenido armónico del sonido.
La imagen más grande
Aunque sólo he ha demostrado cómo se pueden controlar volumen, tono, espacio y timbre mediante la generación de datos de sonido para un solo objeto de IXAudio2SourceVoice, el propio objeto incluye métodos para cambiar el volumen y el espacio e incluso la frecuencia. (También se admite una instalación espacio "3D"). Aunque es posible generar datos de sonido compuestos que combina un montón de tonos individuales con una voz de origen único, puede crear varios objetos IXAudio2SourceVoice y jugarlos todos juntos a través de la misma voz de masterización.
Además, XAudio2 define un IXAudioSubmixVoice que le permite definir filtros y otros efectos como reverberación o eco. Los filtros tienen la capacidad para cambiar el timbre de tonos existentes de forma dinámica, que puede contribuir en gran medida a la creación de sonidos musicales interesantes y realistas.
Quizá la mejora más importante más allá de lo que he mostrado en estos dos programas requiere trabajar con las funciones de devolución de llamada de XAudio2. En lugar de asignar e inicializar los trozos grandes de datos de sonido como estos dos programas, tiene mucho más sentido para un programa para generar dinámicamente los datos de sonido como que se está reproduciendo.
Charles Petzold es colaborador desde hace mucho tiempo de MSDN Magazine y autor de "Programación Windows, 6ª edición" (o ' Reilly Media, 2012), un libro sobre cómo escribir aplicaciones para Windows 8. Su sitio Web es charlespetzold.com.
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Scott Selfon