AudioClip.Create

Can the state of AudioClip.Create be confirmed as working or broken (in webgl) as far as using the pcmreader callback overload Unity - Scripting API: AudioClip.Create ?

With exception handling off (using the april 1st release), i get the same exception as posted here http://forum.unity3d.com/threads/in-webgl-version-of-project-throw-exception.313880/
( i am not using www, to be clear )

With exception handling on, i get
An error occured running the Unity content on this page. See your browser’s JavaScript console for more info. The error was:
ReferenceError: _llvm_memcpy_p0i8_p0i8_i32 is not defined
( similar to this Unity 5 WebGL build error with _llvm_memcpy_p0i8_p0i8_i32 is not defined - Questions & Answers - Unity Discussions )

I’m guessing that its either its not supported yet, or the clip doesn’t get created because an exception occuring prior to where it would be created.

I guess i’d like to know if AudioClip.Create would be the one returning null ( as it does when zero is passed for length ) or if its something in the IL2CPP translation breaking things again.

Thanks

I got it to work but only in Firefox - Chrome didn’t support the underlying Javascript API that Unity uses. Even in Firefox it only worked when I supplied a callback function with streaming disabled - I wasn’t able to supply the audio data after constructing the clip, either using the callback or by using SetData.

I didn’t get the error message you posted though, it was something more audio-specific that I don’t remember now.

Here is some code from the microphone recording unitypackage I posted a few weeks ago. The callback theoretically supports streaming but I guess in this context it will only be called once and asked to fill the entire buffer.

        int pos = -1;
        _clip = AudioClip.Create("recordedclip", numBuffers * BufferSize, 1, 44100, false,
            data =>
            {
                for (int i = 0; i < data.Length; ++i)
                {
                    pos = (pos + 1) % BufferSize;
                    if (pos == 0)
                    {
                        var ok = MicrophoneWebGL.GetBuffer(buffer);
                        if (!ok)
                            Debug.LogError("not ok");
                    }
                    data[i] = buffer[pos];
                }
            });
1 Like

Thanks a lot for the quick response. That’s too bad about not being able to stream and it being reliant on browser-specific API’s. I guess with my errors, it’s more likely that something in the IL2CPP translation is throwing an exception prior to the clips creation but its good to know that it would likely not work after determining what is wrong either way. Do wish I knew this prior though. Thanks

for what its worth, i came up with this fix to at least stop the exception in chrome when using the NON streaming overloads of AudioClip.Create with a PCMReaderCallback. ( trying to catch the exception around AudioClip.Create didn’t seem to help any in regards to not completely crashing the game in browser )

So again, This will not make chrome play audio clips made with AudioClip.Create, nor will it make any browser that i know of work with the streaming parameter set as true work.

Basically, the error is like this ( in chrome )

exception thrown: TypeError: undefined is not a function,TypeError: undefined is not a function
    at _JS_Sound_Load_PCM
    at __ZN4FMOD7SystemI11createSoundEPKcjP22FMOD_CREATESOUNDEXINFOPPNS_5SoundE
    at __ZN4FMOD6System11createSoundEPKcjP22FMOD_CREATESOUNDEXINFOPPNS_5SoundE
    at __ZN12SoundManager11createSoundEPKcjP22FMOD_CREATESOUNDEXINFOR11SoundHandlej4PPtrI10SampleClipE
    at __ZN9AudioClip11CreateSoundEv
    at __ZN9AudioClip15CreateUserSoundERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEjsjb
    at Array.__Z30AudioClip_CUSTOM_Init_InternalP15ScriptingObjectP15ScriptingStringiiih 
    at __Z29AudioClip_Init_Internal_m7185P13AudioClip_t54P8String_tiiibP10MethodInfo
    at __Z22AudioClip_Create_m7181P8Object_tP8String_tiiibP22PCMReaderCallback_t226P28PCMSetPositionCallback_t1231P10MethodInfo
    at __Z22AudioClip_Create_m1030P8Object_tP8String_tiiibP22PCMReaderCallback_t226P10MethodInfo

ultimately, in _JS_Sound_Load_PCM a variable (sound) is missing a function member copyToChannel on chrome. So as a temporary fix i use whats listed here to mimic the behaviour and check for that member being defined or not. To do so I took notes from
WebGL: Interacting with browser scripting

This is likely the first time i’ve ever written javascript outside of unity, so I do not know of any repercussions in calling WEBAudio.audioContext.createBuffer and doing nothing with the result. I do not know if it has to be disposed or whatever else, if it leaks each time the function gets called or anything like that. So use at your own risk.

BPJSUtil.jslib ( goes in “Assets/Plugins/WebGL/BPJSUtil.jslib” )

var BPJSUtilPlugin = {
    WillPCMLoad : function()
    {
        return !(!WEBAudio||WEBAudio.audioWebEnabled==0||!WEBAudio.audioContext.createBuffer(2,40,44100).copyToChannel);
    }
};

mergeInto(LibraryManager.library, BPJSUtilPlugin);

