Hızlı Başlangıç: Uygulamanıza ham medya erişimi ekleme

Bu hızlı başlangıçta, Unity için Azure İletişim Hizmetleri Çağırma SDK'sını kullanarak ham medya erişimini uygulamayı öğreneceksiniz. Azure İletişim Hizmetleri Arama SDK'sı, uygulamaların bir aramada uzak katılımcılardan ham video kareleri göndermek veya işlemek için kendi video çerçevelerini oluşturmasına olanak sağlayan API'ler sunar. Bu hızlı başlangıçta Hızlı Başlangıç: Unity için uygulamanıza 1:1 görüntülü arama ekleme adımları temel alır.

RawVideo erişimi

Uygulama video çerçevelerini oluşturduğundan, uygulamanın Azure İletişim Hizmetleri Çağırma SDK'sını uygulamanın oluşturabileceği video biçimleri hakkında bilgilendirmesi gerekir. Bu bilgiler, Azure İletişim Hizmetleri Arama SDK'sının o sırada ağ koşulları için en iyi video biçimi yapılandırmasını seçmesini sağlar.

Sanal Video

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
16x9 1080p 30
16x9 720p 30
16x9 540p 30
16x9 480p 30
16x9 360p 30
16x9 270p 15
16x9 240p 15
16x9 180p 15
4x3 VGA (640x480) 30
4x3 424x320 15
4x3 QVGA (320x240) 15
4x3 212x160 15
  1. Buradaki adımları izleyin Hızlı Başlangıç: Unity oyunu oluşturmak için uygulamanıza 1:1 görüntülü arama ekleyin. Amaç, çağrıyı başlatmaya hazır bir CallAgent nesne elde etmektir. GitHub'da bu hızlı başlangıcın son halini alan kodu bulun.

  2. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun. Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    var videoStreamFormat = new VideoStreamFormat
    {
        Resolution = VideoStreamResolution.P360, // For VirtualOutgoingVideoStream the width/height should be set using VideoStreamResolution enum
        PixelFormat = VideoStreamPixelFormat.Rgba,
        FramesPerSecond = 15,
        Stride1 = 640 * 4 // It is times 4 because RGBA is a 32-bit format
    };
    VideoStreamFormat[] videoStreamFormats = { videoStreamFormat };
    
  3. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın Formats .

    var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions
    {
        Formats = videoStreamFormats
    };
    
  4. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    var rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
    
  5. Temsilciye RawOutgoingVideoStream.FormatChanged abone olun. Bu olay, listede sağlanan video biçimlerinden birinden değiştirildiğinde bunu bildirir VideoStreamFormat .

    rawOutgoingVideoStream.FormatChanged += (object sender, VideoStreamFormatChangedEventArgs args)
    {
        VideoStreamFormat videoStreamFormat = args.Format;
    }
    
  6. Temsilciye RawOutgoingVideoStream.StateChanged abone olun. Bu olay, her değiştiğinde State bunu bildirir.

    rawOutgoingVideoStream.StateChanged += (object sender, VideoStreamFormatChangedEventArgs args)
    {
        CallVideoStream callVideoStream = e.Stream;
    
        switch (callVideoStream.Direction)
        {
            case StreamDirection.Outgoing:
                OnRawOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
                break;
            case StreamDirection.Incoming:
                OnRawIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
                break;
        }
    }
    
  7. Başlat ve Durdur gibi ham giden video akışı durumu işlemlerini işleyip özel video kareleri oluşturmaya başlayın veya çerçeve oluşturma algoritmasını askıya alın.

    private async void OnRawOutgoingVideoStreamStateChanged(OutgoingVideoStream outgoingVideoStream)
    {
        switch (outgoingVideoStream.State)
        {
            case VideoStreamState.Started:
                switch (outgoingVideoStream.Kind)
                {
                    case VideoStreamKind.VirtualOutgoing:
                        outgoingVideoPlayer.StartGenerateFrames(outgoingVideoStream); // This is where a background worker thread can be started to feed the outgoing video frames.
                        break;
                }
                break;
    
            case VideoStreamState.Stopped:
                switch (outgoingVideoStream.Kind)
                {
                    case VideoStreamKind.VirtualOutgoing:
                        break;
                }
                break;
        }
    }
    

    Giden video çerçevesi oluşturucu örneği aşağıda verilmiştir:

    private unsafe RawVideoFrame GenerateRawVideoFrame(RawOutgoingVideoStream rawOutgoingVideoStream)
    {
        var format = rawOutgoingVideoStream.Format;
        int w = format.Width;
        int h = format.Height;
        int rgbaCapacity = w * h * 4;
    
        var rgbaBuffer = new NativeBuffer(rgbaCapacity);
        rgbaBuffer.GetData(out IntPtr rgbaArrayBuffer, out rgbaCapacity);
    
        byte r = (byte)random.Next(1, 255);
        byte g = (byte)random.Next(1, 255);
        byte b = (byte)random.Next(1, 255);
    
        for (int y = 0; y < h; y++)
        {
            for (int x = 0; x < w*4; x += 4)
            {
                ((byte*)rgbaArrayBuffer)[(w * 4 * y) + x + 0] = (byte)(y % r);
                ((byte*)rgbaArrayBuffer)[(w * 4 * y) + x + 1] = (byte)(y % g);
                ((byte*)rgbaArrayBuffer)[(w * 4 * y) + x + 2] = (byte)(y % b);
                ((byte*)rgbaArrayBuffer)[(w * 4 * y) + x + 3] = 255;
            }
        }
    
        // Call ACS Unity SDK API to deliver the frame
        rawOutgoingVideoStream.SendRawVideoFrameAsync(new RawVideoFrameBuffer() {
            Buffers = new NativeBuffer[] { rgbaBuffer },
            StreamFormat = rawOutgoingVideoStream.Format,
            TimestampInTicks = rawOutgoingVideoStream.TimestampInTicks
        }).Wait();
    
        return new RawVideoFrameBuffer()
        {
            Buffers = new NativeBuffer[] { rgbaBuffer },
            StreamFormat = rawOutgoingVideoStream.Format
        };
    }
    

    Not

    unsafe yerel bellek kaynaklarına erişim gerektirdiğinden NativeBuffer değiştirici bu yöntemde kullanılır. Bu nedenle, Allow unsafe seçeneğin Unity Düzenleyicisi'nde de etkinleştirilmesi gerekir.

  8. Benzer şekilde, video akışı StateChanged olayına yanıt olarak gelen video karelerini işleyebiliriz.

    private void OnRawIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
    {
        switch (incomingVideoStream.State)
        {
            case VideoStreamState.Available:
                {
                    var rawIncomingVideoStream = incomingVideoStream as RawIncomingVideoStream;
                    rawIncomingVideoStream.RawVideoFrameReceived += OnRawVideoFrameReceived;
                    rawIncomingVideoStream.Start();
                    break;
                }
            case VideoStreamState.Stopped:
                break;
            case VideoStreamState.NotAvailable:
                break;
        }
    }
    
    private void OnRawVideoFrameReceived(object sender, RawVideoFrameReceivedEventArgs e)
    {
        incomingVideoPlayer.RenderRawVideoFrame(e.Frame);
    }
    
    public void RenderRawVideoFrame(RawVideoFrame rawVideoFrame)
    {
        var videoFrameBuffer = rawVideoFrame as RawVideoFrameBuffer;
        pendingIncomingFrames.Enqueue(new PendingFrame() {
                frame = rawVideoFrame,
                kind = RawVideoFrameKind.Buffer });
    }
    
  9. Geri arama yönteminin aşırı yüklenmesini MonoBehaviour.Update() önlemek için hem gelen hem de giden video karelerinin bir arabelleğe alma mekanizması aracılığıyla yönetilmesi önerilir. Bu işlem hafif tutulması ve CPU veya ağ ağır görevlerinden kaçınılması ve daha sorunsuz bir video deneyimi sağlanması gerekir. Bu isteğe bağlı iyileştirme, geliştiricilerin kendi senaryolarında en iyi sonucu verecek şekilde karar vermelerine bırakılır.

    Bir iç kuyruktan çağrılarak Graphics.Blit gelen çerçevelerin Unity'ye VideoTexture nasıl işlendiğini gösteren örnek aşağıda verilmiştir:

    private void Update()
    {
        if (pendingIncomingFrames.TryDequeue(out PendingFrame pendingFrame))
        {
            switch (pendingFrame.kind)
            {
                case RawVideoFrameKind.Buffer:
                    var videoFrameBuffer = pendingFrame.frame as RawVideoFrameBuffer;
                    VideoStreamFormat videoFormat = videoFrameBuffer.StreamFormat;
                    int width = videoFormat.Width;
                    int height = videoFormat.Height;
                    var texture = new Texture2D(width, height, TextureFormat.RGBA32, mipChain: false);
    
                    var buffers = videoFrameBuffer.Buffers;
                    NativeBuffer buffer = buffers.Count > 0 ? buffers[0] : null;
                    buffer.GetData(out IntPtr bytes, out int signedSize);
    
                    texture.LoadRawTextureData(bytes, signedSize);
                    texture.Apply();
    
                    Graphics.Blit(source: texture, dest: rawIncomingVideoRenderTexture);
                    break;
    
                case RawVideoFrameKind.Texture:
                    break;
            }
            pendingFrame.frame.Dispose();
        }
    }
    

Bu hızlı başlangıçta, Windows için Azure İletişim Hizmetleri Çağırma SDK'sını kullanarak ham medya erişimini uygulamayı öğreneceksiniz. Azure İletişim Hizmetleri Arama SDK'sı, uygulamaların bir aramada uzak katılımcılara göndermek üzere kendi video çerçevelerini oluşturmasına olanak sağlayan API'ler sunar. Bu hızlı başlangıçta Hızlı Başlangıç: Windows için uygulamanıza 1:1 görüntülü arama ekleme adımları temel alır.

RawAudio erişimi

Ham ses medyasına erişmek, gelen aramanın ses akışına erişmenin yanı sıra arama sırasında özel giden ses akışlarını görüntülemenizi ve göndermenizi sağlar.

Ham Giden ses gönderme

Göndermek istediğimiz ham akış özelliklerini belirten bir options nesnesi yapın.

    RawOutgoingAudioStreamProperties outgoingAudioProperties = new RawOutgoingAudioStreamProperties()
    {
        Format = ACSAudioStreamFormat.Pcm16Bit,
        SampleRate = AudioStreamSampleRate.Hz48000,
        ChannelMode = AudioStreamChannelMode.Stereo,
        BufferDuration = AudioStreamBufferDuration.InMs20
    };
    RawOutgoingAudioStreamOptions outgoingAudioStreamOptions = new RawOutgoingAudioStreamOptions()
    {
        Properties = outgoingAudioProperties
    };

Bir RawOutgoingAudioStream oluşturun ve arama seçeneklerine katılmak için ekleyin; arama bağlandığında akış otomatik olarak başlatılır.

    JoinCallOptions options =  JoinCallOptions(); // or StartCallOptions()
    OutgoingAudioOptions outgoingAudioOptions = new OutgoingAudioOptions();
    RawOutgoingAudioStream rawOutgoingAudioStream = new RawOutgoingAudioStream(outgoingAudioStreamOptions);
    outgoingAudioOptions.Stream = rawOutgoingAudioStream;
    options.OutgoingAudioOptions = outgoingAudioOptions;
    // Start or Join call with those call options.

Aramaya akış ekleme

