[Spice-commits] 6 commits - display.js enums.js main.js playback.js spiceconn.js utils.js webm.js

Jeremy White jwhite at kemper.freedesktop.org
Wed Jul 13 15:45:28 UTC 2016


 display.js   |  289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 enums.js     |    6 +
 main.js      |    6 +
 playback.js  |   74 ++++++++++++++-
 spiceconn.js |    9 +
 utils.js     |   55 +++++++++++
 webm.js      |  109 ++++++++++++++++++++--
 7 files changed, 513 insertions(+), 35 deletions(-)

New commits:
commit a8c406377e0a141aecbaa2069938c272c21785c4
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Fri Jul 1 12:50:27 2016 -0500

    Send stream process reports after we have processed them.
    
    Previously, we sent them strictly upon receipt.  However, receiving
    is fast; by adding in some of the processing time (i.e. queueing and
    so on), we encourage the adaptive rate control to slow down.

diff --git a/display.js b/display.js
index 00b6011..12fbab0 100644
--- a/display.js
+++ b/display.js
@@ -594,18 +594,14 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
             return false;
         }
 
-        var mmtime = (Date.now() - this.parent.our_mm_time) + this.parent.mm_time;
-        var latency = m.base.multi_media_time - mmtime;
+        var time_until_due = m.base.multi_media_time - this.parent.relative_now();
 
         if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG)
-            process_mjpeg_stream_data(this, m, latency);
+            process_mjpeg_stream_data(this, m, time_until_due);
 
         if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_VP8)
             process_video_stream_data(this.streams[m.base.id], m);
 
-        if ("report" in this.streams[m.base.id])
-            process_stream_data_report(this, m, mmtime, latency);
-
         return true;
     }
 
@@ -958,11 +954,14 @@ function handle_draw_jpeg_onload()
 
         this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
     }
+
+    if ("report" in this.o.sc.streams[this.o.id])
+            process_stream_data_report(this.o.sc, this.o.id, this.o.msg_mmtime, this.o.msg_mmtime - this.o.sc.parent.relative_now())
 }
 
-function process_mjpeg_stream_data(sc, m, latency)
+function process_mjpeg_stream_data(sc, m, time_until_due)
 {
-    if (latency < 0)
+    if (time_until_due < 0)
     {
         if ("report" in sc.streams[m.base.id])
             sc.streams[m.base.id].report.num_drops++;
@@ -988,30 +987,32 @@ function process_mjpeg_stream_data(sc, m, latency)
           tag: "mjpeg." + m.base.id,
           descriptor: null,
           sc : sc,
+          id : m.base.id,
+          msg_mmtime : m.base.multi_media_time,
         };
     img.onload = handle_draw_jpeg_onload;
     img.src = tmpstr;
 }
 
-function process_stream_data_report(sc, m, mmtime, latency)
+function process_stream_data_report(sc, id, msg_mmtime, time_until_due)
 {
-    sc.streams[m.base.id].report.num_frames++;
-    if (sc.streams[m.base.id].report.start_frame_mm_time == 0)
-        sc.streams[m.base.id].report.start_frame_mm_time = m.base.multi_media_time;
+    sc.streams[id].report.num_frames++;
+    if (sc.streams[id].report.start_frame_mm_time == 0)
+        sc.streams[id].report.start_frame_mm_time = msg_mmtime;
 
-    if (sc.streams[m.base.id].report.num_frames > sc.streams[m.base.id].max_window_size ||
-        (m.base.multi_media_time - sc.streams[m.base.id].report.start_frame_mm_time) > sc.streams[m.base.id].timeout_ms)
+    if (sc.streams[id].report.num_frames > sc.streams[id].max_window_size ||
+        (msg_mmtime - sc.streams[id].report.start_frame_mm_time) > sc.streams[id].timeout_ms)
     {
-        sc.streams[m.base.id].report.end_frame_mm_time = m.base.multi_media_time;
-        sc.streams[m.base.id].report.last_frame_delay = latency;
+        sc.streams[id].report.end_frame_mm_time = msg_mmtime;
+        sc.streams[id].report.last_frame_delay = time_until_due;
 
         var msg = new SpiceMiniData();
-        msg.build_msg(SPICE_MSGC_DISPLAY_STREAM_REPORT, sc.streams[m.base.id].report);
+        msg.build_msg(SPICE_MSGC_DISPLAY_STREAM_REPORT, sc.streams[id].report);
         sc.send_msg(msg);
 
-        sc.streams[m.base.id].report.start_frame_mm_time = 0;
-        sc.streams[m.base.id].report.num_frames = 0;
-        sc.streams[m.base.id].report.num_drops = 0;
+        sc.streams[id].report.start_frame_mm_time = 0;
+        sc.streams[id].report.num_frames = 0;
+        sc.streams[id].report.num_drops = 0;
     }
 }
 
