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.ts 5.1KB

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