C# Windows Phone 8.1 GetOutputStreamAsync(...) blocking after first use - c#

i'm currently trying to create a very basic test app which should:
1) Broadcast "sometext" on port "1234"
2) Wait a second for answers
3) Return all answers
While the solution posted below works fine for the first time, every subsequent call blocks forever at:
stream = await socket.GetOutputStreamAsync(...)
Till now i tried every possible way of cleaning up (since thats where i suppose the failure), even wrapping everything in using(...) statements.
The problem occurs with the emulator as well as a hardware device using Windows Phone 8.1
Thanks in advance!
The code to start the "discovery":
private void Button_Click(object sender, RoutedEventArgs e)
{
PluginUDP pudp = new PluginUDP();
var task = pudp.scan("asf");
task.Wait();
foreach (string s in task.Result)
output.Text += s + "\r\n";
}
The code for the "discovery" itself:
using System;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using namespace whatever
{
public class PluginUDP
{
private static readonly HostName BroadcastAddress = new HostName("255.255.255.255");
private static readonly string BroadcastPort = "1234";
private static readonly byte[] data = Encoding.UTF8.GetBytes("00wlan-ping00");
ConcurrentBag<string> receivers;
public async System.Threading.Tasks.Task<string[]> scan(string options)
{
receivers = new ConcurrentBag<string>();
receivers.Add("ok");
DatagramSocket socket = null;
IOutputStream stream = null;
DataWriter writer = null;
try
{
socket = new DatagramSocket();
socket.MessageReceived += MessageReceived;
await socket.BindServiceNameAsync("");
stream = await socket.GetOutputStreamAsync(BroadcastAddress, BroadcastPort);
writer = new DataWriter(stream);
writer.WriteBytes(data);
await writer.StoreAsync();
Task.Delay(1000).Wait();
}
catch (Exception exception)
{
receivers.Add(exception.Message);
}
finally
{
if (writer != null)
{
writer.DetachStream();
writer.Dispose();
}
if(stream != null)
stream.Dispose();
if(socket != null)
socket.Dispose();
}
return receivers.ToArray(); ;
}
private async void MessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs args)
{
try
{
var result = args.GetDataStream();
var resultStream = result.AsStreamForRead(1024);
using (var reader = new StreamReader(resultStream))
{
var text = await reader.ReadToEndAsync();
if (text.Contains("pong"))
{
receivers.Add(args.RemoteAddress.ToString());
}
}
}
catch (Exception exception)
{
receivers.Add("ERRCV");
}
}
}
}

Your problem starts here:
task.Wait();
You're blocking on async code, which leads you to a deadlock.
You want:
private async void Button_Click(object sender, RoutedEventArgs e)
{
PluginUDP pudp = new PluginUDP();
string[] result = await pudp.scan("asf");
foreach (string s in result)
output.Text += s + "\r\n";
}
You also want to do:
await Task.Delay(1000);
Instead of:
Task.Delay(1000).Wait();

Related

What are your suggestions for a different logging method in UWP?