@@ -1085,10 +1086,17 @@ function handle_append_video_buffer_done(e)
 {
     var stream = this.stream;
 
+    if (stream.current_frame && "report" in stream)
+    {
+        var sc = this.stream.media.spiceconn;
+        var t = this.stream.current_frame.msg_mmtime;
+        process_stream_data_report(sc, stream.id, t, t - sc.parent.relative_now());
+    }
+
     if (stream.queue.length > 0)
     {
-        var mb = stream.queue.shift();
-        append_video_buffer(stream.source_buffer, mb);
+        stream.current_frame = stream.queue.shift();
+        append_video_buffer(stream.source_buffer, stream.current_frame.mb);
     }
     else
     {
@@ -1102,16 +1110,32 @@ function handle_video_buffer_error(e)
     p.log_err('source_buffer error ' + e.message);
 }
 
+function push_or_queue(stream, msg, mb)
+{
+    var frame =
+    {
+        msg_mmtime : msg.base.multi_media_time,
+    };
+
+    if (stream.append_okay)
+    {
+        stream.current_frame = frame;
+        append_video_buffer(stream.source_buffer, mb);
+    }
+    else
+    {
+        frame.mb = mb;
+        stream.queue.push(frame);
+    }
+}
+
 function video_simple_block(stream, msg, keyframe)
 {
     var simple = new webm_SimpleBlock(msg.base.multi_media_time - stream.cluster_time, msg.data, keyframe);
     var mb = new ArrayBuffer(simple.buffer_size());
     simple.to_buffer(mb);
 
-    if (stream.append_okay)
-        append_video_buffer(stream.source_buffer, mb);
-    else
-        stream.queue.push(mb);
+    push_or_queue(stream, msg, mb);
 }
 
 function new_video_cluster(stream, msg)
@@ -1122,10 +1146,7 @@ function new_video_cluster(stream, msg)
     var mb = new ArrayBuffer(c.buffer_size());
     c.to_buffer(mb);
 
-    if (stream.append_okay)
-        append_video_buffer(stream.source_buffer, mb);
-    else
-        stream.queue.push(mb);
+    push_or_queue(stream, msg, mb);
 
     video_simple_block(stream, msg, true);
 }
diff --git a/main.js b/main.js
index 3d9b13f..afe69bf 100644
--- a/main.js
+++ b/main.js
@@ -478,3 +478,9 @@ SpiceMainConn.prototype.handle_mouse_mode = function(current, supported)
         this.inputs.mouse_mode = current;
 }
 
+/* Shift current time to attempt to get a time matching that of the server */
+SpiceMainConn.prototype.relative_now = function()
+{
+    var ret = (Date.now() - this.our_mm_time) + this.mm_time;
+    return ret;
+}
commit 37780531389c16211d08d1110463538156171789
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Tue Jun 28 08:00:51 2016 -0600

    Add support for the vp8 codec type.

diff --git a/display.js b/display.js
index 819f8bc..00b6011 100644
--- a/display.js
+++ b/display.js
@@ -542,8 +542,40 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
             console.log("Stream " + m.id + " already exists");
         else
             this.streams[m.id] = m;
-        if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
-            console.log("Unhandled stream codec: " + m.codec_type);
+
+        if (m.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8)
+        {
+            var media = new MediaSource();
+            var v = document.createElement("video");
+            v.src = window.URL.createObjectURL(media);
+
+            v.setAttribute('autoplay', true);
+            v.setAttribute('width', m.stream_width);
+            v.setAttribute('height', m.stream_height);
+
+            var left = m.dest.left;
+            var top = m.dest.top;
+            if (this.surfaces[m.surface_id] !== undefined)
+            {
+                left += this.surfaces[m.surface_id].canvas.offsetLeft;
+                top += this.surfaces[m.surface_id].canvas.offsetTop;
+            }
+            document.getElementById(this.parent.screen_id).appendChild(v);
+            v.setAttribute('style', "position: absolute; top:" + top + "px; left:" + left + "px;");
+
+            media.addEventListener('sourceopen', handle_video_source_open, false);
+            media.addEventListener('sourceended', handle_video_source_ended, false);
+            media.addEventListener('sourceclosed', handle_video_source_closed, false);
+
+            this.streams[m.id].video = v;
+            this.streams[m.id].media = media;
+
+            media.stream = this.streams[m.id];
+            media.spiceconn = this;
+            v.spice_stream = this.streams[m.id];
+        }
+        else if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+            console.log("Unhandled stream codec: "+m.codec_type);
         return true;
     }
 
