Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to save the audio conversation of the call? #19

Closed
gfclaveria opened this issue Jun 27, 2017 · 6 comments
Closed

How to save the audio conversation of the call? #19

gfclaveria opened this issue Jun 27, 2017 · 6 comments

Comments

@gfclaveria
Copy link

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,

@waboum
Copy link
Contributor

waboum commented Jun 27, 2017

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;
        }
    }

@ssulzer
Copy link
Member

ssulzer commented Jun 27, 2017

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.

@gfclaveria
Copy link
Author

@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,

@ssulzer
Copy link
Member

ssulzer commented Jun 28, 2017

@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.

@gfclaveria
Copy link
Author

@ssulzer Additional question. May we known the reason why will you be removing group calls feature for bot?

@MalarGit
Copy link
Contributor

MalarGit commented Jul 4, 2017

@gfclaveria currently the bot can record audio and video of any user in the conference which is a privacy concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants