user2871915
user2871915

Reputation: 469

Store typed array in ArrayBuffer

I have an ArrayBuffer of data which is composed of Uint8s. Each is in network byte order (big-endian). My goal is to convert each Uint8 into little-endian order, but then put them back in the ArrayBuffer.

I know I can easily separate the individual Uints by using a typed array, like so:

var uintArr = new Uint8Array(ArrayBuffer);

From there I can swap the endian-ness of each item, and have an array of little-endian Uint8s.

What I don't know is how to get that array back into an ArrayBuffer.

Is there a way to do this in Javascript?

Upvotes: 2

Views: 2725

Answers (1)

user1693593
user1693593

Reputation:

ArrayBuffer is a common byte array for all the views. Typed array simply means there is an associated type on the array such as uint8, int16 etc. All Uin8Array, Int32Array etc. are views on top of the ArrayBuffer to enable reading and writing in the type they represent.

(U)int8 arrays does not have byte-order as they are single bytes (ie. nothing to reorder). The bytes need to represent something wider such as (u)int-16 or -32 (64-bit integers are not supported until ES7, but you do have 32-bit and 64-bit IEEE floats).

Anything you read or write through views ends back up in the same ArrayBuffer they point to - you can even have several views for the same buffer.

To swap order for 16-bit you can simply read the ArrayBuffer using a Uint16Array view and swap manually:

var buffer16 = new Uint16Array(buffer); // use same buffer as for the Uin8Array view

for(var i = 0, v; i < buffer16.length; i++) {
  v = buffer16[i];
  buffer16[i] = ((v & 0xff) << 8) | ((v & 0xff00) >>> 8);  // mask and shift the two bytes
}

If you had a 8-bit view for the same buffer you can now read individual bytes with the new order.

For 32-bits you would do:

var buffer32 = new Uint32Array(buffer);

for(var i = 0, v; i < buffer32.length; i++) {
  v = buffer32[i];
  buffer32[i] = ((v & 0xff) << 24) |       // mask, move byte 0 to 3
                ((v & 0xff00) << 8) |      // mask, move byte 1 to 2
                ((v & 0xff0000) >>> 8) |   // mask, move byte 2 to 1 unsigned
                ((v & 0xff000000) >>> 24); // mask, move byte 3 to 0 unsigned
}

However, these views require aligned buffers meaning the length of the buffer for (u)int16 must be aligned for even lengths, while 32-bits must be aligned to 4 bytes.

If the buffer is not you can instead use a DataView to read and write any lengths, at any position at the cost of some performance:

var view = new DataView(buffer);
var alignedLength = ((buffer.length / 2)|0) * 2;  // aligned to 16-bit

// 16-bit
for(var i = 0, v; i < alignedLength; i += 2) {
  v = view.getUint16(i, false);  // read as big-endian
  view.setUint16(i, v, true);    // write as little-endian
}

and for 32-bits use getUint32/setUint32 instead, incrementing i with 4. Note you have to "keep an eye on" the last read in case the ArrayBuffer is not aligned to 2 or 4 bytes (usually you would align the buffer.length and use that in the loop as shown, use 4's for 32-bit).

If your source buffer is containing mixed lengths (for example if it contains a raw binary file) you will have to parse each value in accordance to the file format specification. For this, use the DataView.

Examples

var buffer = new ArrayBuffer(4),
    b8 = new Uint8Array(buffer),
    b16 = new Uint16Array(buffer),
    b32 = new Uint32Array(buffer),
    view = new DataView(buffer);

setData();
show("Original unsigned big-endian");


// swap the value using 16-bit array
for(var i = 0, v; i < b16.length; i++) {
  v = b16[i];
  b16[i] = ((v & 0xff) << 8) | ((v & 0xff00) >>> 8);
}
show("Byte-order swapped 16-bits");

setData();
for(var i = 0, v; i < b32.length; i++) {
  v = b32[i];
  b32[i] = ((v & 0xff) << 24) |
           ((v & 0xff00) << 8) |
           ((v & 0xff0000) >>> 8) |
           ((v & 0xff000000) >>> 24);
}
show("Byte-order swapped 32-bits");

setData();
for(var i = 0, v; i < buffer.byteLength; i += 2) {
  v = view.getUint16(i, false);  // big-endian
  view.setUint16(i, v, true);    // little-endian
}
show("Byte-order swapped 16-bit using DataView");

setData();
for(var i = 0, v; i < buffer.byteLength; i += 4) {
  v = view.getUint32(i, false);  // big-endian
  view.setUint32(i, v, true);    // little-endian
}
show("Byte-order swapped 32-bit using DataView");


function valToHex(v) {
  return (v>>>0).toString(16)
}

function setData() {
  b8[0] = 255;   // "network" order / big.endian 0xff804020
  b8[1] = 128;
  b8[2] = 64;
  b8[3] = 32;
}

function show(pre) {
  document.write(pre + ": ");
  document.write("0x" + valToHex(b8[0]));
  document.write(valToHex(b8[1]));
  document.write(valToHex(b8[2]));
  document.write(valToHex(b8[3]) + "<br>");
}
body {font:16px monospace}

Upvotes: 4

Related Questions