@@ -568,6 +600,9 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
         if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG)
             process_mjpeg_stream_data(this, m, latency);
 
+        if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_VP8)
+            process_video_stream_data(this.streams[m.base.id], m);
+
         if ("report" in this.streams[m.base.id])
             process_stream_data_report(this, m, mmtime, latency);
 
@@ -601,6 +636,14 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
     {
         var m = new SpiceMsgDisplayStreamDestroy(msg.data);
         DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+
+        if (this.streams[m.id].codec_type == SPICE_VIDEO_CODEC_TYPE_VP8)
+        {
+            document.getElementById(this.parent.screen_id).removeChild(this.streams[m.id].video);
+            this.streams[m.id].source_buffer = null;
+            this.streams[m.id].media = null;
+            this.streams[m.id].video = null;
+        }
         this.streams[m.id] = undefined;
         return true;
     }
@@ -971,3 +1014,186 @@ function process_stream_data_report(sc, m, mmtime, latency)
         sc.streams[m.base.id].report.num_drops = 0;
     }
 }
+
+function handle_video_source_open(e)
+{
+    var stream = this.stream;
+    var p = this.spiceconn;
+
+    if (stream.source_buffer)
+        return;
+
+    var s = this.addSourceBuffer(SPICE_VP8_CODEC);
+    if (! s)
+    {
+        p.log_err('Codec ' + SPICE_VP8_CODEC + ' not available.');
+        return;
+    }
+
+    stream.source_buffer = s;
+    s.spiceconn = p;
+    s.stream = stream;
+
+    stream.queue = new Array();
+    stream.start_time = 0;
+    stream.cluster_time = 0;
+
+    listen_for_video_events(stream);
+
+    var h = new webm_Header();
+    var te = new webm_VideoTrackEntry(this.stream.stream_width, this.stream.stream_height);
+    var t = new webm_Tracks(te);
+
+    var mb = new ArrayBuffer(h.buffer_size() + t.buffer_size())
+
+    var b = h.to_buffer(mb);
+    t.to_buffer(mb, b);
+
+    s.addEventListener('error', handle_video_buffer_error, false);
+    s.addEventListener('updateend', handle_append_video_buffer_done, false);
+
+    append_video_buffer(s, mb);
+}
+
+function handle_video_source_ended(e)
+{
+    var p = this.spiceconn;
+    p.log_err('Video source unexpectedly ended.');
+}
+
+function handle_video_source_closed(e)
+{
+    var p = this.spiceconn;
+    p.log_err('Video source unexpectedly closed.');
+}
+
+function append_video_buffer(sb, mb)
+{
+    try
+    {
+        sb.stream.append_okay = false;
+        sb.appendBuffer(mb);
+    }
+    catch (e)
+    {
+        var p = sb.spiceconn;
+        p.log_err("Error invoking appendBuffer: " + e.message);
+    }
+}
+
+function handle_append_video_buffer_done(e)
+{
+    var stream = this.stream;
+
+    if (stream.queue.length > 0)
+    {
+        var mb = stream.queue.shift();
+        append_video_buffer(stream.source_buffer, mb);
+    }
+    else
+    {
+        stream.append_okay = true;
+    }
+}
+
+function handle_video_buffer_error(e)
+{
+    var p = this.spiceconn;
+    p.log_err('source_buffer error ' + e.message);
+}
+
+function video_simple_block(stream, msg, keyframe)
+{
+    var simple = new webm_SimpleBlock(msg.base.multi_media_time - stream.cluster_time, msg.data, keyframe);
+    var mb = new ArrayBuffer(simple.buffer_size());
+    simple.to_buffer(mb);
+
+    if (stream.append_okay)
+        append_video_buffer(stream.source_buffer, mb);
+    else
+        stream.queue.push(mb);
+}
+
+function new_video_cluster(stream, msg)
+{
+    stream.cluster_time = msg.base.multi_media_time;
+    var c = new webm_Cluster(stream.cluster_time - stream.start_time, msg.data);
+
+    var mb = new ArrayBuffer(c.buffer_size());
+    c.to_buffer(mb);
+
+    if (stream.append_okay)
+        append_video_buffer(stream.source_buffer, mb);
+    else
+        stream.queue.push(mb);
+
+    video_simple_block(stream, msg, true);
+}
+
+function process_video_stream_data(stream, msg)
+{
+    if (! stream.source_buffer)
+        return true;
+
+    if (stream.start_time == 0)
+    {
+        stream.start_time = msg.base.multi_media_time;
+        stream.video.play();
+        new_video_cluster(stream, msg);
+    }
+
+    else if (msg.base.multi_media_time - stream.cluster_time >= MAX_CLUSTER_TIME)
+        new_video_cluster(stream, msg);
+    else
+        video_simple_block(stream, msg, false);
+}
+
+function video_handle_event_debug(e)
+{
+    var s = this.spice_stream;
+    if (s.video)
+    {
+        if (STREAM_DEBUG > 0 || s.video.buffered.len > 1)
+            console.log(s.video.currentTime + ":id " +  s.id + " event " + e.type +
+                dump_media_element(s.video));
+    }
+
+    if (STREAM_DEBUG > 1 && s.media)
+        console.log("  media_source " + dump_media_source(s.media));
+
+    if (STREAM_DEBUG > 1 && s.source_buffer)
+        console.log("  source_buffer " + dump_source_buffer(s.source_buffer));
+
+    if (STREAM_DEBUG > 0 || s.queue.length > 1)
+        console.log('  queue len ' + s.queue.length + '; append_okay: ' + s.append_okay);
+}
+
+function video_debug_listen_for_one_event(name)
+{
+    this.addEventListener(name, video_handle_event_debug);
+}
+
+function listen_for_video_events(stream)
+{
+    var video_0_events = [
+        "abort", "error"
+    ];
+
+    var video_1_events = [
+        "loadstart", "suspend", "emptied", "stalled", "loadedmetadata", "loadeddata", "canplay",
+        "canplaythrough", "playing", "waiting", "seeking", "seeked", "ended", "durationchange",
+        "timeupdate", "play", "pause", "ratechange"
+    ];
+
+    var video_2_events = [
+        "progress",
+        "resize",
+        "volumechange"
+    ];
+
+    video_0_events.forEach(video_debug_listen_for_one_event, stream.video);
+    if (STREAM_DEBUG > 0)
+        video_1_events.forEach(video_debug_listen_for_one_event, stream.video);
+    if (STREAM_DEBUG > 1)
+        video_2_events.forEach(video_debug_listen_for_one_event, stream.video);
+}
diff --git a/enums.js b/enums.js
index bca463a..3ef36dc 100644
--- a/enums.js
+++ b/enums.js
@@ -182,6 +182,11 @@ var SPICE_DISPLAY_CAP_COMPOSITE           = 2;
 var SPICE_DISPLAY_CAP_A8_SURFACE          = 3;
 var SPICE_DISPLAY_CAP_STREAM_REPORT       = 4;
 var SPICE_DISPLAY_CAP_LZ4_COMPRESSION     = 5;