Bunun yerine akışı mevcut Call bir örneğe de ekleyebilirsiniz:

    await call.StartAudio(rawOutgoingAudioStream);

Ham örnekleri göndermeye başlama

Veri göndermeye yalnızca akış durumu olduğunda AudioStreamState.Startedbaşlayabiliriz. Ses akışı durumu değişikliğini gözlemlemek için OnStateChangedListener olaya bir dinleyici ekleyin.

    unsafe private void AudioStateChanged(object sender, AudioStreamStateChanged args)
    {
        if (args.AudioStreamState == AudioStreamState.Started)
        {
            // We can now start sending samples.
        }
    }
    outgoingAudioStream.StateChanged += AudioStateChanged;

Akış başlatıldığında, aramaya ses örnekleri göndermeye MemoryBuffer başlayabiliriz. Ses arabelleği biçimi belirtilen akış özellikleriyle eşleşmelidir.

    void Start()
    {
        RawOutgoingAudioStreamProperties properties = outgoingAudioStream.Properties;
        RawAudioBuffer buffer;
        new Thread(() =>
        {
            DateTime nextDeliverTime = DateTime.Now;
            while (true)
            {
                MemoryBuffer memoryBuffer = new MemoryBuffer((uint)outgoingAudioStream.ExpectedBufferSizeInBytes);
                using (IMemoryBufferReference reference = memoryBuffer.CreateReference())
                {
                    byte* dataInBytes;
                    uint capacityInBytes;
                    ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);
                    // Use AudioGraph here to grab data from microphone if you want microphone data
                }
                nextDeliverTime = nextDeliverTime.AddMilliseconds(20);
                buffer = new RawAudioBuffer(memoryBuffer);
                outgoingAudioStream.SendOutgoingAudioBuffer(buffer);
                TimeSpan wait = nextDeliverTime - DateTime.Now;
                if (wait > TimeSpan.Zero)
                {
                    Thread.Sleep(wait);
                }
            }
        }).Start();
    }

Ham Gelen ses alma

Arama ses akışı örneklerini MemoryBuffer kayıttan yürütmeden önce arama ses akışını işlemek istiyor gibi de alacağız. Almak istediğimiz ham akış özelliklerini belirten bir RawIncomingAudioStreamOptions nesne oluşturun.

    RawIncomingAudioStreamProperties properties = new RawIncomingAudioStreamProperties()
    {
        Format = AudioStreamFormat.Pcm16Bit,
        SampleRate = AudioStreamSampleRate.Hz44100,
        ChannelMode = AudioStreamChannelMode.Stereo
    };
    RawIncomingAudioStreamOptions options = new RawIncomingAudioStreamOptions()
    {
        Properties = properties
    };

Arama seçeneklerine katılmak için bir RawIncomingAudioStream oluşturun ve ekleyin

    JoinCallOptions options =  JoinCallOptions(); // or StartCallOptions()
    RawIncomingAudioStream rawIncomingAudioStream = new RawIncomingAudioStream(audioStreamOptions);
    IncomingAudioOptions incomingAudioOptions = new IncomingAudioOptions()
    {
        Stream = rawIncomingAudioStream
    };
    options.IncomingAudioOptions = incomingAudioOptions;

Bunun yerine akışı mevcut Call bir örneğe de ekleyebiliriz:

    await call.startAudio(context, rawIncomingAudioStream);

Gelen akıştan ham ses arabelleklerini almaya başlamak için, gelen akış durumuna ve alınan arabelleğe alınan olaylara dinleyiciler ekleyin.

    unsafe private void OnAudioStateChanged(object sender, AudioStreamStateChanged args)
    {
        if (args.AudioStreamState == AudioStreamState.Started)
        {
            // When value is `AudioStreamState.STARTED` we'll be able to receive samples.
        }
    }
    private void OnRawIncomingMixedAudioBufferAvailable(object sender, IncomingMixedAudioEventArgs args)
    {
        // Received a raw audio buffers(MemoryBuffer).
        using (IMemoryBufferReference reference = args.IncomingAudioBuffer.Buffer.CreateReference())
        {
            byte* dataInBytes;
            uint capacityInBytes;
            ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);
            // Process the data using AudioGraph class
        }
    }
    rawIncomingAudioStream.StateChanged += OnAudioStateChanged;
    rawIncomingAudioStream.MixedAudioBufferReceived += OnRawIncomingMixedAudioBufferAvailable;

RawVideo erişimi

Uygulama video çerçevelerini oluşturduğundan, uygulamanın Azure İletişim Hizmetleri Çağırma SDK'sını uygulamanın oluşturabileceği video biçimleri hakkında bilgilendirmesi gerekir. Bu bilgiler, Azure İletişim Hizmetleri Arama SDK'sının o sırada ağ koşulları için en iyi video biçimi yapılandırmasını seçmesini sağlar.

Sanal Video

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
16x9 1080p 30
16x9 720p 30
16x9 540p 30
16x9 480p 30
16x9 360p 30
16x9 270p 15
16x9 240p 15
16x9 180p 15
4x3 VGA (640x480) 30
4x3 424x320 15
4x3 QVGA (320x240) 15
4x3 212x160 15
  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun. Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    var videoStreamFormat = new VideoStreamFormat
    {
        Resolution = VideoStreamResolution.P720, // For VirtualOutgoingVideoStream the width/height should be set using VideoStreamResolution enum
        PixelFormat = VideoStreamPixelFormat.Rgba,
        FramesPerSecond = 30,
        Stride1 = 1280 * 4 // It is times 4 because RGBA is a 32-bit format
    };
    VideoStreamFormat[] videoStreamFormats = { videoStreamFormat };
    
  2. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın Formats .

    var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions
    {
        Formats = videoStreamFormats
    };
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    var rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
    
  4. Temsilciye RawOutgoingVideoStream.FormatChanged abone olun. Bu olay, listede sağlanan video biçimlerinden birinden değiştirildiğinde bunu bildirir VideoStreamFormat .

    rawOutgoingVideoStream.FormatChanged += (object sender, VideoStreamFormatChangedEventArgs args)
    {
        VideoStreamFormat videoStreamFormat = args.Format;
    }
    
  5. Arabellek verilerine erişmek için aşağıdaki yardımcı sınıfın bir örneğini oluşturun

    [ComImport]
    [Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    unsafe interface IMemoryBufferByteAccess
    {
        void GetBuffer(out byte* buffer, out uint capacity);
    }
    [ComImport]
    [Guid("905A0FEF-BC53-11DF-8C49-001E4FC686DA")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    unsafe interface IBufferByteAccess
    {
        void Buffer(out byte* buffer);
    }
    internal static class BufferExtensions
    {
        // For accessing MemoryBuffer
        public static unsafe byte* GetArrayBuffer(IMemoryBuffer memoryBuffer)
        {
            IMemoryBufferReference memoryBufferReference = memoryBuffer.CreateReference();
            var memoryBufferByteAccess = memoryBufferReference as IMemoryBufferByteAccess;
            memoryBufferByteAccess.GetBuffer(out byte* arrayBuffer, out uint arrayBufferCapacity);
            GC.AddMemoryPressure(arrayBufferCapacity);
            return arrayBuffer;
        }
        // For accessing MediaStreamSample
        public static unsafe byte* GetArrayBuffer(IBuffer buffer)
        {
            var bufferByteAccess = buffer as IBufferByteAccess;
            bufferByteAccess.Buffer(out byte* arrayBuffer);
            uint arrayBufferCapacity = buffer.Capacity;
            GC.AddMemoryPressure(arrayBufferCapacity);
            return arrayBuffer;
        }
    }
    
  6. Kullanarak rastgele RawVideoFrame'ler oluşturmak için aşağıdaki yardımcı sınıfın bir örneğini oluşturun VideoStreamPixelFormat.Rgba

    public class VideoFrameSender
    {
        private RawOutgoingVideoStream rawOutgoingVideoStream;
        private RawVideoFrameKind rawVideoFrameKind;
        private Thread frameIteratorThread;
        private Random random = new Random();
        private volatile bool stopFrameIterator = false;
        public VideoFrameSender(RawVideoFrameKind rawVideoFrameKind, RawOutgoingVideoStream rawOutgoingVideoStream)
        {
            this.rawVideoFrameKind = rawVideoFrameKind;
            this.rawOutgoingVideoStream = rawOutgoingVideoStream;
        }
        public async void VideoFrameIterator()
        {
            while (!stopFrameIterator)
            {
                if (rawOutgoingVideoStream != null &&
                    rawOutgoingVideoStream.Format != null &&
                    rawOutgoingVideoStream.State == VideoStreamState.Started)
                {
                    await SendRandomVideoFrameRGBA();
                }
            }
        }
        private async Task SendRandomVideoFrameRGBA()
        {
            uint rgbaCapacity = (uint)(rawOutgoingVideoStream.Format.Width * rawOutgoingVideoStream.Format.Height * 4);
            RawVideoFrame videoFrame = null;
            switch (rawVideoFrameKind)
            {
                case RawVideoFrameKind.Buffer:
                    videoFrame = 
                        GenerateRandomVideoFrameBuffer(rawOutgoingVideoStream.Format, rgbaCapacity);
                    break;
                case RawVideoFrameKind.Texture:
                    videoFrame = 
                        GenerateRandomVideoFrameTexture(rawOutgoingVideoStream.Format, rgbaCapacity);
                    break;
            }
            try
            {
                using (videoFrame)
                {
                    await rawOutgoingVideoStream.SendRawVideoFrameAsync(videoFrame);
                }
            }
            catch (Exception ex)
            {
                string msg = ex.Message;
            }
            try
            {
                int delayBetweenFrames = (int)(1000.0 / rawOutgoingVideoStream.Format.FramesPerSecond);
                await Task.Delay(delayBetweenFrames);
            }
            catch (Exception ex)
            {
                string msg = ex.Message;
            }
        }
        private unsafe RawVideoFrame GenerateRandomVideoFrameBuffer(VideoStreamFormat videoFormat, uint rgbaCapacity)
        {
            var rgbaBuffer = new MemoryBuffer(rgbaCapacity);
            byte* rgbaArrayBuffer = BufferExtensions.GetArrayBuffer(rgbaBuffer);
            GenerateRandomVideoFrame(&rgbaArrayBuffer);
            return new RawVideoFrameBuffer()
            {
                Buffers = new MemoryBuffer[] { rgbaBuffer },
                StreamFormat = videoFormat
            };
        }
        private unsafe RawVideoFrame GenerateRandomVideoFrameTexture(VideoStreamFormat videoFormat, uint rgbaCapacity)
        {
            var timeSpan = new TimeSpan(rawOutgoingVideoStream.TimestampInTicks);
            var rgbaBuffer = new Buffer(rgbaCapacity)
            {
                Length = rgbaCapacity
            };
            byte* rgbaArrayBuffer = BufferExtensions.GetArrayBuffer(rgbaBuffer);
            GenerateRandomVideoFrame(&rgbaArrayBuffer);
            var mediaStreamSample = MediaStreamSample.CreateFromBuffer(rgbaBuffer, timeSpan);
            return new RawVideoFrameTexture()
            {
                Texture = mediaStreamSample,
                StreamFormat = videoFormat
            };
        }
        private unsafe void GenerateRandomVideoFrame(byte** rgbaArrayBuffer)
        {
            int w = rawOutgoingVideoStream.Format.Width;
            int h = rawOutgoingVideoStream.Format.Height;
            byte r = (byte)random.Next(1, 255);
            byte g = (byte)random.Next(1, 255);
            byte b = (byte)random.Next(1, 255);
            int rgbaStride = w * 4;
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < rgbaStride; x += 4)
                {
                    (*rgbaArrayBuffer)[(w * 4 * y) + x + 0] = (byte)(y % r);
                    (*rgbaArrayBuffer)[(w * 4 * y) + x + 1] = (byte)(y % g);
                    (*rgbaArrayBuffer)[(w * 4 * y) + x + 2] = (byte)(y % b);
                    (*rgbaArrayBuffer)[(w * 4 * y) + x + 3] = 255;
                }
            }
        }
        public void Start()
        {
            frameIteratorThread = new Thread(VideoFrameIterator);
            frameIteratorThread.Start();
        }
        public void Stop()
        {
            try
            {
                if (frameIteratorThread != null)
                {
                    stopFrameIterator = true;
                    frameIteratorThread.Join();
                    frameIteratorThread = null;
                    stopFrameIterator = false;
                }
            }
            catch (Exception ex)
            {
                string msg = ex.Message;
            }
        }
    }
    
  7. Temsilciye VideoStream.StateChanged abone olun. Bu olay geçerli akışın durumunu bildirir. Durum ile eşit VideoStreamState.Starteddeğilse çerçeve göndermeyin.

    private VideoFrameSender videoFrameSender;
    rawOutgoingVideoStream.StateChanged += (object sender, VideoStreamStateChangedEventArgs args) =>
    {
        CallVideoStream callVideoStream = args.Stream;
        switch (callVideoStream.State)
            {
                case VideoStreamState.Available:
                    // VideoStream has been attached to the call
                    var frameKind = RawVideoFrameKind.Buffer; // Use the frameKind you prefer
                    //var frameKind = RawVideoFrameKind.Texture;
                    videoFrameSender = new VideoFrameSender(frameKind, rawOutgoingVideoStream);
                    break;
                case VideoStreamState.Started:
                    // Start sending frames
                    videoFrameSender.Start();
                    break;
                case VideoStreamState.Stopped:
                    // Stop sending frames
                    videoFrameSender.Stop();
                    break;
            }
    };
    

