As per our requirement we need to be able to record a video using device camera and it should also contain audio from device mic.
We have gone through several solutions regarding this including NatCorder, pmjo’s Next Gen Recorder, OpenCV for Unity etc. But none of them so far seems to completely satisfy our requirements.
What we are trying to achieve is as follows.
Record a video using device camera and mic.
Functioning entirely in Unity (no native dialog or popup)
Audio and video settings being configurable.
Getting output as a file from device storage (its ok if its a temporary file)
Please suggest a solution in which we can achieve this.
There is no way to avoid the native permission dialog, you cannot access the microphone without asking the user (even when using Unity microphone). Assuming you are talking about iOS or Android
If you only need to record the microphone audio (not microphone mixed with app audio) you can record the Unity microphone by adding the Next Gen Recorder (iOS only) AudioSourceRecorder component on top of your microphone AudioSource. To get rid of the possible echo, follow this workaround link. Unity microphone is not good, you need to tweak many settings to make it work… and still the result is not very good.
Hello Pauli, can you explain more how to use the AudioSourceRecorder. I have downloaded the NextGenRecorder. But there is no example file. Do you have a link to a sample?
Hi! You could implement a regular microphone capture code like described in many Unity forum posts and then drop the AudioSourceRecorder component on top of the AudioSource that plays back the microphone. But instead of doing that you could actually try a script that @xzodia created a while ago. I’m pretty sure it gives a lot better results.
Here it is:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace pmjo.NextGenRecorder
{
[AddComponentMenu("Next Gen Recorder/Microphone Recorder")]
public class MicrophoneRecorder : Recorder.AudioRecorderBase
{
int SampleRate = 16000;
const int RecordingLength = 10;
AudioSource audioSource;
string micName;
private void Awake()
{
Recorder.RecordingStarted += Recorder_RecordingStarted;
Recorder.RecordingStopped += Recorder_RecordingStopped;
}
private void OnDestroy()
{
if (gCHandle.IsAllocated)
gCHandle.Free();
if (ListenCoroutine != null) StopCoroutine(ListenCoroutine);
Recorder.RecordingStarted -= Recorder_RecordingStarted;
Recorder.RecordingStopped -= Recorder_RecordingStopped;
}
private void Recorder_RecordingStopped(long sessionId)
{
Microphone.End(micName);
}
private void Recorder_RecordingStarted(long sessionId)
{
AudioConfiguration configuration = AudioSettings.GetConfiguration();
if (configuration.sampleRate < 8000)
{
Debug.Log("Only sample rate >= 8000 is supported");
return;
}
else
{
SampleRate = configuration.sampleRate;
}
if (Microphone.devices.Length == 0)
{
Debug.LogError("No Microphone Available");
return;
}
if (audioSource == null)
audioSource = gameObject.AddComponent<AudioSource>();
micName = Microphone.devices[0];
Debug.Log("Starting Microphone: " + micName);
audioSource.clip = Microphone.Start(micName, true, RecordingLength, SampleRate);
audioSource.loop = true;
audioSource.mute = true;
while (!(Microphone.GetPosition(micName) > 0)) { }
audioSource.Play();
Debug.Log("Microphone Initialized");
ListenCoroutine = StartCoroutine(Listen(audioSource.clip.channels, 2));
}
Coroutine ListenCoroutine = null;
GCHandle gCHandle;
int readSize;
bool AwaitingFilterRead;
IEnumerator Listen(int readChannels, int writeChannels)
{
int MaxSamples = SampleRate * RecordingLength;
float[] fReadData = new float[MaxSamples * readChannels];
float[] fWriteData = new float[MaxSamples * writeChannels];
int unwrappedReadHead = 0;
int readHead = 0;
readSize = 0;
gCHandle = GCHandle.Alloc(fWriteData, GCHandleType.Pinned);
if (gCHandle.IsAllocated == false)
{
Debug.LogError("Failed to allocated GC Handle");
yield break;
}
while (true)
{
yield return null;
if (AwaitingFilterRead) continue;
int writeHead = Microphone.GetPosition(micName);
if (writeHead < unwrappedReadHead) writeHead += MaxSamples;
int positionDelta = writeHead - unwrappedReadHead;
if (positionDelta == 0) continue;
audioSource.clip.GetData(fReadData, readHead);
for (int wc = 0; wc < writeChannels; wc++)
{
var rc = wc % readChannels;
for (int i = 0; i < positionDelta; i++)
fWriteData[(i * writeChannels) + wc] = fReadData[(i * readChannels) + rc];
}
unwrappedReadHead = readHead + positionDelta;
readHead = unwrappedReadHead % audioSource.clip.samples;
readSize = positionDelta * writeChannels;
AwaitingFilterRead = true;
}
}
private void OnAudioFilterRead(float[] d, int c)
{
if (readSize > 0)
{
base.AppendInterleavedAudio(gCHandle.AddrOfPinnedObject(), readSize, c, SampleRate);
readSize = 0;
AwaitingFilterRead = false;
}
}
}
}
@pmjo No problem, one thing I have noticed since writing this though, if the user needs to grant microphone permissions at the start of the recording, the video will continue to record in the background. I haven’t gotten around to fixing this yet, but its just a case of checking the permissions before starting the recording.
I haven’t personally been playing much with this. Sounds like something goes to a bad state. Maybe you should try to close the microphone when the app goes to background by calling End. Like @xzodia mentioned, you also need to implement microphone permission checks to use the above script. It would also be a good idea not to loop forever and timeout if Microphone.GetPosition part loops too long and maybe try to restart or give up after that.