Efek audio kustom

Artikel ini menjelaskan cara membuat komponen Windows Runtime yang mengimplementasikan antarmuka IBasicAudioEffect untuk membuat efek kustom untuk aliran audio. Efek kustom dapat digunakan dengan beberapa API Runtime Windows yang berbeda termasuk MediaCapture, yang menyediakan akses ke kamera perangkat, MediaComposition, yang memungkinkan Anda membuat komposisi kompleks dari klip media, dan AudioGraph yang memungkinkan Anda untuk dengan cepat merakit grafik berbagai input audio, output, dan simpul submix.

Menambahkan efek kustom ke aplikasi Anda

Efek audio kustom didefinisikan dalam kelas yang mengimplementasikan antarmuka IBasicAudioEffect . Kelas ini tidak dapat disertakan langsung dalam proyek aplikasi Anda. Sebagai gantinya, Anda harus menggunakan komponen Windows Runtime untuk menghosting kelas efek audio Anda.

Menambahkan komponen Windows Runtime untuk efek audio Anda

  1. Di Microsoft Visual Studio, dengan solusi Anda terbuka, buka menu File dan pilih Tambahkan> Proyek Baru.
  2. Pilih jenis proyek Windows Runtime Component (Universal Windows).
  3. Untuk contoh ini, beri nama proyek AudioEffectComponent. Nama ini akan dirujuk dalam kode nanti.
  4. Klik OK.
  5. Templat proyek membuat kelas yang disebut Class1.cs. Di Penjelajah Solusi, klik kanan ikon untuk Class1.cs dan pilih Ganti Nama.
  6. Ganti nama file menjadi ExampleAudioEffect.cs. Visual Studio akan menampilkan permintaan yang menanyakan apakah Anda ingin memperbarui semua referensi ke nama baru. Klik Ya.
  7. Buka ExampleAudioEffect.cs dan perbarui definisi kelas untuk mengimplementasikan antarmuka IBasicAudioEffect.
public sealed class ExampleAudioEffect : IBasicAudioEffect

Anda perlu menyertakan namespace berikut dalam file kelas efek Anda untuk mengakses semua jenis yang digunakan dalam contoh dalam artikel ini.

using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using System.Runtime.InteropServices;
using Windows.Media;
using Windows.Foundation;

Menerapkan antarmuka IBasicAudioEffect

Efek audio Anda harus mengimplementasikan semua metode dan properti antarmuka IBasicAudioEffect. Bagian ini memancarkan Anda melalui implementasi sederhana antarmuka ini untuk membuat efek gema dasar.

Properti SupportedEncodingProperties

Sistem memeriksa properti SupportedEncodingProperties untuk menentukan properti pengodean mana yang didukung oleh efek Anda. Perhatikan bahwa jika konsumen efek Anda tidak dapat mengodekan audio menggunakan properti yang Anda tentukan, sistem akan memanggil Tutup pada efek Anda dan akan menghapus efek Anda dari alur audio. Dalam contoh ini, objek AudioEncodingProperties dibuat dan ditambahkan ke daftar yang dikembalikan untuk mendukung 44,1 kHz dan 48 kHz, float 32-bit, pengodean mono.

public IReadOnlyList<AudioEncodingProperties> SupportedEncodingProperties
{
    get
    {
        var supportedEncodingProperties = new List<AudioEncodingProperties>();
        AudioEncodingProperties encodingProps1 = AudioEncodingProperties.CreatePcm(44100, 1, 32);
        encodingProps1.Subtype = MediaEncodingSubtypes.Float;
        AudioEncodingProperties encodingProps2 = AudioEncodingProperties.CreatePcm(48000, 1, 32);
        encodingProps2.Subtype = MediaEncodingSubtypes.Float;

        supportedEncodingProperties.Add(encodingProps1);
        supportedEncodingProperties.Add(encodingProps2);

        return supportedEncodingProperties;
        
    }
}

Metode SetEncodingProperties

Sistem memanggil SetEncodingProperties pada efek Anda untuk memberi tahu Anda properti pengodean untuk aliran audio tempat efek beroperasi. Untuk menerapkan efek gema, contoh ini menggunakan buffer untuk menyimpan satu detik data audio. Metode ini memberikan kesempatan untuk menginisialisasi ukuran buffer ke jumlah sampel dalam satu detik audio, berdasarkan laju sampel di mana audio dikodekan. Efek penundaan juga menggunakan penghitung bilangan bulat untuk melacak posisi saat ini dalam buffer penundaan. Karena SetEncodingProperties dipanggil setiap kali efek ditambahkan ke alur audio, ini adalah waktu yang tepat untuk menginisialisasi nilai tersebut ke 0. Anda mungkin juga ingin mengambil objek AudioEncodingProperties yang diteruskan ke metode ini untuk digunakan di tempat lain dalam efek Anda.

