JTCon
JTCon

Reputation: 509

Its possible to merge two audio 'base64data' strings to create an unique audio file?

Its possible to merge two audio 'base64data' strings to create an unique audio file?

I have in strings two cyclic audio base64 wavs like this:

data:audio/x-wav;base64,UklGRuIfQVZFZm1R7SH$WP90AhICLwKT...

I guess I am doing something very stupid but I wonder if it is possible.

I am trying to merge the two wavs into one that I can play in an audio HTML element. I want to xtract the base64 data of second to merge to the one and then play it but the navigator return an error.

This is my javascript code:

var audio_Data1 = base64_audio1;
var audio_Data2 = base64_audio1.split(',')[1];
var audio_final = audio_Data1 + audio_Data2;

audioControl.src = audio_final;
audioControl.play();

I appreciate your suggestions Thank you !


EDIT:

I am trying to decode base64 chunks to buffer data and concat the buffers for encode to base64 again.

The problem: The base64 returned is "AA=="

I think "AA==" is 0 bytes. Then i am doing something bad when I concat the buffer:

var myB64Data  = myB64WavString.split(',');
var myB64Chunk = myB64Data[1];
var myBuffer1 =  base64ToArrayBuffer(myB64Chunk);
var myBuffer2 = ""; //Same process

var myFinalBuffer = [];
myFinalBuffer.push.apply(myFinalBuffer, myBuffer1);
myFinalBuffer.push.apply(myFinalBuffer, myBuffer2); 

var myFinalB64 = 'data:audio/x-wav;base64,' + arrayBufferToBase64(myFinalBuffer); 

console.log( myFinalB64 );

Its return by console the next: "data:audio/x-wav;base64,AA=="

My javascript working functions for encode/decode:

function base64ToArrayBuffer(base64) {
   var binary_string =  window.atob(base64);
   var len = binary_string.length;
   var bytes = new Uint8Array( len );
   for (var i = 0; i < len; i++)        {
       bytes[i] = binary_string.charCodeAt(i);
   }
   return bytes.buffer;
}

function arrayBufferToBase64( buffer ) {
   var binary = '';
   var bytes = new Uint8Array( buffer );
   var len = bytes.byteLength;
   for (var i = 0; i < len; i++) {
       binary += String.fromCharCode( bytes[ i ] );
   }
   return window.btoa( binary );
}

Upvotes: 2

Views: 2044

Answers (4)

rassar
rassar

Reputation: 5660

To update the answer using the outdated atob:

const decoded1 = Buffer.from(encodedString1, "base64");
const decoded2 = Buffer.from(encodedString2, "base64");
const encodedCombined = Buffer.concat([decoded1, decoded2]).toString("base64");

Upvotes: 1

Genti
Genti

Reputation: 15

The above answer didn't work for me, but this simple one did:

Just decode each B64 String

// Decode the String
var decodedString0 = atob(encodedString0);
var decodedString1 = atob(encodedString1);

Merge them together, then encode back,

var decodedStringMerge = decodedString0 + decodedString1 
var ecodedStringMergeB64 = btoa(decodedStringMerge)

console.log(ecodedStringMergeB64);

Play the merged audio..

Upvotes: 0

JTCon
JTCon

Reputation: 509

I solved it ! (two days after hard and madness work and by copying answer from Convert AudioBuffer to ArrayBuffer / Blob for WAV Download) I hope it helps you save all the work that I had to develop for that ;)

var myB64Data  = myB64WavString.split(',');
var myB64Chunk = myB64Data[1];
var myBuffer1 =  base64ToArrayBuffer(myB64Chunk);
var myBuffer2 = ""; //Same process

var myFinalBuffer = appendBuffer (myBuffer1, myBuffer2);

var myFinalBuffer = getWavBytes( arrBytesFinal, {
  isFloat: false,       // floating point or 16-bit integer
  numChannels: 2,     //1 for mono recordings
  sampleRate: 48000, //Depends on your file audio bitrate !! 32000
})              


var myFinalB64 = 'data:audio/x-wav;base64,' + arrayBufferToBase64(myFinalBuffer); 

console.log( myFinalB64 );

//Then you can asign to an audio HTML control

myAudioControl.src = myFinalB64;

Additional functions:

function appendBuffer(buffer1, buffer2) {
  var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
  tmp.set(new Uint8Array(buffer1), 0);
  tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
  return tmp;
};
 

function getWavBytes(buffer, options) {
  const type = options.isFloat ? Float32Array : Uint16Array
  const numFrames = buffer.byteLength / type.BYTES_PER_ELEMENT

  const headerBytes = getWavHeader(Object.assign({}, options, { numFrames }))
  const wavBytes = new Uint8Array(headerBytes.length + buffer.byteLength);

  // prepend header, then add pcmBytes
  wavBytes.set(headerBytes, 0)
  wavBytes.set(new Uint8Array(buffer), headerBytes.length)

  return wavBytes
}

// adapted from https://gist.github.com/also/900023
// returns Uint8Array of WAV header bytes
function getWavHeader(options) {
  const numFrames =      options.numFrames
  const numChannels =    options.numChannels || 2
  const sampleRate =     options.sampleRate || 44100
  const bytesPerSample = options.isFloat? 4 : 2
  const format =         options.isFloat? 3 : 1

  const blockAlign = numChannels * bytesPerSample
  const byteRate = sampleRate * blockAlign
  const dataSize = numFrames * blockAlign

  const buffer = new ArrayBuffer(44)
  const dv = new DataView(buffer)

  let p = 0

  function writeString(s) {
    for (let i = 0; i < s.length; i++) {
      dv.setUint8(p + i, s.charCodeAt(i))
    }
    p += s.length
  }

  function writeUint32(d) {
    dv.setUint32(p, d, true)
    p += 4
  }

  function writeUint16(d) {
    dv.setUint16(p, d, true)
    p += 2
  }

  writeString('RIFF')              // ChunkID
  writeUint32(dataSize + 36)       // ChunkSize
  writeString('WAVE')              // Format
  writeString('fmt ')              // Subchunk1ID
  writeUint32(16)                  // Subchunk1Size
  writeUint16(format)              // AudioFormat
  writeUint16(numChannels)         // NumChannels
  writeUint32(sampleRate)          // SampleRate
  writeUint32(byteRate)            // ByteRate
  writeUint16(blockAlign)          // BlockAlign
  writeUint16(bytesPerSample * 8)  // BitsPerSample
  writeString('data')              // Subchunk2ID
  writeUint32(dataSize)            // Subchunk2Size

  return new Uint8Array(buffer)
}

Upvotes: 4

Jordan Nelson
Jordan Nelson

Reputation: 1213

To expand on the answer above, here is how it would be done for an array of encoded strings instead of just two.

function joinBuffers(buffers) {
  const byteLength = buffers
    .map((buffer) => buffer.byteLength)
    .reduce((prev, curr) => prev + curr);
  var tmp = new Uint8Array(byteLength);
  var length = 0;
  for (let i = 0; i < buffers.length; i++) {
    const buffer = buffers[i];
    tmp.set(new Uint8Array(buffer), length);
    length += buffer.byteLength;
  }
  return tmp;
}

Upvotes: 0

Related Questions