Closed gfclaveria closed 7 years ago
Hi, If you wish to record audio, you will need to subscribe to the AudioMediaReceived Event m_audioSocket.AudioMediaReceived += OnAudioMediaReceived; This will be raised each time you have a received buffer.
private async void OnAudioMediaReceived(object sender, AudioMediaReceivedEventArgs e)
{
var buffer = e.Buffer;
try
{
if (m_wavFileWriter != null)
{
long length = buffer.Length;
var retrievedBuffer = new byte[length];
Marshal.Copy(buffer.Data, retrievedBuffer, 0, (int) length);
await m_wavFileWriter.EnqueueAsync(retrievedBuffer);
}
}
catch (Exception ex)
{
//log exception
}
finally
{
buffer.Dispose();
}
}
you can use this helper class to write to a wav file, this has dependency on the TPL dataflow nuget and System.Threading -First you need to create the writer
m_wavFileWriter = new WavFileWriter("WavOut" + DateTime.UtcNow.Ticks + ".wav",
new WavFileSettings());
await m_wavFileWriter.InitializeAsync();
-Then, when the app is done recording call shutdown:
await m_wavFileWriter.ShutdownAsync();
This is the code for the helper class
/// <summary>
/// wav file writer, this class will create a wav file
/// from the received buffers in the smart agents.
/// </summary>
internal class WavFileWriter
{
private BufferBlock<byte[]> m_queueBlock;
private FileStream m_fileStream;
private readonly CancellationTokenSource m_queueCancellationTokenSource;
private readonly SemaphoreSlim m_syncLock = new SemaphoreSlim(1);
private readonly WavFileSettings m_wavFileSettings;
private long m_riffChunkSizePosition;
private long m_dataChunkSizePosition;
public bool IsInitialized { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="fileName"></param>
/// <param name="wavFileSettings"></param>
public WavFileWriter(string fileName, WavFileSettings wavFileSettings)
{
m_fileStream = new FileStream(fileName, FileMode.OpenOrCreate,
FileAccess.ReadWrite,
FileShare.None);
m_queueCancellationTokenSource = new CancellationTokenSource();
m_queueBlock = new BufferBlock<byte[]>(new DataflowBlockOptions { CancellationToken = m_queueCancellationTokenSource.Token});
m_wavFileSettings = wavFileSettings;
}
private void WriteRiffChunk(BinaryWriter bw)
{
bw.Write(0x46464952); //'RIFF'
//We remember the riffChunkSizePoistion that we have to update later on stream close
m_riffChunkSizePosition = bw.BaseStream.Position; //Filelength - 8 bytes
bw.Write((int)50); //a 0sec wav file is atleast 58 bytes
bw.Write(0x45564157); //'WAVE'
}
private void WriteFmtChunk(BinaryWriter bw)
{
bw.Write((int)0x20746D66); //'fmt '
bw.Write(16); //16 bytes of format. We produce no 'extra format info'
bw.Write(m_wavFileSettings.CompressionCode); //2bytes
bw.Write(m_wavFileSettings.NumberOfChannels); //2bytes
bw.Write(m_wavFileSettings.SampleRate); //4bytes
bw.Write(m_wavFileSettings.AvgBytesPerSecond); //4bytes
bw.Write((short)2); //alignment
bw.Write((short)16); //significant bits per sample
}
private void WriteFactChunk(BinaryWriter bw)
{
bw.Write((int)0x74636166); //'fact' chunk ID
bw.Write((int)4); //4 byte Fact Chunk size
bw.Write((int)0); //4 byte chunk data.
}
private void WriteDataChunk(BinaryWriter bw)
{
bw.Write(0x61746164); //'data' chunk ID
//We remember the dataChunkPosition that we have to update later on stream close
m_dataChunkSizePosition = bw.BaseStream.Position;
bw.Write((int)0); //initially, we have no data, so we set the chunk size to 0
}
/// <summary>
/// Initializes the consumer of the queue to wait for new items and process them if available
/// </summary>
/// <returns></returns>
public async Task InitializeAsync()
{
if (!IsInitialized)
{
await m_syncLock.WaitAsync();
if (!IsInitialized)
{
IsInitialized = true;
//Initialize the headers
Debug.Assert(m_fileStream != null, "m_fileStream != null");
BinaryWriter bw = new BinaryWriter(m_fileStream);
WriteRiffChunk(bw);
WriteFmtChunk(bw);
WriteFactChunk(bw);
WriteDataChunk(bw);
await Task.Factory.StartNew( () => DequeueAndProcessAsync());
}
m_syncLock.Release();
}
}
/// <summary>
/// Dequeue and process async workitems
/// </summary>
/// <returns></returns>
internal async Task DequeueAndProcessAsync()
{
try
{
while (await m_queueBlock.OutputAvailableAsync(m_queueCancellationTokenSource.Token))
{
var buffer = await m_queueBlock.ReceiveAsync(m_queueCancellationTokenSource.Token);
if (buffer != null)
{
await m_fileStream.WriteAsync(buffer, 0, buffer.Length);
}
this.m_queueCancellationTokenSource.Token.ThrowIfCancellationRequested();
}
}
catch (TaskCanceledException ex)
{
Debug.Write(string.Format("The queue processing task has been cancelled. Exception: {0}", ex));
}
catch (ObjectDisposedException ex)
{
Debug.Write(string.Format("The queue processing task object has been disposed. Exception: {0}", ex));
}
catch (Exception ex)
{
// Catch all other exceptions and log
Debug.Write(string.Format("Caught Exception: {0}", ex));
// Continue processing elements in the queue
await DequeueAndProcessAsync();
}
}
/// <summary>
/// Enqueue a waitable work item
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public async Task EnqueueAsync(byte[] buffer)
{
try
{
await m_queueBlock.SendAsync(buffer, m_queueCancellationTokenSource.Token);
}
catch (TaskCanceledException e)
{
if (m_queueBlock != null)
{
m_queueBlock.Complete();
}
Debug.Write(string.Format("Cannot enqueue because queuing operation has been cancelled. Exception: {0}", e));
}
catch (Exception e)
{
Debug.Write(string.Format("Failed to enqueue: {0}", e));
}
}
/// <summary>
/// ShutDown the queue, this will also cancel current operations
/// </summary>
/// <returns></returns>
public async Task ShutdownAsync()
{
if (IsInitialized)
{
await m_syncLock.WaitAsync();
if (IsInitialized)
{
Debug.Assert(m_queueBlock != null);
// Allow no more processing on the queue
m_queueBlock.Complete();
// Cancel the queue task
m_queueCancellationTokenSource.Cancel();
m_queueCancellationTokenSource.Dispose();
IsInitialized = false;
CloseFileStream();
}
m_syncLock.Release();
}
}
private void CloseFileStream()
{
if (m_fileStream != null)
{
try
{
using(BinaryWriter bw = new BinaryWriter(m_fileStream))
{
//Lets update the riffChunkSize header value
m_fileStream.Seek(m_riffChunkSizePosition, SeekOrigin.Begin);
bw.Write((int)(m_fileStream.Length - 8));
//... and the dataChunksize header value;
m_fileStream.Seek(m_dataChunkSizePosition, SeekOrigin.Begin);
bw.Write((int)(m_fileStream.Length - (m_dataChunkSizePosition + 4)));
}
}
finally
{
m_fileStream.Close();
}
}
}
}
internal class WavFileSettings
{
public short CompressionCode { get; set; }
public short NumberOfChannels { get; set; }
public int SampleRate { get; set; }
public int AvgBytesPerSecond { get; set; }
/// <summary>
/// Default constructor with default PCM 16 mono
/// </summary>
public WavFileSettings()
{
CompressionCode = 1; //PCM
NumberOfChannels = 1; //No Stereo
SampleRate = 16000; //16khz only
AvgBytesPerSecond = 32000;
}
}
Are you trying to record within a multiparty group call? Recording the audio of individual participants in a group call is not supported. Please be aware that bots for group calls are currently not supported and such bots will likely stop working soon.
@waboum Thanks! I will try this out.
@ssulzer Yes, I am hoping i could record multiparty group call. I currently working with the MeetingScreenshotsBot and it seems that bot is working as it should with group call. From what you've said, does that mean that this sample bot you have (or the feature it has) will be ditched out soon? May I know if this will be supported in the future. Also, bots for Skype For Business??
Thanks,
@gfclaveria Upcoming changes will prevent calling and real-time media bots from joining Skype group calls, and unfortunately no timeframe regarding when they will be supported. Calling and real-time media bots will be allowed only in 1-to-1 Skype calls.
No information yet regarding calling and media bots for Skype for Business Online are not supported.
@ssulzer Additional question. May we known the reason why will you be removing group calls feature for bot?
@gfclaveria currently the bot can record audio and video of any user in the conference which is a privacy concern.
Hi
Do you have sample on how to record the audio of the conversation? Most specially the audio of each participant on the call conversation.
thanks,