Ekran Paylaşımı Videosu

Çerçeveleri Windows sistemi oluşturduğundan, çerçeveleri yakalamak ve Azure İletişim Hizmetleri Çağırma API'sini kullanarak göndermek için kendi ön plan hizmetinizi uygulamanız gerekir.

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
Her şey 1080p'ye kadar olan her şey 30

Ekran paylaşımı video akışı oluşturma adımları

  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun. Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.
    var videoStreamFormat = new VideoStreamFormat
    {
        Width = 1280, // Width and height can be used for ScreenShareOutgoingVideoStream for custom resolutions or use one of the predefined values inside VideoStreamResolution
        Height = 720,
        //Resolution = VideoStreamResolution.P720,
        PixelFormat = VideoStreamPixelFormat.Rgba,
        FramesPerSecond = 30,
        Stride1 = 1280 * 4 // It is times 4 because RGBA is a 32-bit format.
    };
    VideoStreamFormat[] videoStreamFormats = { videoStreamFormat };
    
  2. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın VideoFormats .
    var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions
    {
        Formats = videoStreamFormats
    };
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.
    var rawOutgoingVideoStream = new ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions);
    
  4. Video çerçevesini aşağıdaki şekilde yakalayın ve gönderin.
    private async Task SendRawVideoFrame()
    {
        RawVideoFrame videoFrame = null;
        switch (rawVideoFrameKind) //it depends on the frame kind you want to send
        {
            case RawVideoFrameKind.Buffer:
                MemoryBuffer memoryBuffer = // Fill it with the content you got from the Windows APIs
                videoFrame = new RawVideoFrameBuffer()
                {
                    Buffers = memoryBuffer // The number of buffers depends on the VideoStreamPixelFormat
                    StreamFormat = rawOutgoingVideoStream.Format
                };
                break;
            case RawVideoFrameKind.Texture:
                MediaStreamSample mediaStreamSample = // Fill it with the content you got from the Windows APIs
                videoFrame = new RawVideoFrameTexture()
                {
                    Texture = mediaStreamSample, // Texture only receive planar buffers
                    StreamFormat = rawOutgoingVideoStream.Format
                };
                break;
        }
    
        try
        {
            using (videoFrame)
            {
                await rawOutgoingVideoStream.SendRawVideoFrameAsync(videoFrame);
            }
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }
    
        try
        {
            int delayBetweenFrames = (int)(1000.0 / rawOutgoingVideoStream.Format.FramesPerSecond);
            await Task.Delay(delayBetweenFrames);
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }
    }
    

Ham Gelen Video

Bu özellik, bu akışları yerel olarak işlemek için içindeki video çerçevelerine IncomingVideoStreamerişmenizi sağlar

  1. Ayar aracılığıyla JoinCallOptions kümelerin bir örneğini IncomingVideoOptions oluşturmaVideoStreamKind.RawIncoming
    var frameKind = RawVideoFrameKind.Buffer;  // Use the frameKind you prefer to receive
    var incomingVideoOptions = new IncomingVideoOptions
    {
        StreamKind = VideoStreamKind.RawIncoming,
        FrameKind = frameKind
    };
    var joinCallOptions = new JoinCallOptions
    {
        IncomingVideoOptions = incomingVideoOptions
    };
    
  2. Olay ekleme RemoteParticipant.VideoStreamStateChanged temsilcisini aldıktan ParticipantsUpdatedEventArgs sonra. Bu olay nesnelerin durumunu IncomingVideoStream bildirir.
    private List<RemoteParticipant> remoteParticipantList;
    private void OnRemoteParticipantsUpdated(object sender, ParticipantsUpdatedEventArgs args)
    {
        foreach (RemoteParticipant remoteParticipant in args.AddedParticipants)
        {
            IReadOnlyList<IncomingVideoStream> incomingVideoStreamList = remoteParticipant.IncomingVideoStreams; // Check if there are IncomingVideoStreams already before attaching the delegate
            foreach (IncomingVideoStream incomingVideoStream in incomingVideoStreamList)
            {
                OnRawIncomingVideoStreamStateChanged(incomingVideoStream);
            }
            remoteParticipant.VideoStreamStateChanged += OnVideoStreamStateChanged;
            remoteParticipantList.Add(remoteParticipant); // If the RemoteParticipant ref is not kept alive the VideoStreamStateChanged events are going to be missed
        }
        foreach (RemoteParticipant remoteParticipant in args.RemovedParticipants)
        {
            remoteParticipant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
            remoteParticipantList.Remove(remoteParticipant);
        }
    }
    private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs args)
    {
        CallVideoStream callVideoStream = args.Stream;
        OnRawIncomingVideoStreamStateChanged(callVideoStream as RawIncomingVideoStream);
    }
    private void OnRawIncomingVideoStreamStateChanged(RawIncomingVideoStream rawIncomingVideoStream)
    {
        switch (incomingVideoStream.State)
        {
            case VideoStreamState.Available:
                // There is a new IncomingVideoStream
                rawIncomingVideoStream.RawVideoFrameReceived += OnVideoFrameReceived;
                rawIncomingVideoStream.Start();
                break;
            case VideoStreamState.Started:
                // Will start receiving video frames
                break;
            case VideoStreamState.Stopped:
                // Will stop receiving video frames
                break;
            case VideoStreamState.NotAvailable:
                // The IncomingVideoStream should not be used anymore
                rawIncomingVideoStream.RawVideoFrameReceived -= OnVideoFrameReceived;
                break;
        }
    }
    
  3. O sırada, IncomingVideoStream önceki adımda gösterildiği gibi durum ekleme RawIncomingVideoStream.RawVideoFrameReceived temsilcisi vardırVideoStreamState.Available. Bu, yeni RawVideoFrame nesneleri sağlar.
    private async void OnVideoFrameReceived(object sender, RawVideoFrameReceivedEventArgs args)
    {
        RawVideoFrame videoFrame = args.Frame;
        switch (videoFrame.Kind) // The type will be whatever was configured on the IncomingVideoOptions
        {
            case RawVideoFrameKind.Buffer:
                // Render/Modify/Save the video frame
                break;
            case RawVideoFrameKind.Texture:
                // Render/Modify/Save the video frame
                break;
        }
    }
    

Bu hızlı başlangıçta, Android için Azure İletişim Hizmetleri Çağırma SDK'sını kullanarak ham medya erişimini uygulamayı öğreneceksiniz.

Azure İletişim Hizmetleri Arama SDK'sı, uygulamaların bir aramada uzak katılımcılara göndermek üzere kendi video çerçevelerini oluşturmasına olanak sağlayan API'ler sunar.

Bu hızlı başlangıçta Hızlı Başlangıç: Android için uygulamanıza 1:1 görüntülü arama ekleme adımları temel alır.

RawAudio erişimi

Ham ses medyasına erişmek, arama sırasında özel giden ses akışlarını görüntüleme ve gönderme özelliğiyle birlikte aramanın gelen ses akışına erişmenizi sağlar.

Ham Giden ses gönderme

Göndermek istediğimiz ham akış özelliklerini belirten bir options nesnesi yapın.

    RawOutgoingAudioStreamProperties outgoingAudioProperties = new RawOutgoingAudioStreamProperties()
                .setAudioFormat(AudioStreamFormat.PCM16_BIT)
                .setSampleRate(AudioStreamSampleRate.HZ44100)
                .setChannelMode(AudioStreamChannelMode.STEREO)
                .setBufferDuration(AudioStreamBufferDuration.IN_MS20);

    RawOutgoingAudioStreamOptions outgoingAudioStreamOptions = new RawOutgoingAudioStreamOptions()
                .setProperties(outgoingAudioProperties);

Bir RawOutgoingAudioStream oluşturun ve arama seçeneklerine katılmak için ekleyin; arama bağlandığında akış otomatik olarak başlatılır.

    JoinCallOptions options = JoinCallOptions() // or StartCallOptions()

    OutgoingAudioOptions outgoingAudioOptions = new OutgoingAudioOptions();
    RawOutgoingAudioStream rawOutgoingAudioStream = new RawOutgoingAudioStream(outgoingAudioStreamOptions);

    outgoingAudioOptions.setStream(rawOutgoingAudioStream);
    options.setOutgoingAudioOptions(outgoingAudioOptions);

    // Start or Join call with those call options.

Aramaya akış ekleme

Bunun yerine akışı mevcut Call bir örneğe de ekleyebilirsiniz:

    CompletableFuture<Void> result = call.startAudio(context, rawOutgoingAudioStream);

Ham örnekleri göndermeye başlama

Veri göndermeye yalnızca akış durumu olduğunda AudioStreamState.STARTEDbaşlayabiliriz. Ses akışı durumu değişikliğini gözlemlemek için OnStateChangedListener olaya bir dinleyici ekleyin.

    private void onStateChanged(PropertyChangedEvent propertyChangedEvent) {
        // When value is `AudioStreamState.STARTED` we'll be able to send audio samples.
    }

    rawOutgoingAudioStream.addOnStateChangedListener(this::onStateChanged)

Akış başlatıldığında, aramaya ses örnekleri göndermeye java.nio.ByteBuffer başlayabiliriz.