+var SPICE_DISPLAY_CAP_PREF_COMPRESSION    = 6;
+var SPICE_DISPLAY_CAP_GL_SCANOUT          = 7;
+var SPICE_DISPLAY_CAP_MULTI_CODEC         = 8;
+var SPICE_DISPLAY_CAP_CODEC_MJPEG         = 9;
+var SPICE_DISPLAY_CAP_CODEC_VP8           = 10;
 
 var SPICE_AUDIO_DATA_MODE_INVALID       = 0;
 var SPICE_AUDIO_DATA_MODE_RAW           = 1;
@@ -324,6 +329,7 @@ var SPICE_CURSOR_TYPE_ALPHA     = 0,
     SPICE_CURSOR_TYPE_COLOR32   = 6;
 
 var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1;
+var SPICE_VIDEO_CODEC_TYPE_VP8   = 2;
 
 var VD_AGENT_PROTOCOL = 1;
 var VD_AGENT_MAX_DATA_SIZE = 2048;
diff --git a/spiceconn.js b/spiceconn.js
index f20424f..41d2fa3 100644
--- a/spiceconn.js
+++ b/spiceconn.js
@@ -136,7 +136,10 @@ SpiceConn.prototype =
         else if (msg.channel_type == SPICE_CHANNEL_DISPLAY)
             msg.channel_caps.push(
                 (1 << SPICE_DISPLAY_CAP_SIZED_STREAM) |
-                (1 << SPICE_DISPLAY_CAP_STREAM_REPORT)
+                (1 << SPICE_DISPLAY_CAP_STREAM_REPORT) |
+                (1 << SPICE_DISPLAY_CAP_MULTI_CODEC) |
+                (1 << SPICE_DISPLAY_CAP_CODEC_MJPEG) |
+                (1 << SPICE_DISPLAY_CAP_CODEC_VP8)
             );
 
         hdr.size = msg.buffer_size();