During the UWP conversion of an application that was previously designed as a winform, errors occurred in my logging method and I had to change the System.IO reference to Storage. As a result of this change, when the synchronous method started to work asynchronously, I got errors about the request to use the file at the same time. Then I came up with a simple solution by creating the class I mentioned below but this quick amateurish approach do you think is correct?
public class Logging_UWP
{
private List<string> CompletedLog = new List<string>();
private string machineCode = string.Empty;
public Logging_UWP(string MachineCode)
{
machineCode = MachineCode;
Task.Factory.StartNew(()=> WriteLog());
}
public void WriteLogSync(string NewLog)
{
CompletedLog.Add(NewLog);
}
private async void WriteLog()
{
while (true)
{
try
{
foreach (string NewLog in CompletedLog)
{
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile sampleFile = await storageFolder.CreateFileAsync(machineCode + ".log", CreationCollisionOption.OpenIfExists);
var stream = await sampleFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
using (var outputStream = stream.GetOutputStreamAt(stream.Size + 1))
{
using (var dataWriter = new Windows.Storage.Streams.DataWriter(outputStream))
{
dataWriter.WriteString(String.Format("\n[{0}-{1}] >>> {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString(), NewLog));
await dataWriter.StoreAsync();
await outputStream.FlushAsync();
}
}
}
if (CompletedLog.Count > 0)
CompletedLog.Clear();
await Task.Delay(500);
}
catch (Exception)
{ }
}
}
} '''

Continuous Async Ping inside a Windows Service

I need to monitor a list of hosts continuously. After N seconds, i need to check the list again. So, I tried to use the async ping inside a Windows Service.
I tried to follow tips from other posts related to the topic, but always my service stops shortly after starting it.
There are a problem with await in "OnElapsedTime" function.
Any one have an idea what is wrong? Bellow my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Net.NetworkInformation;
namespace PingAsyncService
{
public partial class HyBrazil_Ping : ServiceBase
{
Timer timer = new Timer();
List<string> IPList = new List<string>(); //List of IPs
public HyBrazil_Ping()
{
IPList.Add("192.168.0.1");
IPList.Add("192.168.0.254");
InitializeComponent();
}
protected override void OnStart(string[] args)
{
WriteToFile("Service is started at " + DateTime.Now);
timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
timer.Interval = 5000; //number in miliseconds
timer.Enabled = true;
}
protected override void OnStop()
{
WriteToFile("Service is stopped at " + DateTime.Now);
}
private async void OnElapsedTime(object source, ElapsedEventArgs e)
{
//WriteToFile("Service is recall at " + DateTime.Now);
var ResultList = await PingAsync();
foreach(PingReply reply in ResultList)
{
WriteToFile(reply.Address.ToString() + ";" + reply.Status.ToString());
}
}
private async Task<PingReply> PingAndProcessAsync(Ping pingSender, string ip)
{
var result = await pingSender.SendPingAsync(ip, 2000);
return result;
}
private async Task<List<PingReply>> PingAsync()
{
Ping pingSender = new Ping();
var tasks = IPList.Select(ip => PingAndProcessAsync(pingSender, ip));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
public void WriteToFile(string Message)
{
string path = AppDomain.CurrentDomain.BaseDirectory + "\\Logs";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\Logs\\ServiceLog_" + DateTime.Now.Date.ToShortDateString().Replace('/', '_') + ".txt";
if (!File.Exists(filepath))
{
// Create a file to write to.
using (StreamWriter sw = File.CreateText(filepath))
{
sw.WriteLine(Message);
}
}
else
{
using (StreamWriter sw = File.AppendText(filepath))
{
sw.WriteLine(Message);
}
}
}
}
}
Thanks a lot!
In one of the comments you mentioned the error message as
"An asynchronous call is already in progress. It must be completed or canceled before you can call this method."
Chances are, the Ping object does not let simultaneous asynchronous calls.
Using a new Ping object everytime, on each call, might help as below.
private async Task<List<PingReply>> PingAsync()
{
// Ping pingSender = new Ping();
var tasks = IPList.Select(ip =>
{
using (var p= new Ping())
{
return PingAndProcessAsync(p, ip);
}
});
var results = await Task.WhenAll(tasks);
return results.ToList();
}

.Net DownloadFileTaskAsync robust WPF code

The WPF code below hangs forever when network connection is lost for 3 or more minutes. When connection is restored it neither throws nor continues downloading nor timeouts. If network connection is lost for a shorter period say half a minute, it throws after connection is restored. How can i make it more robust to survive network outage?
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Windows;
namespace WebClientAsync
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
NetworkChange.NetworkAvailabilityChanged +=
(sender, e) => Dispatcher.Invoke(delegate()
{
this.Title = "Network is " + (e.IsAvailable ? " available" : "down");
});
}
const string SRC = "http://ovh.net/files/10Mio.dat";
const string TARGET = #"d:\stuff\10Mio.dat";
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
btnDownload.Content = "Downloading " + SRC;
try {
using (var wcl = new WebClient())
{
wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
await wcl.DownloadFileTaskAsync(new Uri(SRC), TARGET);
btnDownload.Content = "Downloaded";
}
}
catch (Exception ex)
{
btnDownload.Content = ex.Message + Environment.NewLine
+ ((ex.InnerException != null) ? ex.InnerException.Message : String.Empty);
}
btnDownload.IsEnabled = true;
}
}
}
UPDATE
Current solution is based on restarting Timer in DownloadProgressChangedEventHandler, so the timer fires only if no DownloadProgressChanged events occur within the timeout. Looks like an ugly hack, still looking for a better solution.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace WebClientAsync
{
public partial class MainWindow : Window
{
const string SRC = "http://ovh.net/files/10Mio.dat";
const string TARGET = #"d:\stuff\10Mio.dat";
// Time needed to restore network connection
const int TIMEOUT = 30 * 1000;
public MainWindow()
{
InitializeComponent();
}
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
btnDownload.Content = "Downloading " + SRC;
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Timer timer = new Timer((o) =>
{
// Force async cancellation
cts.Cancel();
}
, null //state
, TIMEOUT
, Timeout.Infinite // once
);
DownloadProgressChangedEventHandler handler = (sa, ea) =>
{
// Restart timer
if (ea.BytesReceived < ea.TotalBytesToReceive && timer != null)
{
timer.Change(TIMEOUT, Timeout.Infinite);
}
};
btnDownload.Content = await DownloadFileTA(token, handler);
// Note ProgressCallback will fire once again after awaited.
timer.Dispose();
btnDownload.IsEnabled = true;
}
private async Task<string> DownloadFileTA(CancellationToken token, DownloadProgressChangedEventHandler handler)
{
string res = null;
WebClient wcl = new WebClient();
wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
wcl.DownloadProgressChanged += handler;
try
{
using (token.Register(() => wcl.CancelAsync()))
{
await wcl.DownloadFileTaskAsync(new Uri(SRC), TARGET);
}
res = "Downloaded";
}
catch (Exception ex)
{
res = ex.Message + Environment.NewLine
+ ((ex.InnerException != null) ? ex.InnerException.Message : String.Empty);
}
wcl.Dispose();
return res;
}
}
}
You need to implement proper timeout for that download. But you don't need to use timer, just use Task.Delay and Task.WaitAny. For example:
static async Task DownloadFile(string url, string output, TimeSpan timeout) {
using (var wcl = new WebClient())
{
wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var download = wcl.DownloadFileTaskAsync(url, output);
// await two tasks - download and delay, whichever completes first
await Task.WhenAny(Task.Delay(timeout), download);
var exception = download.Exception; // need to observe exception, if any
bool cancelled = !download.IsCompleted && exception == null;
// download is not completed yet, nor it is failed - cancel
if (cancelled) {
wcl.CancelAsync();
}
if (cancelled || exception != null) {
// delete partially downloaded file if any (note - need to do with retry, might not work with a first try, because CancelAsync is not immediate)
int fails = 0;
while (true) {
try {
File.Delete(output);
break;
}
catch {
fails++;
if (fails >= 10)
break;
await Task.Delay(1000);
}
}
}
if (exception != null) {
throw new Exception("Failed to download file", exception);
}
if (cancelled) {
throw new Exception($"Failed to download file (timeout reached: {timeout})");
}
}
}
Usage:
const string SRC = "http://ovh.net/files/10Mio.dat";
const string TARGET = #"d:\stuff\10Mio.dat";
// Time needed to restore network connection
TimeSpam TIMEOUT = TimeSpan.FromSeconds(30);
DownloadFile(SRC,TARGET, TIMEOUT); // might want to await this to handle exceptions
Update in response to comment. If you want timeout based on received data, not on whole operation time, it's also possible with Task.Delay. For example:
static async Task DownloadFile(string url, string output, TimeSpan timeout)
{
using (var wcl = new WebClient())
{
wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
DateTime? lastReceived = null;
wcl.DownloadProgressChanged += (o, e) =>
{
lastReceived = DateTime.Now;
};
var download = wcl.DownloadFileTaskAsync(url, output);
// await two tasks - download and delay, whichever completes first
// do that until download fails, completes, or timeout expires
while (lastReceived == null || DateTime.Now - lastReceived < timeout) {
await Task.WhenAny(Task.Delay(1000), download); // you can replace 1 second with more reasonable value
if (download.IsCompleted || download.IsCanceled || download.Exception != null)
break;
}
var exception = download.Exception; // need to observe exception, if any
bool cancelled = !download.IsCompleted && exception == null;
// download is not completed yet, nor it is failed - cancel
if (cancelled)
{
wcl.CancelAsync();
}
if (cancelled || exception != null)
{
// delete partially downloaded file if any (note - need to do with retry, might not work with a first try, because CancelAsync is not immediate)
int fails = 0;
while (true)
{
try
{
File.Delete(output);
break;
}
catch
{
fails++;
if (fails >= 10)
break;
await Task.Delay(1000);
}
}
}
if (exception != null)
{
throw new Exception("Failed to download file", exception);
}
if (cancelled)
{
throw new Exception($"Failed to download file (timeout reached: {timeout})");
}
}
}
Personally, if I were to make a robust download solution, I would add a Network connection monitor because that's what we are actually waiting for. For simplicity, something like this will be enough.
online = true;
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
_isNetworkOnline = NetworkInterface.GetIsNetworkAvailable();
void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
online = e.IsAvailable;
}
Then you can actually check for network availability and wait as appropriate before you attempt to download or progress... I will definitely accept that a simple ping solution seems to work better than this at times based on experience.
Depending on the size of what you're downloading, monitoring the network speed may also help so you can decide how to chunk in case of choppy connections. Take a look at this project for ideas.

The process cannot access the file because it is being used by another process

I am running a program where a file gets uploaded to a folder in IIS,and then is processed to extract some values from it. I use a WCF service to perform the process, and BackgroundUploader to upload the file to IIS. However, after the upload process is complete, I get the error "The process cannot access the file x because it is being used by another process." Based on similar questions asked here, I gathered that the file concerned needs to be in a using statement. I tried to modify my code to the following, but it didn't work, and I am not sure if it is even right.
namespace App17
{
public sealed partial class MainPage : Page, IDisposable
{
private CancellationTokenSource cts;
public void Dispose()
{
if (cts != null)
{
cts.Dispose();
cts = null;
}
GC.SuppressFinalize(this);
}
public MainPage()
{
this.InitializeComponent();
cts = new CancellationTokenSource();
}
public async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
Uri uri = new Uri(serverAddressField.Text.Trim());
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
using (var stream = await file.OpenAsync(FileAccessMode.Read))
{
GlobalClass.filecontent = file.Name;
GlobalClass.filepath = file.Path;
BackgroundUploader uploader = new BackgroundUploader();
uploader.SetRequestHeader("Filename", file.Name);
UploadOperation upload = uploader.CreateUpload(uri, file);
await HandleUploadAsync(upload, true);
stream.Dispose();
}
}
catch (Exception ex)
{
string message = ex.ToString();
var dialog = new MessageDialog(message);
await dialog.ShowAsync();
Log(message);
}
}
private void CancelAll(object sender, RoutedEventArgs e)
{
Log("Canceling all active uploads");
cts.Cancel();
cts.Dispose();
cts = new CancellationTokenSource();
}
private async Task HandleUploadAsync(UploadOperation upload, bool start)
{
try
{
Progress<UploadOperation> progressCallback = new Progress<UploadOperation>(UploadProgress);
if (start)
{
await upload.StartAsync().AsTask(cts.Token, progressCallback);
}
else
{
// The upload was already running when the application started, re-attach the progress handler.
await upload.AttachAsync().AsTask(cts.Token, progressCallback);
}
ResponseInformation response = upload.GetResponseInformation();
Log(String.Format("Completed: {0}, Status Code: {1}", upload.Guid, response.StatusCode));
cts.Dispose();
}
catch (TaskCanceledException)
{
Log("Upload cancelled.");
}
catch (Exception ex)
{
string message = ex.ToString();
var dialog = new MessageDialog(message);
await dialog.ShowAsync();
Log(message);
}
}
private void Log(string message)
{
outputField.Text += message + "\r\n";
}
private async void LogStatus(string message)
{
var dialog = new MessageDialog(message);
await dialog.ShowAsync();
Log(message);
}
private void UploadProgress(UploadOperation upload)
{
BackgroundUploadProgress currentProgress = upload.Progress;
MarshalLog(String.Format(CultureInfo.CurrentCulture, "Progress: {0}, Status: {1}", upload.Guid,
currentProgress.Status));
double percentSent = 100;
if (currentProgress.TotalBytesToSend > 0)
{
percentSent = currentProgress.BytesSent * 100 / currentProgress.TotalBytesToSend;
}
MarshalLog(String.Format(CultureInfo.CurrentCulture,
" - Sent bytes: {0} of {1} ({2}%), Received bytes: {3} of {4}", currentProgress.BytesSent,
currentProgress.TotalBytesToSend, percentSent, currentProgress.BytesReceived, currentProgress.TotalBytesToReceive));
if (currentProgress.HasRestarted)
{
MarshalLog(" - Upload restarted");
}
if (currentProgress.HasResponseChanged)
{
MarshalLog(" - Response updated; Header count: " + upload.GetResponseInformation().Headers.Count);
}
}
private void MarshalLog(string value)
{
var ignore = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
Log(value);
});
}
}
}
After this is done, the file name is sent to a WCF service which will access and process the uploaded file to extract certain values. It is at this point I receive the error. I would truly appreciate some help.
public async void Extract_Button_Click(object sender, RoutedEventArgs e)
{
ServiceReference1.Service1Client MyService = new ServiceReference1.Service1Client();
string filename = GlobalClass.filecontent;
string filepath = #"C:\Users\R\Documents\Visual Studio 2015\Projects\WCF\WCF\Uploads\"+ filename;
bool x = await MyService.ReadECGAsync(filename, filepath);
}
EDIT: Code before I added the using block
try
{
Uri uri = new Uri(serverAddressField.Text.Trim());
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
GlobalClass.filecontent = file.Name;
GlobalClass.filepath = file.Path;
BackgroundUploader uploader = new BackgroundUploader();
uploader.SetRequestHeader("Filename", file.Name);
UploadOperation upload = uploader.CreateUpload(uri, file);
await HandleUploadAsync(upload, true);
}
When you work with stream writers you actually create a process, which you can close it from task manager. And after stream.Dispose() put stream.Close().
This should solve your problem.
You should also close the stream that writes the file to disk (look at your implementation of CreateUpload).
i got such error in DotNet Core 2 using this code:
await file.CopyToAsync(new FileStream(fullFileName, FileMode.Create));
counter++;
and this is how I managed to get rid of message (The process cannot access the file x because it is being used by another process):
using (FileStream DestinationStream = new FileStream(fullFileName, FileMode.Create))
{
await file.CopyToAsync(DestinationStream);
counter++;
}

Extract Frames from Video C#

I'm trying to make an app that use the camera to record a video and process the images of the video. Here is what I want. First, my app records a 10 second video with Torch. Second, I use a method to playback the video to see what I record.
I'm stuck on three things.
How can I convert my video into individual frames (images)?
Is it possible to asynchronously convert the video while it is being recorded?
When I do convert the video into individual frames, how do I work with them? Are they JPEGs? Can I simply display them as images? Etc.
Main code:
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace App3
{
public sealed partial class MainPage : Page
{
DispatcherTimer D;
double basetimer = 0;
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
D = new DispatcherTimer();
D.Interval = new TimeSpan(0, 0, 1);
D.Tick += timer_Tick;
txt.Text = basetimer.ToString();
Play.IsEnabled = false;
}
public Library Library = new Library();
public object PreviewImage { get; private set; }
void timer_Tick(object sender, object e)
{
basetimer = basetimer - 1;
txt.Text = basetimer.ToString();
if (basetimer == 0)
{
D.Stop();
Preview.Source = null;
Library.Stop();
Record.IsEnabled = false;
Play.IsEnabled = true;
Clear.IsEnabled = true;
if (Library._tc.Enabled)
{
Library._tc.Enabled = false;
}
}
}
private void Record_Click(object sender, RoutedEventArgs e)
{
if (Library.Recording)
{
Preview.Source = null;
Library.Stop();
Record.Icon = new SymbolIcon(Symbol.Video);
}
else
{
basetimer = 11;
D.Start();
//D.Tick += timer_Tick;
Display.Source = null;
Library.Record(Preview);
Record.Icon = new SymbolIcon(Symbol.VideoChat);
Record.IsEnabled = false;
Play.IsEnabled = false;
}
}
private async void Play_Click(object sender, RoutedEventArgs e)
{
await Library.Play(Dispatcher, Display);
//Extract_Image_From_Video(Library.buffer);
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
Display.Source = null;
Record.Icon = new SymbolIcon(Symbol.Video);
txt.Text = "0";
basetimer= 0;
Play.IsEnabled = false;
Record.IsEnabled =true;
if (Library.capture != null)
{
D.Stop();
Library.Recording = false;
Preview.Source = null;
Library.capture.Dispose();
Library.capture = null;
basetimer = 11;
}
}
}
}
Library Class:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Media.Capture;
using Windows.Media.Devices;
using Windows.Media.MediaProperties;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Emgu.CV.Structure;
using Emgu.CV;
using System.Collections.Generic;
public class Library
{
private const string videoFilename = "video.mp4";
private string filename;
public MediaCapture capture;
public InMemoryRandomAccessStream buffer;
public static bool Recording;
public TorchControl _tc;
public int basetimer ;
public async Task<bool> init()
{
if (buffer != null)
{
buffer.Dispose();
}
buffer = new InMemoryRandomAccessStream();
if (capture != null)
{
capture.Dispose();
}
try
{
if (capture == null)
{
var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
DeviceInformation cameraDevice =
allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null &&
x.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back);
capture = new MediaCapture();
var mediaInitSettings = new MediaCaptureInitializationSettings { VideoDeviceId = cameraDevice.Id };
// Initialize
try
{
await capture.InitializeAsync(mediaInitSettings);
var videoDev = capture.VideoDeviceController;
_tc = videoDev.TorchControl;
Recording = false;
_tc.Enabled = false;
}
catch (UnauthorizedAccessException)
{
Debug.WriteLine("UnauthorizedAccessExeption>>");
}
catch (Exception ex)
{
Debug.WriteLine("Exception when initializing MediaCapture with {0}: {1}", cameraDevice.Id, ex.ToString());
}
}
capture.Failed += (MediaCapture sender, MediaCaptureFailedEventArgs errorEventArgs) =>
{
Recording = false;
_tc.Enabled = false;
throw new Exception(string.Format("Code: {0}. {1}", errorEventArgs.Code, errorEventArgs.Message));
};
}
catch (Exception ex)
{
if (ex.InnerException != null && ex.InnerException.GetType() == typeof(UnauthorizedAccessException))
{
throw ex.InnerException;
}
throw;
}
return true;
}
public async void Record(CaptureElement preview)
{
await init();
preview.Source = capture;
await capture.StartPreviewAsync();
await capture.StartRecordToStreamAsync(MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto), buffer);
if (Recording) throw new InvalidOperationException("cannot excute two records at the same time");
Recording = true;
_tc.Enabled = true;
}
public async void Stop()
{
await capture.StopRecordAsync();
Recording = false;
_tc.Enabled = false;
}
public async Task Play(CoreDispatcher dispatcher, MediaElement playback)
{
IRandomAccessStream video = buffer.CloneStream();
if (video == null) throw new ArgumentNullException("buffer");
StorageFolder storageFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
if (!string.IsNullOrEmpty(filename))
{
StorageFile original = await storageFolder.GetFileAsync(filename);
await original.DeleteAsync();
}
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
StorageFile storageFile = await storageFolder.CreateFileAsync(videoFilename, CreationCollisionOption.GenerateUniqueName);
filename = storageFile.Name;
using (IRandomAccessStream fileStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
{
await RandomAccessStream.CopyAndCloseAsync(video.GetInputStreamAt(0), fileStream.GetOutputStreamAt(0));
await video.FlushAsync();
video.Dispose();
}
IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.Read);
playback.SetSource(stream, storageFile.FileType);
playback.Play();
});
}
I ended up using MediaToolkit to solve a similar problem after having a ton of trouble with Accord.
I needed to save an image for every second of a video:
using (var engine = new Engine())
{
var mp4 = new MediaFile { Filename = mp4FilePath };
engine.GetMetadata(mp4);
var i = 0;
while (i < mp4.Metadata.Duration.TotalSeconds)
{
var options = new ConversionOptions { Seek = TimeSpan.FromSeconds(i) };
var outputFile = new MediaFile { Filename = string.Format("{0}\\image-{1}.jpeg", outputPath, i) };
engine.GetThumbnail(mp4, outputFile, options);
i++;
}
}
Hope this helps someone some day.
UPDATE for .NET 5:
Recently, I have needed to update this code to work in .NET 5. To do so, I am using MediaToolkit.NetCore, which has been in preview for over a year. Also note: you will need to make the latest ffmpeg, including all 3 .exe files (ffmpeg, ffplay, ffprobe) available to your app.
Without further ado, here is the updated code:
// _env is the injected IWebHostEnvironment
// _tempPath is temporary file storage
var ffmpegPath = Path.Combine(_env.ContentRootPath, "<path-to-ffmpeg.exe>");
var mediaToolkitService = MediaToolkitService.CreateInstance(ffmpegPath);
var metadataTask = new FfTaskGetMetadata(_tempFile);
var metadata = await mediaToolkitService.ExecuteAsync(metadataTask);
var i = 0;
while (i < metadata.Metadata.Streams.First().DurationTs)
{
var outputFile = string.Format("{0}\\image-{1:0000}.jpeg", _imageDir, i);
var thumbTask = new FfTaskSaveThumbnail(_tempFile, outputFile, TimeSpan.FromSeconds(i));
_ = await mediaToolkitService.ExecuteAsync(thumbTask);
i++;
}
I figured this out just yesterday.
Here is full and easy to understand example with picking video file and saving snapshot in 1st second of video.
You can take parts that fits your project and change some of them (i.e. getting video resolution from camera)
1) and 3)
TimeSpan timeOfFrame = new TimeSpan(0, 0, 1);
//pick mp4 file
var picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
picker.FileTypeFilter.Add(".mp4");
StorageFile pickedFile = await picker.PickSingleFileAsync();
if (pickedFile == null)
{
return;
}
///
//Get video resolution
List<string> encodingPropertiesToRetrieve = new List<string>();
encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
IDictionary<string, object> encodingProperties = await pickedFile.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
///
//Use Windows.Media.Editing to get ImageStream
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
var composition = new MediaComposition();
composition.Clips.Add(clip);
var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
///
//generate bitmap
var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
writableBitmap.SetSource(imageStream);
//generate some random name for file in PicturesLibrary
var saveAsTarget = await KnownFolders.PicturesLibrary.CreateFileAsync("IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg");
//get stream from bitmap
Stream stream = writableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)writableBitmap.PixelWidth,
(uint)writableBitmap.PixelHeight,
96,
96,
pixels);
await encoder.FlushAsync();
using (var outputStream = writeStream.GetOutputStreamAt(0))
{
await outputStream.FlushAsync();
}
}
If you want to display frames in xaml Image, you should use imageStream
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
XAMLImage.Source = bitmapImage;
If you want to extract more frames, there is also composition.GetThumbnailsAsync
2) Use your mediaCapture, when your timer is ticking
EDIT:
used includes:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Media.Editing;
using Windows.Storage;
using Windows.UI.Xaml.Media.Imaging;
Use ffmpeg and install Accord.Video.FFMPEG
using (var vFReader = new VideoFileReader())
{
vFReader.Open("video.mp4");
for (int i = 0; i < vFReader.FrameCount; i++)
{
Bitmap bmpBaseOriginal = vFReader.ReadVideoFrame();
}
vFReader.Close();
}
Another one way to get it :
I used FFMpegCore, official docker image with .net core 3.1 + ubuntu (list of available images)
Dockerfile :
FROM mcr.microsoft.com/dotnet/runtime:3.1-bionic
RUN apt-get update && apt-get install -y ffmpeg libgdiplus
COPY bin/Release/netcoreapp3.1/publish/ App/
WORKDIR /App
ENTRYPOINT ["dotnet", "YouConsoleAppNameHere.dll"]
short code version:
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "/usr/bin", TemporaryFilesFolder = "/tmp" }); //configuring ffmpeg location
string filePath = AppContext.BaseDirectory + "sample.mp4";
FFMpegArguments.FromFileInput(filePath).OutputToFile("tmp/Video/Frame%05d.png", true, Options => { Options.WithVideoCodec(VideoCodec.Png); }).ProcessSynchronously();
extended version (with some console logs):
using FFMpegCore;
using FFMpegCore.Enums;
...
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "/usr/bin", TemporaryFilesFolder = "/tmp" }); //configuring ffmpeg location
string filePath = AppContext.BaseDirectory + "sample.mp4";
Console.WriteLine(filePath) ;
Console.WriteLine(File.Exists(filePath));
var mediaInfo = FFProbe.Analyse(filePath);
Console.WriteLine("mp4 duration : " + mediaInfo.Duration);
Directory.CreateDirectory("tmp");
Directory.CreateDirectory("tmp/Video");
Console.WriteLine("started " + DateTime.Now.ToLongTimeString());
FFMpegArguments.FromFileInput(filePath).OutputToFile("tmp/Video/Frame%05d.png", true, Options => { Options.WithVideoCodec(VideoCodec.Png); }).ProcessSynchronously();
Console.WriteLine("processed " + DateTime.Now.ToLongTimeString());
Console.WriteLine(string.Join(", ", Directory.EnumerateFiles("tmp/Video/")));
As a result - png files will be extracted to tmp/Video folder. Of course, you can do the same without docker if you need.

Categories