Ses arabelleği biçimi belirtilen akış özellikleriyle eşleşmelidir.

    Thread thread = new Thread(){
        public void run() {
            RawAudioBuffer buffer;
            Calendar nextDeliverTime = Calendar.getInstance();
            while (true)
            {
                nextDeliverTime.add(Calendar.MILLISECOND, 20);
                byte data[] = new byte[outgoingAudioStream.getExpectedBufferSizeInBytes()];
                //can grab microphone data from AudioRecord
                ByteBuffer dataBuffer = ByteBuffer.allocateDirect(outgoingAudioStream.getExpectedBufferSizeInBytes());
                dataBuffer.rewind();
                buffer = new RawAudioBuffer(dataBuffer);
                outgoingAudioStream.sendOutgoingAudioBuffer(buffer);
                long wait = nextDeliverTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
                if (wait > 0)
                {
                    try {
                        Thread.sleep(wait);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };
    thread.start();

Ham Gelen ses alma

Sesi kayıttan yürütmeden önce işlemek istiyor gibi arama ses akışı örneklerini java.nio.ByteBuffer de alacağız.

Almak istediğimiz ham akış özelliklerini belirten bir RawIncomingAudioStreamOptions nesne oluşturun.

    RawIncomingAudioStreamOptions options = new RawIncomingAudioStreamOptions();
    RawIncomingAudioStreamProperties properties = new RawIncomingAudioStreamProperties()
                .setAudioFormat(AudioStreamFormat.PCM16_BIT)
                .setSampleRate(AudioStreamSampleRate.HZ44100)
                .setChannelMode(AudioStreamChannelMode.STEREO);
    options.setProperties(properties);

Arama seçeneklerine katılmak için bir RawIncomingAudioStream oluşturun ve ekleyin

    JoinCallOptions options =  JoinCallOptions() // or StartCallOptions()
    IncomingAudioOptions incomingAudioOptions = new IncomingAudioOptions();

    RawIncomingAudioStream rawIncomingAudioStream = new RawIncomingAudioStream(audioStreamOptions);
    incomingAudioOptions.setStream(rawIncomingAudioStream);
    options.setIncomingAudioOptions(incomingAudioOptions);

Bunun yerine akışı mevcut Call bir örneğe de ekleyebiliriz:


    CompletableFuture<Void> result = call.startAudio(context, rawIncomingAudioStream);

Gelen akıştan ham ses arabelleklerini almaya başlamak için, gelen akış durumuna ve alınan arabelleğe alınan olaylara dinleyiciler ekleyin.

    private void onStateChanged(PropertyChangedEvent propertyChangedEvent) {
        // When value is `AudioStreamState.STARTED` we'll be able to receive samples.
    }

    private void onMixedAudioBufferReceived(IncomingMixedAudioEvent incomingMixedAudioEvent) {
        // Received a raw audio buffers(java.nio.ByteBuffer).
    }

    rawIncomingAudioStream.addOnStateChangedListener(this::onStateChanged);
    rawIncomingAudioStream.addMixedAudioBufferReceivedListener(this::onMixedAudioBufferReceived);

Geçerli arama Call örneğinde ses akışını durdurmayı unutmamak da önemlidir:


    CompletableFuture<Void> result = call.stopAudio(context, rawIncomingAudioStream);

RawVideo erişimi

Uygulama video çerçevelerini oluşturduğundan, uygulamanın Azure İletişim Hizmetleri Çağırma SDK'sını uygulamanın oluşturabileceği video biçimleri hakkında bilgilendirmesi gerekir. Bu bilgiler, Azure İletişim Hizmetleri Arama SDK'sının o sırada ağ koşulları için en iyi video biçimi yapılandırmasını seçmesini sağlar.

Sanal Video

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
16x9 1080p 30
16x9 720p 30
16x9 540p 30
16x9 480p 30
16x9 360p 30
16x9 270p 15
16x9 240p 15
16x9 180p 15
4x3 VGA (640x480) 30
4x3 424x320 15
4x3 QVGA (320x240) 15
4x3 212x160 15
  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun.

    Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    VideoStreamFormat videoStreamFormat = new VideoStreamFormat();
    videoStreamFormat.setResolution(VideoStreamResolution.P360);
    videoStreamFormat.setPixelFormat(VideoStreamPixelFormat.RGBA);
    videoStreamFormat.setFramesPerSecond(framerate);
    videoStreamFormat.setStride1(w * 4); // It is times 4 because RGBA is a 32-bit format
    
    List<VideoStreamFormat> videoStreamFormats = new ArrayList<>();
    videoStreamFormats.add(videoStreamFormat);
    
  2. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın Formats .

    RawOutgoingVideoStreamOptions rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions();
    rawOutgoingVideoStreamOptions.setFormats(videoStreamFormats);
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    VirtualOutgoingVideoStream rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
    
  4. Temsilciye RawOutgoingVideoStream.addOnFormatChangedListener abone olun. Bu olay, listede sağlanan video biçimlerinden birinden değiştirildiğinde bunu bildirir VideoStreamFormat .

    virtualOutgoingVideoStream.addOnFormatChangedListener((VideoStreamFormatChangedEvent args) -> 
    {
        VideoStreamFormat videoStreamFormat = args.Format;
    });
    
  5. Kullanarak rastgele RawVideoFrame'ler oluşturmak için aşağıdaki yardımcı sınıfın bir örneğini oluşturun VideoStreamPixelFormat.RGBA

    public class VideoFrameSender
    {
        private RawOutgoingVideoStream rawOutgoingVideoStream;
        private Thread frameIteratorThread;
        private Random random = new Random();
        private volatile boolean stopFrameIterator = false;
    
        public VideoFrameSender(RawOutgoingVideoStream rawOutgoingVideoStream)
        {
            this.rawOutgoingVideoStream = rawOutgoingVideoStream;
        }
    
        public void VideoFrameIterator()
        {
            while (!stopFrameIterator)
            {
                if (rawOutgoingVideoStream != null && 
                    rawOutgoingVideoStream.getFormat() != null && 
                    rawOutgoingVideoStream.getState() == VideoStreamState.STARTED)
                {
                    SendRandomVideoFrameRGBA();
                }
            }
        }
    
        private void SendRandomVideoFrameRGBA()
        {
            int rgbaCapacity = rawOutgoingVideoStream.getFormat().getWidth() * rawOutgoingVideoStream.getFormat().getHeight() * 4;
    
            RawVideoFrame videoFrame = GenerateRandomVideoFrameBuffer(rawOutgoingVideoStream.getFormat(), rgbaCapacity);
    
            try
            {
                rawOutgoingVideoStream.sendRawVideoFrame(videoFrame).get();
    
                int delayBetweenFrames = (int)(1000.0 / rawOutgoingVideoStream.getFormat().getFramesPerSecond());
                Thread.sleep(delayBetweenFrames);
            }
            catch (Exception ex)
            {
                String msg = ex.getMessage();
            }
            finally
            {
                videoFrame.close();
            }
        }
    
        private RawVideoFrame GenerateRandomVideoFrameBuffer(VideoStreamFormat videoStreamFormat, int rgbaCapacity)
        {
            ByteBuffer rgbaBuffer = ByteBuffer.allocateDirect(rgbaCapacity); // Only allocateDirect ByteBuffers are allowed
            rgbaBuffer.order(ByteOrder.nativeOrder());
    
            GenerateRandomVideoFrame(rgbaBuffer, rgbaCapacity);
    
            RawVideoFrameBuffer videoFrameBuffer = new RawVideoFrameBuffer();
            videoFrameBuffer.setBuffers(Arrays.asList(rgbaBuffer));
            videoFrameBuffer.setStreamFormat(videoStreamFormat);
    
            return videoFrameBuffer;
        }
    
        private void GenerateRandomVideoFrame(ByteBuffer rgbaBuffer, int rgbaCapacity)
        {
            int w = rawOutgoingVideoStream.getFormat().getWidth();
            int h = rawOutgoingVideoStream.getFormat().getHeight();
    
            byte rVal = (byte)random.nextInt(255);
            byte gVal = (byte)random.nextInt(255);
            byte bVal = (byte)random.nextInt(255);
            byte aVal = (byte)255;
    
            byte[] rgbaArrayBuffer = new byte[rgbaCapacity];
    
            int rgbaStride = w * 4;
    
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < rgbaStride; x += 4)
                {
                    rgbaArrayBuffer[(w * 4 * y) + x + 0] = rVal;
                    rgbaArrayBuffer[(w * 4 * y) + x + 1] = gVal;
                    rgbaArrayBuffer[(w * 4 * y) + x + 2] = bVal;
                    rgbaArrayBuffer[(w * 4 * y) + x + 3] = aVal;
                }
            }
    
            rgbaBuffer.put(rgbaArrayBuffer);
            rgbaBuffer.rewind();
        }
    
        public void Start()
        {
            frameIteratorThread = new Thread(this::VideoFrameIterator);
            frameIteratorThread.start();
        }
    
        public void Stop()
        {
            try
            {
                if (frameIteratorThread != null)
                {
                    stopFrameIterator = true;
    
                    frameIteratorThread.join();
                    frameIteratorThread = null;
    
                    stopFrameIterator = false;
                }
            }
            catch (InterruptedException ex)
            {
                String msg = ex.getMessage();
            }
        }
    }
    
  6. Temsilciye VideoStream.addOnStateChangedListener abone olun. Bu temsilci geçerli akışın durumunu bildirir. Durum ile eşit VideoStreamState.STARTEDdeğilse çerçeve göndermeyin.

    private VideoFrameSender videoFrameSender;
    
    rawOutgoingVideoStream.addOnStateChangedListener((VideoStreamStateChangedEvent args) ->
    {
        CallVideoStream callVideoStream = args.getStream();
    
        switch (callVideoStream.getState())
        {
            case AVAILABLE:
                videoFrameSender = new VideoFrameSender(rawOutgoingVideoStream);
                break;
            case STARTED:
                // Start sending frames
                videoFrameSender.Start();
                break;
            case STOPPED:
                // Stop sending frames
                videoFrameSender.Stop();
                break;
        }
    });
    

ScreenShare Videosu

Çerçeveleri Windows sistemi oluşturduğundan, çerçeveleri yakalamak ve Azure İletişim Hizmetleri Çağırma API'sini kullanarak göndermek için kendi ön plan hizmetinizi uygulamanız gerekir.

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
Her şey 1080p'ye kadar olan her şey 30

Ekran paylaşımı video akışı oluşturma adımları

  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun.

    Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    VideoStreamFormat videoStreamFormat = new VideoStreamFormat();
    videoStreamFormat.setWidth(1280); // Width and height can be used for ScreenShareOutgoingVideoStream for custom resolutions or use one of the predefined values inside VideoStreamResolution
    videoStreamFormat.setHeight(720);
    //videoStreamFormat.setResolution(VideoStreamResolution.P360);
    videoStreamFormat.setPixelFormat(VideoStreamPixelFormat.RGBA);
    videoStreamFormat.setFramesPerSecond(framerate);
    videoStreamFormat.setStride1(w * 4); // It is times 4 because RGBA is a 32-bit format
    
    List<VideoStreamFormat> videoStreamFormats = new ArrayList<>();
    videoStreamFormats.add(videoStreamFormat);
    
  2. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın VideoFormats .

    RawOutgoingVideoStreamOptions rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions();
    rawOutgoingVideoStreamOptions.setFormats(videoStreamFormats);
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    ScreenShareOutgoingVideoStream rawOutgoingVideoStream = new ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions);
    
  4. Video çerçevesini aşağıdaki şekilde yakalayın ve gönderin.

    private void SendRawVideoFrame()
    {
        ByteBuffer byteBuffer = // Fill it with the content you got from the Windows APIs
        RawVideoFrameBuffer videoFrame = new RawVideoFrameBuffer();
        videoFrame.setBuffers(Arrays.asList(byteBuffer)); // The number of buffers depends on the VideoStreamPixelFormat
        videoFrame.setStreamFormat(rawOutgoingVideoStream.getFormat());
    
        try
        {
            rawOutgoingVideoStream.sendRawVideoFrame(videoFrame).get();
        }
        catch (Exception ex)
        {
            String msg = ex.getMessage();
        }
        finally
        {
            videoFrame.close();
        }
    }
    

Ham Gelen Video

Bu özellik, bu çerçeveleri yerel olarak işlemek için nesnelerin içindeki IncomingVideoStream video çerçevelerine erişmenizi sağlar

  1. Ayar aracılığıyla JoinCallOptions kümelerin bir örneğini IncomingVideoOptions oluşturmaVideoStreamKind.RawIncoming

    IncomingVideoOptions incomingVideoOptions = new IncomingVideoOptions()
            .setStreamType(VideoStreamKind.RAW_INCOMING);
    
    JoinCallOptions joinCallOptions = new JoinCallOptions()
            .setIncomingVideoOptions(incomingVideoOptions);
    
  2. Olay ekleme RemoteParticipant.VideoStreamStateChanged temsilcisini aldıktan ParticipantsUpdatedEventArgs sonra. Bu olay nesnenin IncomingVideoStream durumunu bildirir.

    private List<RemoteParticipant> remoteParticipantList;
    
    private void OnRemoteParticipantsUpdated(ParticipantsUpdatedEventArgs args)
    {
        for (RemoteParticipant remoteParticipant : args.getAddedParticipants())
        {
            List<IncomingVideoStream> incomingVideoStreamList = remoteParticipant.getIncomingVideoStreams(); // Check if there are IncomingVideoStreams already before attaching the delegate
            for (IncomingVideoStream incomingVideoStream : incomingVideoStreamList)
            {
                OnRawIncomingVideoStreamStateChanged(incomingVideoStream);
            }
    
            remoteParticipant.addOnVideoStreamStateChanged(this::OnVideoStreamStateChanged);
            remoteParticipantList.add(remoteParticipant); // If the RemoteParticipant ref is not kept alive the VideoStreamStateChanged events are going to be missed
        }
    
        for (RemoteParticipant remoteParticipant : args.getRemovedParticipants())
        {
            remoteParticipant.removeOnVideoStreamStateChanged(this::OnVideoStreamStateChanged);
            remoteParticipantList.remove(remoteParticipant);
        }
    }
    
    private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs args)
    {
        CallVideoStream callVideoStream = args.getStream();
    
        OnRawIncomingVideoStreamStateChanged((RawIncomingVideoStream) callVideoStream);
    }
    
    private void OnRawIncomingVideoStreamStateChanged(RawIncomingVideoStream rawIncomingVideoStream)
    {
        switch (incomingVideoStream.State)
        {
            case AVAILABLE:
                // There is a new IncomingvideoStream
                rawIncomingVideoStream.addOnRawVideoFrameReceived(this::OnVideoFrameReceived);
                rawIncomingVideoStream.Start();
    
                break;
            case STARTED:
                // Will start receiving video frames
                break;
            case STOPPED:
                // Will stop receiving video frames
                break;
            case NOT_AVAILABLE:
                // The IncomingvideoStream should not be used anymore
                rawIncomingVideoStream.removeOnRawVideoFrameReceived(this::OnVideoFrameReceived);
    
                break;
        }
    }
    
  3. O sırada, IncomingVideoStream önceki adımda gösterildiği gibi durum ekleme RawIncomingVideoStream.RawVideoFrameReceived temsilcisi vardırVideoStreamState.Available. Bu temsilci yeni RawVideoFrame nesneleri sağlar.

    private void OnVideoFrameReceived(RawVideoFrameReceivedEventArgs args)
    {
        // Render/Modify/Save the video frame
        RawVideoFrameBuffer videoFrame = (RawVideoFrameBuffer) args.getFrame();
    }
    

