Condividi tramite


Impostare il formato, la risoluzione e la frequenza dei fotogrammi per MediaCapture

Questo articolo illustra come usare l'interfaccia IMediaEncodingProperties per impostare la risoluzione e la frequenza dei fotogrammi del flusso di anteprima della fotocamera e le foto e i video acquisiti. Viene inoltre illustrato come assicurarsi che le proporzioni del flusso di anteprima corrispondano a quella dei supporti acquisiti.

I profili fotocamera offrono un modo più avanzato di individuare e impostare le proprietà del flusso della fotocamera, ma non sono supportate per tutti i dispositivi. Per ulteriori informazioni, vedere Profili fotocamera.

Il codice in questo articolo è stato adattato dall'esempio CameraResolution. È possibile scaricare questo esempio per visualizzare il codice usato nel contesto o per usare gli esempi come punto di partenza per la propria app.

Nota

Questo articolo si basa sui concetti e sul codice descritti in Acquisizione di foto, video e audio di base con MediaCapture, che descrive i passaggi necessari per implementare l'acquisizione di foto e video di base. È consigliabile acquisire familiarità con il modello di acquisizione multimediale di base in questo articolo prima di passare a scenari di acquisizione più avanzati. Il codice in questo articolo presuppone che l'app abbia già un'istanza di MediaCapture che è stata inizializzata correttamente.

Classe helper delle proprietà di codifica multimediale

La creazione di una semplice classe helper per eseguire il wrapping delle funzionalità dell'interfaccia IMediaEncodingProperties semplifica la selezione di un set di proprietà di codifica che soddisfano criteri specifici. Questa classe helper è particolarmente utile a causa del comportamento seguente della funzionalità delle proprietà di codifica:

Avvertenza Il metodo VideoDeviceController.GetAvailableMediaStreamProperties accetta un membro dell'enumerazione MediaStreamType, come VideoRecord o Photo e restituisce un elenco di oggetti ImageEncodingProperties o VideoEncodingProperties che trasmettono le impostazioni di codifica del flusso, ad esempio la risoluzione della foto o del video acquisito. I risultati della chiamata a GetAvailableMediaStreamProperties possono includere ImageEncodingProperties o VideoEncodingProperties indipendentemente da quale valore di MediaStreamType sia specificato. Per questo motivo, è consigliabile controllare sempre il tipo di ogni valore restituito ed eseguirne il cast al tipo appropriato prima di tentare di accedere a uno dei valori delle proprietà.

La classe helper definita di seguito gestisce il controllo dei tipi e il cast per ImageEncodingProperties o VideoEncodingProperties in modo che il codice dell'app non debba distinguere tra i due tipi. Oltre a questo, la classe helper espone le proprietà per le proporzioni delle proprietà, la frequenza dei fotogrammi (solo per le proprietà di codifica video) e un nome descrittivo che semplifica la visualizzazione delle proprietà di codifica nell'interfaccia utente dell'app.

È necessario includere lo spazio dei nomi Windows.Media.MediaProperties nel file di origine per la classe helper.

using Windows.Media.MediaProperties;
using Windows.Media.Capture.Frames;
class StreamPropertiesHelper
{
    private IMediaEncodingProperties _properties;

    public StreamPropertiesHelper(IMediaEncodingProperties properties)
    {
        if (properties == null)
        {
            throw new ArgumentNullException(nameof(properties));
        }

        // This helper class only uses VideoEncodingProperties or VideoEncodingProperties
        if (!(properties is ImageEncodingProperties) && !(properties is VideoEncodingProperties))
        {
            throw new ArgumentException("Argument is of the wrong type. Required: " + typeof(ImageEncodingProperties).Name
                + " or " + typeof(VideoEncodingProperties).Name + ".", nameof(properties));
        }

        // Store the actual instance of the IMediaEncodingProperties for setting them later
        _properties = properties;
    }

    public uint Width
    {
        get
        {
            if (_properties is ImageEncodingProperties)
            {
                return (_properties as ImageEncodingProperties).Width;
            }
            else if (_properties is VideoEncodingProperties)
            {
                return (_properties as VideoEncodingProperties).Width;
            }

            return 0;
        }
    }

    public uint Height
    {
        get
        {
            if (_properties is ImageEncodingProperties)
            {
                return (_properties as ImageEncodingProperties).Height;
            }
            else if (_properties is VideoEncodingProperties)
            {
                return (_properties as VideoEncodingProperties).Height;
            }

            return 0;
        }
    }

    public uint FrameRate
    {
        get
        {
            if (_properties is VideoEncodingProperties)
            {
                if ((_properties as VideoEncodingProperties).FrameRate.Denominator != 0)
                {
                    return (_properties as VideoEncodingProperties).FrameRate.Numerator / 
                        (_properties as VideoEncodingProperties).FrameRate.Denominator;
                }
           }

            return 0;
        }
    }

    public double AspectRatio
    {
        get { return Math.Round((Height != 0) ? (Width / (double)Height) : double.NaN, 2); }
    }

    public IMediaEncodingProperties EncodingProperties
    {
        get { return _properties; }
    }

    public string GetFriendlyName(bool showFrameRate = true)
    {
        if (_properties is ImageEncodingProperties ||
            !showFrameRate)
        {
            return Width + "x" + Height + " [" + AspectRatio + "] " + _properties.Subtype;
        }
        else if (_properties is VideoEncodingProperties)
        {
            return Width + "x" + Height + " [" + AspectRatio + "] " + FrameRate + "FPS " + _properties.Subtype;
        }

        return String.Empty;
    }
    
}

Determinare se i flussi di anteprima e acquisizione sono indipendenti

In alcuni dispositivi, lo stesso pin hardware viene usato sia per i flussi di anteprima che per i flussi di acquisizione. In questi dispositivi, l'impostazione delle proprietà di codifica di una imposta anche l'altra. Nei dispositivi che usano pin hardware diversi per l'acquisizione e l'anteprima, è possibile impostare le proprietà per ogni flusso in modo indipendente. Usare il codice seguente per determinare se i flussi di anteprima e acquisizione sono indipendenti. È necessario modificare l'interfaccia utente per abilitare o disabilitare l'impostazione dei flussi in modo indipendente in base al risultato di questo test.

private void CheckIfStreamsAreIdentical()
{
    if (_mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.AllStreamsIdentical ||
        _mediaCapture.MediaCaptureSettings.VideoDeviceCharacteristic == VideoDeviceCharacteristic.PreviewRecordStreamsIdentical)
    {
        ShowMessageToUser("Preview and video streams for this device are identical. Changing one will affect the other");
    }
}

Ottenere un elenco delle proprietà del flusso disponibili

Ottenere un elenco delle proprietà di flusso disponibili per un dispositivo di acquisizione recuperando VideoDeviceController per l'oggetto MediaCapture dell'app e quindi chiamare GetAvailableMediaStreamProperties e passare in uno dei valori MediaStreamType, VideoPreview, VideoRecord o Photo. In questo esempio viene usata la sintassi Linq per creare un elenco di oggetti StreamPropertiesHelper , definiti in precedenza in questo articolo, per ognuno dei valori IMediaEncodingProperties restituiti da GetAvailableMediaStreamProperties. In questo esempio vengono prima utilizzati i metodi di estensione Linq per ordinare le proprietà restituite in base alla risoluzione e quindi alla frequenza dei fotogrammi.

Se l'app ha requisiti specifici di risoluzione o frequenza dei fotogrammi, è possibile selezionare un set di proprietà di codifica multimediale a livello di codice. Un'app della fotocamera tipica espone invece l'elenco delle proprietà disponibili nell'interfaccia utente e consente all'utente di selezionare le impostazioni desiderate. Viene creato un controllo ComboBoxItem per ogni elemento nell'elenco di oggetti StreamPropertiesHelper nell'elenco. Il contenuto viene impostato sul nome descrittivo restituito dalla classe helper e il tag viene impostato sulla classe helper stessa in modo che possa essere usata in un secondo momento per recuperare le proprietà di codifica associate. Ogni ComboBoxItem viene quindi aggiunto al controllo ComboBox passato al metodo .

private void PopulateStreamPropertiesUI(MediaStreamType streamType, ComboBox comboBox, bool showFrameRate = true)
{
    // Query all properties of the specified stream type 
    IEnumerable<StreamPropertiesHelper> allStreamProperties = 
        _mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(streamType).Select(x => new StreamPropertiesHelper(x));

    // Order them by resolution then frame rate
    allStreamProperties = allStreamProperties.OrderByDescending(x => x.Height * x.Width).ThenByDescending(x => x.FrameRate);

    // Populate the combo box with the entries
    foreach (var property in allStreamProperties)
    {
        ComboBoxItem comboBoxItem = new ComboBoxItem();
        comboBoxItem.Content = property.GetFriendlyName(showFrameRate);
        comboBoxItem.Tag = property;
        comboBox.Items.Add(comboBoxItem);
    }
}

Impostare le proprietà del flusso desiderate