diff --git a/utils.js b/utils.js
index 2583666..9093a24 100644
--- a/utils.js
+++ b/utils.js
@@ -23,6 +23,7 @@
 **--------------------------------------------------------------------------*/
 var DEBUG = 0;
 var PLAYBACK_DEBUG = 0;
+var STREAM_DEBUG = 0;
 var DUMP_DRAWS = false;
 var DUMP_CANVASES = false;
 
diff --git a/webm.js b/webm.js
index 7d27b86..8faa8e7 100644
--- a/webm.js
+++ b/webm.js
@@ -61,6 +61,10 @@ var WEBM_CODEC_DELAY =                      [ 0x56, 0xAA ];
 var WEBM_CODEC_PRIVATE =                    [ 0x63, 0xA2 ];
 var WEBM_CODEC_ID =                         [ 0x86 ];
 
+var WEBM_VIDEO =                            [ 0xE0 ] ;
+var WEBM_PIXEL_WIDTH =                      [ 0xB0 ] ;
+var WEBM_PIXEL_HEIGHT =                     [ 0xBA ] ;
+
 var WEBM_AUDIO =                            [ 0xE1 ] ;
 var WEBM_SAMPLING_FREQUENCY =               [ 0xB5 ] ;
 var WEBM_CHANNELS =                         [ 0x9F ] ;
@@ -82,6 +86,8 @@ var MAX_CLUSTER_TIME                        = 1000;
 
 var GAP_DETECTION_THRESHOLD                 = 50;
 
