<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>189337</bug_id>
          
          <creation_ts>2018-09-06 05:58:23 -0700</creation_ts>
          <short_desc>decodeAudioData memory garbage collected too late</short_desc>
          <delta_ts>2018-09-11 11:27:40 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>Web Audio</component>
          <version>Other</version>
          <rep_platform>iPhone / iPad</rep_platform>
          <op_sys>iOS 11</op_sys>
          <bug_status>NEW</bug_status>
          <resolution></resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Minor</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter>ae</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>jer.noble</cc>
    
    <cc>simon.fraser</cc>
    
    <cc>webkit-bug-importer</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>1457039</commentid>
    <comment_count>0</comment_count>
    <who name="">ae</who>
    <bug_when>2018-09-06 05:58:23 -0700</bug_when>
    <thetext>When using decodeAudioData repeatedly with large files (or files that result in a large uncompressed buffer), it takes a very long time until the memory used by decodeAudioData is freed.

I use decodeAudioData to decode an audio file and then just get an overview (peaks) from it, which is only a few kilobytes. I do not hold any references to the buffer, channel data, or file after doing this.

Yet, potentially several hundred MBs of memory are being held by the &quot;Page&quot; process for around one minute after decodeAudioData, and then finally get freed.

Can&apos;t this large amount of data be collected earlier?</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1457045</commentid>
    <comment_count>1</comment_count>
    <who name="">ae</who>
    <bug_when>2018-09-06 06:11:25 -0700</bug_when>
    <thetext>Note: I&apos;m asking because I&apos;m using a WKWebView in an iOS app, and it will randomly get killed (it turns blank) by the OS because it appearently uses too much memory.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458010</commentid>
    <comment_count>2</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2018-09-08 23:48:35 -0700</bug_when>
    <thetext>&lt;rdar://problem/44271160&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458219</commentid>
    <comment_count>3</comment_count>
    <who name="Simon Fraser (smfr)">simon.fraser</who>
    <bug_when>2018-09-10 14:05:12 -0700</bug_when>
    <thetext>I wonder if WebAudio-related classes are not reporting their memory costs to GC correctly?</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458297</commentid>
    <comment_count>4</comment_count>
    <who name="Jer Noble">jer.noble</who>
    <bug_when>2018-09-10 16:15:16 -0700</bug_when>
    <thetext>(In reply to Simon Fraser (smfr) from comment #3)
&gt; I wonder if WebAudio-related classes are not reporting their memory costs to
&gt; GC correctly?

AudioBuffer (which contains all the decoded audio data) implements memoryCost(), which should be what the GC uses to determine object size. I&apos;m guessing that there&apos;s an AudioBufferSourceNode which is retaining the decoded AudioBuffer and that the ABSN is being kept alive long after its playback has completed.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458306</commentid>
    <comment_count>5</comment_count>
    <who name="">ae</who>
    <bug_when>2018-09-10 16:27:54 -0700</bug_when>
    <thetext>(In reply to Jer Noble from comment #4)
&gt; I&apos;m
&gt; guessing that there&apos;s an AudioBufferSourceNode which is retaining the
&gt; decoded AudioBuffer and that the ABSN is being kept alive long after its
&gt; playback has completed.

As I said, I do not play back the buffer. I just use it for generating an overview. Here&apos;s the complete code (it&apos;s Tea, our in-house programming language that transcompiles to JS, but I hope it&apos;s readable enough!)

HTTP.get(&quot;sevenapp:///#{encodeURIComponent(@playingPath)}&quot;, (audioData) =&gt;
	if not audioData?
		@clearPeaks()
		&lt;-
	Audio.context.decodeAudioData(audioData, (buffer) =&gt;
		if not buffer?
			@clearPeaks()
			&lt;-
		samples = buffer.getChannelData(0)
		# 30 samples per second
		step = buffer.sampleRate / 30
		@peaks[@playingPath] = peaks = []
		for frame in [0...samples.length/step]
			sum = 0
			for index in [frame*step...(frame+1)*step] by 256 # accuracy, higher = worse, 1 = best
				if index &gt;= samples.length
					break
				sum += Math.abs(samples[Math.floor(index)])
			sum /= step/256
			peaks.push(sum)
		@renderPeaks()
	)
)</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458530</commentid>
    <comment_count>6</comment_count>
    <who name="Jer Noble">jer.noble</who>
    <bug_when>2018-09-11 11:09:50 -0700</bug_when>
    <thetext>(In reply to ae from comment #5)
&gt; (In reply to Jer Noble from comment #4)
&gt; &gt; I&apos;m
&gt; &gt; guessing that there&apos;s an AudioBufferSourceNode which is retaining the
&gt; &gt; decoded AudioBuffer and that the ABSN is being kept alive long after its
&gt; &gt; playback has completed.
&gt; 
&gt; As I said, I do not play back the buffer. I just use it for generating an
&gt; overview. Here&apos;s the complete code (it&apos;s Tea, our in-house programming
&gt; language that transcompiles to JS, but I hope it&apos;s readable enough!)
&gt; 
&gt; HTTP.get(&quot;sevenapp:///#{encodeURIComponent(@playingPath)}&quot;, (audioData) =&gt;
&gt; 	if not audioData?
&gt; 		@clearPeaks()
&gt; 		&lt;-
&gt; 	Audio.context.decodeAudioData(audioData, (buffer) =&gt;
&gt; 		if not buffer?
&gt; 			@clearPeaks()
&gt; 			&lt;-
&gt; 		samples = buffer.getChannelData(0)

Unless your in-house transpiler adds scoping statements to these variables, your going to be setting window.samples to a large Float32Array.  If your in-house transpiler /is/ adding scoping statements, it would be much more useful to post the transpiled source.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1458544</commentid>
    <comment_count>7</comment_count>
    <who name="">ae</who>
    <bug_when>2018-09-11 11:27:40 -0700</bug_when>
    <thetext>(In reply to Jer Noble from comment #6)
&gt; (In reply to ae from comment #5)
&gt; &gt; (In reply to Jer Noble from comment #4)
&gt; &gt; &gt; I&apos;m
&gt; &gt; &gt; guessing that there&apos;s an AudioBufferSourceNode which is retaining the
&gt; &gt; &gt; decoded AudioBuffer and that the ABSN is being kept alive long after its
&gt; &gt; &gt; playback has completed.
&gt; &gt; 
&gt; &gt; As I said, I do not play back the buffer. I just use it for generating an
&gt; &gt; overview. Here&apos;s the complete code (it&apos;s Tea, our in-house programming
&gt; &gt; language that transcompiles to JS, but I hope it&apos;s readable enough!)
&gt; &gt; 
&gt; &gt; HTTP.get(&quot;sevenapp:///#{encodeURIComponent(@playingPath)}&quot;, (audioData) =&gt;
&gt; &gt; 	if not audioData?
&gt; &gt; 		@clearPeaks()
&gt; &gt; 		&lt;-
&gt; &gt; 	Audio.context.decodeAudioData(audioData, (buffer) =&gt;
&gt; &gt; 		if not buffer?
&gt; &gt; 			@clearPeaks()
&gt; &gt; 			&lt;-
&gt; &gt; 		samples = buffer.getChannelData(0)
&gt; 
&gt; Unless your in-house transpiler adds scoping statements to these variables,
&gt; your going to be setting window.samples to a large Float32Array.  If your
&gt; in-house transpiler /is/ adding scoping statements, it would be much more
&gt; useful to post the transpiled source.


Oh sorry, of course it makes &apos;samples&apos; local (var samples), I&apos;m just so used to this that I forgot. Here&apos;s the transpiled code (not too readable):

    getPeaks: function() {
      element(&apos;player-peaks&apos;).classList.remove(&apos;show&apos;);
      if (this.peaks[this.playingPath] != null) {
        timer(0.5, (function(_this) {
          return function() {
            _this.renderPeaks();
          };
        })(this));
        return;
      }
      if (Audio.context == null) {
        this.needPeaksOnResume = true;
        return;
      }
      this.needPeaksOnResume = false;
      HTTP.get(&quot;sevenapp:///&quot; + (encodeURIComponent(this.playingPath)), (function(_this) {
        return function(audioData) {
          if (audioData == null) {
            _this.clearPeaks();
            return;
          }
          Audio.context.decodeAudioData(audioData, function(buffer) {
            var frame, i, index, j, peaks, ref, ref1, ref2, samples, step, sum;
            if (buffer == null) {
              _this.clearPeaks();
              return;
            }
            samples = buffer.getChannelData(0);
            step = buffer.sampleRate / 30;
            _this.peaks[_this.playingPath] = peaks = [];
            for (frame = i = 0, ref = samples.length / step; 0 &lt;= ref ? i &lt; ref : i &gt; ref; frame = 0 &lt;= ref ? ++i : --i) {
              sum = 0;
              for (index = j = ref1 = frame * step, ref2 = (frame + 1) * step; j &lt; ref2; index = j += 256) {
                if (index &gt;= samples.length) {
                  break;
                }
                sum += Math.abs(samples[Math.floor(index)]);
              }
              sum /= step / 256;
              peaks.push(sum);
            }
            _this.renderPeaks();
          });
        };
      })(this));
    },</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>