You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

red.js 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. const MAX_TIMESTAMP = 0x100000000;
  2. /**
  3. * An encoder for RFC 2198 redundancy using WebRTC Insertable Streams.
  4. */
  5. export class RFC2198Encoder {
  6. /**
  7. * @param {Number} targetRedundancy the desired amount of redundancy.
  8. */
  9. constructor(targetRedundancy = 1) {
  10. this.targetRedundancy = targetRedundancy;
  11. this.frameBuffer = new Array(targetRedundancy);
  12. this.payloadType = undefined;
  13. }
  14. /**
  15. * Set the desired level of redudancy. 4 means "four redundant frames plus current frame.
  16. * It is possible to reduce this to 0 to minimize the overhead to one byte.
  17. * @param {Number} targetRedundancy the desired amount of redundancy.
  18. */
  19. setRedundancy(targetRedundancy) {
  20. const currentBuffer = this.frameBuffer;
  21. if (targetRedundancy > this.targetRedundancy) {
  22. this.frameBuffer = new Array(targetRedundancy);
  23. for (let i = 0; i < currentBuffer.length; i++) {
  24. this.frameBuffer[i + targetRedundancy - this.targetRedundancy] = currentBuffer[i];
  25. }
  26. } else if (targetRedundancy < this.targetRedundancy) {
  27. this.frameBuffer = new Array(targetRedundancy);
  28. for (let i = 0; i < this.frameBuffer.length; i++) {
  29. this.frameBuffer[i] = currentBuffer[i + this.targetRedundancy - targetRedundancy];
  30. }
  31. }
  32. this.targetRedundancy = targetRedundancy;
  33. }
  34. /**
  35. * Set the "inner opus payload type". This is typically our RED payload type that we tell
  36. * the other side as our opus payload type. Can be queried from the sender using getParameters()
  37. * after setting the answer.
  38. * @param {Number} payloadType the payload type to use for opus.
  39. */
  40. setPayloadType(payloadType) {
  41. this.payloadType = payloadType;
  42. }
  43. /**
  44. * This is the actual transform to add redundancy to a raw opus frame.
  45. * @param {RTCEncodedAudioFrame} encodedFrame - Encoded audio frame.
  46. * @param {TransformStreamDefaultController} controller - TransportStreamController.
  47. */
  48. addRedundancy(encodedFrame, controller) {
  49. // TODO: should this ensure encodedFrame.type being not set and
  50. // encodedFrame.getMetadata().payloadType being the same as before?
  51. /*
  52. * From https://datatracker.ietf.org/doc/html/rfc2198#section-3:
  53. 0 1 2 3
  54. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  55. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  56. |F| block PT | timestamp offset | block length |
  57. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  58. 0 1 2 3 4 5 6 7
  59. +-+-+-+-+-+-+-+-+
  60. |0| Block PT |
  61. +-+-+-+-+-+-+-+-+
  62. */
  63. const data = new Uint8Array(encodedFrame.data);
  64. const newFrame = data.slice(0);
  65. newFrame.timestamp = encodedFrame.timestamp;
  66. let allFrames = this.frameBuffer.filter(x => Boolean(x)).concat(newFrame);
  67. // TODO: determine how much we can fit into the available size (which we need to assume as 1190 bytes or so)
  68. let needLength = 1 + newFrame.length;
  69. for (let i = allFrames.length - 2; i >= 0; i--) {
  70. const frame = allFrames[i];
  71. // TODO: timestamp wraparound?
  72. if ((allFrames[i + 1].timestamp - frame.timestamp + MAX_TIMESTAMP) % MAX_TIMESTAMP >= 16384) {
  73. allFrames = allFrames.slice(i + 1);
  74. break;
  75. }
  76. needLength += 4 + frame.length;
  77. }
  78. const newData = new Uint8Array(needLength);
  79. const newView = new DataView(newData.buffer);
  80. // Construct the header.
  81. let frameOffset = 0;
  82. for (let i = 0; i < allFrames.length - 1; i++) {
  83. const frame = allFrames[i];
  84. // Ensure correct behaviour on wraparound.
  85. const tOffset = (encodedFrame.timestamp - frame.timestamp + MAX_TIMESTAMP) % MAX_TIMESTAMP;
  86. // eslint-disable-next-line no-bitwise
  87. newView.setUint8(frameOffset, (this.payloadType & 0x7f) | 0x80);
  88. // eslint-disable-next-line no-bitwise
  89. newView.setUint16(frameOffset + 1, (tOffset << 2) ^ (frame.byteLength >> 8));
  90. newView.setUint8(frameOffset + 3, frame.byteLength & 0xff); // eslint-disable-line no-bitwise
  91. frameOffset += 4;
  92. }
  93. // Last block header.
  94. newView.setUint8(frameOffset++, this.payloadType);
  95. // Construct the frame.
  96. for (let i = 0; i < allFrames.length; i++) {
  97. const frame = allFrames[i];
  98. newData.set(frame, frameOffset);
  99. frameOffset += frame.byteLength;
  100. }
  101. encodedFrame.data = newData.buffer;
  102. this.frameBuffer.shift();
  103. this.frameBuffer.push(newFrame);
  104. controller.enqueue(encodedFrame);
  105. }
  106. }