+var SPICE_VP8_CODEC                         = 'video/webm; codecs="vp8"';
+
 /*----------------------------------------------------------------------------
 **  EBML utility functions
 **      These classes can create the binary representation of a webm file
@@ -291,6 +297,34 @@ webm_Audio.prototype =
     },
 }
 
+function webm_Video(width, height)
+{
+    this.id = WEBM_VIDEO;
+    this.width = width;
+    this.height = height;
+}
+
+webm_Video.prototype =
+{
+    to_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new DataView(a);
+        at = EBML_write_array(this.id, dv, at);
+        at = EBML_write_u64_data_len(this.buffer_size() - 8 - this.id.length, dv, at);
+        at = EBML_write_u16_value(WEBM_PIXEL_WIDTH, this.width, dv, at)
+        at = EBML_write_u16_value(WEBM_PIXEL_HEIGHT, this.height, dv, at)
+        return at;
+    },
+    buffer_size: function()
+    {
+        return this.id.length + 8 +
+            WEBM_PIXEL_WIDTH.length + 1 + 2 +
+            WEBM_PIXEL_HEIGHT.length + 1 + 2;
+    },
+}
+
+
 
 /* ---------------------------
    SeekHead not currently used.  Hopefully not needed.
@@ -431,6 +465,70 @@ webm_AudioTrackEntry.prototype =
             this.audio.buffer_size();
     },
 }
+
+function webm_VideoTrackEntry(width, height)
+{
+    this.id = WEBM_TRACK_ENTRY;
+    this.number = 1;
+    this.uid = 1;
+    this.type = 1; // Video
+    this.flag_enabled = 1;
+    this.flag_default = 1;
+    this.flag_forced = 1;
+    this.flag_lacing = 0;
+    this.min_cache = 0; // fixme - check
+    this.max_block_addition_id = 0;
+    this.codec_decode_all = 0; // fixme - check
+    this.seek_pre_roll = 0; // 80000000; // fixme - check
+    this.codec_delay =   80000000; // Must match codec_private.preskip
+    this.codec_id = "V_VP8";
+    this.video = new webm_Video(width, height);
+}
+
+webm_VideoTrackEntry.prototype =
+{
+    to_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new DataView(a);
+        at = EBML_write_array(this.id, dv, at);
+        at = EBML_write_u64_data_len(this.buffer_size() - 8 - this.id.length, dv, at);
+        at = EBML_write_u8_value(WEBM_TRACK_NUMBER, this.number, dv, at);
+        at = EBML_write_u8_value(WEBM_TRACK_UID, this.uid, dv, at);
+        at = EBML_write_u8_value(WEBM_FLAG_ENABLED, this.flag_enabled, dv, at);
+        at = EBML_write_u8_value(WEBM_FLAG_DEFAULT, this.flag_default, dv, at);
+        at = EBML_write_u8_value(WEBM_FLAG_FORCED, this.flag_forced, dv, at);
+        at = EBML_write_u8_value(WEBM_FLAG_LACING, this.flag_lacing, dv, at);
+        at = EBML_write_data(WEBM_CODEC_ID, this.codec_id, dv, at);
+        at = EBML_write_u8_value(WEBM_MIN_CACHE, this.min_cache, dv, at);
+        at = EBML_write_u8_value(WEBM_MAX_BLOCK_ADDITION_ID, this.max_block_addition_id, dv, at);
+        at = EBML_write_u8_value(WEBM_CODEC_DECODE_ALL, this.codec_decode_all, dv, at);
+        at = EBML_write_u32_value(WEBM_CODEC_DELAY, this.codec_delay, dv, at);
+        at = EBML_write_u32_value(WEBM_SEEK_PRE_ROLL, this.seek_pre_roll, dv, at);
+        at = EBML_write_u8_value(WEBM_TRACK_TYPE, this.type, dv, at);
+        at = this.video.to_buffer(a, at);
+        return at;
+    },
+    buffer_size: function()
+    {
+        return this.id.length + 8 +
+            WEBM_TRACK_NUMBER.length + 1 + 1 +
+            WEBM_TRACK_UID.length + 1 + 1 +
+            WEBM_FLAG_ENABLED.length + 1 + 1 +
+            WEBM_FLAG_DEFAULT.length + 1 + 1 +
+            WEBM_FLAG_FORCED.length + 1 + 1 +
+            WEBM_FLAG_LACING.length + 1 + 1 +
+            WEBM_CODEC_ID.length + this.codec_id.length + 1 +
+            WEBM_MIN_CACHE.length + 1 + 1 +
+            WEBM_MAX_BLOCK_ADDITION_ID.length + 1 + 1 +
+            WEBM_CODEC_DECODE_ALL.length + 1 + 1 +
+            WEBM_CODEC_DELAY.length + 1 + 4 +
+            WEBM_SEEK_PRE_ROLL.length + 1 + 4 +
+            WEBM_TRACK_TYPE.length + 1 + 1 +
+            this.video.buffer_size();
+    },
+}
+
 function webm_Tracks(entry)
 {
     this.id = WEBM_TRACKS;
commit 5b2e795bf6a65b5d0023c4dd20be7b97bf827709
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Tue Jun 28 07:59:57 2016 -0600

    Revise the webm files to more correctly identify audio tracks.

diff --git a/playback.js b/playback.js
index 96efb08..ea5017f 100644
--- a/playback.js
+++ b/playback.js
@@ -186,10 +186,13 @@ SpicePlaybackConn.prototype.start_playback = function(data)
     this.start_time = data.time;
 
     var h = new webm_Header();
+    var te = new webm_AudioTrackEntry;
+    var t = new webm_Tracks(te);
 
-    var mb = new ArrayBuffer(h.buffer_size())
+    var mb = new ArrayBuffer(h.buffer_size() + t.buffer_size())
 
     this.bytes_written = h.to_buffer(mb);
+    this.bytes_written = t.to_buffer(mb, this.bytes_written);
 
     this.source_buffer.addEventListener('error', handle_sourcebuffer_error, false);
     this.source_buffer.addEventListener('updateend', handle_append_buffer_done, false);
diff --git a/webm.js b/webm.js
index 35cbc07..7d27b86 100644
--- a/webm.js
+++ b/webm.js
@@ -356,7 +356,7 @@ webm_SeekHead.prototype =
    End of Seek Head
 */
 
-function webm_TrackEntry()
+function webm_AudioTrackEntry()
 {
     this.id = WEBM_TRACK_ENTRY;
     this.number = 1;
@@ -385,7 +385,7 @@ function webm_TrackEntry()
                            ];
 }
 
