Microsoft Teams call recording.....audio issues - c#

Issue
My code is based upon this sample. When I capture audio it is extremely jittery/stuttery and sounds slightly 'sped up' as the playback duration is approx a quarter of the expected duration.
Code Snippet
Within my callhandler i have the following....my 'plan' is to try and hive off the received buffer asap to avoid any potential bottleneck(s) and then process the 'raw' bytes later and convert them into a WAV file.
private async void OnAudioMediaReceived(object sender, AudioMediaReceivedEventArgs e)
{
try
{
if (e.Buffer.IsSilence) return;
var managedArray = new byte[e.Buffer.Length];
Marshal.Copy(e.Buffer.Data, managedArray, 0, (int)e.Buffer.Length);
using (var fs = new FileStream($"c:\\tmp\\myTelephoneCallAudio.raw",FileMode.Append))
{
fs.Write(managedArray, 0, managedArray.Length);
fs.Close();
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
finally
{
e.Buffer.Dispose();
}
}
......then once the telephone call has ended i process the .raw file created above into a .wav by basically generating a wav file header and appending the wav content from the raw file.
Expected behavior
A clear and audible recording of the call lasting the expected duration.
Graph SDK(s) in use:
Microsoft.Graph (Nuget) - v3.12.0
Microsoft.Graph.Core (Nuget) - v1.21.0
Microsoft.GraphCommunications.Calls (Nuget) - v1.2.0.1702
Microsoft.GraphCommunications.Calls.Media (Nuget) - v1.2.0.1702
Microsoft.GraphCommunications.Client (Nuget) - v1.2.0.1702
Microsoft.GraphCommunications.Common (Nuget) - v1.2.0.1702
Microsoft.GraphCommunications.Core (Nuget) - v1.2.0.1702
Other information
I've raised an issue here but experience tells me I'm not going get a response. But within that raised issue I attached is a zip with the 'mangled' wav. This wav file is only 1m 25s but the call was 6.5 minutes long.
For a time I thought of the possibility of maybe the audio 'frames' were coming to me/being processed by me slightly out of sync/order....therefore i did try storing the frames in a dictionary where the key was the timestamp of the frame/buffer.....then i could order by the timestamp before attempting the conversion to the wav file.
Also after looking around online at expected wav file sizes, i.e. 10MB/min, etc.. it appears that the amount of data I'm capturing currently is a lot lower than expected for the assumed values, i.e. mono (1 channel), PCM 16K, with 32K sample rate, etc.
final note... every time when debugging OnAudioMediaReceived(), the buffer only has 640 bytes, meaning I'm appending at a rate of 640 per time...is this the same for anyone else who may be working on this?

Related

c# parallel writes to Azure Data Lake File

In our Azure Data Lake, we have daily files recording events and coordinates for those events. We need to take these coordinates and lookup what State, County, Township, and Section these coordinates fall into. I've attempted several versions of this code.
I attempted to do this in U-SQL. I even uploaded a custom assembly that implemented Microsoft.SqlServer.Types.SqlGeography methods, only to find ADLA isn't set up to perform row-by-row operations like geocoding.
I pulled all the rows into SQL Server, converted the coordinates into a SQLGeography and built T-SQL code that would perform the State, County, etc. lookups. After much optimization, I got this process down to ~700ms / row. (with 133M rows in the backlog and ~16k rows added daily we're looking at nearly 3 years to catch up. So I parallelized the T-SQL, things got better, but not enough.
I took the T-SQL code, and built the process as a console application, since the SqlGeography library is actually a .Net library, not a native SQL Server product. I was able to get single threaded processing down t0 ~ 500ms. Adding in .Net's parallelism (parallel.ForEach) and throwing 10/20 of the cores of my machine at it does a lot, but still isn't enough.
I attempted to rewrite this code as an Azure Function and processing files in the data lake file-by-file. Most of the files timed out, since they took longer than 10 minutes to process. So I've updated the code to read in the files, and shread the rows into Azure Queue storage. Then I have a second Azure function that fires for each row in the queue. The idea is, Azure Functions can scale out far greater than any single machine can.
And this is where I'm stuck. I can't reliably write rows to files in ADLS. Here is the code as I have it now.
public static void WriteGeocodedOutput(string Contents, String outputFileName, ILogger log) {
AdlsClient client = AdlsClient.CreateClient(ADlSAccountName, adlCreds);
//if the file doesn't exist write the header first
try {
if (!client.CheckExists(outputFileName)) {
using (var stream = client.CreateFile(outputFileName, IfExists.Fail)) {
byte[] headerByteArray = Encoding.UTF8.GetBytes("EventDate, Longitude, Latitude, RadarSiteID, CellID, RangeNauticalMiles, Azimuth, SevereProbability, Probability, MaxSizeinInchesInUS, StateCode, CountyCode, TownshipCode, RangeCode\r\n");
//stream.Write(headerByteArray, 0, headerByteArray.Length);
client.ConcurrentAppend(outputFileName, true, headerByteArray, 0, headerByteArray.Length);
}
}
} catch (Exception e) {
log.LogInformation("multiple attempts to create the file. Ignoring this error, since the file was created.");
}
//the write the data
byte[] textByteArray = Encoding.UTF8.GetBytes(Contents);
for (int attempt = 0; attempt < 5; attempt++) {
try {
log.LogInformation("prior to write, the outputfile size is: " + client.GetDirectoryEntry(outputFileName).Length);
var offset = client.GetDirectoryEntry(outputFileName).Length;
client.ConcurrentAppend(outputFileName, false, textByteArray, 0, textByteArray.Length);
log.LogInformation("AFTER write, the outputfile size is: " + client.GetDirectoryEntry(outputFileName).Length);
//if successful, stop trying to write this row
attempt = 6;
}
catch (Exception e){
log.LogInformation($"exception on adls write: {e}");
}
Random rnd = new Random();
Thread.Sleep(rnd.Next(attempt * 60));
}
}
The file will be created when it needs to be, but I do get several messages in my log that several threads tried to create it. I'm not always getting the header row written.
I also no longer get any data rows only:
"BadRequest ( IllegalArgumentException concurrentappend failed with error 0xffffffff83090a6f
(Bad request. The target file does not support this particular type of append operation.
If the concurrent append operation has been used with this file in the past, you need to append to this file using the concurrent append operation.
If the append operation with offset has been used in the past, you need to append to this file using the append operation with offset.
On the same file, it is not possible to use both of these operations.). []
I feel like I'm missing some fundamental design idea here. The code should try to write a row into a file. If the file doesn't yet exist, create it and put the header row in. Then, put in the row.
What's the best-practice way to accomplish this kind of write scenario?
Any other suggestions of how to handle this kind of parallel-write workload in ADLS?
I am a bit late to this but I guess one of the problems could be due to the use of "Create" and "ConcurrentAppend" on the same file stream?
ADLS documentation mentions that they can't be used on the same file. Maybe, try changing the "Create" command to "ConcurrentAppend" as the latter can be used to create a file if it doesn't exist.
Also, if you found a better way to do it, please do post your solution here.

C# I/O async (copyAsync): how to avoid file fragmentation?

Within a tool copying big files between disks, I replaced the
System.IO.FileInfo.CopyTo method by System.IO.Stream.CopyToAsync.
This allow a faster copy and a better control during the copy, e.g. I can stop the copy.
But this create even more fragmentation of the copied files. It is especially annoying when I copy file of many hundreds megabytes.
How can I avoid disk fragmentation during copy?
With the xcopy command, the /j switch copies files without buffering. And it is recommended for very large file in TechNet
It seems indeed to avoid file fragmentation (while a simple file copy within windows 10 explorer DOES fragment my file!)
A copy without buffering seems to be the opposite way than this async copy. Or it there any way to do async copy without buffering?
Here it my current code for aync copy. I let the default buffersize of 81920 bytes, i.e. 10*1024*size(int64).
I am working with NTFS file systems, thus 4096 bytes clusters.
EDIT: I updated the code with SetLength as suggested, added the FileOptions Async while creating the destinationStream and fix setting the attributes AFTER setting the time (otherwise, exception is thrown for ReadOnly files)
int bufferSize = 81920;
try
{
using (FileStream sourceStream = source.OpenRead())
{
// Remove existing file first
if (File.Exists(destinationFullPath))
File.Delete(destinationFullPath);
using (FileStream destinationStream = File.Create(destinationFullPath, bufferSize, FileOptions.Asynchronous))
{
try
{
destinationStream.SetLength(sourceStream.Length); // avoid file fragmentation!
await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken);
}
catch (OperationCanceledException)
{
operationCanceled = true;
}
} // properly disposed after the catch
}
}
catch (IOException e)
{
actionOnException(e, "error copying " + source.FullName);
}
if (operationCanceled)
{
// Remove the partially written file
if (File.Exists(destinationFullPath))
File.Delete(destinationFullPath);
}
else
{
// Copy meta data (attributes and time) from source once the copy is finished
File.SetCreationTimeUtc(destinationFullPath, source.CreationTimeUtc);
File.SetLastWriteTimeUtc(destinationFullPath, source.LastWriteTimeUtc);
File.SetAttributes(destinationFullPath, source.Attributes); // after set time if ReadOnly!
}
I fear also that the File.SetAttributes and Time at the end on my code could increase file fragmentation.
Is there a proper way to create a 1:1 asynchronous file copy without any file fragmentation, i.e. asking the HDD that the file steam get only contiguous sectors?
Other topics regarding file fragmentation like How can I limit file fragmentation while working with .NET suggests incrementing the file size in larger chunks, but it does not seem to be a direct answer to my question.
but the SetLength method does the job
It does not do the job. It only updates the file size in the directory entry, it does not allocate any clusters. The easiest way to see this for yourself is by doing this on a very large file, say 100 gigabytes. Note how the call completes instantly. Only way it can be instant is when the file system does not also do the job of allocating and writing the clusters. Reading from the file is actually possible, even though the file contains no actual data, the file system simply returns binary zeros.
This will also mislead any utility that reports fragmentation. Since the file has no clusters, there can be no fragmentation. So it only looks like you solved your problem.
The only thing you can do to force the clusters to be allocated is to actually write to the file. It is in fact possible to allocate 100 gigabytes worth of clusters with a single write. You must use Seek() to position to Length-1, then write a single byte with Write(). This will take a while on a very large file, it is in effect no longer async.
The odds that it will reduce fragmentation are not great. You merely reduced the risk somewhat that the writes will be interleaved by writes from other processes. Somewhat, actual writing is done lazily by the file system cache. Core issue is that the volume was fragmented before you began writing, it will never be less fragmented after you're done.
Best thing to do is to just not fret about it. Defragging is automatic on Windows these days, has been since Vista. Maybe you want to play with the scheduling, maybe you want to ask more about it at superuser.com
I think, FileStream.SetLength is what you need.
Considering Hans Passant answer,
in my code above, an alternative to
destinationStream.SetLength(sourceStream.Length);
would be, if I understood it properly:
byte[] writeOneZero = {0};
destinationStream.Seek(sourceStream.Length - 1, SeekOrigin.Begin);
destinationStream.Write(writeOneZero, 0, 1);
destinationStream.Seek(0, SeekOrigin.Begin);
It seems indeed to consolidate the copy.
But a look at the source code of FileStream.SetLengthCore seems it does almost the same, seeking at the end but without writing one byte:
private void SetLengthCore(long value)
{
Contract.Assert(value >= 0, "value >= 0");
long origPos = _pos;
if (_exposedHandle)
VerifyOSHandlePosition();
if (_pos != value)
SeekCore(value, SeekOrigin.Begin);
if (!Win32Native.SetEndOfFile(_handle)) {
int hr = Marshal.GetLastWin32Error();
if (hr==__Error.ERROR_INVALID_PARAMETER)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig"));
__Error.WinIOError(hr, String.Empty);
}
// Return file pointer to where it was before setting length
if (origPos != value) {
if (origPos < value)
SeekCore(origPos, SeekOrigin.Begin);
else
SeekCore(0, SeekOrigin.End);
}
}
Anyway, not sure that theses method guarantee no fragmentation, but at least avoid it for most of the cases. Thus the auto defragment tool will finish the job at a low performance expense.
My initial code without this Seek calls created hundred of thousands of fragments for 1 GB file, slowing down my machine when the defragment tool went active.

Read Second audio track stream from mp4 file using SharpDx or IMSourceReader

I have a requirement in my application where I have to read all the available track stream from mp4 file.
Mp4 file is encoded with number of tracks in AAC format. I have to decode to get all available tracks from the file. Currently I am using SharpDX and IMSourceReader (Media Foundation dlls) to read the Streams. But by default SourceReader returns only the first audio stream from the file. Is it I am doing correct ? Or I have to use any other third party libraries to achieve this ?
When configuring the reader, you can select which streams will be delivered when reading samples. Often times you do not wish to select the stream. An example would be a movie which has additional audio streams (spanish, french, or perhaps director commentary). As a result, most of the time stream selection is as simple as the following:
// error checking omitted for brevity
hr = reader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, nullptr, audioMediaType);
hr = reader->SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, true);
However if you look at SetStreamSelection, the first parameter takes either the enumeration used above, or a specific stream index.
// 0–0xFFFFFFFB <-- The zero-based index of a stream.
// 0xFFFFFFFC <-- MF_SOURCE_READER_FIRST_VIDEO_STREAM
// 0xFFFFFFFD <-- MF_SOURCE_READER_FIRST_AUDIO_STREAM
// 0xFFFFFFFE <-- MF_SOURCE_READER_ALL_STREAMS
// 0xFFFFFFFE <-- MF_SOURCE_READER_ANY_STREAM
// 0xFFFFFFFF <-- MF_SOURCE_READER_INVALID_STREAM_INDEX
I have never used SharpDX, but this enumeration is documented here.
Pertaining to video, sometimes additional video streams are available (usually closed captioning).
When reading the samples, using a callback or synchronously, pay close attention to the stream index, and process the sample accordingly.
You may also find these answers valuable or interesting:
Aggregate Media Source
MP4 IMFSinkWriter
Adding Audio Sample to Video
Creating NV12 Encoded Video
IMFSinkWriter Configuration
IMFSinkWriter CPU Utilization
I hope this helps.