Indicare al controller del dispositivo video di usare le proprietà di codifica desiderate chiamando SetMediaStreamPropertiesAsync, passando nel valore MediaStreamType che indica se le proprietà di foto, video o anteprima devono essere impostate. In questo esempio vengono impostate le proprietà di codifica richieste quando l'utente seleziona un elemento in uno degli oggetti ComboBox compilati con il metodo helper PopulateStreamPropertiesUI.

private async void PreviewSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, encodingProperties);
    }
}
private async void PhotoSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, encodingProperties);
    }
}
private async void VideoSettings_Changed(object sender, RoutedEventArgs e)
{
    if (_isPreviewing)
    {
        var selectedItem = (sender as ComboBox).SelectedItem as ComboBoxItem;
        var encodingProperties = (selectedItem.Tag as StreamPropertiesHelper).EncodingProperties;
        await _mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoRecord, encodingProperties);
    }
}

Trovare la corrispondenza con le proporzioni dei flussi di anteprima e acquisizione

Un'app per fotocamera tipica fornirà all'utente l'interfaccia utente per selezionare la risoluzione di acquisizione video o foto, ma imposta a livello di codice la risoluzione dell'anteprima. Esistono alcune strategie diverse per selezionare la migliore risoluzione del flusso di anteprima per l'app:

  • Selezionare la risoluzione di anteprima più elevata disponibile, consentendo al framework dell'interfaccia utente di eseguire qualsiasi ridimensionamento necessario dell'anteprima.

  • Selezionare la risoluzione di anteprima più vicina alla risoluzione di acquisizione in modo che l'anteprima visualizzi la rappresentazione più vicina al supporto acquisito finale.

  • Selezionare la risoluzione di anteprima più vicina alle dimensioni di CaptureElement in modo che non vengano trasmessi più pixel del necessario attraverso la pipeline del flusso di anteprima.

Importante È possibile, in alcuni dispositivi, impostare proporzioni diverse per il flusso di anteprima della fotocamera e il flusso di acquisizione. Il ritaglio dei fotogrammi causato da questa mancata corrispondenza può comportare la presenza di contenuto nel supporto acquisito non visibile nell'anteprima, che può causare un'esperienza utente negativa. Si consiglia vivamente di usare le stesse proporzioni, all'interno di una piccola finestra di tolleranza, per i flussi di anteprima e acquisizione. È bene avere risoluzioni completamente diverse abilitate per l'acquisizione e l'anteprima, purché le proporzioni corrispondano strettamente.

Per assicurarsi che i flussi di acquisizione di foto o video corrispondano alle proporzioni del flusso di anteprima, questo esempio chiama VideoDeviceController.GetMediaStreamProperties e passa il valore nell'enumerazione VideoPreview per richiedere le proprietà correnti del flusso per il flusso di anteprima. Successivamente viene definita una piccola finestra di tolleranza delle proporzioni in modo da poter includere proporzioni che non corrispondono esattamente al flusso di anteprima, purché siano vicine. Viene quindi usato un metodo di estensione Linq per selezionare solo gli oggetti StreamPropertiesHelper in cui le proporzioni si trovano all'interno dell'intervallo di tolleranza definito del flusso di anteprima.

private void MatchPreviewAspectRatio(MediaStreamType streamType, ComboBox comboBox)
{
    // Query all properties of the specified stream type
    IEnumerable<StreamPropertiesHelper> allVideoProperties = 
        _mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(streamType).Select(x => new StreamPropertiesHelper(x));

    // Query the current preview settings
    StreamPropertiesHelper previewProperties = new StreamPropertiesHelper(_mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview));

    // Get all formats that have the same-ish aspect ratio as the preview
    // Allow for some tolerance in the aspect ratio comparison
    const double ASPECT_RATIO_TOLERANCE = 0.015;
    var matchingFormats = allVideoProperties.Where(x => Math.Abs(x.AspectRatio - previewProperties.AspectRatio) < ASPECT_RATIO_TOLERANCE);

    // Order them by resolution then frame rate
    allVideoProperties = matchingFormats.OrderByDescending(x => x.Height * x.Width).ThenByDescending(x => x.FrameRate);

    // Clear out old entries and populate the video combo box with new matching entries
    comboBox.Items.Clear();
    foreach (var property in allVideoProperties)
    {
        ComboBoxItem comboBoxItem = new ComboBoxItem();
        comboBoxItem.Content = property.GetFriendlyName();
        comboBoxItem.Tag = property;
        comboBox.Items.Add(comboBoxItem);
    }
    comboBox.SelectedIndex = -1;
}