-webm_TrackEntry.prototype =
+webm_AudioTrackEntry.prototype =
 {
     to_buffer: function(a, at)
     {
@@ -526,9 +526,6 @@ function webm_Header()
     this.info = new webm_SegmentInformation;
 
     this.seek_head.track.pos = this.seek_head.info.pos + this.info.buffer_size();
-
-    this.track_entry = new webm_TrackEntry;
-    this.tracks = new webm_Tracks(this.track_entry);
 }
 
 webm_Header.prototype =
@@ -539,7 +536,6 @@ webm_Header.prototype =
         at = this.ebml.to_buffer(a, at);
         at = this.segment.to_buffer(a, at);
         at = this.info.to_buffer(a, at);
-        at = this.tracks.to_buffer(a, at);
 
         return at;
     },
@@ -547,7 +543,6 @@ webm_Header.prototype =
     {
         return this.ebml.buffer_size() +
                this.segment.buffer_size() +
-               this.info.buffer_size() +
-               this.tracks.buffer_size();
+               this.info.buffer_size();
     },
 }
commit 275850f7e61ce28fe7acf6a7f36b16b74ebc2697
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Fri Jul 1 11:41:00 2016 -0500

    Track and make note of requests that take an unusual amount of time.

diff --git a/spiceconn.js b/spiceconn.js
index 903f3b0..f20424f 100644
--- a/spiceconn.js
+++ b/spiceconn.js
@@ -334,6 +334,7 @@ SpiceConn.prototype =
     process_message: function(msg)
     {
         var rc;
+        var start = Date.now();
         DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
         rc = this.process_common_messages(msg);
         if (! rc)
@@ -361,6 +362,9 @@ SpiceConn.prototype =
             }
         }
 
+        var delta = Date.now() - start;
+        if (DEBUG > 0 || delta > GAP_DETECTION_THRESHOLD)
+            console.log("delta " + this.channel_type() + ":" + msg.type + " " + delta);
         return rc;
     },
 
commit 5f6760153454debc5d6948ccfda25edb46b462bd
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Fri Jul 1 11:16:33 2016 -0500

    Add tools for debugging media source playback events.

diff --git a/playback.js b/playback.js
index 36e6611..96efb08 100644
--- a/playback.js
+++ b/playback.js
@@ -72,6 +72,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
             this.media_source.spiceconn = this;
 
             this.audio = document.createElement("audio");
+            this.audio.spiceconn = this;
             this.audio.setAttribute('autoplay', true);
             this.audio.src = window.URL.createObjectURL(this.media_source);
             document.getElementById(this.parent.screen_id).appendChild(this.audio);
@@ -240,6 +241,12 @@ function handle_source_open(e)
         p.log_err('Codec ' + SPICE_PLAYBACK_CODEC + ' not available.');
         return;
     }
+
+    if (PLAYBACK_DEBUG > 0)
+        playback_handle_event_debug.call(this, e);
+
+    listen_for_audio_events(p);
+
     p.source_buffer.spiceconn = p;
     p.source_buffer.mode = "segments";
 
@@ -263,9 +270,13 @@ function handle_source_closed(e)
     p.log_err('Audio source unexpectedly closed.');
 }
 
-function handle_append_buffer_done(b)
+function handle_append_buffer_done(e)
 {
     var p = this.spiceconn;
+
+    if (PLAYBACK_DEBUG > 1)
+        playback_handle_event_debug.call(this, e);
+
     if (p.queue.length > 0)
     {
         var mb = p.queue.shift();
@@ -294,3 +305,53 @@ function playback_append_buffer(p, b)
         p.log_err("Error invoking appendBuffer: " + e.message);
     }
 }
