I am developing a voice recorder app for Windows Phone 8.1 that stores the recordings on the local storage and a cloud storage service.
Everything's almost done except the fact that being able to pause an ongoing recording is a strong requirement for this app and I have to get it done.
Now, since PauseRecordAsync() and ResumeRecordAsync() are not available for Windows Phone 8.1 in the MediaCapture class but they will be available in Windows 10, I had to make a workaround: Every time the pause button is pressed, an audio chunk is saved in the temp folder and that file is stored in an array. When the stop button is pressed, the last chunk is stored in the array and the following Concatenation function is called and a final audio temp file is created:
public async Task<IStorageFile> ConcatenateAudio([ReadOnlyArray]IStorageFile[] audioFiles, IStorageFolder outputFolder, string outputfileName)
{
IStorageFile _OutputFile = await outputFolder.CreateFileAsync(outputfileName, CreationCollisionOption.ReplaceExisting);
MediaComposition _MediaComposition = new MediaComposition();
MediaEncodingProfile _MediaEncodingProfile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High);
foreach (IStorageFile _AudioFile in audioFiles)
{
if(_AudioFile != null)
{
BackgroundAudioTrack _BackgroundAudioTrack = await BackgroundAudioTrack.CreateFromFileAsync(_AudioFile);
MediaClip _MediaClip = MediaClip.CreateFromColor(Windows.UI.Colors.Black, _BackgroundAudioTrack.TrimmedDuration); // A dummy black video is created witn the size of the current audio chunk.
// Without this, the duration of the MediaComposition object is always 0.
// It's a messy workaround but it gets the job done.
// Windows 10 will dirrectly support PauseRecordAsync() and ResumeRecordAsync() for MediaCapture tho'. Yay! :D
_MediaClip.Volume = 0;
_BackgroundAudioTrack.Volume = 1;
_MediaComposition.Clips.Add(_MediaClip);
_MediaComposition.BackgroundAudioTracks.Add(_BackgroundAudioTrack);
}
}
TranscodeFailureReason _TranscodeFailureReason = await _MediaComposition.RenderToFileAsync(_OutputFile, MediaTrimmingPreference.Fast, _MediaEncodingProfile);
if (_TranscodeFailureReason != TranscodeFailureReason.None)
{
throw new Exception("Audio Concatenation Failed: " + _TranscodeFailureReason.ToString());
}
return _OutputFile;
}
The problem is that when I play the file, all the audio chunks are played from the beginning of the final audio file at the same time instead of playing the second one right after the first one ended and so on. They are all playing one over the other. The length of the file on the other hand is correct and after all audio files finished playing, it's total silence.
I figured it out. I had to manually set the delay for BackgroundAudioTrack.
Here is the working code:
public async Task<IStorageFile> ConcatenateAudio([ReadOnlyArray]IStorageFile[] audioFiles, IStorageFolder outputFolder, string outputfileName)
{
IStorageFile _OutputFile = await outputFolder.CreateFileAsync(outputfileName, CreationCollisionOption.ReplaceExisting);
MediaComposition _MediaComposition = new MediaComposition();
MediaEncodingProfile _MediaEncodingProfile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High);
TimeSpan totalDelay = TimeSpan.Zero;
foreach (IStorageFile _AudioFile in audioFiles)
{
if (_AudioFile != null)
{
BackgroundAudioTrack _BackgroundAudioTrack = await BackgroundAudioTrack.CreateFromFileAsync(_AudioFile);
MediaClip _MediaClip = MediaClip.CreateFromColor(Windows.UI.Colors.Black, _BackgroundAudioTrack.TrimmedDuration); // A dummy black video is created witn the size of the current audio chunk.
// Without this, the duration of the MediaComposition object is always 0.
// It's a messy workaround but it gets the job done.
// Windows 10 will dirrectly support PauseRecordAsync() and ResumeRecordAsync() for MediaCapture tho'. Yay! :D
_MediaClip.Volume = 0;
_BackgroundAudioTrack.Volume = 1;
_MediaComposition.Clips.Add(_MediaClip);
_MediaComposition.BackgroundAudioTracks.Add(_BackgroundAudioTrack);
_BackgroundAudioTrack.Delay = totalDelay;
totalDelay += _BackgroundAudioTrack.TrimmedDuration;
}
}
TranscodeFailureReason _TranscodeFailureReason = await _MediaComposition.RenderToFileAsync(_OutputFile, MediaTrimmingPreference.Fast, _MediaEncodingProfile);
if (_TranscodeFailureReason != TranscodeFailureReason.None)
{
throw new Exception("Audio Concatenation Failed: " + _TranscodeFailureReason.ToString());
}
return _OutputFile;
}
Related
I just converted one of my apps to target Android API 9 (was targeting API 8); now when notifications are sent out, the volume of media is lowered and never comes back to full volume.
The app uses WebView to play media files. This was not happening prior to targeting API 9. I had to convert the app into level 9 so that I could upload to the Google Play Store. I am running a Samsung S7 which was originally designed for API level 6 (with the OS upgraded to 8.0), not sure if that has something to do with the issue. Another detail is that I use Xamarin.Android for development, not sure if that matters either.
Additionally, I forced the notifications to play a blank sound (a very short[couple ms] blank mp3) in the same build that I converted the app to target API 9:
var channelSilent = new Android.App.NotificationChannel(CHANNEL_ID, name + " Silent", Android.App.NotificationImportance.High)
{
Description = description
};
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build()
//blank is blank mp3 file with nothing in it, a few ms in duration
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3")
channelSilent.SetSound(uri, alarmAttributes);
...so it could also be the blank sound that is causing the ducking to malfunction, not the API change. Is there something to do with notification sound ducking that could be causing the issue? Is there any other way to mute a notification with Xamarin.Android other than playing a blank sound? That is one route I think would be worth trying to fix this issue.
Here is the code I am using to generate notifications:
private static List<CustomNotification> _sentNotificationList = new List<CustomNotification>();
private static NotificationManagerCompat _notificationManager;
public async void SendNotifications(List<CustomNotification> notificationList)
{
await Task.Run(() =>
{
try
{
var _ctx = Android.App.Application.Context;
if (_notificationManager == null)
{
_notificationManager = Android.Support.V4.App.NotificationManagerCompat.From(_ctx);
}
if (notificationList.Count == 0)
{
return;
}
int notePos = 0;
foreach (var note in notificationList)
{
var resultIntent = new Intent(_ctx, typeof(MainActivity));
var valuesForActivity = new Bundle();
valuesForActivity.PutInt(MainActivity.COUNT_KEY, _count);
valuesForActivity.PutString("URL", note._noteLink);
resultIntent.PutExtras(valuesForActivity);
var resultPendingIntent = PendingIntent.GetActivity(_ctx, MainActivity.NOTIFICATION_ID, resultIntent, PendingIntentFlags.UpdateCurrent);
resultIntent.AddFlags(ActivityFlags.SingleTop);
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build();
//I am playing this blank sound to prevent android from spamming sounds as the notifications get sent out
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3");
//if the notification is the first in our batch then use this
//code block to send the notifications with sound
if (!_sentNotificationList.Contains(note) && notePos == 0)
{
var builder = new Android.Support.V4.App.NotificationCompat.Builder(_ctx, MainActivity.CHANNEL_ID + 1)
.SetAutoCancel(true)
.SetContentIntent(resultPendingIntent) // Start up this activity when the user clicks the intent.
.SetContentTitle(note._noteText) // Set the title
.SetNumber(1) // Display the count in the Content Info
.SetSmallIcon(Resource.Drawable.bitchute_notification2)
.SetContentText(note._noteType)
.SetPriority(NotificationCompat.PriorityMin);
MainActivity.NOTIFICATION_ID++;
_notificationManager.Notify(MainActivity.NOTIFICATION_ID, builder.Build());
_sentNotificationList.Add(note);
notePos++;
}
//if the notification isn't the first in our batch, then use this
//code block to send the notifications without sound
else if (!_sentNotificationList.Contains(note))
{
var builder = new Android.Support.V4.App.NotificationCompat.Builder(_ctx, MainActivity.CHANNEL_ID)
.SetAutoCancel(true) // Dismiss the notification from the notification area when the user clicks on it
.SetContentIntent(resultPendingIntent) // Start up this activity when the user clicks the intent.
.SetContentTitle(note._noteText) // Set the title
.SetNumber(1) // Display the count in the Content Info
.SetSmallIcon(Resource.Drawable.bitchute_notification2)
.SetContentText(note._noteType)
.SetPriority(NotificationCompat.PriorityHigh);
MainActivity.NOTIFICATION_ID++;
_notificationManager.Notify(MainActivity.NOTIFICATION_ID, builder.Build());
_sentNotificationList.Add(note);
notePos++;
}
ExtStickyService._notificationsHaveBeenSent = true;
}
}
catch
{
}
});
}
In my MainActivity I've created two different notification channels: one is silent; the other uses default notification setting for the device:
void CreateNotificationChannel()
{
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build();
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3");
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var name = "BitChute";
var description = "BitChute for Android";
var channelSilent = new Android.App.NotificationChannel(CHANNEL_ID, name + " Silent", Android.App.NotificationImportance.High)
{
Description = description
};
var channel = new Android.App.NotificationChannel(CHANNEL_ID + 1, name, Android.App.NotificationImportance.High)
{
Description = description
};
channel.LockscreenVisibility = NotificationVisibility.Private;
//here is where I set the sound for the silent channel... this could be the issue?
var notificationManager = (Android.App.NotificationManager)GetSystemService(NotificationService);
channelSilent.SetSound(uri, alarmAttributes);
notificationManager.CreateNotificationChannel(channel);
notificationManager.CreateNotificationChannel(channelSilent);
}
Full source: https://github.com/hexag0d/BitChute_Mobile_Android_BottomNav/tree/APILevel9
EDIT: something really interesting is that if I pulldown the system ui bar, the volume goes back to normal. Very strange workaround but it might help diagnose the cause.
DOUBLE EDIT: I used .SetSound(null, null) instead of using the blank .mp3 and the ducking works fine now. See comments
I want to record screen audio using AppRecordingManager in UWP.
I found StartRecordingToFileAsync function in AppRecordingManager Class to write audio and video content in UWP.
Here is the official document URL about this function:
https://learn.microsoft.com/en-us/uwp/api/windows.media.apprecording.apprecordingmanager.startrecordingtofileasync#Windows_Media_AppRecording_AppRecordingManager_StartRecordingToFileAsync_Windows_Storage_StorageFile_
Although I try to use this function to record audio, I always keep getting this error:
The request is invalid in the current state. (Exception from HRESULT: 0xC00D36B2)
I can not find solution for StartRecordingToFileAsync.
How can I solve this error?
Here is my c# code giving me this error.
AppRecordingManager manager = AppRecordingManager.GetDefault();
var status = manager.GetStatus();
if (status.CanRecord || status.CanRecordTimeSpan)
{
var myVideo = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Videos);
StorageFolder projectFolder = await myVideo.SaveFolder.CreateFolderAsync("DataFolder", CreationCollisionOption.OpenIfExists);
var audio = await projectFolder.CreateFileAsync("audio_record.wav", CreationCollisionOption.GenerateUniqueName);
var result = await manager.StartRecordingToFileAsync(audio);
if (result.Succeeded)
{
Debug.WriteLine(result.Succeeded);
}
else
{
Debug.WriteLine(result.ExtendedError.Message);
}
}
I managed to reproduce same error with your code.
But when I changed "audio_record.wav" to "audio_record.mp4" it worked and it saved video to specified location.
Since documentation states that StartRecordingToFileAsync "Writes audio and video content of the current app" it's possible that you can't save just audio using that method.
I just developed sample UWP app to capture images from web cam. this is working smoothly
this is the code I have used
public class CameraCaptureService : ICaptureService
{
public async Task<StorageFile> CapturePhotoAsync()
{
var dialog = new CameraCaptureUI();
dialog.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.HighestAvailable;
dialog.PhotoSettings.Format = CameraCaptureUIPhotoFormat.JpegXR;
dialog.PhotoSettings.AllowCropping = true;
var file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Photo);
...
return (file == null) ? null : file;
}
}
but once after I click Image Capture button
this is awaiting until I confirm If I want to proceed this picture or discard the process and restart (with right sign and incorrect sign)
I'm trying to skip that stage and directly save this to local storage
Is this possible to do, using CameraCaptureUI class PhotoSettings ?
NB : dialog.CaptureFileAsync(CameraCaptureUIMode.Photo); is async method
I'm implementing a Windows Phone 8.1 App with a QR Code reader. I use ZXing.NET to analyze the taken image and try to parse the QR. To increase it's efficiency I also set autofocus to the camera. It works pretty well at the first start, but not with the second try (f.e. after suspend - resume or restart capturing). As I tested, the FocusAsync method doesn't return sometimes and blocks everything.
What happens here? What could be the problem?
Here is my current code.
Focus
var focusSettings = new Windows.Media.Devices.FocusSettings();
focusSettings.AutoFocusRange = Windows.Media.Devices.AutoFocusRange.Normal;
focusSettings.Mode = Windows.Media.Devices.FocusMode.Auto;
CaptureManager.VideoDeviceController.FocusControl.Configure(focusSettings);
MainProcess
... Initialization ...
ImageEncodingProperties imaggeProperties = ImageEncodingProperties.CreateJpeg();
imaggeProperties.Width = ViewModel.ImageWidth;
imaggeProperties.Height = ViewModel.ImageHeight;
InMemoryRandomAccessStream memoryStream = new InMemoryRandomAccessStream();
LoggingAdapter.Instance.WriteDebugLog("Scanning is in progress. " + Environment.CurrentManagedThreadId);
await CaptureManager.VideoDeviceController.FocusControl.FocusAsync();
await CaptureManager.CapturePhotoToStreamAsync(imaggeProperties, memoryStream);
LoggingAdapter.Instance.WriteDebugLog("Photo captured.");
var bcReader = new BarcodeReader();
... Processing the barcode ...
Cleaning
if (CaptureManager != null)
{
if (InProgress)
{
InProgress = false;
await CaptureManager.StopPreviewAsync();
}
CaptureManager.Dispose();
Capture.Source = null;
}
Thanks for advance!
I succeeded to implement a working solution. I set the the WaitForFocus to false in the FocusSettings and it seems to be working fine, also with suspending or cancelling.
I am trying to record audio and play it directly (I want to hear my voice in the headphone without saving it) however the MediaElement and the MediaCapture seems non to work at the same time.
I initialized my MediaCapture so:
_mediaCaptureManager = new MediaCapture();
var settings = new MediaCaptureInitializationSettings();
settings.StreamingCaptureMode = StreamingCaptureMode.Audio;
settings.MediaCategory = MediaCategory.Other;
settings.AudioProcessing = AudioProcessing.Default;
await _mediaCaptureManager.InitializeAsync(settings);
However I don't really know how to proceed; I am wonderign if one of these ways could work (I tryied implement them without success, and I have not found examples):
Is there a way to use StartPreviewAsync() recording Audio, or it only works for Videos? I noticed that I get the following error:"The specified object or value does not exist" while setting my CaptureElement Source; it only happens if I write "settings.StreamingCaptureMode = StreamingCaptureMode.Audio;" while everyting works for .Video.
How can I record to a stream using StartRecordToStreamAsync(); I mean, how have I to initialize the IRandomAccessStream and read from it? Can I write on a stream while I keep reading for it?
I read that changing AudioCathegory of the MediaElement and the MediaCathegory of the MediaCapture to Communication there is a possibility it could work. However, while my code works (it just have to record and save in a file) with the previous setting, it don't works if I wrote "settings.MediaCategory = MediaCategory.Communication;" instead of "settings.MediaCategory = MediaCategory.Other;". Can you tell me why?
Here is my current program that just record, save and play:
private async void CaptureAudio()
{
try
{
_recordStorageFile = await KnownFolders.VideosLibrary.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
MediaEncodingProfile recordProfile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.Auto);
await _mediaCaptureManager.StartRecordToStorageFileAsync(recordProfile, this._recordStorageFile);
_recording = true;
}
catch (Exception e)
{
Debug.WriteLine("Failed to capture audio:"+e.Message);
}
}
private async void StopCapture()
{
if (_recording)
{
await _mediaCaptureManager.StopRecordAsync();
_recording = false;
}
}
private async void PlayRecordedCapture()
{
if (!_recording)
{
var stream = await _recordStorageFile.OpenAsync(FileAccessMode.Read);
playbackElement1.AutoPlay = true;
playbackElement1.SetSource(stream, _recordStorageFile.FileType);
playbackElement1.Play();
}
}
If you have any suggestion I'll be gratefull.
Have a good day.
Would you consider targeting Windows 10 instead? The new AudioGraph API allows you to do just this, and the Scenario 2 (Device Capture) in the SDK sample demonstrates it well.
First, the sample populates all output devices into a list:
private async Task PopulateDeviceList()
{
outputDevicesListBox.Items.Clear();
outputDevices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioRenderSelector());
outputDevicesListBox.Items.Add("-- Pick output device --");
foreach (var device in outputDevices)
{
outputDevicesListBox.Items.Add(device.Name);
}
}
Then it gets to building the AudioGraph:
AudioGraphSettings settings = new AudioGraphSettings(AudioRenderCategory.Media);
settings.QuantumSizeSelectionMode = QuantumSizeSelectionMode.LowestLatency;
// Use the selected device from the outputDevicesListBox to preview the recording
settings.PrimaryRenderDevice = outputDevices[outputDevicesListBox.SelectedIndex - 1];
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
if (result.Status != AudioGraphCreationStatus.Success)
{
// TODO: Cannot create graph, propagate error message
return;
}
AudioGraph graph = result.Graph;
// Create a device output node
CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await graph.CreateDeviceOutputNodeAsync();
if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
// TODO: Cannot create device output node, propagate error message
return;
}
deviceOutputNode = deviceOutputNodeResult.DeviceOutputNode;
// Create a device input node using the default audio input device
CreateAudioDeviceInputNodeResult deviceInputNodeResult = await graph.CreateDeviceInputNodeAsync(MediaCategory.Other);
if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
// TODO: Cannot create device input node, propagate error message
return;
}
deviceInputNode = deviceInputNodeResult.DeviceInputNode;
// Because we are using lowest latency setting, we need to handle device disconnection errors
graph.UnrecoverableErrorOccurred += Graph_UnrecoverableErrorOccurred;
// Start setting up the output file
FileSavePicker saveFilePicker = new FileSavePicker();
saveFilePicker.FileTypeChoices.Add("Pulse Code Modulation", new List<string>() { ".wav" });
saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new List<string>() { ".wma" });
saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new List<string>() { ".mp3" });
saveFilePicker.SuggestedFileName = "New Audio Track";
StorageFile file = await saveFilePicker.PickSaveFileAsync();
// File can be null if cancel is hit in the file picker
if (file == null)
{
return;
}
MediaEncodingProfile fileProfile = CreateMediaEncodingProfile(file);
// Operate node at the graph format, but save file at the specified format
CreateAudioFileOutputNodeResult fileOutputNodeResult = await graph.CreateFileOutputNodeAsync(file, fileProfile);
if (fileOutputNodeResult.Status != AudioFileNodeCreationStatus.Success)
{
// TODO: FileOutputNode creation failed, propagate error message
return;
}
fileOutputNode = fileOutputNodeResult.FileOutputNode;
// Connect the input node to both output nodes
deviceInputNode.AddOutgoingConnection(fileOutputNode);
deviceInputNode.AddOutgoingConnection(deviceOutputNode);
Once all of that is done, you can record to a file while at the same time playing the recorded audio like so:
private async Task ToggleRecordStop()
{
if (recordStopButton.Content.Equals("Record"))
{
graph.Start();
recordStopButton.Content = "Stop";
}
else if (recordStopButton.Content.Equals("Stop"))
{
// Good idea to stop the graph to avoid data loss
graph.Stop();
TranscodeFailureReason finalizeResult = await fileOutputNode.FinalizeAsync();
if (finalizeResult != TranscodeFailureReason.None)
{
// TODO: Finalization of file failed. Check result code to see why, propagate error message
return;
}
recordStopButton.Content = "Record";
}
}