NAudio - Changing Bitrate of Recorded WAV file

i am trying to implement audio recording using NAudio to a Wav file, but the default bitrate set by the WasapiLoopbackCapture class can't be changed programmatically.
I am recording the audio output to a MemoryStream (recordedStream in snippet below). However the default bitrate set by the WasapiLoobpackCapture doesn't fit my needs.
I would like to have a bit rate of 320KBPS and i tried to convert the recorded file programmatically using the WaveFormatConversionStream class, but i couldn't make it work.
WaveFormat targetFormat = WaveFormat.CreateCustomFormat(waveIn.WaveFormat.Encoding,
waveIn.WaveFormat.SampleRate, //SampleRate
waveIn.WaveFormat.Channels, //Channels
320000, //Average Bytes per Second
waveIn.WaveFormat.BlockAlign, //Block Align
waveIn.WaveFormat.BitsPerSample); //Bits per Sample
using (WaveStream inputStream = new RawSourceWaveStream(recordedStream, waveIn.WaveFormat))
{
try
{
using (var converter = new WaveFormatConversionStream(targetFormat, inputStream))
{
// ...
}
}
catch (Exception)
{
throw;
}
recordedStream.Dispose();
}
I always get an "AcmNotPossible calling acmStreamOpen" conversion exception. As you see i am using exactly the same format as the recorded WAV file (Extension encoding, 44100 etc.), except the bitrate which is lower in the target waveformat.
What would be the correct codeto do the bitrate conversion from a Wav file contained in a MemoryStream? my goal is to get a 320KBPS file.
For a given sample rate, bit depth, and channel count, PCM audio always has the same bitrate (calculated by multiplying those three values together). If you want to reduce the bitrate, you must change one of those three (lowering the sample rate is probably the best option unless you can go from stereo to mono).
Really you should be thinking of encoding to a format like MP3, WMA or AAC, which will let you select your preferred bitrate.

