Quickstart: Onbewerkte mediatoegang toevoegen aan uw app
In deze quickstart leert u hoe u onbewerkte mediatoegang implementeert met behulp van de Azure Communication Services Calling SDK voor Unity. De Azure Communication Services Calling SDK biedt API's waarmee apps hun eigen videoframes kunnen genereren om onbewerkte videoframes te verzenden of weer te geven van externe deelnemers aan een gesprek. Deze quickstart bouwt voort op quickstart: 1:1 videogesprekken toevoegen aan uw app voor Unity.
RawVideo-toegang
Omdat de app de videoframes genereert, moet de app de Azure Communication Services Calling SDK informeren over de video-indelingen die de app kan genereren. Met deze informatie kan de Sdk voor aanroepen van Azure Communication Services de beste configuratie van de video-indeling kiezen voor de netwerkvoorwaarden op dat moment.
Virtuele video
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum 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 |
Volg de stappen in deze quickstart: 1:1 videogesprekken toevoegen aan uw app om unity-games te maken. Het doel is om een
CallAgent
object te verkrijgen dat gereed is om de aanroep te starten. Zoek de voltooide code voor deze quickstart op GitHub.Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund. Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.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 };
Maak
RawOutgoingVideoStreamOptions
en stel deze inFormats
met het eerder gemaakte object.var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions { Formats = videoStreamFormats };
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.var rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
Abonneer u op de
RawOutgoingVideoStream.FormatChanged
gemachtigde. Met deze gebeurtenis wordt opgegeven wanneer deVideoStreamFormat
video is gewijzigd van een van de video-indelingen in de lijst.rawOutgoingVideoStream.FormatChanged += (object sender, VideoStreamFormatChangedEventArgs args) { VideoStreamFormat videoStreamFormat = args.Format; }
Abonneer u op de
RawOutgoingVideoStream.StateChanged
gemachtigde. Met deze gebeurtenis wordt opgegeven wanneer deState
wijziging is doorgevoerd.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; } }
Verwerking van onbewerkte uitgaande transacties voor videostreams, zoals Starten en Stoppen en beginnen met het genereren van aangepaste videoframes of het algoritme voor het genereren van frame onderbreken.
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; } }
Hier volgt een voorbeeld van een uitgaande videoframegenerator:
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 }; }
Notitie
unsafe
modifier wordt gebruikt voor deze methode omdatNativeBuffer
toegang tot systeemeigen geheugenbronnen is vereist.Allow unsafe
Daarom moet de optie ook worden ingeschakeld in Unity Editor.Op dezelfde manier kunnen we binnenkomende videoframes verwerken als reactie op de gebeurtenis van de videostream
StateChanged
.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 }); }
Het wordt ten zeerste aanbevolen om zowel binnenkomende als uitgaande videoframes te beheren via een buffermechanisme om overbelasting van de
MonoBehaviour.Update()
callback-methode te voorkomen, die licht moet worden gehouden en cpu- of netwerkzware taken moet voorkomen en een soepelere video-ervaring te garanderen. Deze optionele optimalisatie wordt aan ontwikkelaars overgelaten om te bepalen wat het beste werkt in hun scenario's.Hier volgt een voorbeeld van hoe de binnenkomende frames kunnen worden weergegeven in een Unity
VideoTexture
door een interne wachtrij aan te roepenGraphics.Blit
: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(); } }
In deze quickstart leert u hoe u onbewerkte mediatoegang implementeert met behulp van de Azure Communication Services Calling SDK voor Windows. De Azure Communication Services Calling SDK biedt API's waarmee apps hun eigen videoframes kunnen genereren om te verzenden naar externe deelnemers in een gesprek. Deze quickstart bouwt voort op quickstart: 1:1 videogesprekken toevoegen aan uw app voor Windows.
RawAudio-toegang
Als u toegang krijgt tot onbewerkte audiomedia, hebt u toegang tot de audiostream van het binnenkomende gesprek, samen met de mogelijkheid om aangepaste uitgaande audiostreams tijdens een gesprek weer te geven en te verzenden.
Onbewerkte uitgaande audio verzenden
Maak een optiesobject waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen verzenden.
RawOutgoingAudioStreamProperties outgoingAudioProperties = new RawOutgoingAudioStreamProperties()
{
Format = ACSAudioStreamFormat.Pcm16Bit,
SampleRate = AudioStreamSampleRate.Hz48000,
ChannelMode = AudioStreamChannelMode.Stereo,
BufferDuration = AudioStreamBufferDuration.InMs20
};
RawOutgoingAudioStreamOptions outgoingAudioStreamOptions = new RawOutgoingAudioStreamOptions()
{
Properties = outgoingAudioProperties
};
Maak een RawOutgoingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties en de stream wordt automatisch gestart wanneer de oproep is verbonden.
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.
Stream koppelen aan een gesprek
U kunt de stream ook koppelen aan een bestaand Call
exemplaar:
await call.StartAudio(rawOutgoingAudioStream);
Beginnen met het verzenden van onbewerkte voorbeelden
We kunnen alleen beginnen met het verzenden van gegevens zodra de stroomstatus is AudioStreamState.Started
.
Als u de status van de audiostream wilt bekijken, voegt u een listener toe aan de OnStateChangedListener
gebeurtenis.
unsafe private void AudioStateChanged(object sender, AudioStreamStateChanged args)
{
if (args.AudioStreamState == AudioStreamState.Started)
{
// We can now start sending samples.
}
}
outgoingAudioStream.StateChanged += AudioStateChanged;
Wanneer de stream is gestart, kunnen we beginnen met het verzenden van MemoryBuffer
audiovoorbeelden naar het gesprek.
De audiobufferindeling moet overeenkomen met de opgegeven stroomeigenschappen.
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();
}
Raw-audio ontvangen
We kunnen ook de voorbeelden van de gespreksaudiostream ontvangen alsof MemoryBuffer
we de audiostream van de oproep willen verwerken voordat ze worden afgespeeld.
Maak een RawIncomingAudioStreamOptions
object waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen ontvangen.
RawIncomingAudioStreamProperties properties = new RawIncomingAudioStreamProperties()
{
Format = AudioStreamFormat.Pcm16Bit,
SampleRate = AudioStreamSampleRate.Hz44100,
ChannelMode = AudioStreamChannelMode.Stereo
};
RawIncomingAudioStreamOptions options = new RawIncomingAudioStreamOptions()
{
Properties = properties
};
Maak een RawIncomingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties
JoinCallOptions options = JoinCallOptions(); // or StartCallOptions()
RawIncomingAudioStream rawIncomingAudioStream = new RawIncomingAudioStream(audioStreamOptions);
IncomingAudioOptions incomingAudioOptions = new IncomingAudioOptions()
{
Stream = rawIncomingAudioStream
};
options.IncomingAudioOptions = incomingAudioOptions;
Of we kunnen de stream ook koppelen aan een bestaand Call
exemplaar:
await call.startAudio(context, rawIncomingAudioStream);
Als u onbewerkte audiobuffers van de binnenkomende stream wilt ontvangen, voegt u listeners toe aan de status van de binnenkomende stroom en buffer ontvangen gebeurtenissen.
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-toegang
Omdat de app de videoframes genereert, moet de app de Azure Communication Services Calling SDK informeren over de video-indelingen die de app kan genereren. Met deze informatie kan de Sdk voor aanroepen van Azure Communication Services de beste configuratie van de video-indeling kiezen voor de netwerkvoorwaarden op dat moment.
Virtuele video
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum 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 |
Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund. Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.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 };
Maak
RawOutgoingVideoStreamOptions
en stel deze inFormats
met het eerder gemaakte object.var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions { Formats = videoStreamFormats };
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.var rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
Abonneer u op de
RawOutgoingVideoStream.FormatChanged
gemachtigde. Met deze gebeurtenis wordt opgegeven wanneer deVideoStreamFormat
video is gewijzigd van een van de video-indelingen in de lijst.rawOutgoingVideoStream.FormatChanged += (object sender, VideoStreamFormatChangedEventArgs args) { VideoStreamFormat videoStreamFormat = args.Format; }
Een exemplaar van de volgende helperklasse maken voor toegang tot de buffergegevens
[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; } }
Een exemplaar van de volgende helperklasse maken om willekeurige
RawVideoFrame
exemplaren te genereren met behulp vanVideoStreamPixelFormat.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; } } }
Abonneer u op de
VideoStream.StateChanged
gemachtigde. Met deze gebeurtenis wordt de status van de huidige stream geïnformeerd. Verzend geen frames als de status niet gelijk is aanVideoStreamState.Started
.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; } };
Video delen op het scherm
Omdat het Windows-systeem de frames genereert, moet u uw eigen voorgrondservice implementeren om de frames vast te leggen en te verzenden met behulp van de Api voor aanroepen van Azure Communication Services.
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum FPS |
---|---|---|
Iets | Alles tot 1080p | 30 |
Stappen voor het maken van een videostream voor het delen van een scherm
- Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund. Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.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 };
- Maak
RawOutgoingVideoStreamOptions
en stel deze inVideoFormats
met het eerder gemaakte object.var rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions { Formats = videoStreamFormats };
- Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.var rawOutgoingVideoStream = new ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions);
- Leg het videoframe op de volgende manier vast en verzend het.
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; } }
Onbewerkte binnenkomende video
Met deze functie krijgt u toegang tot de videoframes in de IncomingVideoStream
's om deze streams lokaal te bewerken
- Een exemplaar van
IncomingVideoOptions
die sets maken viaJoinCallOptions
instellingVideoStreamKind.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 };
- Zodra u een gedelegeerde voor het koppelen
RemoteParticipant.VideoStreamStateChanged
van gebeurtenissenParticipantsUpdatedEventArgs
ontvangt. Met deze gebeurtenis wordt de status van deIncomingVideoStream
objecten geïnformeerd.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; } }
- Op het moment heeft
VideoStreamState.Available
deIncomingVideoStream
status gedelegeerde voor het koppelenRawIncomingVideoStream.RawVideoFrameReceived
van de status, zoals wordt weergegeven in de vorige stap. Dit biedt de nieuweRawVideoFrame
objecten.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; } }
In deze quickstart leert u hoe u onbewerkte mediatoegang implementeert met behulp van de Azure Communication Services Calling SDK voor Android.
De Azure Communication Services Calling SDK biedt API's waarmee apps hun eigen videoframes kunnen genereren om te verzenden naar externe deelnemers in een gesprek.
Deze quickstart bouwt voort op quickstart: 1:1 videogesprekken toevoegen aan uw app voor Android.
RawAudio-toegang
Door toegang te krijgen tot onbewerkte audiomedia hebt u toegang tot de binnenkomende audiostream van het gesprek, samen met de mogelijkheid om aangepaste uitgaande audiostreams tijdens een gesprek weer te geven en te verzenden.
Onbewerkte uitgaande audio verzenden
Maak een optiesobject waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen verzenden.
RawOutgoingAudioStreamProperties outgoingAudioProperties = new RawOutgoingAudioStreamProperties()
.setAudioFormat(AudioStreamFormat.PCM16_BIT)
.setSampleRate(AudioStreamSampleRate.HZ44100)
.setChannelMode(AudioStreamChannelMode.STEREO)
.setBufferDuration(AudioStreamBufferDuration.IN_MS20);
RawOutgoingAudioStreamOptions outgoingAudioStreamOptions = new RawOutgoingAudioStreamOptions()
.setProperties(outgoingAudioProperties);
Maak een RawOutgoingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties en de stream wordt automatisch gestart wanneer de oproep is verbonden.
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.
Stream koppelen aan een gesprek
U kunt de stream ook koppelen aan een bestaand Call
exemplaar:
CompletableFuture<Void> result = call.startAudio(context, rawOutgoingAudioStream);
Beginnen met het verzenden van onbewerkte voorbeelden
We kunnen alleen beginnen met het verzenden van gegevens zodra de stroomstatus is AudioStreamState.STARTED
.
Als u de status van de audiostream wilt bekijken, voegt u een listener toe aan de OnStateChangedListener
gebeurtenis.
private void onStateChanged(PropertyChangedEvent propertyChangedEvent) {
// When value is `AudioStreamState.STARTED` we'll be able to send audio samples.
}
rawOutgoingAudioStream.addOnStateChangedListener(this::onStateChanged)
Wanneer de stream is gestart, kunnen we beginnen met het verzenden van java.nio.ByteBuffer
audiovoorbeelden naar het gesprek.
De audiobufferindeling moet overeenkomen met de opgegeven stroomeigenschappen.
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();
Raw-audio ontvangen
We kunnen ook de voorbeelden van de gespreksaudiostream ontvangen alsof java.nio.ByteBuffer
we de audio willen verwerken voordat ze worden afgespeeld.
Maak een RawIncomingAudioStreamOptions
object waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen ontvangen.
RawIncomingAudioStreamOptions options = new RawIncomingAudioStreamOptions();
RawIncomingAudioStreamProperties properties = new RawIncomingAudioStreamProperties()
.setAudioFormat(AudioStreamFormat.PCM16_BIT)
.setSampleRate(AudioStreamSampleRate.HZ44100)
.setChannelMode(AudioStreamChannelMode.STEREO);
options.setProperties(properties);
Maak een RawIncomingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties
JoinCallOptions options = JoinCallOptions() // or StartCallOptions()
IncomingAudioOptions incomingAudioOptions = new IncomingAudioOptions();
RawIncomingAudioStream rawIncomingAudioStream = new RawIncomingAudioStream(audioStreamOptions);
incomingAudioOptions.setStream(rawIncomingAudioStream);
options.setIncomingAudioOptions(incomingAudioOptions);
Of we kunnen de stream ook koppelen aan een bestaand Call
exemplaar:
CompletableFuture<Void> result = call.startAudio(context, rawIncomingAudioStream);
Als u onbewerkte audiobuffers van de binnenkomende stream wilt ontvangen, voegt u listeners toe aan de status van de binnenkomende stroom en buffer ontvangen gebeurtenissen.
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);
Het is ook belangrijk om te onthouden dat u de audiostream in het huidige gespreksexemplaren Call
stopt:
CompletableFuture<Void> result = call.stopAudio(context, rawIncomingAudioStream);
RawVideo-toegang
Omdat de app de videoframes genereert, moet de app de Azure Communication Services Calling SDK informeren over de video-indelingen die de app kan genereren. Met deze informatie kan de Sdk voor aanroepen van Azure Communication Services de beste configuratie van de video-indeling kiezen voor de netwerkvoorwaarden op dat moment.
Virtuele video
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum 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 |
Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund.Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.
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);
Maak
RawOutgoingVideoStreamOptions
en stel deze inFormats
met het eerder gemaakte object.RawOutgoingVideoStreamOptions rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions(); rawOutgoingVideoStreamOptions.setFormats(videoStreamFormats);
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.VirtualOutgoingVideoStream rawOutgoingVideoStream = new VirtualOutgoingVideoStream(rawOutgoingVideoStreamOptions);
Abonneer u op de
RawOutgoingVideoStream.addOnFormatChangedListener
gemachtigde. Met deze gebeurtenis wordt opgegeven wanneer deVideoStreamFormat
video is gewijzigd van een van de video-indelingen in de lijst.virtualOutgoingVideoStream.addOnFormatChangedListener((VideoStreamFormatChangedEvent args) -> { VideoStreamFormat videoStreamFormat = args.Format; });
Een exemplaar van de volgende helperklasse maken om willekeurige
RawVideoFrame
exemplaren te genereren met behulp vanVideoStreamPixelFormat.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(); } } }
Abonneer u op de
VideoStream.addOnStateChangedListener
gemachtigde. Deze gemachtigde informeert de status van de huidige stream. Verzend geen frames als de status niet gelijk is aanVideoStreamState.STARTED
.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 Video
Omdat het Windows-systeem de frames genereert, moet u uw eigen voorgrondservice implementeren om de frames vast te leggen en te verzenden met behulp van de Api voor aanroepen van Azure Communication Services.
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum FPS |
---|---|---|
Iets | Alles tot 1080p | 30 |
Stappen voor het maken van een videostream voor het delen van een scherm
Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund.Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.
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);
Maak
RawOutgoingVideoStreamOptions
en stel deze inVideoFormats
met het eerder gemaakte object.RawOutgoingVideoStreamOptions rawOutgoingVideoStreamOptions = new RawOutgoingVideoStreamOptions(); rawOutgoingVideoStreamOptions.setFormats(videoStreamFormats);
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.ScreenShareOutgoingVideoStream rawOutgoingVideoStream = new ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions);
Leg het videoframe op de volgende manier vast en verzend het.
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(); } }
Onbewerkte binnenkomende video
Met deze functie krijgt u toegang tot de videoframes in de IncomingVideoStream
objecten om deze frames lokaal te bewerken
Een exemplaar van
IncomingVideoOptions
die sets maken viaJoinCallOptions
instellingVideoStreamKind.RawIncoming
IncomingVideoOptions incomingVideoOptions = new IncomingVideoOptions() .setStreamType(VideoStreamKind.RAW_INCOMING); JoinCallOptions joinCallOptions = new JoinCallOptions() .setIncomingVideoOptions(incomingVideoOptions);
Zodra u een gedelegeerde voor het koppelen
RemoteParticipant.VideoStreamStateChanged
van gebeurtenissenParticipantsUpdatedEventArgs
ontvangt. Met deze gebeurtenis wordt de status van hetIncomingVideoStream
object geïnformeerd.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; } }
Op het moment heeft
VideoStreamState.Available
deIncomingVideoStream
status gedelegeerde voor het koppelenRawIncomingVideoStream.RawVideoFrameReceived
van de status, zoals wordt weergegeven in de vorige stap. Deze gedelegeerde biedt de nieuweRawVideoFrame
objecten.private void OnVideoFrameReceived(RawVideoFrameReceivedEventArgs args) { // Render/Modify/Save the video frame RawVideoFrameBuffer videoFrame = (RawVideoFrameBuffer) args.getFrame(); }
In deze quickstart leert u hoe u onbewerkte mediatoegang implementeert met behulp van de Azure Communication Services Calling SDK voor iOS.
De Azure Communication Services Calling SDK biedt API's waarmee apps hun eigen videoframes kunnen genereren om te verzenden naar externe deelnemers in een gesprek.
Deze quickstart bouwt voort op quickstart: 1:1 videogesprekken toevoegen aan uw app voor iOS.
RawAudio-toegang
Als u toegang krijgt tot onbewerkte audiomedia, hebt u toegang tot de audiostream van het binnenkomende gesprek, samen met de mogelijkheid om aangepaste uitgaande audiostreams tijdens een gesprek weer te geven en te verzenden.
Onbewerkte uitgaande audio verzenden
Maak een optiesobject waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen verzenden.
let outgoingAudioStreamOptions = RawOutgoingAudioStreamOptions()
let properties = RawOutgoingAudioStreamProperties()
properties.sampleRate = .hz44100
properties.bufferDuration = .inMs20
properties.channelMode = .mono
properties.format = .pcm16Bit
outgoingAudioStreamOptions.properties = properties
Maak een RawOutgoingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties en de stream wordt automatisch gestart wanneer de oproep is verbonden.
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.
Stream koppelen aan een gesprek
U kunt de stream ook koppelen aan een bestaand Call
exemplaar:
call.startAudio(stream: self.rawOutgoingAudioStream) { error in
// Stream attached to `Call`.
}
Onbewerkte voorbeelden verzenden
We kunnen alleen beginnen met het verzenden van gegevens zodra de stroomstatus is AudioStreamState.started
.
Om de statuswijziging van de audiostream te observeren, implementeren we de RawOutgoingAudioStreamDelegate
. En stel deze in als de streamdelegeer.
func rawOutgoingAudioStream(_ rawOutgoingAudioStream: RawOutgoingAudioStream,
didChangeState args: AudioStreamStateChangedEventArgs) {
// When value is `AudioStreamState.started` we will be able to send audio samples.
}
self.rawOutgoingAudioStream.delegate = DelegateImplementer()
of op basis van sluiting gebruiken
self.rawOutgoingAudioStream.events.onStateChanged = { args in
// When value is `AudioStreamState.started` we will be able to send audio samples.
}
Wanneer de stream is gestart, kunnen we beginnen met het verzenden van AVAudioPCMBuffer
audiovoorbeelden naar het gesprek.
De audiobufferindeling moet overeenkomen met de opgegeven stroomeigenschappen.
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()
}
}
Het is ook belangrijk om te onthouden dat u de audiostream in het huidige gespreksexemplaren Call
stopt:
call.stopAudio(stream: self.rawOutgoingAudioStream) { error in
// Stream detached from `Call` and stopped.
}
Voorbeelden van microfoon vastleggen
Met behulp van Apple AVAudioEngine
kunnen we microfoonframes vastleggen door te tikken op het invoerknooppunt van de audio-engine. En het vastleggen van de microfoongegevens en het gebruik van onbewerkte audiofunctionaliteit, we kunnen de audio verwerken voordat deze naar een gesprek wordt verzonden.
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()
}
}
Notitie
De voorbeeldfrequentie van het invoerknooppunt van de audio-engine is standaard ingesteld op een >waarde van de gewenste samplefrequentie voor de gedeelde audiosessie. Tik dus niet in dat knooppunt met een andere waarde.
We moeten er dus voor zorgen dat de samplefrequentie van de RawOutgoingStream
eigenschappen overeenkomt met de waarde die we krijgen van tap in microfoonvoorbeelden of de tapbuffers converteren naar de indeling die overeenkomt met wat er wordt verwacht voor de uitgaande stream.
Met dit kleine voorbeeld hebben we geleerd hoe we de microfoongegevens AVAudioEngine
kunnen vastleggen en deze voorbeelden naar een gesprek kunnen verzenden met behulp van een onbewerkte uitgaande audiofunctie.
Raw-audio ontvangen
We kunnen ook de voorbeelden van de gespreksaudiostream ontvangen alsof AVAudioPCMBuffer
we de audio willen verwerken voordat ze worden afgespeeld.
Maak een RawIncomingAudioStreamOptions
object waarin de onbewerkte stroomeigenschappen worden opgegeven die we willen ontvangen.
let options = RawIncomingAudioStreamOptions()
let properties = RawIncomingAudioStreamProperties()
properties.format = .pcm16Bit
properties.sampleRate = .hz44100
properties.channelMode = .stereo
options.properties = properties
Maak een RawOutgoingAudioStream
en voeg deze toe om deel te nemen aan gespreksopties
let options = JoinCallOptions() // or StartCallOptions()
let incomingAudioOptions = IncomingAudioOptions()
self.rawIncomingStream = RawIncomingAudioStream(rawIncomingAudioStreamOptions: audioStreamOptions)
incomingAudioOptions.stream = self.rawIncomingStream
options.incomingAudioOptions = incomingAudioOptions
Of we kunnen de stream ook koppelen aan een bestaand Call
exemplaar:
call.startAudio(stream: self.rawIncomingStream) { error in
// Stream attached to `Call`.
}
Voor het ontvangen van onbewerkte audiobuffer van de binnenkomende stream implementeert u het RawIncomingAudioStreamDelegate
volgende:
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()
or
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-toegang
Omdat de app de videoframes genereert, moet de app de Azure Communication Services Calling SDK informeren over de video-indelingen die de app kan genereren. Met deze informatie kan de Sdk voor aanroepen van Azure Communication Services de beste configuratie van de video-indeling kiezen voor de netwerkvoorwaarden op dat moment.
Virtuele video
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum 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 |
Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund. Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.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)
Maak
RawOutgoingVideoStreamOptions
en stel opmaak in met het eerder gemaakte object.var rawOutgoingVideoStreamOptions = RawOutgoingVideoStreamOptions() rawOutgoingVideoStreamOptions.formats = videoStreamFormats
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.var rawOutgoingVideoStream = VirtualOutgoingVideoStream(videoStreamOptions: rawOutgoingVideoStreamOptions)
Implementeren voor de
VirtualOutgoingVideoStreamDelegate
gemachtigde. DedidChangeFormat
gebeurtenis informeert wanneer deVideoStreamFormat
video is gewijzigd van een van de video-indelingen in de lijst.virtualOutgoingVideoStream.delegate = /* Attach delegate and implement didChangeFormat */
Een exemplaar van de volgende helperklasse maken voor toegang tot
CVPixelBuffer
gegevensfinal 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 } }
Een exemplaar van de volgende helperklasse maken om willekeurige
RawVideoFrameBuffer
exemplaren te genereren met behulp vanVideoStreamPixelFormat.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 } } }
Implementeren op de
VirtualOutgoingVideoStreamDelegate
. DedidChangeState
gebeurtenis informeert de status van de huidige stream. Verzend geen frames als de status niet gelijk is aanVideoStreamState.started
./*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 Video
Omdat het Windows-systeem de frames genereert, moet u uw eigen voorgrondservice implementeren om de frames vast te leggen en te verzenden met behulp van de Api voor aanroepen van Azure Communication Services.
Ondersteunde videoresoluties
Hoogte-breedteverhouding | Oplossing | Maximum FPS |
---|---|---|
Iets | Alles tot 1080p | 30 |
Stappen voor het maken van een videostream voor het delen van een scherm
Maak een matrix van het gebruik van
VideoFormat
de VideoStream PixelFormat die door de SDK wordt ondersteund. Wanneer er meerdere indelingen beschikbaar zijn, heeft de volgorde van de indelingen in de lijst geen invloed op of geeft deze prioriteit aan welke wordt gebruikt. De criteria voor het selecteren van opmaak zijn gebaseerd op externe factoren, zoals netwerkbandbreedte.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)
Maak
RawOutgoingVideoStreamOptions
en stel deze inVideoFormats
met het eerder gemaakte object.var rawOutgoingVideoStreamOptions = RawOutgoingVideoStreamOptions() rawOutgoingVideoStreamOptions.formats = videoStreamFormats
Maak een exemplaar van met behulp van
VirtualOutgoingVideoStream
hetRawOutgoingVideoStreamOptions
exemplaar dat u eerder hebt gemaakt.var rawOutgoingVideoStream = ScreenShareOutgoingVideoStream(rawOutgoingVideoStreamOptions)
Leg het videoframe op de volgende manier vast en verzend het.
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*/ } }
Onbewerkte binnenkomende video
Met deze functie krijgt u toegang tot de videoframes in de IncomingVideoStream
's om deze streamobjecten lokaal te bewerken
Een exemplaar van
IncomingVideoOptions
die sets maken viaJoinCallOptions
instellingVideoStreamKind.RawIncoming
var incomingVideoOptions = IncomingVideoOptions() incomingVideoOptions.streamType = VideoStreamKind.rawIncoming var joinCallOptions = JoinCallOptions() joinCallOptions.incomingVideoOptions = incomingVideoOptions
Zodra u een gedelegeerde voor het koppelen
RemoteParticipant.delegate.didChangedVideoStreamState
van gebeurtenissenParticipantsUpdatedEventArgs
ontvangt. Met deze gebeurtenis wordt de status van deIncomingVideoStream
objecten geïnformeerd.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 } }
Op het moment heeft
VideoStreamState.available
deIncomingVideoStream
status gedelegeerde voor het koppelenRawIncomingVideoStream.delegate.didReceivedRawVideoFrame
van de status, zoals wordt weergegeven in de vorige stap. Deze gebeurtenis biedt de nieuweRawVideoFrame
objecten.func rawIncomingVideoStream(_ rawIncomingVideoStream: RawIncomingVideoStream, didRawVideoFrameReceived args: RawVideoFrameReceivedEventArgs) { /* Render/Modify/Save the video frame */ let videoFrame = args.frame as! RawVideoFrameBuffer }
Als ontwikkelaar hebt u toegang tot de onbewerkte media voor binnenkomende en uitgaande audio, video en scherm delen tijdens een gesprek, zodat u audio-/video-inhoud kunt vastleggen, analyseren en verwerken. Toegang tot onbewerkte audio-, onbewerkte video- en onbewerkte schermshares aan de clientzijde van Azure Communication Services biedt ontwikkelaars een bijna onbeperkte mogelijkheid om audio, video en scherminhoud te bekijken en te bewerken die plaatsvindt binnen de Azure Communication Services Calling SDK. In deze quickstart leert u hoe u onbewerkte mediatoegang implementeert met behulp van de Azure Communication Services Calling SDK voor JavaScript.
Voorbeeld:
- U kunt de audio-/videostream van het gesprek rechtstreeks op het oproepobject openen en aangepaste uitgaande audio-/videostreams verzenden tijdens het gesprek.
- U kunt audio- en videostreams inspecteren om aangepaste AI-modellen uit te voeren voor analyse. Dergelijke modellen kunnen natuurlijke taalverwerking bevatten om gesprekken te analyseren of om realtime inzichten en suggesties te bieden om de productiviteit van agents te verhogen.
- Organisaties kunnen audio- en videomediastreams gebruiken om sentiment te analyseren bij het bieden van virtuele zorg voor patiënten of om hulp op afstand te bieden tijdens videogesprekken die mixed reality gebruiken. Met deze mogelijkheid wordt een pad geopend voor ontwikkelaars om innovaties toe te passen om interactie-ervaringen te verbeteren.
Vereisten
Belangrijk
De voorbeelden hier zijn beschikbaar in 1.13.1 van de Calling SDK voor JavaScript. Zorg ervoor dat u die versie of nieuwer gebruikt wanneer u deze quickstart probeert.
Toegang tot onbewerkte audio
Als u toegang krijgt tot onbewerkte audiomedia, hebt u toegang tot de audiostream van het binnenkomende gesprek, samen met de mogelijkheid om aangepaste uitgaande audiostreams tijdens een gesprek weer te geven en te verzenden.
Toegang tot een binnenkomende onbewerkte audiostream
Gebruik de volgende code voor toegang tot de audiostream van een binnenkomend gesprek.
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);
Een gesprek plaatsen met een aangepaste audiostream
Gebruik de volgende code om een gesprek te starten met een aangepaste audiostream in plaats van het microfoonapparaat van een gebruiker te gebruiken.
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);
Overschakelen naar een aangepaste audiostream tijdens een gesprek
Gebruik de volgende code om een invoerapparaat over te schakelen naar een aangepaste audiostream in plaats van het microfoonapparaat van een gebruiker te gebruiken tijdens een gesprek.
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);
Een aangepaste audiostream stoppen
Gebruik de volgende code om te stoppen met het verzenden van een aangepaste audiostream nadat deze tijdens een gesprek is ingesteld.
call.stopAudio();
Onbewerkte video openen
Onbewerkte videomedia geven u het exemplaar van een MediaStream
object. (Zie de JavaScript-documentatie voor meer informatie.) Onbewerkte videomedia bieden specifiek toegang tot het MediaStream
object voor binnenkomende en uitgaande oproepen. Voor onbewerkte video kunt u dat object gebruiken om filters toe te passen met behulp van machine learning om frames van de video te verwerken.
Verwerkte onbewerkte uitgaande videoframes kunnen worden verzonden als een uitgaande video van de afzender. Verwerkte onbewerkte binnenkomende videoframes kunnen aan de ontvangerzijde worden weergegeven.
Een gesprek plaatsen met een aangepaste videostream
U hebt toegang tot de onbewerkte videostream voor een uitgaande oproep. U gebruikt MediaStream
voor de uitgaande onbewerkte videostream om frames te verwerken met behulp van machine learning en om filters toe te passen. De verwerkte uitgaande video kan vervolgens worden verzonden als een videostream van een afzender.
In dit voorbeeld worden canvasgegevens naar een gebruiker verzonden als uitgaande video.
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);
Overschakelen naar een aangepaste videostream tijdens een gesprek
Gebruik de volgende code om een invoerapparaat over te schakelen naar een aangepaste videostream in plaats van het cameraapparaat van een gebruiker te gebruiken tijdens een gesprek.
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);
Een aangepaste videostream stoppen
Gebruik de volgende code om te stoppen met het verzenden van een aangepaste videostream nadat deze tijdens een gesprek is ingesteld.
// Stop video by passing the same `localVideoStream` instance that was used to start video
await call.stopVideo(localVideoStream);
Wanneer u overschakelt van een camera waarop aangepaste effecten zijn toegepast op een ander cameraapparaat, moet u eerst de video stoppen, de bron op de LocalVideoStream
en de video opnieuw starten.
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);
}
Toegang tot binnenkomende videostream van een externe deelnemer
U hebt toegang tot de onbewerkte videostream voor een inkomende oproep. U gebruikt MediaStream
voor de binnenkomende onbewerkte videostream om frames te verwerken met behulp van machine learning en om filters toe te passen. De verwerkte binnenkomende video kan vervolgens worden weergegeven aan de ontvangerzijde.
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();
Belangrijk
Deze functie van Azure Communication Services is momenteel beschikbaar als preview-versie.
Preview-API's en SDK's worden aangeboden zonder een service level agreement. U wordt aangeraden deze niet te gebruiken voor productieworkloads. Sommige functies worden mogelijk niet ondersteund of hebben mogelijk beperkte mogelijkheden.
Raadpleeg aanvullende gebruiksvoorwaarden voor Microsoft Azure Previews voor meer informatie.
Onbewerkte toegang tot schermdeling is beschikbaar in openbare preview en is beschikbaar als onderdeel van versie 1.15.1-beta.1+.
Toegang tot onbewerkt scherm delen
Onbewerkte schermsharemedia bieden specifiek toegang tot het MediaStream
object voor binnenkomende en uitgaande schermsharestreams. Voor het delen van onbewerkte schermen kunt u dat object gebruiken om filters toe te passen met behulp van machine learning om frames van de schermshare te verwerken.
Verwerkte frames voor onbewerkte schermshares kunnen worden verzonden als een uitgaande schermshare van de afzender. Verwerkte onbewerkte binnenkomende schermshareframes kunnen worden weergegeven aan de ontvangerzijde.
Opmerking: het verzenden van schermshares wordt alleen ondersteund in de bureaubladbrowser.
Scherm delen met een aangepaste stream voor schermshares
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'
Open de stream voor onbewerkte schermshares vanaf een scherm, browsertabblad of app en pas effecten toe op de stream
Hier volgt een voorbeeld van het toepassen van een zwart-wit effect op de stream voor het delen van onbewerkte schermen vanaf een scherm, browsertabblad of app. OPMERKING: Het canvascontextfilter = 'grayscale(1)' API wordt niet ondersteund in Safari.
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;
Stoppen met het verzenden van de stream voor schermshares
Gebruik de volgende code om te stoppen met het verzenden van een aangepaste stream voor schermshares nadat deze is ingesteld tijdens een gesprek.
// Stop sending raw screen sharing stream
await call.stopScreenSharing(localScreenSharingStream);
Toegang tot de stream voor binnenkomende schermshares van een externe deelnemer
U hebt toegang tot de stream voor onbewerkte schermshares van een externe deelnemer. U gebruikt MediaStream
de stroom voor de binnenkomende onbewerkte schermshare om frames te verwerken met behulp van machine learning en om filters toe te passen. De verwerkte stream voor binnenkomende schermshares kan vervolgens worden weergegeven aan de ontvangerzijde.
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();
Volgende stappen
Raadpleeg voor meer informatie de volgende artikelen:
- Bekijk het voorbeeld van de aanroepende hero.
- Aan de slag met de UI-bibliotheek.
- Meer informatie over de mogelijkheden van calling SDK.
- Meer informatie over hoe bellen werkt.