Bu hızlı başlangıçta, iOS için Azure İletişim Hizmetleri Çağırma SDK'sını kullanarak ham medya erişimini uygulamayı öğreneceksiniz.

Azure İletişim Hizmetleri Arama SDK'sı, uygulamaların bir aramada uzak katılımcılara göndermek üzere kendi video çerçevelerini oluşturmasına olanak sağlayan API'ler sunar.

Bu hızlı başlangıçta Hızlı Başlangıç: iOS için uygulamanıza 1:1 görüntülü arama ekleme adımları temel alır.

RawAudio erişimi

Ham ses medyasına erişmek, gelen aramanın ses akışına erişmenin yanı sıra arama sırasında özel giden ses akışlarını görüntülemenizi ve göndermenizi sağlar.

Ham Giden ses gönderme

Göndermek istediğimiz ham akış özelliklerini belirten bir options nesnesi yapın.

    let outgoingAudioStreamOptions = RawOutgoingAudioStreamOptions()
    let properties = RawOutgoingAudioStreamProperties()
    properties.sampleRate = .hz44100
    properties.bufferDuration = .inMs20
    properties.channelMode = .mono
    properties.format = .pcm16Bit
    outgoingAudioStreamOptions.properties = properties

Bir RawOutgoingAudioStream oluşturun ve arama seçeneklerine katılmak için ekleyin; arama bağlandığında akış otomatik olarak başlatılır.

    let options = JoinCallOptions() // or StartCallOptions()

    let outgoingAudioOptions = OutgoingAudioOptions()
    self.rawOutgoingAudioStream = RawOutgoingAudioStream(rawOutgoingAudioStreamOptions: outgoingAudioStreamOptions)
    outgoingAudioOptions.stream = self.rawOutgoingAudioStream
    options.outgoingAudioOptions = outgoingAudioOptions

    // Start or Join call passing the options instance.

Aramaya akış ekleme

Bunun yerine akışı mevcut Call bir örneğe de ekleyebilirsiniz:


    call.startAudio(stream: self.rawOutgoingAudioStream) { error in 
        // Stream attached to `Call`.
    }

Ham Örnekleri göndermeye başlama

Veri göndermeye yalnızca akış durumu olduğunda AudioStreamState.startedbaşlayabiliriz. Ses akışı durumu değişikliğini gözlemlemek için uygulamasını RawOutgoingAudioStreamDelegateuygularız. Akış temsilcisi olarak ayarlayın.

    func rawOutgoingAudioStream(_ rawOutgoingAudioStream: RawOutgoingAudioStream,
                                didChangeState args: AudioStreamStateChangedEventArgs) {
        // When value is `AudioStreamState.started` we will be able to send audio samples.
    }

    self.rawOutgoingAudioStream.delegate = DelegateImplementer()

veya kapatma tabanlı kullanın

    self.rawOutgoingAudioStream.events.onStateChanged = { args in
        // When value is `AudioStreamState.started` we will be able to send audio samples.
    }

Akış başlatıldığında, aramaya ses örnekleri göndermeye AVAudioPCMBuffer başlayabiliriz.

Ses arabelleği biçimi belirtilen akış özellikleriyle eşleşmelidir.

    protocol SamplesProducer {
        func produceSample(_ currentSample: Int, 
                           options: RawOutgoingAudioStreamOptions) -> AVAudioPCMBuffer
    }

    // Let's use a simple Tone data producer as example.
    // Producing PCM buffers.
    func produceSamples(_ currentSample: Int,
                        stream: RawOutgoingAudioStream,
                        options: RawOutgoingAudioStreamOptions) -> AVAudioPCMBuffer {
        let sampleRate = options.properties.sampleRate
        let channelMode = options.properties.channelMode
        let bufferDuration = options.properties.bufferDuration
        let numberOfChunks = UInt32(1000 / bufferDuration.value)
        let bufferFrameSize = UInt32(sampleRate.valueInHz) / numberOfChunks
        let frequency = 400

        guard let format = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                         sampleRate: sampleRate.valueInHz,
                                         channels: channelMode.channelCount,
                                         interleaved: channelMode == .stereo) else {
            fatalError("Failed to create PCM Format")
        }

        guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: bufferFrameSize) else {
            fatalError("Failed to create PCM buffer")
        }

        buffer.frameLength = bufferFrameSize

        let factor: Double = ((2 as Double) * Double.pi) / (sampleRate.valueInHz/Double(frequency))
        var interval = 0
        for sampleIdx in 0..<Int(buffer.frameCapacity * channelMode.channelCount) {
            let sample = sin(factor * Double(currentSample + interval))
            // Scale to maximum amplitude. Int16.max is 37,767.
            let value = Int16(sample * Double(Int16.max))
            
            guard let underlyingByteBuffer = buffer.mutableAudioBufferList.pointee.mBuffers.mData else {
                continue
            }
            underlyingByteBuffer.assumingMemoryBound(to: Int16.self).advanced(by: sampleIdx).pointee = value
            interval += channelMode == .mono ? 2 : 1
        }

        return buffer
    }

    final class RawOutgoingAudioSender {
        let stream: RawOutgoingAudioStream
        let options: RawOutgoingAudioStreamOptions
        let producer: SamplesProducer

        private var timer: Timer?
        private var currentSample: Int = 0
        private var currentTimestamp: Int64 = 0

        init(stream: RawOutgoingAudioStream,
             options: RawOutgoingAudioStreamOptions,
             producer: SamplesProducer) {
            self.stream = stream
            self.options = options
            self.producer = producer
        }

        func start() {
            let properties = self.options.properties
            let interval = properties.bufferDuration.timeInterval

            let channelCount = AVAudioChannelCount(properties.channelMode.channelCount)
            let format = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                       sampleRate: properties.sampleRate.valueInHz,
                                       channels: channelCount,
                                       interleaved: channelCount > 1)!
            self.timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
                guard let self = self else { return }
                let sample = self.producer.produceSamples(self.currentSample, options: self.options)
                let rawBuffer = RawAudioBuffer()
                rawBuffer.buffer = sample
                rawBuffer.timestampInTicks = self.currentTimestamp
                self.stream.send(buffer: rawBuffer, completionHandler: { error in
                    if let error = error {
                        // Handle possible error.
                    }
                })

                self.currentTimestamp += Int64(properties.bufferDuration.value)
                self.currentSample += 1
            }
        }

        func stop() {
            self.timer?.invalidate()
            self.timer = nil
        }

        deinit {
            stop()
        }
    }

Geçerli arama Call örneğinde ses akışını durdurmayı unutmamak da önemlidir:


    call.stopAudio(stream: self.rawOutgoingAudioStream) { error in 
        // Stream detached from `Call` and stopped.
    }

Mikrofon örneklerini yakalama