+
+function playback_handle_event_debug(e)
+{
+    var p = this.spiceconn;
+    if (p.audio)
+    {
+        if (PLAYBACK_DEBUG > 0 || p.audio.buffered.len > 1)
+            console.log(p.audio.currentTime + ": event " + e.type +
+                dump_media_element(p.audio));
+    }
+
+    if (PLAYBACK_DEBUG > 1 && p.media_source)
+        console.log("  media_source " + dump_media_source(p.media_source));
+
+    if (PLAYBACK_DEBUG > 1 && p.source_buffer)
+        console.log("  source_buffer " + dump_source_buffer(p.source_buffer));
+
+    if (PLAYBACK_DEBUG > 0 || p.queue.length > 1)
+        console.log('  queue len ' + p.queue.length + '; append_okay: ' + p.append_okay);
+}
+
+function playback_debug_listen_for_one_event(name)
+{
+    this.addEventListener(name, playback_handle_event_debug);
+}
+
+function listen_for_audio_events(spiceconn)
+{
+    var audio_0_events = [
+        "abort", "error"
+    ];
+
+    var audio_1_events = [
+        "loadstart", "suspend", "emptied", "stalled", "loadedmetadata", "loadeddata", "canplay",
+        "canplaythrough", "playing", "waiting", "seeking", "seeked", "ended", "durationchange",
+        "timeupdate", "play", "pause", "ratechange"
+    ];
+
+    var audio_2_events = [
+        "progress",
+        "resize",
+        "volumechange"
+    ];
+
+    audio_0_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+    if (PLAYBACK_DEBUG > 0)
+        audio_1_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+    if (PLAYBACK_DEBUG > 1)
+        audio_2_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+}
diff --git a/utils.js b/utils.js
index 44f9679..2583666 100644
--- a/utils.js
+++ b/utils.js
@@ -264,3 +264,56 @@ function keycode_to_end_scan(code)
         return 0x80e0 | ((scancode - 0x100) << 8);
     }
 }
+
+function dump_media_element(m)
+{
+    var ret =
+            "[networkState " + m.networkState +
+            "|readyState " + m.readyState +
+            "|error " + m.error +
+            "|seeking " + m.seeking +
+            "|duration " + m.duration +
+            "|paused " + m.paused +
+            "|ended " + m.error +
+            "|buffered " + dump_timerange(m.buffered) +
+            "]";
+    return ret;
+}
+
+function dump_media_source(ms)
+{
+    var ret =
+            "[duration " + ms.duration +
+            "|readyState " + ms.readyState + "]";
+    return ret;
+}
+
+function dump_source_buffer(sb)
+{
+    var ret =
+            "[appendWindowStart " + sb.appendWindowStart +
+            "|appendWindowEnd " + sb.appendWindowEnd +
+            "|buffered " + dump_timerange(sb.buffered) +
+            "|timeStampOffset " + sb.timeStampOffset +
+            "|updating " + sb.updating +
+            "]";
+    return ret;
+}
+
+function dump_timerange(tr)
+{
+    var ret;
+
+    if (tr)
+    {
+        var i = tr.length;
+        ret = "{len " + i;
+        if (i > 0)
+            ret += "; start " + tr.start(0) + "; end " + tr.end(i - 1);
+        ret += "}";
+    }
+    else
+        ret = "N/A";
+
+    return ret;
+}
commit a05764c0d6f88add5ede328dd804231b18fac532
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Fri Jul 1 11:07:42 2016 -0500

    Give the playback channel a separate debug control.

diff --git a/playback.js b/playback.js
index 9659381..36e6611 100644
--- a/playback.js
+++ b/playback.js
@@ -46,7 +46,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
     {
         var start = new SpiceMsgPlaybackStart(msg.data);
 
-        DEBUG > 0 && console.log("PlaybackStart; frequency " + start.frequency);
+        PLAYBACK_DEBUG > 0 && console.log("PlaybackStart; frequency " + start.frequency);
 
         if (start.frequency != OPUS_FREQUENCY)
         {
@@ -95,7 +95,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
         {
             // FIXME - this is arguably wrong.  But delaying the transmission was worse,
             //          in initial testing.  Could use more research.
-            DEBUG > 1 && console.log("Hacking time of " + data.time + " to " + this.last_data_time + 1);
+            PLAYBACK_DEBUG > 1 && console.log("Hacking time of " + data.time + " to " + this.last_data_time + 1);
             data.time = this.last_data_time + 1;
         }
 
@@ -117,7 +117,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
         this.last_data_time = data.time;
 
 
-        DEBUG > 1 && console.log("PlaybackData; time " + data.time + "; length " + data.data.byteLength);
+        PLAYBACK_DEBUG > 1 && console.log("PlaybackData; time " + data.time + "; length " + data.data.byteLength);
 
         if (! this.source_buffer)
             return true;
diff --git a/utils.js b/utils.js
index f1a5748..44f9679 100644
--- a/utils.js
+++ b/utils.js
@@ -22,6 +22,7 @@
 **  Utility settings and functions for Spice
 **--------------------------------------------------------------------------*/
 var DEBUG = 0;
+var PLAYBACK_DEBUG = 0;
 var DUMP_DRAWS = false;
 var DUMP_CANVASES = false;
 


More information about the Spice-commits mailing list