BPJSUtil.cs ( goes in “Assets/Plugins/BPJSUtil.cs” )

using UnityEngine;
using System.Collections;
#if UNITY_WEBGL
internal static class BPJSUtil
{
    [System.Runtime.InteropServices.DllImport("__Internal")]
    public extern static bool WillPCMLoad();
}
#endif

public static class WebGLAudio
{
    /// <summary>
    /// returns true if AudioClip.Create will work or not given context.
    /// </summary>
    /// <param name="Streaming">is this going to be a streaming audio clip? ( afaik, no browsers support streaming audio clips. )</param>
    /// <param name="FailIfExceptionInJS">if you want an unknown exception (likely coming from the jslib code) to return false, set this as true.</param>
    /// <returns>true if AudioClip.Create will likely not kill the game.</returns>
    public static bool CanCreateAudioClip(bool Streaming = false, bool FailIfExceptionInJS=false)
    {
#if !UNITY_WEBGL
        return true;
#else
        // i don't know that any browser supports streaming, and unsure of what to look for.
        // returning false mimics the right response in browser i suppose.
        if( Streaming )
            return false;
        // try to invoke the js.
        try
        {
            return BPJSUtil.WillPCMLoad();
        }
#if UNITY_EDITOR
        catch( System.EntryPointNotFoundException )
        {
            // yea. in editor this will happen. because its not going to be running the jslib.
            // we'll assume firefox in editor.
            return true;
        }
#endif
        catch(System.Exception e)
        {
            // likely, something wrong in the js function..
            Debug.LogException(e);

            return !FailIfExceptionInJS;
        }
#endif
    }
}

Then before you use AudioClip.Create check that WebGLAudio.CanCreateAudioClip returns true before calling Create, otherwise never call AudioClip.Create.

Notes:
again, I do not know if the call is going to leak memory each time its made. While the result could be cached on the first call to the function, I do not know if that would work at runtime or if any of that could change and thus why i do not cache the value. If it never changes then the call to WillPCMLoad should occur once and the return cached. Either way, i would limit calling this function.

This does not explicitly check what browser is being used, but rather if the undefined member is defined or not. Which may or may not be better long term as if it’s fixed by unity, who knows if the function names will remain the same, and if its fixed by chrome then the check is likely unnecessary.

If the streaming argument of CanCreateAudioClip is true, right now false is always returned ( when WEBGL ), because it seems streaming audio works on no platform at all at the moment. So, if streaming clips made with AudioClip.Create ever do work, this will still report they will not.

Any tips on improving the javascript, as again i am very unfamiliar with it, would be greatly welcome.

As has been correctly debugged by you guys (you rock!), the issue is that Chrome lacks the
copyToChannel method (which is required by the Web Audio spec). Chromium has an open bug on the problem (with a suggested patch to fix it):

https://code.google.com/p/chromium/issues/detail?id=361859

Awesome. Does this mean we’ll get streaming clips to work too?

I’m getting the same issue with Mac Safari - I’m a little unclear on the solution here (ie. copyToBuffer is undefined). It seems a bit of a shame as I have gone round the houses to get audio buffers from the mic passed back through an adapted version of gfoot’s (above) plug-in that also uses a flash polyfill for this platform - seems like most of the hard work is done …

Is there likely to be a fix here in Unity, or is there another workaround I can use to get my buffers into an audio clip?

sorry I meant copyToChannel - should this be webkitCopyToChannel for Safari as per what’s documented here, or have I misunderstood the docs?

Indeed, we should probably check for the prefixed version, and use that if available, which should fix Safari. Will take a note to fix that.

Thanks Jonas - do you have a suggested workaround for the time being - I have been trying to figure out how to replace the function globally or by modifying Audio.js but have not yet figured out how to get that working

Dom, what I suggested before was wrong, but you can try this Javascript instead, anywhere that executes before the error occurs:

AudioBuffer.prototype.copyToChannel = AudioBuffer.prototype.copyToChannel || AudioBuffer.prototype.webkitCopyToChannel;

I think it should work - it aliases the method in the prototype, so from this point on all AudioBuffers will inherit the alias, including ones that have already been created, and Unity’s code should then work fine.

I can’t test it properly right now though because my Mac Mini is gathering dust on a shelf somewhere.

I tested this this morning, and no joy - it doesn’t look like webkitCopyToChannel is defined … I looked at a compatibility table this morning which seemed to denote the method as object and not function (not sure if that has any bearing as I did not understand the implication there), however I have also found this link which might demonstrate a way forward with this … https://raw.githubusercontent.com/mohayonao/web-audio-api-shim/master/build/web-audio-api-shim.js … I’ll see if I can get it working …

this (link in my last post) does appear to close the loop for us on getting the audio clips created on Safari so hopefully something similar will work for you Jonas - thanks for all the help george - much appreciated

Just took a look - thanks to everyone who collected information on this. I have fix in Unity now which will probably ship in 5.3. Until then, the workaround linked to by @dominicjackson should do the trick.