Apple'ları AVAudioEngine kullanarak ses motoru giriş düğümüne dokunarak mikrofon çerçevelerini yakalayabiliriz. Mikrofon verilerini yakalamak ve ham ses işlevini kullanabilmek için, sesi bir aramaya göndermeden önce işleyebiliyoruz.

    import AVFoundation
    import AzureCommunicationCalling

    enum MicrophoneSenderError: Error {
        case notMatchingFormat
    }

    final class MicrophoneDataSender {
        private let stream: RawOutgoingAudioStream
        private let properties: RawOutgoingAudioStreamProperties
        private let format: AVAudioFormat
        private let audioEngine: AVAudioEngine = AVAudioEngine()

        init(properties: RawOutgoingAudioStreamProperties) throws {
            // This can be different depending on which device we are running or value set for
            // `try AVAudioSession.sharedInstance().setPreferredSampleRate(...)`.
            let nodeFormat = self.audioEngine.inputNode.outputFormat(forBus: 0)
            let matchingSampleRate = AudioSampleRate.allCases.first(where: { $0.valueInHz == nodeFormat.sampleRate })
            guard let inputNodeSampleRate = matchingSampleRate else {
                throw MicrophoneSenderError.notMatchingFormat
            }

            // Override the sample rate to one that matches audio session (Audio engine input node frequency).
            properties.sampleRate = inputNodeSampleRate

            let options = RawOutgoingAudioStreamOptions()
            options.properties = properties

            self.stream = RawOutgoingAudioStream(rawOutgoingAudioStreamOptions: options)
            let channelCount = AVAudioChannelCount(properties.channelMode.channelCount)
            self.format = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                        sampleRate: properties.sampleRate.valueInHz,
                                        channels: channelCount,
                                        interleaved: channelCount > 1)!
            self.properties = properties
        }

        func start() throws {
            guard !self.audioEngine.isRunning else {
                return
            }

            // Install tap documentations states that we can get between 100 and 400 ms of data.
            let framesFor100ms = AVAudioFrameCount(self.format.sampleRate * 0.1)

            // Note that some formats may not be allowed by `installTap`, so we have to specify the 
            // correct properties.
            self.audioEngine.inputNode.installTap(onBus: 0, bufferSize: framesFor100ms, 
                                                  format: self.format) { [weak self] buffer, _ in
                guard let self = self else { return }
                
                let rawBuffer = RawAudioBuffer()
                rawBuffer.buffer = buffer
                // Although we specified either 10ms or 20ms, we allow sending up to 100ms of data
                // as long as it can be evenly divided by the specified size.
                self.stream.send(buffer: rawBuffer) { error in
                    if let error = error {
                        // Handle error
                    }
                }
            }

            try audioEngine.start()
        }

        func stop() {
            audioEngine.stop()
        }
    }

Not

Ses altyapısı giriş düğümünün örnek hızı varsayılan olarak >paylaşılan ses oturumu için tercih edilen örnek oranı değeridir. Bu nedenle farklı bir değer kullanarak bu düğüme dokunma yükleyemiyoruz. Bu nedenle, özellik örnek oranının RawOutgoingStream mikrofon örneklerine dokunmadan aldığımızla eşleştiğinden emin olmamız veya dokunma arabelleklerini giden akışta beklenen biçime dönüştürmemiz gerekir.

Bu küçük örnekle, ham giden ses özelliğini kullanarak mikrofon AVAudioEngine verilerini yakalamayı ve bu örnekleri bir aramaya göndermeyi öğrendik.

Ham Gelen ses alma

Sesi kayıttan yürütmeden önce işlemek istiyor gibi arama ses akışı örneklerini AVAudioPCMBuffer de alacağız.

Almak istediğimiz ham akış özelliklerini belirten bir RawIncomingAudioStreamOptions nesne oluşturun.

    let options = RawIncomingAudioStreamOptions()
    let properties = RawIncomingAudioStreamProperties()
    properties.format = .pcm16Bit
    properties.sampleRate = .hz44100
    properties.channelMode = .stereo
    options.properties = properties

Arama seçeneklerine katılmak için bir RawOutgoingAudioStream oluşturun ve ekleyin

    let options =  JoinCallOptions() // or StartCallOptions()
    let incomingAudioOptions = IncomingAudioOptions()

    self.rawIncomingStream = RawIncomingAudioStream(rawIncomingAudioStreamOptions: audioStreamOptions)
    incomingAudioOptions.stream = self.rawIncomingStream
    options.incomingAudioOptions = incomingAudioOptions

Bunun yerine akışı mevcut Call bir örneğe de ekleyebiliriz:


    call.startAudio(stream: self.rawIncomingStream) { error in 
        // Stream attached to `Call`.
    }

Gelen akıştan ham ses arabelleği almaya başlamak için uygulamasını RawIncomingAudioStreamDelegateuygulayın:

    class RawIncomingReceiver: NSObject, RawIncomingAudioStreamDelegate {
        func rawIncomingAudioStream(_ rawIncomingAudioStream: RawIncomingAudioStream,
                                    didChangeState args: AudioStreamStateChangedEventArgs) {
            // To be notified when stream started and stopped.
        }
        
        func rawIncomingAudioStream(_ rawIncomingAudioStream: RawIncomingAudioStream,
                                    mixedAudioBufferReceived args: IncomingMixedAudioEventArgs) {
            // Receive raw audio buffers(AVAudioPCMBuffer) and process using AVAudioEngine API's.
        }
    }

    self.rawIncomingStream.delegate = RawIncomingReceiver()

veya

    rawIncomingAudioStream.events.mixedAudioBufferReceived = { args in
        // Receive raw audio buffers(AVAudioPCMBuffer) and process them using AVAudioEngine API's.
    }

    rawIncomingAudioStream.events.onStateChanged = { args in
        // To be notified when stream started and stopped.
    }

RawVideo erişimi

Uygulama video çerçevelerini oluşturduğundan, uygulamanın Azure İletişim Hizmetleri Çağırma SDK'sını uygulamanın oluşturabileceği video biçimleri hakkında bilgilendirmesi gerekir. Bu bilgiler, Azure İletişim Hizmetleri Arama SDK'sının o sırada ağ koşulları için en iyi video biçimi yapılandırmasını seçmesini sağlar.

Sanal Video

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
16x9 1080p 30
16x9 720p 30
16x9 540p 30
16x9 480p 30
16x9 360p 30
16x9 270p 15
16x9 240p 15
16x9 180p 15
4x3 VGA (640x480) 30
4x3 424x320 15
4x3 QVGA (320x240) 15
4x3 212x160 15
  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun. Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    var videoStreamFormat = VideoStreamFormat()
    videoStreamFormat.resolution = VideoStreamResolution.p360
    videoStreamFormat.pixelFormat = VideoStreamPixelFormat.nv12
    videoStreamFormat.framesPerSecond = framerate
    videoStreamFormat.stride1 = w // w is the resolution width
    videoStreamFormat.stride2 = w / 2 // w is the resolution width
    
    var videoStreamFormats: [VideoStreamFormat] = [VideoStreamFormat]()
    videoStreamFormats.append(videoStreamFormat)
    
  2. oluşturun RawOutgoingVideoStreamOptionsve daha önce oluşturulan nesneyle biçimler ayarlayın.

    var rawOutgoingVideoStreamOptions = RawOutgoingVideoStreamOptions()
    rawOutgoingVideoStreamOptions.formats = videoStreamFormats
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    var rawOutgoingVideoStream = VirtualOutgoingVideoStream(videoStreamOptions: rawOutgoingVideoStreamOptions)
    
  4. Temsilciye VirtualOutgoingVideoStreamDelegate uygulayın. olay didChangeFormat , listede sağlanan video biçimlerinden birinden değiştirildiğinde bunu bildirir VideoStreamFormat .

    virtualOutgoingVideoStream.delegate = /* Attach delegate and implement didChangeFormat */
    
  5. Verilere erişmek CVPixelBuffer için aşağıdaki yardımcı sınıfın bir örneğini oluşturun

    final class BufferExtensions: NSObject {
        public static func getArrayBuffersUnsafe(cvPixelBuffer: CVPixelBuffer) -> Array<UnsafeMutableRawPointer?>
        {
            var bufferArrayList: Array<UnsafeMutableRawPointer?> = [UnsafeMutableRawPointer?]()
    
            let cvStatus: CVReturn = CVPixelBufferLockBaseAddress(cvPixelBuffer, .readOnly)
    
            if cvStatus == kCVReturnSuccess {
                let bufferListSize = CVPixelBufferGetPlaneCount(cvPixelBuffer);
                for i in 0...bufferListSize {
                    let bufferRef = CVPixelBufferGetBaseAddressOfPlane(cvPixelBuffer, i)
                    bufferArrayList.append(bufferRef)
                }
            }
    
            return bufferArrayList
        }
    }
    
  6. Kullanarak rastgele RawVideoFrameBuffer'ler oluşturmak için aşağıdaki yardımcı sınıfın bir örneğini oluşturun VideoStreamPixelFormat.rgba

    final class VideoFrameSender : NSObject
    {
        private var rawOutgoingVideoStream: RawOutgoingVideoStream
        private var frameIteratorThread: Thread
        private var stopFrameIterator: Bool = false
    
        public VideoFrameSender(rawOutgoingVideoStream: RawOutgoingVideoStream)
        {
            self.rawOutgoingVideoStream = rawOutgoingVideoStream
        }
    
        @objc private func VideoFrameIterator()
        {
            while !stopFrameIterator {
                if rawOutgoingVideoStream != nil &&
                   rawOutgoingVideoStream.format != nil &&
                   rawOutgoingVideoStream.state == .started {
                    SendRandomVideoFrameNV12()
                }
           }
        }
    
        public func SendRandomVideoFrameNV12() -> Void
        {
            let videoFrameBuffer = GenerateRandomVideoFrameBuffer()
    
            rawOutgoingVideoStream.send(frame: videoFrameBuffer) { error in
                /*Handle error if non-nil*/
            }
    
            let rate = 0.1 / rawOutgoingVideoStream.format.framesPerSecond
            let second: Float = 1000000
            usleep(useconds_t(rate * second))
        }
    
        private func GenerateRandomVideoFrameBuffer() -> RawVideoFrame
        {
            var cvPixelBuffer: CVPixelBuffer? = nil
            guard CVPixelBufferCreate(kCFAllocatorDefault,
                                    rawOutgoingVideoStream.format.width,
                                    rawOutgoingVideoStream.format.height,
                                    kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                                    nil,
                                    &cvPixelBuffer) == kCVReturnSuccess else {
                fatalError()
            }
    
            GenerateRandomVideoFrameNV12(cvPixelBuffer: cvPixelBuffer!)
    
            CVPixelBufferUnlockBaseAddress(cvPixelBuffer!, .readOnly)
    
            let videoFrameBuffer = RawVideoFrameBuffer()
            videoFrameBuffer.buffer = cvPixelBuffer!
            videoFrameBuffer.streamFormat = rawOutgoingVideoStream.format
    
            return videoFrameBuffer
        }
    
       private func GenerateRandomVideoFrameNV12(cvPixelBuffer: CVPixelBuffer) {
            let w = rawOutgoingVideoStream.format.width
            let h = rawOutgoingVideoStream.format.height
    
            let bufferArrayList = BufferExtensions.getArrayBuffersUnsafe(cvPixelBuffer: cvPixelBuffer)
    
            guard bufferArrayList.count >= 2, let yArrayBuffer = bufferArrayList[0], let uvArrayBuffer = bufferArrayList[1] else {
                return
            }
    
            let yVal = Int32.random(in: 1..<255)
            let uvVal = Int32.random(in: 1..<255)
    
            for y in 0...h
            {
                for x in 0...w
                {
                    yArrayBuffer.storeBytes(of: yVal, toByteOffset: Int((y * w) + x), as: Int32.self)
                }
            }
    
            for y in 0...(h/2)
            {
                for x in 0...(w/2)
                {
                    uvArrayBuffer.storeBytes(of: uvVal, toByteOffset: Int((y * w) + x), as: Int32.self)
                }
            }
        }
    
        public func Start() {
            stopFrameIterator = false
            frameIteratorThread = Thread(target: self, selector: #selector(VideoFrameIterator), object: "VideoFrameSender")
            frameIteratorThread?.start()
        }
    
        public func Stop() {
            if frameIteratorThread != nil {
                stopFrameIterator = true
                frameIteratorThread?.cancel()
                frameIteratorThread = nil
            }
        }
    }
    
  7. uygulamasına uygulayın VirtualOutgoingVideoStreamDelegate. Olay geçerli didChangeState akışın durumunu bildirir. Durum ile eşit VideoStreamState.starteddeğilse çerçeve göndermeyin.

    /*Delegate Implementer*/ 
    private var videoFrameSender: VideoFrameSender
    func virtualOutgoingVideoStream(
        _ virtualOutgoingVideoStream: VirtualOutgoingVideoStream,
        didChangeState args: VideoStreamStateChangedEventArgs) {
        switch args.stream.state {
            case .available:
                videoFrameSender = VideoFrameSender(rawOutgoingVideoStream)
                break
            case .started:
                /* Start sending frames */
                videoFrameSender.Start()
                break
            case .stopped:
                /* Stop sending frames */
                videoFrameSender.Stop()
                break
        }
    }
    

ScreenShare Videosu

Çerçeveleri Windows sistemi oluşturduğundan, çerçeveleri yakalamak ve Azure İletişim Hizmetleri Çağırma API'sini kullanarak göndermek için kendi ön plan hizmetinizi uygulamanız gerekir.

Desteklenen video çözünürlükleri

En boy oranı Çözüm Maksimum FPS
Her şey 1080p'ye kadar olan her şey 30

Ekran paylaşımı video akışı oluşturma adımları

  1. SDK'nın VideoFormat desteklediği VideoStreamPixelFormat kullanarak bir dizi oluşturun. Birden çok biçim kullanılabilir olduğunda, listedeki biçimlerin sırası hangisinin kullanıldığını etkilemez veya öncelik belirlemez. Biçim seçimi ölçütleri, ağ bant genişliği gibi dış faktörleri temel alır.

    let videoStreamFormat = VideoStreamFormat()
    videoStreamFormat.width = 1280 /* Width and height can be used for ScreenShareOutgoingVideoStream for custom resolutions or use one of the predefined values inside VideoStreamResolution */
    videoStreamFormat.height = 720
    /*videoStreamFormat.resolution = VideoStreamResolution.p360*/
    videoStreamFormat.pixelFormat = VideoStreamPixelFormat.rgba
    videoStreamFormat.framesPerSecond = framerate
    videoStreamFormat.stride1 = w * 4 /* It is times 4 because RGBA is a 32-bit format */
    
    var videoStreamFormats: [VideoStreamFormat] = []
    videoStreamFormats.append(videoStreamFormat)
    
  2. oluşturun RawOutgoingVideoStreamOptionsve önceden oluşturulan nesneyle ayarlayın VideoFormats .

    var rawOutgoingVideoStreamOptions = RawOutgoingVideoStreamOptions()
    rawOutgoingVideoStreamOptions.formats = videoStreamFormats
    
  3. Daha önce oluşturduğunuz örneği VirtualOutgoingVideoStream kullanarak RawOutgoingVideoStreamOptions örneğini oluşturun.

    var rawOutgoingVideoStream = ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions)
    
  4. Video çerçevesini aşağıdaki şekilde yakalayın ve gönderin.

    private func SendRawVideoFrame() -> Void
    {
        CVPixelBuffer cvPixelBuffer = /* Fill it with the content you got from the Windows APIs, The number of buffers depends on the VideoStreamPixelFormat */
        let videoFrameBuffer = RawVideoFrameBuffer()
        videoFrameBuffer.buffer = cvPixelBuffer!
        videoFrameBuffer.streamFormat = rawOutgoingVideoStream.format
    
        rawOutgoingVideoStream.send(frame: videoFrame) { error in
            /*Handle error if not nil*/
        }
    }
    

