RenderHeads / UnityPlugin-AVProMovieCapture

AVPro Movie Capture is a Unity Plugin for advanced video capture to AVI/MP4/MOV files
https://renderheads.com/products/avpro-movie-capture/
48 stars 8 forks source link

FMOD support #81

Open joonjoonjoon opened 3 years ago

joonjoonjoon commented 3 years ago

I raised this in the wrong repo earlier, but...

Fmod is a popular and high-end audio engine plugin for Unity. Unity uses a forked (dated) version of FMOD under the hood but it is not compatible with FMOD studio which sound designers like to use. FMOD as a plugin entirely replaces Unity's audio system and is incompatible with it.

The Unity video capture plugin now allows recording audio in offline mode, which is perfect, but is not compatible with FMOD.

https://www.fmod.com/unity

I initially posted here, but got a bit confused about stuff. I hope this issue makes more sense and is in the right place. https://github.com/RenderHeads/UnityPlugin-AVProVideo/issues/526#issuecomment-739893476

AndrewRH commented 3 years ago

Thanks, I see - I guess this is a bit like WWISE - they also have a plugin that can be used for all audio in Unity and it's relatively popular.. We did look at writing an audio capture solution for WWISE previously but ran into some issues.

I'm not familiar with the FMOD plugin for Unity so this is something we would have to investigate.

ChadKeating commented 2 years ago

@AndrewRH is offline audio now supported for wwise/fmod with this update?: https://www.renderheads.com/content/docs/AVProMovieCapture/versions/4.6.0.html

chadefranklin commented 2 years ago

Yes, FMOD support would be very helpful!

robinW-D commented 1 year ago

Hiya, any update on FMOD support?

Chris-RH commented 1 year ago

nothing as of yet

Grhyll commented 1 year ago

Hi, just for anyone who could use that info, based on:

There are still a few things I don't really understand, and some optimization needed, but as a starting point:

  using System;
  using System.Collections.Generic;
  using UnityEngine;
  using System.Runtime.InteropServices;
  using RenderHeads.Media.AVProMovieCapture;

  public class CaptureAudioFromFmodDsp : MonoBehaviour
  {
      //public int SampleRate => mSampleRate;
      //public int ChannelCount => mChannels;

      const string busToRecord = "bus:/Ingame/SFX";
      CaptureFromScreen capture;

      private FMOD.DSP_READ_CALLBACK mReadCallback;
      private FMOD.DSP mCaptureDSP;
      private float[] mDataBuffer;
      private GCHandle mObjHandle;
      private uint mBufferLength;

      [AOT.MonoPInvokeCallback(typeof(FMOD.DSP_READ_CALLBACK))]
      static FMOD.RESULT CaptureDSPReadCallback(ref FMOD.DSP_STATE dsp_state, IntPtr inbuffer, IntPtr outbuffer, uint length, int inchannels, ref int outchannels)
      {
          FMOD.DSP_STATE_FUNCTIONS functions = (FMOD.DSP_STATE_FUNCTIONS)Marshal.PtrToStructure(dsp_state.functions, typeof(FMOD.DSP_STATE_FUNCTIONS));

          IntPtr userData;
          functions.getuserdata(ref dsp_state, out userData);
          GCHandle objHandle = GCHandle.FromIntPtr(userData);

          //int sampleRate = 0;
          //functions.getsamplerate(ref dsp_state, ref sampleRate);

          CaptureAudioFromFmodDsp obj = objHandle.Target as CaptureAudioFromFmodDsp;

          // Copy the incoming buffer to process later
          int lengthElements = (int)length * inchannels;
          Marshal.Copy(inbuffer, obj.mDataBuffer, 0, lengthElements);
          if (obj.capture != null && obj.capture.IsCapturing() && !obj.capture.IsPaused())
          {
              float[] t = new float[(int)length * 2];
              for (int i = 0; i < length; i++)
              {
                  if (inchannels >= 2)
                  {
                      for (int j = 0; j < 2; j++)
                      {
                          t[i * 2 + j] = obj.mDataBuffer[i * inchannels + j];
                      }
                  }
                  else
                  {
                      t[i * 2] = obj.mDataBuffer[i];
                      t[i * 2 + 1] = 0;
                  }
              }
              obj.capture.EncodeAudio(t);
          }

          // Copy the inbuffer to the outbuffer so we can still hear it
          Marshal.Copy(obj.mDataBuffer, 0, outbuffer, lengthElements);

          return FMOD.RESULT.OK;
      }

      void RegisterToReadCallback()
      {
          // Assign the callback to a member variable to avoid garbage collection
          mReadCallback = CaptureDSPReadCallback;

          // Allocate a data buffer large enough for 8 channels, pin the memory to avoid garbage collection
          uint bufferLength;
          int numBuffers;
          FMODUnity.RuntimeManager.CoreSystem.getDSPBufferSize(out bufferLength, out numBuffers);
          mDataBuffer = new float[bufferLength * 8];
          mBufferLength = bufferLength;

          // Get a handle to this object to pass into the callback
          mObjHandle = GCHandle.Alloc(this);
          if (mObjHandle != null)
          {
              // Define a basic DSP that receives a callback each mix to capture audio
              FMOD.DSP_DESCRIPTION desc = new FMOD.DSP_DESCRIPTION();
              desc.numinputbuffers = 1;
              desc.numoutputbuffers = 1;
              desc.read = mReadCallback;
              desc.userdata = GCHandle.ToIntPtr(mObjHandle);

              // Create an instance of the capture DSP and attach it to the wanted channel group to capture its audio
              //FMOD.ChannelGroup masterCG;
              //if (FMODUnity.RuntimeManager.CoreSystem.getMasterChannelGroup(out masterCG) == FMOD.RESULT.OK)

              FMOD.Studio.Bus bus = FMODUnity.RuntimeManager.GetBus(busToRecord);
              bus.lockChannelGroup(); // Force to create the channel group
              FMODUnity.RuntimeManager.StudioSystem.flushCommands(); // Force the channel group creation to happen right now
              if (bus.getChannelGroup(out FMOD.ChannelGroup cg) == FMOD.RESULT.OK)
              {
                  if (FMODUnity.RuntimeManager.CoreSystem.createDSP(ref desc, out mCaptureDSP) == FMOD.RESULT.OK)
                  {
                      if (cg.addDSP(0, mCaptureDSP) != FMOD.RESULT.OK)
                      {
                          Debug.LogWarningFormat("FMOD: Unable to add mCaptureDSP to the master channel group");
                      }
                  }
                  else
                  {
                      Debug.LogWarningFormat("FMOD: Unable to create a DSP: mCaptureDSP");
                  }
              }
              else
              {
                  Debug.LogWarningFormat("FMOD: Unable to create a channel group from sfxBus, isValid: " + bus.isValid() + ", return " + bus.getChannelGroup(out cg));
              }
              bus.unlockChannelGroup();
          }
          else
          {
              Debug.LogWarningFormat("FMOD: Unable to create a GCHandle: mObjHandle");
          }
      }

      void OnDestroy()
      {
          UnregisterFromReadCallback();
      }
      void UnregisterFromReadCallback()
      {
          if (mObjHandle.IsAllocated)
          {
              FMOD.Studio.Bus bus = FMODUnity.RuntimeManager.GetBus(busToRecord);
              bus.lockChannelGroup();
              FMODUnity.RuntimeManager.StudioSystem.flushCommands();
              if (bus.getChannelGroup(out FMOD.ChannelGroup cg) == FMOD.RESULT.OK)
              {
                  if (mCaptureDSP.hasHandle())
                  {
                      cg.removeDSP(mCaptureDSP);

                      // Release the DSP and free the object handle
                      mCaptureDSP.release();
                  }
              }
              bus.unlockChannelGroup();
              mObjHandle.Free();
          }
      }

      public void PrepareCapture()
      {
      }
      public void StartCapture(CaptureFromScreen capture)
      {
          RegisterToReadCallback();
          this.capture = capture;
      }
      public void StopCapture()
      {
          UnregisterFromReadCallback();
          capture = null;
      }
      public void PauseCapture()
      {
      }
      public void ResumeCapture()
      {
      }
  }

(Sorry the "code" markdown doesn't seem to work correctly!)

Chris-RH commented 1 year ago

Thanks you. Interested to see if it works in build :)

Grhyll commented 1 year ago

Looks like it works fine in build as well :)

Chris-RH commented 1 year ago

Awesome, that's great, thanks for letting us know 😀

On Wed, 12 Jul 2023, 11:16 Grhyll, @.***> wrote:

Looks like it works fine in build as well :)

— Reply to this email directly, view it on GitHub https://github.com/RenderHeads/UnityPlugin-AVProMovieCapture/issues/81#issuecomment-1632234774, or unsubscribe https://github.com/notifications/unsubscribe-auth/AYRROUIU4P5O4ME3M22NTYLXPZ2OPANCNFSM4UTXGRDQ . You are receiving this because you commented.Message ID: @.*** com>

stale[bot] commented 9 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.