Screen capturing in C#/UWP - c#

i am working with UWP desktop window app and I am trying to implement the functionality of taking the screenshots with saving it to a local file, but i got the error message :
System.NullReferenceException: 'Object reference not set to an instance of an object.
on await frame.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f); in the method SaveImageAsync.
Could you tell me what is going wrong?
namespace App5
{
public sealed partial class MainPage : Page
{
private SizeInt32 _lastSize;
private GraphicsCaptureItem _item;
private Direct3D11CaptureFramePool _framePool;
private GraphicsCaptureSession _session;
private CanvasDevice _canvasDevice;
private CompositionGraphicsDevice _compositionGraphicsDevice;
private Compositor _compositor;
private CompositionDrawingSurface _surface;
private CanvasBitmap _currentFrame;
private string _screenshotFilename = "test.png";
public MainPage()
{
this.InitializeComponent();
Setup();
}
private void Setup()
{
_canvasDevice = new CanvasDevice();
_compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
Window.Current.Compositor,
_canvasDevice);
_compositor = Window.Current.Compositor;
_surface = _compositionGraphicsDevice.CreateDrawingSurface(
new Size(400, 400),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
var visual = _compositor.CreateSpriteVisual();
visual.RelativeSizeAdjustment = Vector2.One;
var brush = _compositor.CreateSurfaceBrush(_surface);
brush.HorizontalAlignmentRatio = 0.5f;
brush.VerticalAlignmentRatio = 0.5f;
brush.Stretch = CompositionStretch.Uniform;
visual.Brush = brush;
ElementCompositionPreview.SetElementChildVisual(this, visual);
}
public async Task StartCaptureAsync()
{
var picker = new GraphicsCapturePicker();
GraphicsCaptureItem item = await picker.PickSingleItemAsync();
if (item != null)
{
StartCaptureInternal(item);
}
}
private void StartCaptureInternal(GraphicsCaptureItem item)
{
// Stop the previous capture if we had one.
StopCapture();
_item = item;
_lastSize = _item.Size;
_framePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, // D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, // Pixel format
2, // Number of frames
_item.Size); // Size of the buffers
_framePool.FrameArrived += (s, a) =>
{
using (var frame = _framePool.TryGetNextFrame())
{
ProcessFrame(frame);
}
};
_item.Closed += (s, a) =>
{
StopCapture();
};
_session = _framePool.CreateCaptureSession(_item);
_session.StartCapture();
}
public void StopCapture()
{
_session?.Dispose();
_framePool?.Dispose();
_item = null;
_session = null;
_framePool = null;
}
private void ProcessFrame(Direct3D11CaptureFrame frame)
{
bool needsReset = false;
bool recreateDevice = false;
if ((frame.ContentSize.Width != _lastSize.Width) ||
(frame.ContentSize.Height != _lastSize.Height))
{
needsReset = true;
_lastSize = frame.ContentSize;
}
try
{
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface);
_currentFrame = canvasBitmap;
FillSurfaceWithBitmap(canvasBitmap);
}
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
needsReset = true;
recreateDevice = true;
}
if (needsReset)
{
ResetFramePool(frame.ContentSize, recreateDevice);
}
}
private void FillSurfaceWithBitmap(CanvasBitmap canvasBitmap)
{
CanvasComposition.Resize(_surface, canvasBitmap.Size);
using (var session = CanvasComposition.CreateDrawingSession(_surface))
{
session.Clear(Colors.Transparent);
session.DrawImage(canvasBitmap);
}
}
private void ResetFramePool(SizeInt32 size, bool recreateDevice)
{
do
{
try
{
if (recreateDevice)
{
_canvasDevice = new CanvasDevice();
}
_framePool.Recreate(
_canvasDevice,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
2,
size);
}
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
_canvasDevice = null;
recreateDevice = true;
}
} while (_canvasDevice == null);
}
private async void Button_ClickAsync(object sender, RoutedEventArgs e)
{
await StartCaptureAsync();
await SaveImageAsync(_screenshotFilename, _currentFrame);
}
private async Task SaveImageAsync(string filename, CanvasBitmap frame)
{
StorageFolder pictureFolder = KnownFolders.SavedPictures;
StorageFile file = await pictureFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await frame.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
}
}
}
}

