[Spice-devel] [PATCH spice-html5 11/12] Add support for the vp8 codec type.

Jeremy White jwhite at codeweavers.com
Wed May 13 13:32:19 PDT 2015


Signed-off-by: Jeremy White <jwhite at codeweavers.com>
---
 display.js | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 enums.js   |   1 +
 webm.js    |  98 ++++++++++++++++++++++++++++++++++
 3 files changed, 272 insertions(+), 1 deletion(-)

diff --git a/display.js b/display.js
index 814ada6..e24a34b 100644
--- a/display.js
+++ b/display.js
@@ -472,7 +472,39 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
             console.log("Stream already exists");
         else
             this.streams[m.id] = m;
-        if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+
+        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;");
+            console.log(v);
+
+            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;
+        }
+        else if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
             console.log("Unhandled stream codec: "+m.codec_type);
         return true;
     }
@@ -510,6 +542,8 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
             img.onload = handle_draw_jpeg_onload;
             img.src = tmpstr;
         }
+        if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_VP8)
+            process_video_stream_data(this.streams[m.base.id], m);
         return true;
     }
 
@@ -525,6 +559,11 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
     {
         var m = new SpiceMsgDisplayStreamDestroy(msg.data);
         DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+
+        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;
     }
@@ -821,3 +860,136 @@ function handle_draw_jpeg_onload()
         this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
     }
 }
+
+
+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;
+
+    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);
+}
+
diff --git a/enums.js b/enums.js
index 17d77cb..50385c1 100644
--- a/enums.js
+++ b/enums.js
@@ -310,6 +310,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/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;
-- 
2.1.4



More information about the Spice-devel mailing list