private float[] echoBuffer;
private int currentActiveSampleIndex;
private AudioEncodingProperties currentEncodingProperties;
public void SetEncodingProperties(AudioEncodingProperties encodingProperties)
{
    currentEncodingProperties = encodingProperties;
    echoBuffer = new float[encodingProperties.SampleRate]; // exactly one second delay
    currentActiveSampleIndex = 0;
}

Metode SetProperties

Metode SetProperties memungkinkan aplikasi yang menggunakan efek Anda untuk menyesuaikan parameter efek. Properti diteruskan sebagai peta IPropertySet dari nama dan nilai properti.

IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}

Contoh sederhana ini akan mencampur sampel audio saat ini dengan nilai dari buffer penundaan sesuai dengan nilai properti Mix . Properti dideklarasikan dan TryGetValue digunakan untuk mendapatkan nilai yang ditetapkan oleh aplikasi panggilan. Jika tidak ada nilai yang ditetapkan, nilai default .5 akan digunakan. Perhatikan bahwa properti ini bersifat baca-saja. Nilai properti harus diatur menggunakan SetProperties.

public float Mix
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("Mix", out val))
        {
            return (float)val;
        }
        return .5f;
    }
}

Metode ProcessFrame

Metode ProcessFrame adalah tempat efek Anda memodifikasi data audio aliran. Metode ini dipanggil sekali per bingkai dan diteruskan objek ProcessAudioFrameContext. Objek ini berisi objek AudioFrame input yang berisi bingkai masuk yang akan diproses dan objek AudioFrame output tempat Anda menulis data audio yang akan diteruskan ke sisa alur audio. Bingkai audio adalah buffer sampel audio yang mewakili ikatan singkat data audio.

Mengakses buffer data AudioFrame memerlukan interop COM, jadi Anda harus menyertakan namespace Layanan System.Runtime.InteropServices dalam file kelas efek Anda lalu menambahkan kode berikut di dalam namespace layanan agar efek Anda mengimpor antarmuka untuk mengakses buffer audio.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

Catatan

Karena teknik ini mengakses buffer gambar asli yang tidak dikelola, Anda harus mengonfigurasi proyek Anda untuk mengizinkan kode yang tidak aman.

  1. Di Penjelajah Solusi, klik kanan proyek AudioEffectComponent dan pilih Properti.
  2. Pilih tab Build .
  3. Pilih kotak centang Perbolehkan kode tidak aman.

 

Sekarang Anda dapat menambahkan implementasi metode ProcessFrame ke efek Anda. Pertama, metode ini mendapatkan objek AudioBuffer dari bingkai audio input dan output. Perhatikan bahwa bingkai output dibuka untuk menulis dan input untuk dibaca. Selanjutnya, IMemoryBufferReference diperoleh untuk setiap buffer dengan memanggil CreateReference. Kemudian, buffer data aktual diperoleh dengan mentransmisikan objek IMemoryBufferReference sebagai antarmuka interop COM yang ditentukan di atas, IMemoryByteAccess, lalu memanggil GetBuffer.

Sekarang setelah buffer data diperoleh, Anda dapat membaca dari buffer input dan menulis ke buffer output. Untuk setiap sampel dalam inputbuffer, nilai diperoleh dan dikalikan dengan 1 - Mix untuk mengatur nilai sinyal kering efek. Selanjutnya, sampel diambil dari posisi saat ini di buffer echo dan dikalikan dengan Mix untuk mengatur nilai efek basah. Sampel output diatur ke jumlah nilai kering dan basah. Terakhir, setiap sampel input disimpan di buffer echo dan indeks sampel saat ini bertahap.