This code is from a Windows Forms .Net Core project, but I just pasted in a WPF project and it works.
In WPF you have to add Nuget package System.Drawing.Common:
You also have to add references to:
using System.Windows.Forms;
using WindowsFormsIntegration;
using System.Drawing;
// take a screenshot
Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
// to save:
bitmap.Save(fileName);
The above code came from this project. I helped someone create sub images and search for sub images in a larger image. They had the screenshot code.
https://github.com/DataJuggler/SubImageCreator
This project is used to create sub images from a larger image and search for sub images in a larger image.

Related

class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback result is always null

I have converted my old StartActivityForResult code to the new RegisterForActivityResult as StartActivityForResult is depreciated in the android API 29 and higher. My new code works perfectly, except that result in class ActivityResultCallback is always null. I need to be able to know when the user cancels the taking of the picture when they hit the back button, otherwise the app crashes (if a previous picture doesn't already exist) or a previously taken picture is processed again. My code (showing only the relevant code):
public class Photo : AppCompatActivity
{
public ImageView MyImageView;
public ImageButton camerabutton;
public bool PictureTaken = false;
private CurrentSubjectInfo MySubjectInfo;
private ActivityResultCallback _activityResultCallback;
private ActivityResultLauncher _activityResultLauncher;
private Uri uri;
protected override void OnCreate(Bundle bundle)
{
try
{
_activityResultCallback = new ActivityResultCallback();
_activityResultCallback.OnActivityResultCalled += ActivityResultCallback_ActivityResultCalled;
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.TakePicture(), _activityResultCallback);
RequestedOrientation = Android.Content.PM.ScreenOrientation.Portrait;
base.OnCreate(bundle);
SetContentView(Resource.Layout.Photo);
PictureTaken = false;
MySubjectInfo = new CurrentSubjectInfo("", "", "", "", "");
// retrieve subject information from previous activity
MySubjectInfo = Mybundle.GetParcelable("MySubjectInfo", Java.Lang.Class.FromType(typeof(CurrentSubjectInfo))) as CurrentSubjectInfo;
ImageButton camerabutton = FindViewById<ImageButton>(Resource.Id.button1);
MyImageView = FindViewById<ImageView>(Resource.Id.imageView1);
camerabutton.Click += (sender, evt) =>
{
var cameraispresent = CheckCameraHardware();
if (cameraispresent)
{
try
{
// get directory where pictures are stored
App._dir = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
// build file name
MySubjectInfo.Name = MySubjectInfo.Name.Replace(",", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(" ", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(".", "");
var filename = MySubjectInfo.Name + ".jpg";
App._file = new File(App._dir, String.Format(filename, Guid.NewGuid()));
uri = FileProvider.GetUriForFile(this, this.ApplicationContext.PackageName + ".provider",App._file);
// launch camera activity
_activityResultLauncher.Launch(uri);
}
catch (Exception e)
{
// trap error and log it
};
}
};
}
}
Boolean CheckCameraHardware()
{
Android.Content.PM.PackageManager pm = PackageManager;
if (pm.HasSystemFeature(Android.Content.PM.PackageManager.FeatureCamera))
{
// this device has a camera
return true;
}
else
{
// no camera on this device
return false;
}
}
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
// result is always null, so I don't check it
try
{
// Because the resulting bitmap is rotated and too large, I set original orientation and resize
// suffice it to say it works perfectly so I won't post the code here
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
// save the resized bitmap, overwriting original file
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
// set the imageview to the resulting bitmap
MyImageView.SetImageBitmap (App.bitmap);
// cleanup
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
catch (Exception ex)
{
PictureTaken = false;
// trap and log error
}
}
}
This is the ActivityResultCallback class:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult = result as ActivityResult;
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
As stated all this code executes perfectly with no errors except that Java.Lang.Object result is always null. I need to know when the user cancels the camera activity, I would assume that Java.Lang.Object result would indicate cancelled but I get nothing. What am I missing?
Ok, after some playing around I noticed that the parameter Java.Lang.Object result in the ActivityResultCallback class was either true or false depending on what the user did with the camera. So I changed the class to:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult;
if ((bool)result)
{
activityResult = new ActivityResult((int)Result.Ok, null);
} else
{
activityResult = new ActivityResult((int)Result.Canceled, null);
}
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
In the ActivityResultCallback_ActivityResultCalled function, I modified it to this:
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
try
{
if (result.ResultCode == (int)Result.Ok)
{
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
MyImageView.SetImageBitmap(App.bitmap);
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
}
catch (Exception ex)
{
// trap and log error
}
}
activityResult apparently has, at a minimum, two parameters, the result and data. I already had what I needed for data so I set that to null and cast Result.Ok or Result.Cancelled to int depending on whether result was true or false. I still don't totally understand how to use the new ActivityForResult API, but this works for me and I'm running with it.

Streaming SharpDX Bitmap from one device to another by converting to byte array

I'm trying to capture a bitmap from desktop and then stream that to another device on the local network via socket connection.
I have a working example of this using gdi that sends a bitmap via socket to the client device and renders it to a picture box, but this solution is pretty inefficient as I'm sending unchanged bitmaps and it requires a lot of cpu processing on the client.
I'll include this example as a reference point
public class Serial
public static byte[] BitmapToBytes(Bitmap bitmap) {
using(MemoryStream ms = new MemoryStream()) {
using(BinaryWriter bw = new BinaryWriter(ms)) {
using(MemoryStream msb = new MemoryStream()) {
bitmap.Save(msb, ImageFormat.Jpeg);
byte[] bitmapBytes = msb.ToArray();
bw.Write(bitmapBytes.Length);
bw.Write(bitmapBytes);
return ms.ToArray();
}
}
}
}
public static Bitmap BytesToBitmap(byte[] bytes) {
using(MemoryStream ms = new MemoryStream(bytes)) {
using(BinaryReader br = new BinaryReader(ms)) {
int len = br.ReadInt32();
byte[] bitmapBytes = br.ReadBytes(len);
if(len == bitmapBytes.Length) {
using(MemoryStream msb = new MemoryStream(bitmapBytes)) {
return new Bitmap(msb);
}
} else {
return null;
}
}
}
}
}
public class Send {
private ClientInfo Instance;
public Send(ClientInfo instance) {
Instance = instance;
}
public void Bitmap(Bitmap bitmap) {
if(!Instanced) { return; }
Instance.SendMessage((uint)Code.Bitmap, Serial.BitmapToBytes(bitmap));
}
}
public class Server { //Same code for Client
public event EventHandler<BitmapEventArgs> BitmapReceived = delegate { };
//Socket connection setup code stripped as it's not relevant
private void Data_Received(ClientInfo c, uint code, byte[] bytes, int len) {
switch((Code)code) {
case Code.BitmapReceived:
BitmapReceived(this, new BitmapEventArgs(Serial.BytesToBitmap(bytes)));
break;
}
}
}
public partial class Form1 : Form {
private Client Client;
private Server Server;
private Send Send;
private void StartSocket() {
new Thread(() => {
StopSocket();
Client = new Client();
Client.ClientConnected += Client_Connected;
Client.ClientDisconnected += Client_Disconnected;
Client.BitmapReceived += Bitmap_Received;
if(!Client.Start(Config.Connection.IP, Config.Connection.Port)) {
Client = null;
Server = new Server();
Server.ClientConnected += Client_Connected;
Server.ClientDisconnected += Client_Disconnected;
Server.BitmapReceived += Bitmap_Received;
Server.Start(Config.Connection.Port);
}
}).Start();
}
private void Client_Connected(object sender, EventArgs e) {
if(sender is Server) {
Send = new Send(Server.Instance);
} else {
Send = new Send(Client.Instance);
}
}
private void SendBitmap() {
if(Send == null) { return; }
new Thread(() => {
while(Send != null) {
Bitmap b = CaptureBitmap();
if(b != null) {
Send.Bitmap(b);
Thread.Sleep(16); //around 60 times a second
}
}
}).Start();
}
private void Bitmap_Received(object sender, BitmapEventArgs e) {
if(Send != null && e.Bitmap != null) {
BitmapPicBox.Image = e.Bitmap;
}
}
private Bitmap CaptureBitmap() {
IntPtr deskHandle = WinApi.GetDesktopWindow();
IntPtr dc = WinApi.GetWindowDC(deskHandle);
if(dc != IntPtr.Zero) {
if(WinApi.GetWindowRect(deskHandle, out WinApi.RECT winRect)) {
IntPtr hdcDest = WinApi.CreateCompatibleDC(dc);
IntPtr hBitmap = WinApi.CreateCompatibleBitmap(dc, winRect.Width, winRect.Height);
IntPtr hOld = WinApi.SelectObject(hdcDest, hBitmap);
if(WinApi.BitBlt(hdcDest, 0, 0, winRect.Width, winRect.Height, dc, winRect.Left, winRect.Top, WinApi.TernaryRasterOperations.SRCCOPY)) {
WinApi.SelectObject(hdcDest, hOld);
WinApi.DeleteDC(hdcDest);
WinApi.ReleaseDC(deskHandle, dc);
Bitmap b = Image.FromHbitmap(hBitmap);
WinApi.DeleteObject(hBitmap);
return b;
} else {
WinApi.DeleteDC(hdcDest);
WinApi.ReleaseDC(deskHandle, dc);
WinApi.DeleteObject(hBitmap);
}
}
}
return null;
}
}
I have since tried a SharpDX implementation. I used the example from this github project for getting desktop image by desktop duplication and feeding that to a panel on the form which renders it.
But I'm not sure how I'd integrate this into my socket connection, I'd need to pass the frame being created as a byte array, and then back into a Bitmap1 that can be rendered.
Receiving the byte array and converting to Bitmap1 would be here:
private void RenderDuplicatedFrame(byte[] bitmapBytes) {
//Convert bitmapBytes to SharpDX.Direct2D1.Bitmap1
SharpDX.Direct2D1.Bitmap1 bitmap = //???
_backBufferDc.Value.BeginDraw();
_backBufferDc.Value.Clear(new RawColor4(0, 0, 0, 1));
using(bitmap) {
var renderX = (Size.Width - RenderSize.Width) / 2;
var renderY = (Size.Height - RenderSize.Height) / 2;
_backBufferDc.Value.DrawBitmap(bitmap, new RawRectangleF(renderX, renderY, renderX + RenderSize.Width, renderY + RenderSize.Height), 1, BitmapInterpolationMode.Linear);
}
_backBufferDc.Value.EndDraw();
_swapChain.Value.Present(1, 0);
}
Sending the Bitmap1 would be here:
private void AcquireFrame() {
var od = _outputDuplication.Value;
SharpDX.DXGI.Resource frame;
OutputDuplicateFrameInformation frameInfo;
od.AcquireNextFrame(500, out frameInfo, out frame);
using(frame) {
if(frameInfo.LastPresentTime != 0) {
using(var frameSurface = frame.QueryInterface<Surface>()) {
using(var frameDc = new SharpDX.Direct2D1.DeviceContext(_2D1Device.Value, DeviceContextOptions.EnableMultithreadedOptimizations)) {
using(var frameBitmap = new Bitmap1(frameDc, frameSurface)) {
//Convert frameBitmap to byte array here to send over socket
}
}
}
}
od.ReleaseFrame();
}
}
So how do I go about converting to and from a byte array?

Image from a web page that doesn't stop loading

I'm looking to take a screenshot from a web page that is streaming from an IP camera and as such doesn't triggerWebBrowserReadyState.Complete.
The webpage in question is literally streaming a mjpeg and the browser is always loading/streaming.
I can generate images for other URLs but cannot guess this to give anything other than 'Navigation Cancelled'.
I have tried WebBrowser.Stop() but to no avail...
The webpage is simply an mJpeg:
<img class"shrink" src="192.168.1.124/Streaming/channels/1/httpPreview"; alt="192.168.1.124/Streaming/channels/1/httpPreview"; class="shrinkToFit" height="148" width="217"> </img>
using Telegram.Bot;
using System.Threading.Tasks;
using System.IO;
using Telegram.Bot.Types;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
namespace Telegram_Alerter
{
public static class BitmapExtensions
{
public static void SaveJPG100(this Bitmap bmp, string filename)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(filename, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static void SaveJPG100(this Bitmap bmp, Stream stream)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(stream, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageDecoders();
foreach (var codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
// Return
return null;
}
}
class Program
{
static public void Main(string[] args)
{
TelegramMessageAsync().Wait();
}
static public async Task TelegramMessageAsync()
{
TelegramBotClient Bot = new TelegramBotClient("<mykey>");
WebsiteToImage websiteToImage = new WebsiteToImage("http://192.168.1.124/Streaming/channels/1/httpPreview", Application.StartupPath + "file.jpg");
websiteToImage.Generate();
using (FileStream fs = System.IO.File.OpenRead(Application.StartupPath + "file.jpg"))
{
FileToSend fts = new FileToSend();
fts.Content = fs;
fts.Filename = "file.jpg";
await Bot.SendPhotoAsync(<bot>, fts, "message here");
}
}
public class WebsiteToImage
{
private Bitmap m_Bitmap;
private string m_Url;
private string m_FileName = string.Empty;
public WebsiteToImage(string url)
{
// Without file
m_Url = url;
}
public WebsiteToImage(string url, string fileName)
{
// With file
m_Url = url;
m_FileName = fileName;
}
public Bitmap Generate()
{
// Thread
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Capture
var browser = (WebBrowser)sender;
browser.ClientSize = new Size(1024,768);
browser.ScrollBarsEnabled = false;
m_Bitmap = new Bitmap(1024,768);
browser.BringToFront();
browser.DrawToBitmap(m_Bitmap, browser.Bounds);
// Save as file?
if (m_FileName.Length > 0)
{
// Save
m_Bitmap.SaveJPG100(m_FileName);
}
}
}
}
}
Maybe that Page contains Iframes that never load.If yes:
private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// to check if it is not Iframe
if (e.Url.AbsolutePath != this.webBrowser.Url.AbsolutePath)
{
// Capture
var browser = (WebBrowser)sender;
browser.ClientSize = new Size(1024,768);
browser.ScrollBarsEnabled = false;
m_Bitmap = new Bitmap(1024,768);
browser.BringToFront();
browser.DrawToBitmap(m_Bitmap, browser.Bounds);
// Save as file?
if (m_FileName.Length > 0)
{
// Save
m_Bitmap.SaveJPG100(m_FileName);
}
}
}
Thanks for the efforts but I answered the question by using an FFMpeg .NET wrapper called N.Reco meaning I did not have to worry about parsing the MJPEG to obtain a single frame and then convert to a JPEG.
This was achieved in two lines of code but FFMPEG does add 20Mb to your application.
var ffmpeg = new NReco.VideoConverter.FFMpegConverter();
ffmpeg.GetVideoThumbnail(pathToVideoFile, Application.StartupPath + stamp);

Realtime audio stream from outpud device to input device ( from system mixer to microphone )

I need to stream my OS mixer sound to the microphone. (to transfer them both via skype).
Important thing is -- I need to have ability to configure delay/latency of the stream.
(VAC) Virtual Audio Cable is not solution for me: it doesn't work in my case. I have an external USB sound device an app doesn't work for me. I have tried it. Truly.
I google it more than 2 days and found nothing.
So I want to try wrote my own app for this. Have no experience with NAudio.
At the moment all I have is:
using System;
using System.Collections.Generic;
using NAudio.Wave;
namespace SoundApp
{
public class AudioCable
{
private WaveOut _wvOut = null;
private DirectSoundOut _output = null;
private WaveIn _sourceStreamIn = null;
private WaveOut _sourceStreamOut = null;
public void InitDevices(string fromDevice, string toDevice)
{
Dispose();
_sourceStreamIn = new WaveIn();
_sourceStreamOut = new WaveOut();
_output = new DirectSoundOut();
if (fromDevice.Contains("INPUT"))
{
var tmp = fromDevice.Split('|')[0].Replace("INPUT: ", "").Trim(' ');
int tmpDeviceNumber = int.Parse(tmp);
_sourceStreamIn.DeviceNumber = tmpDeviceNumber;
}
else
{
var tmp = toDevice.Split('|')[0].Replace("OUTPUT: ", "").Trim(' ');
int tmpDeviceNumber = int.Parse(tmp);
_sourceStreamOut.DeviceNumber = tmpDeviceNumber;
}
Guid deviceGuid;
if (toDevice.Contains("INPUT"))
{
var tmp = toDevice.Split('|')[0].Replace("INPUT: ", "").Trim(' ');
int tmpDeviceNumber = int.Parse(tmp);
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(tmpDeviceNumber);
deviceGuid = deviceInfo.ManufacturerGuid;
}
else
{
var tmp = toDevice.Split('|')[0].Replace("OUTPUT: ", "").Trim(' ');
int tmpDeviceNumber = int.Parse(tmp);
WaveOutCapabilities deviceInfo = WaveOut.GetCapabilities(tmpDeviceNumber);
deviceGuid = deviceInfo.ManufacturerGuid;
}
_output = new DirectSoundOut(deviceGuid);
}
public void PlaySound()
{
WaveInProvider waveIn = null;
if (_sourceStreamIn != null)
{
waveIn = new WaveInProvider(_sourceStreamIn);
}
else if (_sourceStreamOut != null)
{
throw new Exception("Sorry, not supported right now");
//waveIn = new WaveInProvider(_sourceStreamOut);
}
_output.Init(waveIn);
_output.Play();
}
public void Dispose()
{
_sourceStreamIn = null;
_sourceStreamOut = null;
if (_output != null)
{
if (_output.PlaybackState == PlaybackState.Playing)
{
_output.Stop();
}
_output.Dispose();
_output = null;
}
}
public string[] GetAudioDevices()
{
var devices = new List<string>();
for (int i = 0; i < WaveIn.DeviceCount; i++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(i);
devices.Add($"INPUT: {i} | {deviceInfo.ProductName}");
}
for (int i = 0; i < WaveOut.DeviceCount; i++)
{
WaveOutCapabilities deviceInfo = WaveOut.GetCapabilities(i);
devices.Add($"OUTPUT: {i} | {deviceInfo.ProductName}");
}
return devices.ToArray();
}
}
}
And the form source:
namespace SoundApp
{
public partial class Form1 : Form
{
AudioCable _ac = new AudioCable();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
cBoxSource.DropDownStyle = ComboBoxStyle.DropDownList;
cBoxTarget.DropDownStyle = ComboBoxStyle.DropDownList;
cBoxSource.Items.AddRange(_ac.GetAudioDevices());
cBoxTarget.Items.AddRange(_ac.GetAudioDevices());
}
private void btnStartStop_Click(object sender, EventArgs e)
{
_ac.InitDevices(cBoxSource.SelectedItem.ToString(), cBoxTarget.SelectedItem.ToString());
_ac.PlaySound();
}
}
}
So at the first I'm trying to stream microphone to the sound output device.... And it does not work at all.
So My questions is:
There are no microphone sound and I'm do not understand why so. Code is valid and dont throw any errors.
Looks like there is no ability to stream outputDevice sound stream to the inputDevice with NAudio. Is there exist some hack for this?

Xamarin Android take screenshot

I am working on a project which makes drawing.
I don't use axml because I do my drawing in a class called filledpolygon and calling the function in MainActivity. I just want to take screenshot in my project. Is there any basic function, which I can call in onCreate method? So, when the program runs, it will automatically take the screenshot. I found answers except Xamarin platform.
Since Android 28 DrawingCacheEnabled is deprecated and without it we are forcing our view to to redraw on our custom canvas wich can cause artifacts with custom controls and renderers and the screenshot version might be different from what we see on screen.
The legacy code that is still working on simple cases is:
public byte[] CaptureScreenshot()
{
var view=
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView;
if (view.Height < 1 || view.Width < 1)
return null;
byte[] buffer = null;
view.DrawingCacheEnabled = true;
using (var screenshot = Bitmap.CreateBitmap(
view.Width,
view.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
view.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
view.DrawingCacheEnabled = false;
return buffer;
}
Use legacy method above as follows
if ((int)Android.OS.Build.VERSION.SdkInt < 28)
{
//legacy
}
The DrawingCacheEnabled obsolete warning redirects us to use PixelCopy. This method is acting with a callback so to use it synchronously have made some helpers:
Usage:
public byte[] CaptureScreenshot()
{
using var helper = new ScreenshotHelper(
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView,
Xamarin.Essentials.Platform.CurrentActivity);
byte[] buffer = null;
bool wait = true;
Task.Run(async () =>
{
helper.Capture((Bitmap bitmap) =>
{
try
{
if (!helper.Error)
{
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
wait = false;
}
});
}).ConfigureAwait(false);
while (wait)
{
Task.Delay(10).Wait();
}
return buffer;
}
The helper:
public class ScreenshotHelper : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
{
public void OnPixelCopyFinished(int copyResult)
{
var stop = true;
if (copyResult == (int) PixelCopyResult.Success)
{
Error = false;
//todo CallbackGotScreenshot();
_callback(_bitmap);
}
else
{
Error = true;
}
_callback(_bitmap);
}
public bool Error { get; protected set; }
public ScreenshotHelper(Android.Views.View view, Activity activity)
{
_view = view;
_activity = activity;
_bitmap = Bitmap.CreateBitmap(
_view.Width,
_view.Height,
Bitmap.Config.Argb8888);
}
// Starts a background thread and its {#link Handler}.
private void StartBackgroundThread()
{
_BackgroundThread = new HandlerThread("ScreeshotMakerBackground");
_BackgroundThread.Start();
_BackgroundHandler = new Handler(_BackgroundThread.Looper);
}
// Stops the background thread and its {#link Handler}.
private void StopBackgroundThread()
{
try
{
_BackgroundThread.QuitSafely();
_BackgroundThread.Join();
_BackgroundThread = null;
_BackgroundHandler = null;
}
catch (Exception e)
{
//e.PrintStackTrace();
}
}
public void Capture(Action<Bitmap> callback)
{
//var locationOfViewInWindow = new int[2];
//_view.GetLocationInWindow(locationOfViewInWindow);
_callback = callback;
try
{
StartBackgroundThread();
//todo could create-use background handler
PixelCopy.Request(_activity.Window, _bitmap, this,
_BackgroundHandler);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Task.Run(StopBackgroundThread);
}
}
private Android.Views.View _view;
private Activity _activity;
private Bitmap _bitmap;
private HandlerThread _BackgroundThread;
private Handler _BackgroundHandler;
private Action<Bitmap> _callback;
public new void Dispose()
{
_bitmap?.Dispose();
_bitmap= null;
_activity = null;
_view = null;
_callback = null;
base.Dispose();
}
}
In your View you could run the following code which will take a screenshot. I have not tried running it in OnCreate() before so you may need to test that out to make sure the view has been fully rendered.
*Edit: According to this post you may have trouble running this code in OnCreate() so you will need to find a better place. I was unable to figure out what post the user was referring to in the link he posted.
*Edit #2: Just found out that Compress() does not take the quality parameter (which is listed as 0 below) into account since PNG is lossless, but if you change the format to JPEG for example, then you may want to turn up the quality parameter since your image will look like garbage.
public byte[] SaveImage() {
DrawingCacheEnabled = true; //Enable cache for the next method below
Bitmap bitmap = GetDrawingCache(true); //Gets the image from the cache
byte[] bitmapData;
using(MemoryStream stream = new MemoryStream()) {
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}

Categories

Resources