Crash safe on-the-fly compression with GZipStream

I'm compressing a log file as data is written to it, something like:
using (var fs = new FileStream("Test.gz", FileMode.Create, FileAccess.Write, FileShare.None))
{
using (var compress = new GZipStream(fs, CompressionMode.Compress))
{
for (int i = 0; i < 1000000; i++)
{
// Clearly this isn't what is happening in production, just
// a simply example
byte[] message = RandomBytes();
compress.Write(message, 0, message.Length);
// Flush to disk (in production we will do this every x lines,
// or x milliseconds, whichever comes first)
if (i % 20 == 0)
{
compress.Flush();
}
}
}
}
What I want to ensure is that if the process crashes or is killed, the archive is still valid and readable. I had hoped that anything since the last flush would be safe, but instead I am just ending up with a corrupt archive.
Is there any way to ensure I end up with a readable archive after each flush?
Note: it isn't essential that we use GZipStream, if something else will give us the desired result.
An option is to let Windows handle the compression. Just enable compression on the folder where you're storing your log files. There are some performance considerations you should be aware of when copying the compressed files, and I don't know how well NT compression performs in comparision to GZipStream or other compression options. You'll probably want to compare compression ratios and CPU load.
There's also the option of opening a compressed file, if you don't want to enable compression on the entire folder. I haven't tried this, but you might want to look into it: http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1b63b4a4-b197-4286-8f3f-af2498e3afe5
Good news: GZip is a streaming format. Therefore corruption at the end of the stream cannot affect the beginning which was already written.
So even if your streaming writes are interrupted at an arbitrary point, most of the stream is still good. You can write yourself a little tool that reads from it and just stops at the first exception it sees.
If you want an error-free solution I'd recommend splitting the log into one file every x seconds (maybe x = 1 or 10?). Write into a file with extensions ".gz.tmp" and rename to ".gz" after the file was completely written and closed.
Yes, but it's more involved than just flushing. Take a look at gzlog.h and gzlog.c in the zlib distribution. It does exactly what you want, efficiently adding short log entries to a gzip file, and always leaving a valid gzip file behind. It also has protection against crashes or shutdowns during the process, still leaving a valid gzip file behind and not losing any log entries.
I recommend not using GZIPStream. It is buggy and does not provide the necessary functionality. Use DotNetZip instead as your interface to zlib.

Categories