Ham Gelen Video

Bu özellik, bu akış nesnelerini yerel olarak işlemek için içindeki video çerçevelerine IncomingVideoStreamerişmenizi sağlar

  1. Ayar aracılığıyla JoinCallOptions kümelerin bir örneğini IncomingVideoOptions oluşturmaVideoStreamKind.RawIncoming

    var incomingVideoOptions = IncomingVideoOptions()
    incomingVideoOptions.streamType = VideoStreamKind.rawIncoming
    var joinCallOptions = JoinCallOptions()
    joinCallOptions.incomingVideoOptions = incomingVideoOptions
    
  2. Olay ekleme RemoteParticipant.delegate.didChangedVideoStreamState temsilcisini aldıktan ParticipantsUpdatedEventArgs sonra. Bu olay nesnelerin durumunu IncomingVideoStream bildirir.

    private var remoteParticipantList: [RemoteParticipant] = []
    
    func call(_ call: Call, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
        args.addedParticipants.forEach { remoteParticipant in
            remoteParticipant.incomingVideoStreams.forEach { incomingVideoStream in
                OnRawIncomingVideoStreamStateChanged(incomingVideoStream: incomingVideoStream)
            }
            remoteParticipant.delegate = /* Attach delegate OnVideoStreamStateChanged*/
        }
    
        args.removedParticipants.forEach { remoteParticipant in
            remoteParticipant.delegate = nil
        }
    }
    
    func remoteParticipant(_ remoteParticipant: RemoteParticipant, 
                           didVideoStreamStateChanged args: VideoStreamStateChangedEventArgs) {
        OnRawIncomingVideoStreamStateChanged(rawIncomingVideoStream: args.stream)
    }
    
    func OnRawIncomingVideoStreamStateChanged(rawIncomingVideoStream: RawIncomingVideoStream) {
        switch incomingVideoStream.state {
            case .available:
                /* There is a new IncomingVideoStream */
                rawIncomingVideoStream.delegate /* Attach delegate OnVideoFrameReceived*/
                rawIncomingVideoStream.start()
                break;
            case .started:
                /* Will start receiving video frames */
                break
            case .stopped:
                /* Will stop receiving video frames */
                break
            case .notAvailable:
                /* The IncomingVideoStream should not be used anymore */
                rawIncomingVideoStream.delegate = nil
                break
        }
    }
    
  3. O sırada, IncomingVideoStream önceki adımda gösterildiği gibi durum ekleme RawIncomingVideoStream.delegate.didReceivedRawVideoFrame temsilcisi vardırVideoStreamState.available. Bu olay yeni RawVideoFrame nesneleri sağlar.

    func rawIncomingVideoStream(_ rawIncomingVideoStream: RawIncomingVideoStream, 
                                didRawVideoFrameReceived args: RawVideoFrameReceivedEventArgs) {
        /* Render/Modify/Save the video frame */
        let videoFrame = args.frame as! RawVideoFrameBuffer
    }
    

Geliştirici olarak, arama sırasında gelen ve giden ses, video ve ekran paylaşımı içeriğinin ham medyasına erişebilir, böylece ses/video içeriğini yakalayabilir, analiz edebilir ve işleyebilirsiniz. Azure İletişim Hizmetleri istemci tarafı ham ses, ham video ve ham ekran paylaşımına erişim, geliştiricilere Azure İletişim Hizmetleri Arama SDK'sı içinde gerçekleşen ses, video ve ekran paylaşımı içeriğini görüntüleme ve düzenleme olanağı sağlar. Bu hızlı başlangıçta JavaScript için Azure İletişim Hizmetleri Çağırma SDK'sını kullanarak ham medya erişimini uygulamayı öğreneceksiniz.

Örneğin,

  • Aramanın ses/video akışına doğrudan arama nesnesi üzerinden erişebilir ve arama sırasında özel giden ses/video akışları gönderebilirsiniz.
  • Analiz için özel yapay zeka modellerini çalıştırmak için ses ve video akışlarını inceleyebilirsiniz. Bu tür modeller, konuşmaları analiz etmek veya aracı üretkenliğini artırmak için gerçek zamanlı içgörüler ve öneriler sağlamak için doğal dil işlemeyi içerebilir.
  • Kuruluşlar, hastalar için sanal bakım sağlarken yaklaşımı analiz etmek veya karma gerçeklik kullanan görüntülü aramalar sırasında uzaktan yardım sağlamak için ses ve video medya akışlarını kullanabilir. Bu özellik, geliştiricilerin etkileşim deneyimlerini geliştirmek için yenilikler uygulamasına yönelik bir yol açar.

Önkoşullar

Önemli

Buradaki örnekler JavaScript için Çağırma SDK'sının 1.13.1'inde verilmiştir. Bu hızlı başlangıcı denerken bu sürümü veya daha yenisini kullandığınızdan emin olun.

Ham sese erişme

Ham ses medyasına erişmek, gelen aramanın ses akışına erişmenin yanı sıra arama sırasında özel giden ses akışlarını görüntülemenizi ve göndermenizi sağlar.

Gelen ham ses akışına erişme

Gelen aramanın ses akışına erişmek için aşağıdaki kodu kullanın.

const userId = 'acs_user_id';
const call = callAgent.startCall(userId);
const callStateChangedHandler = async () => {
    if (call.state === "Connected") {
        const remoteAudioStream = call.remoteAudioStreams[0];
        const mediaStream = await remoteAudioStream.getMediaStream();
	// process the incoming call's audio media stream track
    }
};

callStateChangedHandler();
call.on("stateChanged", callStateChangedHandler);

Özel ses akışıyla arama yerleştirme

Kullanıcının mikrofon cihazını kullanmak yerine özel bir ses akışıyla arama başlatmak için aşağıdaki kodu kullanın.

const createBeepAudioStreamToSend = () => {
    const context = new AudioContext();
    const dest = context.createMediaStreamDestination();
    const os = context.createOscillator();
    os.type = 'sine';
    os.frequency.value = 500;
    os.connect(dest);
    os.start();
    const { stream } = dest;
    return stream;
};

...
const userId = 'acs_user_id';
const mediaStream = createBeepAudioStreamToSend();
const localAudioStream = new LocalAudioStream(mediaStream);
const callOptions = {
    audioOptions: {
        localAudioStreams: [localAudioStream]
    }
};
callAgent.startCall(userId, callOptions);

Arama sırasında özel ses akışına geçme

Arama sırasında kullanıcının mikrofon cihazını kullanmak yerine giriş cihazını özel ses akışına geçmek için aşağıdaki kodu kullanın.

const createBeepAudioStreamToSend = () => {
    const context = new AudioContext();
    const dest = context.createMediaStreamDestination();
    const os = context.createOscillator();
    os.type = 'sine';
    os.frequency.value = 500;
    os.connect(dest);
    os.start();
    const { stream } = dest;
    return stream;
};

...

const userId = 'acs_user_id';
const mediaStream = createBeepAudioStreamToSend();
const localAudioStream = new LocalAudioStream(mediaStream);
const call = callAgent.startCall(userId);
const callStateChangedHandler = async () => {
    if (call.state === 'Connected') {
        await call.startAudio(localAudioStream);
    }
};

callStateChangedHandler();
call.on('stateChanged', callStateChangedHandler);

Özel ses akışını durdurma

Arama sırasında ayarlandıktan sonra özel bir ses akışı göndermeyi durdurmak için aşağıdaki kodu kullanın.

call.stopAudio();

Ham videoya erişme

Ham video medyası size bir MediaStream nesnenin örneğini verir. (Daha fazla bilgi için JavaScript belgelerine bakın.) Ham video medyası, özellikle gelen ve giden aramalar için nesneye MediaStream erişim sağlar. Ham video için, videonun çerçevelerini işlemek için makine öğrenmesini kullanarak filtre uygulamak için bu nesneyi kullanabilirsiniz.

İşlenen ham giden video çerçeveleri gönderenin giden videosu olarak gönderilebilir. İşlenen ham gelen video kareleri alıcı tarafında işlenebilir.

Özel video akışıyla arama yerleştirme

Giden arama için ham video akışına erişebilirsiniz. Makine öğrenmesini kullanarak çerçeveleri işlemek ve filtreleri uygulamak için giden ham video akışı için kullanırsınız MediaStream . İşlenen giden video daha sonra gönderen video akışı olarak gönderilebilir.

Bu örnek, tuval verilerini kullanıcıya giden video olarak gönderir.

const createVideoMediaStreamToSend = () => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1500;
    canvas.height = 845;
    ctx.fillStyle = 'blue';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const colors = ['red', 'yellow', 'green'];
    window.setInterval(() => {
        if (ctx) {
            ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
            const x = Math.floor(Math.random() * canvas.width);
            const y = Math.floor(Math.random() * canvas.height);
            const size = 100;
            ctx.fillRect(x, y, size, size);
        }
    }, 1000 / 30);

    return canvas.captureStream(30);
};

...
const userId = 'acs_user_id';
const mediaStream = createVideoMediaStreamToSend();
const localVideoStream = new LocalVideoStream(mediaStream);
const callOptions = {
    videoOptions: {
        localVideoStreams: [localVideoStream]
    }
};
callAgent.startCall(userId, callOptions);

Arama sırasında özel video akışına geçme

Arama sırasında kullanıcının kamera cihazını kullanmak yerine giriş cihazını özel bir video akışına değiştirmek için aşağıdaki kodu kullanın.

const createVideoMediaStreamToSend = () => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1500;
    canvas.height = 845;
    ctx.fillStyle = 'blue';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const colors = ['red', 'yellow', 'green'];
    window.setInterval(() => {
        if (ctx) {
            ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
            const x = Math.floor(Math.random() * canvas.width);
            const y = Math.floor(Math.random() * canvas.height);
            const size = 100;
            ctx.fillRect(x, y, size, size);
	 }
    }, 1000 / 30);

    return canvas.captureStream(30);
};

...

const userId = 'acs_user_id';
const call = callAgent.startCall(userId);
const callStateChangedHandler = async () => {
    if (call.state === 'Connected') {    	
        const mediaStream = createVideoMediaStreamToSend();
        const localVideoStream = this.call.localVideoStreams.find((stream) => { return stream.mediaStreamType === 'Video' });
        await localVideoStream.setMediaStream(mediaStream);
    }
};

callStateChangedHandler();
call.on('stateChanged', callStateChangedHandler);

Özel video akışını durdurma

Arama sırasında ayarlandıktan sonra özel video akışı göndermeyi durdurmak için aşağıdaki kodu kullanın.

// Stop video by passing the same `localVideoStream` instance that was used to start video
await call.stopVideo(localVideoStream);

Başka bir kamera cihazına özel efektler uygulanmış bir kameradan geçiş yaparken, önce videoyu durdurun, üzerindeki LocalVideoStreamkaynağı değiştirin ve videoyu yeniden başlatın.

const cameras = await this.deviceManager.getCameras();
const newCameraDeviceInfo = cameras.find(cameraDeviceInfo => { return cameraDeviceInfo.id === '<another camera that you want to switch to>' });
// If current camera is using custom raw media stream and video is on
if (this.localVideoStream.mediaStreamType === 'RawMedia' && this.state.videoOn) {
	// Stop raw custom video first
	await this.call.stopVideo(this.localVideoStream);
	// Switch the local video stream's source to the new camera to use
	this.localVideoStream?.switchSource(newCameraDeviceInfo);
	// Start video with the new camera device
	await this.call.startVideo(this.localVideoStream);

// Else if current camera is using normal stream from camera device and video is on
} else if (this.localVideoStream.mediaStreamType === 'Video' && this.state.videoOn) {
	// You can just switch the source, no need to stop and start again. Sent video will automatically switch to the new camera to use
	this.localVideoStream?.switchSource(newCameraDeviceInfo);
}

Uzak katılımcıdan gelen video akışına erişme

Gelen arama için ham video akışına erişebilirsiniz. Makine öğrenmesini kullanarak çerçeveleri işlemek ve filtreler uygulamak için gelen ham video akışı için kullanırsınız MediaStream . İşlenen gelen video daha sonra alıcı tarafında işlenebilir.

const remoteVideoStream = remoteParticipants[0].videoStreams.find((stream) => { return stream.mediaStreamType === 'Video'});
const processMediaStream = async () => {
    if (remoteVideoStream.isAvailable) {
	// remote video stream is turned on, process the video's raw media stream.
	const mediaStream = await remoteVideoStream.getMediaStream();
    } else {
	// remote video stream is turned off, handle it
    }
};

remoteVideoStream.on('isAvailableChanged', async () => {
    await processMediaStream();
});

await processMediaStream();

Önemli

Azure İletişim Hizmetleri'nin bu özelliği şu anda önizleme aşamasındadır.

Önizleme API'leri ve SDK'ları hizmet düzeyi sözleşmesi olmadan sağlanır. Bunları üretim iş yükleri için kullanmamanızı öneririz. Bazı özellikler desteklenmeyebilir veya kısıtlı özelliklere sahip olabilir.

Daha fazla bilgi için Microsoft Azure Önizlemeleri için Ek Kullanım Koşulları'nı gözden geçirin.

Ham ekran paylaşımı erişimi genel önizleme aşamasındadır ve 1.15.1-beta.1+ sürümünün bir parçası olarak kullanılabilir.

Ham ekran paylaşımına erişme

Ham ekran paylaşımı medyası, özellikle gelen ve giden ekran paylaşımı akışları için nesneye MediaStream erişim sağlar. Ham ekran paylaşımı için, ekran paylaşımının çerçevelerini işlemek için makine öğrenmesini kullanarak filtre uygulamak için bu nesneyi kullanabilirsiniz.

İşlenen ham ekran paylaşımı çerçeveleri gönderenin giden ekran paylaşımı olarak gönderilebilir. İşlenen ham gelen ekran paylaşımı çerçeveleri alıcı tarafında işlenebilir.

Not: Ekran paylaşımı gönderme işlemi yalnızca masaüstü tarayıcılarında desteklenir.

Özel bir ekran paylaşımı akışıyla ekran paylaşımını başlatma

const createVideoMediaStreamToSend = () => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1500;
    canvas.height = 845;
    ctx.fillStyle = 'blue';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const colors = ['red', 'yellow', 'green'];
    window.setInterval(() => {
        if (ctx) {
            ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
            const x = Math.floor(Math.random() * canvas.width);
            const y = Math.floor(Math.random() * canvas.height);
            const size = 100;
            ctx.fillRect(x, y, size, size);
        }
    }, 1000 / 30);

    return canvas.captureStream(30);
};

...
const mediaStream = createVideoMediaStreamToSend();
const localScreenSharingStream = new LocalVideoStream(mediaStream);
// Will start screen sharing with custom raw media stream
await call.startScreenSharing(localScreenSharingStream);
console.log(localScreenSharingStream.mediaStreamType) // 'RawMedia'

Ham ekran paylaşımı akışına bir ekrandan, tarayıcı sekmesinden veya uygulamadan erişin ve akışa efektler uygulayın

Aşağıda, bir ekrandan, tarayıcı sekmesinden veya uygulamadan ham ekran paylaşım akışına siyah beyaz efekti uygulama örneği verilmiştir. NOT: Tuval bağlam filtresi = "gri tonlama(1)" API'si Safari'de desteklenmez.

let bwTimeout;
let bwVideoElem;

const applyBlackAndWhiteEffect = function (stream) {
	let width = 1280, height = 720;
	bwVideoElem = document.createElement("video");
	bwVideoElem.srcObject = stream;
	bwVideoElem.height = height;
	bwVideoElem.width = width;
	bwVideoElem.play();
	const canvas = document.createElement('canvas');
	const bwCtx = canvas.getContext('2d', { willReadFrequently: true });
	canvas.width = width;
	canvas.height = height;
	
	const FPS = 30;
	const processVideo = function () {
	    try {
		let begin = Date.now();
		// start processing.
		// NOTE: The Canvas context filter API is not supported in Safari
		bwCtx.filter = "grayscale(1)";
		bwCtx.drawImage(bwVideoElem, 0, 0, width, height);
		const imageData = bwCtx.getImageData(0, 0, width, height);
		bwCtx.putImageData(imageData, 0, 0);
		// schedule the next one.
		let delay = Math.abs(1000/FPS - (Date.now() - begin));
		bwTimeout = setTimeout(processVideo, delay);
	    } catch (err) {
		console.error(err);
	    }
	}
	
	// schedule the first one.
	bwTimeout = setTimeout(processVideo, 0);
	return canvas.captureStream(FPS);
}

// Call startScreenSharing API without passing any stream parameter. Browser will prompt the user to select the screen, browser tab, or app to share in the call.
await call.startScreenSharing();
const localScreenSharingStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing' });
console.log(localScreenSharingStream.mediaStreamType); // 'ScreenSharing'
// Get the raw media stream from the screen, browser tab, or application
const rawMediaStream = await localScreenSharingStream.getMediaStream();
// Apply effects to the media stream as you wish
const blackAndWhiteMediaStream = applyBlackAndWhiteEffect(rawMediaStream);
// Set the media stream with effects no the local screen sharing stream
await localScreenSharingStream.setMediaStream(blackAndWhiteMediaStream);

// Stop screen sharing and clean up the black and white video filter
await call.stopScreenSharing();
clearTimeout(bwTimeout);
bwVideoElem.srcObject.getVideoTracks().forEach((track) => { track.stop(); });
bwVideoElem.srcObject = null;

Ekran paylaşımı akışı göndermeyi durdurma

Arama sırasında ayarlandıktan sonra özel bir ekran paylaşımı akışı göndermeyi durdurmak için aşağıdaki kodu kullanın.

// Stop sending raw screen sharing stream
await call.stopScreenSharing(localScreenSharingStream);

Uzak katılımcıdan gelen ekran paylaşımı akışına erişme

Ham ekran paylaşımı akışına uzak bir katılımcıdan erişebilirsiniz. Makine öğrenmesini kullanarak çerçeveleri işlemek ve filtreleri uygulamak için gelen ham ekran paylaşımı akışı için kullanırsınız MediaStream . İşlenen gelen ekran paylaşımı akışı daha sonra alıcı tarafında işlenebilir.

const remoteScreenSharingStream = remoteParticipants[0].videoStreams.find((stream) => { return stream.mediaStreamType === 'ScreenSharing'});
const processMediaStream = async () => {
    if (remoteScreenSharingStream.isAvailable) {
	// remote screen sharing stream is turned on, process the stream's raw media stream.
	const mediaStream = await remoteScreenSharingStream.getMediaStream();
    } else {
	// remote video stream is turned off, handle it
    }
};

remoteScreenSharingStream.on('isAvailableChanged', async () => {
    await processMediaStream();
});

await processMediaStream();

Sonraki adımlar

Daha fazla bilgi için aşağıdaki makaleleri inceleyin: