Опубликован: 14.08.2012 | Уровень: специалист | Доступ: платный
Самостоятельная работа 27:

Акселерометр, микрофон, панель приложения

34.2. Работа с микрофоном, панель приложения

В основу этого примера лёг образец от Microsoft (http://msdn.microsoft.com/en-us/library/gg442302%28v=vs.92%29.aspx). Здесь создан пользовательский интерфейс на основе панели приложения. С помощью кнопок на панели приложения мы управляем записью звуков посредством микрофона и воспроизведением этих звуков.

Панель приложения обычно представлена набором кнопок в нижней части окна программы (при портретной ориентации). Кнопки располагаются достаточно компактно, если прикоснуться к области панели с тремя точками, панель развернётся, показывая подписи к кнопкам, и, возможно, пункты меню, позволяющие произвести какие-либо дополнительные действия. На рис. 34.2 показан пользовательский интерфейс нашей программы при запуске в эмуляторе.

Окно программы

Рис. 34.2. Окно программы

Кнопки отображены пустыми значками, так как для них использованы пустые заготовки изображений, которые можно нарисовать самостоятельно, учитывая особенности оформления Windows Phone-приложений.

В листинге 34.3 приведен XAML-код страницы MainPage

<phone:PhoneApplicationPage 
    x:Class="P27_2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel содержит имя приложения и заголовок страницы-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="МИКРОФОН" 
            Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="микрофон" Margin="9,-7,0,0" 
            Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel — поместите здесь дополнительное содержимое-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Image x:Name="StatusImage" Height="334" Width="366" 
               HorizontalAlignment="Center" VerticalAlignment="Center"
               Source="Images/blank.png" Margin="45,79,45,18" />
            <TextBlock Height="30" HorizontalAlignment="Left" Margin="12,6,0,0" 
            Name="UserHelp" Text="Нажмите кнопку Запись" VerticalAlignment="Top" 
            Width="414" />
        </Grid>
    </Grid>

    <!--Пример кода, иллюстрирующий использование ApplicationBar-->
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="False">
            <shell:ApplicationBar.Buttons>
                <shell:ApplicationBarIconButton x:Name="recordButton" Text="запись" 
                IconUri="/Images/record.png" Click="recordButton_Click" IsEnabled="True"/>
                <shell:ApplicationBarIconButton x:Name="playButton" Text="проиграть" 
                IconUri="/Images/play.png" Click="playButton_Click" IsEnabled="False"/>
                <shell:ApplicationBarIconButton x:Name="stopButton" Text="стоп" 
                IconUri="/Images/stop.png" Click="stopButton_Click" IsEnabled="False"/>
            </shell:ApplicationBar.Buttons>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>
Листинг 34.3. Код страницы MainPage

Обратите внимание на описание ApplicationBar. Здесь задано три кнопки на панели приложения. Одна из них, кнопка записи, активирована по умолчанию, доступ к двум другим закрыт, так как сразу после запуска приложения единственное, что может сделать пользователь – это сделать новую запись с микрофона.

Для данного проекта мы должны подключить библиотеку Microsoft.Xna.Framework, объектное представление аппаратного микрофона представлено именно в ней. В листинге 34.4 вы можете видеть программный код, обеспечивающий работу примера. Он подробно прокомментирован.

using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;

namespace P27_2
{
    public partial class MainPage : PhoneApplicationPage
    {
// Объект, представляющий физический микрофон на устройстве
        private Microphone microphone = Microphone.Default;  
        //Динамический буфер для получения аудиоданных с микрофона
        private byte[] buffer;               
        //Хранение аудиоданных для последующего воспроизведения           
        private MemoryStream stream = new MemoryStream();   
        //Используется для воспроизведения звука
        private SoundEffectInstance soundInstance;          
        //Флаг для отслеживания состояния проигрывания звука
        private bool soundIsPlaying = false;

        // Изображения, сигнализирующие о состоянии программы
        private BitmapImage blankImage;
        private BitmapImage microphoneImage;
        private BitmapImage speakerImage;

        /// <summary>
        /// Конструктор
        /// </summary>
        public MainPage()
        {
            InitializeComponent();

            // Таймер для организации игрового цикла XNA (обект Microphone взят 
            // из XNA Framework). Мы так же используем этот таймер для отслеживания
            // состояния воспроизведения аудио, что позволяем нам обновлять
            // состояние пользовательского интерфейса
            DispatcherTimer dt = new DispatcherTimer();
            dt.Interval = TimeSpan.FromMilliseconds(33);
            dt.Tick += new EventHandler(dt_Tick);
            dt.Start();

            //Обработчик событя для получения аудиоданных, когда буфер заполнится 
            microphone.BufferReady += new EventHandler<EventArgs>(microphone_BufferReady);
            //Подготовка ссылок на изображения
            blankImage = new BitmapImage(new Uri("Images/blank.png", UriKind.RelativeOrAbsolute));
            microphoneImage = new BitmapImage(new Uri("Images/microphone.png", UriKind.RelativeOrAbsolute));
            speakerImage = new BitmapImage(new Uri("Images/speaker.png", UriKind.RelativeOrAbsolute));
        }

        /// <summary>
        /// Вызвает XNA FrameworkDispatcher и проверяет, проигрывается ли звук.
        /// Если проигрывание завершено, обновляет состояние пользовательского интерфейса.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void dt_Tick(object sender, EventArgs e)
        {
            //Организуем игровой цикл XNA
            try { FrameworkDispatcher.Update(); }
            catch { }
            //Если звук проигрывается
            if (true == soundIsPlaying)
            {
                //Если проигрывание завершено
                if (soundInstance.State != SoundState.Playing)
                {
                    // Проигрывание завершено
                    soundIsPlaying = false;

                    // Обновляет пользовательский интерфейс
                    // того, чтобы отразить, что прекращено проигрывание звука
                    SetButtonStates(true, true, false);
                    UserHelp.Text = "Нажмите кнопку Проиграть или Запись";
                    StatusImage.Source = blankImage;
                }
            }
        }

        /// <summary>
        /// Обработчик события Microphone.BufferReady.
        /// Получает аудиоданные с микрофона, сохраняет их в буфер,
        /// затем записывает содержимое буфера в поток для последующего воспроизведения.
        /// Любые действия в этом обработчике должны быть как можно более быстрыми!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void microphone_BufferReady(object sender, EventArgs e)
        {
            // Получаем аудиоданные
            microphone.GetData(buffer);

            // Сохраняем данные в поток
            stream.Write(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Обрабатывает событие Click для кнопки record (запись).
        /// Устанавливает микрофон и буферы данных для получения аудиоданных,
        /// затем запускает микрофон. Кроме того, обновляет пользовательский интерфейс.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void recordButton_Click(object sender, EventArgs e)
        {
            // Получение аудиоданных участками по 1/2 секунды
            microphone.BufferDuration = TimeSpan.FromMilliseconds(500);

            // Выделяет память для хранения аудиоданных
            buffer = new byte[microphone.GetSampleSizeInBytes(microphone.BufferDuration)];

            // Сбрасывает поток в 0 для того случая, если он уже содержит
            //какие-либо данные
            stream.SetLength(0);

            // Начало записи
            microphone.Start();
            
            // Обновление пользовательского интерфейса
            SetButtonStates(false, false, true);
            UserHelp.Text = "Идёт запись...";
            StatusImage.Source = microphoneImage;
        }

        /// <summary>
        /// Обрабатывает событие Click для кнопки stop (стоп).
        /// Останавливает запись звука с микрофона и обновляет пользовательский интерфейс.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void stopButton_Click(object sender, EventArgs e)
        {
            //Если запись идёт
            if (microphone.State == MicrophoneState.Started)
            {
                // В режиме записи пользователь нажимает на кнопку  
                // stop для того, чтобы прекратить запись
                microphone.Stop();
            }
            // Если звук воспроизводится
            else if (soundInstance.State == SoundState.Playing)
            {
                // В режиме воспроизведения пользоатель нажимает на кнопку 
                // stop для того, чтобы остановить воспроизведение
                soundInstance.Stop();
            }
            // Обновляет пользовательский интерфейс
            SetButtonStates(true, true, false);
            UserHelp.Text = "Готово";
            StatusImage.Source = blankImage;
        }

        /// <summary>
        /// Обрабатывает событие Click для кнопки play (воспроизведение).
        /// Проигрывает звук, записанный в прошлом сеансе записи с микрофона
        /// и обновляет пользовательский интерфейс.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void playButton_Click(object sender, EventArgs e)
        {
            if (stream.Length > 0)
            {
                // Обновление пользовательского интефейса,
                // указывающее на то, что звук проигрывается
                SetButtonStates(false, false, true);
                UserHelp.Text = "Воспроизведение записи";
                StatusImage.Source = speakerImage;

                // Проигрывает звук в новом потоке, что позволяет
                //пользовательскому интерфейсу обновляться.
                Thread soundThread = new Thread(new ThreadStart(playSound));
                soundThread.Start();
            }
        }

        /// <summary>
        /// Воспроизводит звук, используя SoundEffectInstance 
        /// это позволяет нам отслеживать состояние проигрывания.
        /// </summary>
        private void playSound()
        {
            // Воспроизводит звук, используя SoundEffectInstance 
            // что позволяет нам отслеживать состояние воспроизведения 
            // и обновлять пользовательский интерфейс в обработчике dt_Tick, когда воспроизведение завершитсяg.
            SoundEffect sound = new SoundEffect(stream.ToArray(), microphone.SampleRate, AudioChannels.Mono);
            soundInstance = sound.CreateInstance();
            soundIsPlaying = true;
            soundInstance.Play();
        }

        /// <summary>
        /// Вспомогательный метод для изменения свойства IsEnabled 
        /// для объектов ApplicationBarIconButtons (кнопок на панели приложения).
        /// </summary>
        /// <param name="recordEnabled">Новое состояние для кнопки record.</param>
        /// <param name="playEnabled">Новое состояние для  play .</param>
        /// <param name="stopEnabled">Новое состояние для stop .</param>
        private void SetButtonStates(bool recordEnabled, bool playEnabled, bool stopEnabled)
        {
            (ApplicationBar.Buttons[0] as ApplicationBarIconButton).IsEnabled = recordEnabled;
            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = playEnabled;
            (ApplicationBar.Buttons[2] as ApplicationBarIconButton).IsEnabled = stopEnabled;
        }
    }
}
Листинг 34.4. Программный код страницы MainPage

Так как в работе мы пользуемся механизмами XNA, нам нужно организовать таймер, имитирующий игровой цикл XNA. Этот таймер мы запускаем в конструкторе, с его помощью мы, кроме того, отслеживаем состояние проигрывания записи и соответствующим образом обновляем пользовательский интерфейс.

В конструкторе же мы добавляем обработчик события BufferReady, которое происходит, когда буфер микрофона (то есть – записанные данные) готов к обработке.

По кнопке старта записи мы подготавливаем всё, что нужно для работы с данными микрофона и запускаем микрофон. По кнопке остановки, которая останавливает запись, если запись идёт, или останавливает воспроизведение при проигрывании записи, мы, в первом случае, останавливаем микрофон (после чего происходит событие BufferReady, данные с микрофона сохраняются), во втором – останавливаем воспроизведение.

При нажатии на кнопку воспроизведения мы проверяем, есть ли данные в потоке, хранящем данные последней записи, если они есть – запускаем проигрывание в отдельном потоке, пользуясь методом PlaySound().

Большинство операций, имеющих отношение к работе со звуком, сопровождаются настройками пользовательского интерфейса. Как правило, меняется сообщение для пользователя, которое выводится в текстовый блок UserHelp, кроме того, с помощью вспомогательного метода SetButtonStates настраивается доступность кнопок панели приложения.

34.3. Выводы

В данной лабораторной работе мы ознакомились с использованием показаний акселерометра для управления экранными объектами в Silverlight-приложениях, рассмотрели элемент управления Canvas и концепцию присоединенных свойств, научились работать с микрофоном и оснащать программу панелью приложения

34.4. Задание

Выясните, какими еще сенсорами и аппаратными устройствами телефона можно воспользоваться из Silverlight-программы. Выберите один из сенсоров или аппаратных устройств и реализуйте приложение, использующее его возможности.

Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто
Сашечка Огнев
Сашечка Огнев
Россия, Красноярский край
Андрей Корягин
Андрей Корягин
Россия, Пенза, Вазерская средняя школа, 2001