unsafe public void ProcessFrame(ProcessAudioFrameContext context)
{
    AudioFrame inputFrame = context.InputFrame;
    AudioFrame outputFrame = context.OutputFrame;

    using (AudioBuffer inputBuffer = inputFrame.LockBuffer(AudioBufferAccessMode.Read),
                        outputBuffer = outputFrame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference inputReference = inputBuffer.CreateReference(),
                                    outputReference = outputBuffer.CreateReference())
    {
        byte* inputDataInBytes;
        byte* outputDataInBytes;
        uint inputCapacity;
        uint outputCapacity;

        ((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputDataInBytes, out inputCapacity);
        ((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputDataInBytes, out outputCapacity);

        float* inputDataInFloat = (float*)inputDataInBytes;
        float* outputDataInFloat = (float*)outputDataInBytes;

        float inputData;
        float echoData;

        // Process audio data
        int dataInFloatLength = (int)inputBuffer.Length / sizeof(float);

        for (int i = 0; i < dataInFloatLength; i++)
        {
            inputData = inputDataInFloat[i] * (1.0f - this.Mix);
            echoData = echoBuffer[currentActiveSampleIndex] * this.Mix;
            outputDataInFloat[i] = inputData + echoData;
            echoBuffer[currentActiveSampleIndex] = inputDataInFloat[i];
            currentActiveSampleIndex++;

            if (currentActiveSampleIndex == echoBuffer.Length)
            {
                // Wrap around (after one second of samples)
                currentActiveSampleIndex = 0;
            }
        }
    }
}

Metode tutup

Sistem akan memanggil metode TutupTutup pada kelas Anda ketika efek harus dimatikan. Anda harus menggunakan metode ini untuk membuang sumber daya apa pun yang telah Anda buat. Argumen untuk metode ini adalah MediaEffectClosedReason yang memungkinkan Anda mengetahui apakah efek ditutup secara normal, jika terjadi kesalahan, atau jika efek tidak mendukung format pengodean yang diperlukan.

public void Close(MediaEffectClosedReason reason)
{
    // Dispose of effect resources
    echoBuffer = null;
}

Metode DiscardQueuedFrames

Metode DiscardQueuedFrames dipanggil ketika efek Anda harus direset. Skenario umum untuk ini adalah jika efek Anda menyimpan bingkai yang diproses sebelumnya untuk digunakan dalam memproses bingkai saat ini. Ketika metode ini dipanggil, Anda harus membuang set bingkai sebelumnya yang Anda simpan. Metode ini dapat digunakan untuk mengatur ulang status apa pun yang terkait dengan bingkai sebelumnya, tidak hanya akumulasi bingkai audio.

public void DiscardQueuedFrames()
{
    // Reset contents of the samples buffer
    Array.Clear(echoBuffer, 0, echoBuffer.Length - 1);
    currentActiveSampleIndex = 0;
}

Properti TimeIndependent

Properti TimeIndependent TimeIndependent memungkinkan sistem mengetahui apakah efek Anda tidak memerlukan waktu yang seragam. Ketika diatur ke true, sistem dapat menggunakan pengoptimalan yang meningkatkan performa efek.

public bool TimeIndependent { get { return true; } }

Properti UseInputFrameForOutput

Atur properti UseInputFrameForOutput ke true untuk memberi tahu sistem bahwa efek Anda akan menulis outputnya ke buffer audio InputFrame dari ProcessAudioFrameContext yang diteruskan ke ProcessFrame alih-alih menulis ke OutputFrame.

public bool UseInputFrameForOutput { get { return false; } }

Menambahkan efek kustom ke aplikasi Anda

Untuk menggunakan efek audio dari aplikasi, Anda harus menambahkan referensi ke proyek efek ke aplikasi Anda.

  1. Di Penjelajah Solusi, di bawah proyek aplikasi Anda, klik kanan Referensi dan pilih Tambahkan referensi.
  2. Perluas tab Proyek , pilih Solusi, lalu pilih kotak centang untuk nama proyek efek Anda. Untuk contoh ini, namanya adalah AudioEffectComponent.
  3. Klik OK

Jika kelas efek audio Anda dinyatakan adalah namespace layanan yang berbeda, pastikan untuk menyertakan namespace tersebut dalam file kode Anda.

using AudioEffectComponent;

Menambahkan efek kustom Anda ke simpul AudioGraph

Untuk informasi umum tentang menggunakan grafik audio, lihat Grafik audio. Cuplikan kode berikut menunjukkan kepada Anda cara menambahkan contoh efek gema yang diperlihatkan dalam artikel ini ke simpul grafik audio. Pertama, PropertySet dibuat dan nilai untuk properti Mix, yang ditentukan oleh efek, diatur. Selanjutnya, konstruktor AudioEffectDefinition dipanggil, meneruskan nama kelas lengkap dari jenis efek kustom dan kumpulan properti. Terakhir, definisi efek ditambahkan ke properti EffectDefinitions dari FileInputNode yang ada, menyebabkan audio yang dipancarkan diproses oleh efek kustom.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);
fileInputNode.EffectDefinitions.Add(echoEffectDefinition);

Setelah ditambahkan ke simpul, efek kustom dapat dinonaktifkan dengan memanggil DisableEffectsByDefinition dan meneruskan objek AudioEffectDefinition. Untuk informasi selengkapnya tentang menggunakan grafik audio di aplikasi Anda, lihat AudioGraph.

Menambahkan efek kustom Anda ke klip di MediaComposition

Cuplikan kode berikut menunjukkan penambahan efek audio kustom ke klip video dan trek audio latar belakang dalam komposisi media. Untuk panduan umum untuk membuat komposisi media dari klip video dan menambahkan trek audio latar belakang, lihat Komposisi dan pengeditan media.

// Create a property set and add a property/value pair
PropertySet echoProperties = new PropertySet();
echoProperties.Add("Mix", 0.5f);

// Instantiate the custom effect defined in the 'AudioEffectComponent' project
AudioEffectDefinition echoEffectDefinition = new AudioEffectDefinition(typeof(ExampleAudioEffect).FullName, echoProperties);

// Add custom audio effect to the current clip in the timeline
var currentClip = composition.Clips.FirstOrDefault(
    mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
    mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);
currentClip.AudioEffectDefinitions.Add(echoEffectDefinition);

// Add custom audio effect to the first background audio track
if (composition.BackgroundAudioTracks.Count > 0)
{
    composition.BackgroundAudioTracks[0].AudioEffectDefinitions.Add(echoEffectDefinition);
}