RFC 5219

A More Loss-Tolerant RTP Payload Format for MP3 Audio

πŸ“… published February 2008, from ietf

RFC 5219: A More Loss-Tolerant RTP Payload Format for MP3 Audio πŸŽ€

Okay bestie, let's start with the header stuff πŸ’… This one is RFC 5219, written by R. Finlayson from Live Networks, Inc. It was published in February 2008 as a Standard Track doc, and it obsoletes RFC 3119 ✨ It doesn't list any updates.

Status of This Memo

This is an Internet standards track protocol for the Internet community, and it's asking everyone for discussion and suggestions for improvements πŸ¦‹ For the current standardization state and status, check the "Internet Official Protocol Standards" (STD 1). Distribution of this memo is unlimited 🌸

Abstract

So this doc describes an RTP (Real-Time Protocol) payload format for carrying MPEG (Moving Picture Experts Group) 1 or 2, layer III audio, which everyone knows as "MP3" 🎧 It's an alternative to the one in RFC 2250, and it performs better when packets get lost πŸ’– It also obsoletes RFC 3119, fixing typos in the "SDP usage" section and the pseudo-code appendices πŸ™ˆ

Table of Contents

1. Introduction ....................................................2
2. Terminology .....................................................3
3. The Structure of MP3 Frames .....................................3
4. A New Payload Format ............................................4
   4.1. ADU Frames .................................................4
   4.2. ADU Descriptors ............................................4
   4.3. Packing Rules ..............................................5
   4.4. RTP Header Fields ..........................................6
   4.5. Handling Received Data .....................................6
5. Handling Multiple MPEG Audio Layers .............................6
6. Frame Packetizing and Depacketizing .............................7
7. ADU Frame Interleaving ..........................................8
8. IANA Considerations ............................................10
9. SDP Usage ......................................................11
10. Security Considerations .......................................11
11. Acknowledgements ..............................................11
12. Normative References ..........................................12
Appendix A. Translating between "MP3 Frames" and "ADU Frames" .....13
   A.1. Converting a Sequence of "MP3 Frames"
        to a Sequence of "ADU Frames" .............................14
   A.2. Converting a Sequence of "ADU Frames"
        to a Sequence of "MP3 Frames" .............................15
Appendix B. Interleaving and Deinterleaving .......................18
   B.1. Interleaving a Sequence of "ADU Frames" ...................18
   B.2. Deinterleaving a Sequence of (Interleaved) "ADU Frames" ...19
Appendix C. Changes from RFC 3119 .................................20

1. Introduction ✨

Okay so RFC 2250 [1] is generally fine for all forms of MPEG audio or video, BUT it's kinda sub-optimal for MPEG-1 or 2, layer III audio (aka MP3) 😭 The reason is that an MP3 frame isn't a true "Application Data Unit" -- it has a back-pointer to data in earlier frames, so it can't be decoded on its own without those earlier frames. RFC 2250 says packet boundaries line up with frame boundaries, so when it carries MP3 data it handles loss badly. Losing one MP3 frame makes some data in previous (or future) frames useless, even if those frames arrived totally fine. Rude πŸ’€

So in this doc, we define an alternative RTP payload format for MP3 audio πŸ¦‹ It does a data-preserving rearrangement of the original MPEG frames, so packet boundaries now match up with true MP3 "Application Data Units." These can also (optionally) get rearranged in an interleaving pattern. This new format is way more data efficient than RFC 2250 when packets go missing, which is so iconic πŸ‘‘

2. Terminology 🌈

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [2] πŸ“–

3. The Structure of MP3 Frames 🧚

In this section we give a quick overview of what an MP3 frame looks like. (For the full details, see the MPEG-1 audio [3] and MPEG-2 audio [4] specs.)

Each MPEG audio frame starts with a 4-byte header πŸ“ That header tells you stuff like:

  • Whether the audio is MPEG-1 or MPEG-2.
  • Whether the audio is layer I, II, or III. (The rest of this doc assumes layer III, i.e., "MP3" frames.)
  • Whether the audio is mono or stereo.
  • Whether or not there's a 2-byte CRC field after the header.
  • (indirectly) The size of the frame.

After the header, you'll find these structures:

  • (optionally) A 2-byte Cyclic Redundancy Check (CRC) field
  • A "side info" structure. Its length is:
    • 32 bytes for MPEG-1 stereo
    • 17 bytes for MPEG-1 mono, or for MPEG-2 stereo
    • 9 bytes for MPEG-2 mono
  • Encoded audio data, plus optional ancillary data (filling out the rest of the frame)

For this doc, the "side info" structure is the most important one, because it defines the location and size of the "Application Data Unit" (ADU) that an MP3 decoder processes πŸ”₯ Specifically, "side info" defines:

  • main_data_begin: This is a back-pointer (in bytes) to the start of the ADU. It's counted from the beginning of the frame, and it only counts encoded audio data and any ancillary data (so it ignores the header, CRC, and "side info" fields).

An MP3 decoder processes each ADU independently πŸ’… ADUs will generally vary in length, but their average length will be the length of the MP3 frames (minus the header, CRC, and "side info" fields). (In MPEG literature this ADU is sometimes called a "bit reservoir." Iykyk πŸ™ˆ)

4. A New Payload Format πŸ’–

As noted in [5], a payload format should be designed so that packet boundaries line up with "codec frame boundaries" -- so, with ADUs. In the RFC 2250 payload format for MPEG audio [1], each RTP packet payload carries MP3 frames. In this new payload format for MP3 audio, though, each RTP packet payload carries "ADU frames," and each one is preceded by an "ADU descriptor" ✨

4.1. ADU Frames 🌸

An "ADU frame" is made up of:

  • The 4-byte MPEG header (same as the original MP3 frame, except that the first 11 bits are (optionally) replaced by an "Interleaving Sequence Number," as described in Section 7 below)
  • The optional 2-byte CRC field (same as the original MP3 frame)
  • The "side info" structure (same as the original MP3 frame)
  • The complete sequence of encoded audio data (and any ancillary data) for the ADU (i.e., running from the start of this MP3 frame's main_data_begin back-pointer, up to the start of the next MP3 frame's back-pointer)

Note that there's a one-to-one mapping between MP3 frames and ADU frames πŸ¦‹ Because MP3 frames are self-describing, with the bitrate (and sampling frequency) encoded in the 4-byte MPEG header, the same is true for ADU frames. So, just like with MP3 streams, the bitrate can change within a stream and may be used for congestion control. Slay πŸ‘‘

4.2. ADU Descriptors πŸŽ€

Inside each RTP packet payload, each "ADU frame" is preceded by a 1- or 2-byte "ADU descriptor." It tells you the size of the ADU and whether this packet's data continues the previous packet's data. (That only happens when a single "ADU descriptor" + "ADU frame" is too big to fit in one RTP packet.) An ADU descriptor has these fields:

  • C: Continuation flag (1 bit): 1, if the data after the ADU descriptor is a continuation of an ADU frame that was too large to fit within a single RTP packet; 0 otherwise.
  • T: Descriptor Type flag (1 bit): 0 if this is a 1-byte ADU descriptor; 1 if this is a 2-byte ADU descriptor.
  • ADU size (6 or 14 bits): The size (in bytes) of the ADU frame that follows this ADU descriptor (i.e., not including the size of the descriptor itself). A 2-byte ADU descriptor (with a 14-bit ADU size field) is used for ADU frame sizes of 64 bytes or more. For smaller ADU frame sizes, senders MAY alternatively use a 1-byte ADU descriptor (with a 6-bit ADU size field). Receivers MUST be able to accept an ADU descriptor of either size.

So a 1-byte ADU descriptor looks like this:

       0 1 2 3 4 5 6 7
      +-+-+-+-+-+-+-+-+
      |C|0|  ADU size |
      +-+-+-+-+-+-+-+-+

and a 2-byte ADU descriptor looks like this:

       0                   1
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |C|1|     ADU size (14 bits)    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

4.3. Packing Rules 🧚

Each RTP packet payload starts with an "ADU descriptor," followed by "ADU frame" data. Normally this "ADU descriptor" + "ADU frame" fits completely inside the RTP packet. In that case, more than one successive "ADU descriptor" + "ADU frame" MAY be packed into a single RTP packet, as long as they all fit completely πŸ“

But if a single "ADU descriptor" + "ADU frame" is too big for one RTP packet, the "ADU frame" gets split across two or more successive RTP packets. Each of those packets starts with an ADU descriptor. The first packet's descriptor has a "C" (continuation) flag of 0, and the following packets' descriptors each have a "C" flag of 1. In this case, every descriptor has the same "ADU size" value: the size of the entire "ADU frame" (not just the part that fits in one RTP packet). Each such packet (even the last one) contains only one "ADU descriptor" πŸ’œ

4.4. RTP Header Fields πŸ’…

  • Payload Type: The (static) payload type 14 that was defined for MPEG audio [6] MUST NOT be used. Instead, a different, dynamic payload type MUST be used -- i.e., one within the range [96..127]. ⚠️
  • M bit: This payload format doesn't define any use for this bit. Senders SHOULD set this bit to zero in each outgoing packet.
  • Timestamp: This is a 32-bit, 90 kHz timestamp, representing the presentation time of the first ADU packed within the packet. ⏰

4.5. Handling Received Data 🫢

No information is lost when you convert a sequence of MP3 frames into a corresponding sequence of "ADU frames," so a receiving RTP implementation can either feed the ADU frames straight into an appropriately modified MP3 decoder, or convert them back into a sequence of MP3 frames, as described in Appendix A.2 below 🌈


Part 1 is done, and there's a lot more coming 🧚✨

5. Handling Multiple MPEG Audio Layers ✨

Okay bestie, this payload format is only meant for MPEG-1 or 2, layer III audio ("MP3") 🎧. Layer I and layer II frames are self-contained, with no back-pointer to earlier frames 🌸. Mixing is possible (although unusual πŸ™ˆ), so a sequence can contain layer III frames plus layer I or II frames. Only the layer III frames get converted to ADUs. Layer I and II frames go out 'as is', except for the prepended "ADU descriptor" πŸ’….

The receiver leaves layer I and II frames alone (after stripping the ADU descriptor) and converts the layer III ADU frames back into regular MP3 frames. Reminder that each frame's layer is identified from its 4-byte MPEG header πŸ”₯.

If you're sending a stream made only of layer I or layer II frames (no MP3 data at all), this format gives you no benefit, unless you're using the interleaving mechanism from Section 7 below πŸ¦‹.


6. Frame Packetizing and Depacketizing πŸŽ€

Here's how a sequence of MP3 frames gets sent, step by step:

         MP3 frames
                 -1-> ADU frames
                     -2-> interleaved ADU frames
                           -3-> RTP packets

Step 1 is turning the MP3 frames into a matching sequence of ADU frames. It happens the way Sections 3 and 4.1 above describe πŸ’–. (Also peek at the pseudo-code in Appendix A.1 🧚.)

Step 2 is the optional part where the ADU frames get shuffled into an interleaving pattern before packetization, as explained in Section 7 below πŸ¦‹. Interleaving helps soften the blow of packet loss by spreading consecutive ADU frames across non-consecutive packets. (Heads up: because of the back-pointer in MP3 frames, interleaving generally only works on ADU frames. That's why interleaving wasn't possible for RFC 2250 😭. Also see the pseudo-code in Appendix B.1.)

Step 3 is packing the (interleaved) ADU frames into RTP packets, as described in Section 4.3 above πŸ“¦. Each packet's RTP timestamp is the presentation time of the first ADU packed inside it ⏰. If you interleaved in Step 2, the RTP timestamps on outgoing packets won't necessarily be monotonically nondecreasing, so don't panic if they look a little jumpy πŸ™ƒ.

Receiving works the other way around, like a reverse glow-up:

         RTP packets
               -4-> RTP packets ordered by RTP sequence number
                     -5-> interleaved ADU frames
                           -6-> ADU frames
                                 -7-> MP3 frames

Step 4 is the usual sorting of incoming RTP packets by RTP sequence number πŸ—‚οΈ.

Step 5 is pulling ADU frames back out of the RTP packets, which is the reverse of Step 3 ✨. The receiver uses the "C" (continuation) flag in the ADU descriptor to notice when an ADU frame has been split across more than one packet. If one of those packets gets lost, the receiver throws out the whole ADU frame πŸ’€.

Step 6 is putting the ADU frames back in their original order (ADU frames lost to packet loss are the only exception) 🧩. This is covered in Section 7 below. Also check the pseudo-code in Appendix B.2 🌈.

Step 7 is turning the sequence of ADU frames back into MP3 frames, the reverse of Step 1 πŸ‘‘. Check the pseudo-code in Appendix A.2 too. If your MP3 decoder has been appropriately modified, you can skip this step and feed the ADU frames straight into that decoder instead πŸ”₯.


7. ADU Frame Interleaving πŸ¦‹

In MPEG audio frames (MPEG-1 or 2, all layers), the high-order 11 bits of the 4-byte MPEG header (the syncword) are always all-ones, which is 0xFFE πŸ’œ. When you reorder ADU frames for transmission, we reuse those 11 bits as an "Interleaving Sequence Number" (ISN) πŸŽ€. (When they arrive, they get set back to 0xFFE, so nothing's lost in translation 🫢.)

The ISN is structured as (a,b), where:

  • a == bits 0-7: 8-bit Interleave Index (within Cycle)
  • b == bits 8-10: 3-bit Interleave Cycle Count

So the 4-byte MPEG header gets reused like this:

     0                   1                   2                   3
     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
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |Interleave Idx |CycCt|   The rest of the original MPEG header  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Let's do an example πŸ“. Picture this interleave cycle (of size 8):

            1,3,5,7,0,2,4,6

This particular pattern is so good 😀. Any loss of up to four consecutive ADUs in the interleaved stream still leaves a deinterleaved stream with no gaps greater than one. It produces this sequence of ISNs:

   (1,0) (3,0) (5,0) (7,0) (0,0) (2,0) (4,0) (6,0) (1,1) (3,1) (5,1)
   etc.

So in this example, a sequence of ADU frames like this:

   f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 (etc.)

would get reordered in Step 2 into:

   (1,0)f1 (3,0)f3 (5,0)f5 (7,0)f7 (0,0)f0 (2,0)f2 (4,0)f4 (6,0)f6
   (1,1)f9 (3,1)f11 (5,1)f13 (etc.)

The reverse reordering (plus putting the 0xFFE back) happens on reception πŸ”.

Why split the ISN into an "Interleave Cycle Count" and an "Interleave Index", instead of just treating it as one 11-bit counter? Because it gives receivers a way to know when an ADU frame is ready to be released to the ADU-to-MP3 conversion (Step 7 above) without waiting around for more interleaved ADU frames πŸ•°οΈ. In the example above, when the receiver sees a frame with ISN (<something>,1), it knows it can release all the previously seen frames with ISN (<something>,0), even if some other (<something>,0) frames are still missing because of packet loss πŸ™Œ. An 8-bit Interleave Index lets interleave cycles go up to 256 in size πŸ“.

You can pick your interleaving order completely separately from RTP packetization 🧠. A simple implementation could choose an interleaving order first, reorder the ADU frames to match (Step 2), and then just pack them into RTP packets in order (Step 3). The catch is that ADU frames vary in size, so the number that fit into each RTP packet also varies. A more optimal implementation would combine Steps 2 and 3, choosing an interleaving order that better reflects how many ADU frames end up in each packet πŸ’….

Each receiving implementation of this payload format MUST recognize the ISN and be able to deinterleave incoming ADU frames (Step 6) πŸ’–. However, a sending implementation of this payload format MAY choose not to interleave at all, which means skipping Step 2 πŸ¦‹. In that case, the high-order 11 bits of each 4-byte MPEG header stay at 0xFFE. Receiving implementations would then see a run of identical ISNs (all 0xFFE). They'd handle that the same way they'd handle an Interleave Cycle Count that changed with every ADU frame: they'd just release the incoming ADU frames one by one to the ADU-to-MP3 conversion (Step 7), with no reordering needed 🧚. (Also see the pseudo-code in Appendix B.2.)


8. IANA Considerations πŸ“‹

Here's the registration template for the media type, copied exactly as it's written so nothing gets lost πŸ’Œ:

   Media type name: audio

   Media subtype: mpa-robust

   Required parameters: none

   Optional parameters: none

   Encoding considerations:
         This type is defined only for transfer via RTP, as specified in
         RFC 5219.

      Security considerations:
         See the "Security Considerations" section of RFC 5219.

      Interoperability considerations:
         This encoding is incompatible with both the "audio/mpa" and
         "audio/mpeg" media types.

      Published specification:
         The ISO/IEC MPEG-1 [3] and MPEG-2 [4] audio specifications, and
         RFC 5219.

      Applications that use this media type:
         Audio streaming tools (transmitting and receiving)

      Additional information: none

      Person & email address to contact for further information:
         Ross Finlayson
         finlayson@live555.com

      Intended usage: COMMON

      Author/Change controller:
         Author: Ross Finlayson
         Change controller: IETF AVT Working Group

9. SDP Usage πŸ“‘

When you're conveying info by SDP [7], the encoding name SHALL be mpa-robust (same as the media subtype) πŸŽ€. Here's an example of the media representation in SDP:

      m=audio 49000 RTP/AVP 121
      a=rtpmap:121 mpa-robust/90000

Note that the RTP timestamp frequency MUST be 90000 ⏱️. No exceptions, bestie πŸ’….


10. Security Considerations πŸ”

Okay, this part is serious 😭. If a session using this payload format is encrypted AND interleaving is in use, then the sender SHOULD ensure that any change of encryption key lines up with the start of a new interleave cycle πŸ”‘. Apart from that, the security considerations for this payload format are identical to the ones noted for RFC 2250 [1] πŸ›‘οΈ.


11. Acknowledgements 🫢

Huge shoutout to the people who made this happen 🌸. The idea of adding an interleaving option (using the first bits of the MPEG syncword, which would otherwise be all-ones, as an interleaving index) came from Dave Singer and Stefan Gewinner πŸ‘‘. Dave Singer also gave valuable feedback that helped clarify and improve how this payload format is described πŸ’–. And Chris Sloan's feedback led to the "ADU descriptor" that goes in front of each ADU frame in the RTP packet πŸ¦‹.


12. Normative References πŸ“š

  • [1] Hoffman, D., Fernando, G., Goyal, V., and M. Civanlar, "RTP Payload Format for MPEG1/MPEG2 Video", RFC 2250, January 1998.
  • [2] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [3] ISO/IEC International Standard 11172-3; "Coding of moving pictures and associated audio for digital storage media up to about 1,5 Mbits/s -- Part 3: Audio", 1993.
  • [4] ISO/IEC International Standard 13818-3; "Generic coding of moving pictures and associated audio information -- Part 3: Audio", 1998.
  • [5] Handley, M. and C. Perkins, "Guidelines for Writers of RTP Payload Format Specifications", BCP 36, RFC 2736, December 1999.
  • [6] Schulzrinne, H. and S. Casner, "RTP Profile for Audio and Video Conferences with Minimal Control", STD 65, RFC 3551, July 2003.
  • [7] Handley, M., Jacobson, V., and C. Perkins, "SDP: Session Description Protocol", RFC 4566, July 2006.

Appendix A. Translating between "MP3 Frames" and "ADU Frames" ✨

okay bestie, this appendix is basically a recipe πŸ“ It's written as pseudo code, and it shows how a sender using this payload format can turn a line of regular "MP3 Frames" into "ADU Frames", and how a receiver can go the other way, from "ADU Frames" back to "MP3 Frames" πŸ¦‹

First, we set up some abstract data structures πŸ’œ

  • "Segment": a record that stands for either an "MP3 Frame" or an "ADU Frame". It has these fields:
    • header: the 4-byte MPEG header
    • headerSize: a constant (== 4)
    • sideInfo: the 'side info' structure, including the optional 2-byte CRC field, if present
    • sideInfoSize: the size (in bytes) of the structure above
    • frameData: the remaining data in this frame
    • frameDataSize: the size (in bytes) of the data above
    • backpointer: the value (in bytes) of the backpointer for this frame
    • aduDataSize: the size (in bytes) of the ADU associated with this frame. (If the frame is already an "ADU Frame", then aduDataSize == frameDataSize)
    • mp3FrameSize: the total size (in bytes) this frame would be if it were a regular "MP3 Frame". (If it already is an "MP3 Frame", then mp3FrameSize == headerSize + sideInfoSize + frameDataSize.) Note that this size can be worked out entirely from header 🧚
  • "SegmentQueue": a FIFO queue of "Segments", with these operations:
    • void enqueue(Segment)
    • Segment dequeue()
    • Boolean isEmpty()
    • Segment head()
    • Segment tail()
    • Segment previous(Segment): returns the segment prior to a given one
    • Segment next(Segment): returns the segment after a given one
    • unsigned totalDataSize(): returns the sum of the frameDataSize fields of each entry in the queue

A.1. Converting a Sequence of "MP3 Frames" to a Sequence of "ADU Frames" πŸŽ€

This is the sender's side, and it's giving very organised vibes πŸ’…

   SegmentQueue pendingMP3Frames; // initially empty
   while (1) {
            // Enqueue new MP3 Frames, until we have enough data to
            // generate the ADU for a frame:
            do {
                    int totalDataSizeBefore
                            = pendingMP3Frames.totalDataSize();

                    Segment newFrame = 'the next MP3 Frame';
                    pendingMP3Frames.enqueue(newFrame);

                    int totalDataSizeAfter
                            = pendingMP3Frames.totalDataSize();
            } while (totalDataSizeBefore < newFrame.backpointer ||
                      totalDataSizeAfter < newFrame.aduDataSize);

            // We now have enough data to generate the ADU for the most
            // recently enqueued frame (i.e., the tail of the queue).
            // (The earlier frames in the queue -- if any -- must be
            // discarded, as we don't have enough data to generate
            // their ADUs.)
            Segment tailFrame = pendingMP3Frames.tail();

            // Output the header and side info:
            output(tailFrame.header);
            output(tailFrame.sideInfo);

            // Go back to the frame that contains the start of our
            // ADU data:
            int offset = 0;
            Segment curFrame = tailFrame;
            int prevBytes = tailFrame.backpointer;
            while (prevBytes > 0) {
                    curFrame = pendingMP3Frames.previous(curFrame);
                    int dataHere = curFrame.frameDataSize;
                    if (dataHere < prevBytes) {
                            prevBytes -= dataHere;
                    } else {
                            offset = dataHere - prevBytes;
                            break;
                    }
            }

            // Dequeue any frames that we no longer need:
            while (pendingMP3Frames.head() != curFrame) {

                    pendingMP3Frames.dequeue();
            }

            // Output, from the remaining frames, the ADU data that
            // we want:
            int bytesToUse = tailFrame.aduDataSize;
            while (bytesToUse > 0) {
                    int dataHere = curFrame.frameDataSize - offset;
                    int bytesUsedHere
                            = dataHere < bytesToUse ? dataHere :
                            bytesToUse;

                    output("bytesUsedHere" bytes from
                           curFrame.frameData, starting from "offset");

                    bytesToUse -= bytesUsedHere;
                    offset = 0;
                    curFrame = pendingMP3Frames.next(curFrame);
            }
   }

A.2. Converting a Sequence of "ADU Frames" to a Sequence of "MP3 Frames" 🌸

Now the receiver's side, where we rebuild the "MP3 Frames" from the "ADU Frames" πŸ§šβ€β™€οΈ

   SegmentQueue pendingADUFrames; // initially empty
   while (1) {
            while (needToGetAnADU()) {
                    Segment newADU = 'the next ADU Frame';
                    pendingADUFrames.enqueue(newADU);

                    insertDummyADUsIfNecessary();
            }

            generateFrameFromHeadADU();
   }

   Boolean needToGetAnADU() {
            // Checks whether we need to enqueue one or more new ADUs
            // before we have enough data to generate a frame for the
            // head ADU.
            Boolean needToEnqueue = True;

            if (!pendingADUFrames.isEmpty()) {
                    Segment curADU = pendingADUFrames.head();
                    int endOfHeadFrame = curADU.mp3FrameSize
                            - curADU.headerSize - curADU.sideInfoSize;
                    int frameOffset = 0;
                    while (1) {
                           int endOfData = frameOffset
                                   - curADU.backpointer +
                                     curADU.aduDataSize;
                           if (endOfData >= endOfHeadFrame) {
                                   // We have enough data to generate a
                                   // frame.
                                   needToEnqueue = False;
                                   break;
                           }

                           frameOffset += curADU.mp3FrameSize
                                   - curADU.headerSize
                                   - curADU.sideInfoSize;
                           if (curADU == pendingADUFrames.tail()) break;
                           curADU = pendingADUFrames.next(curADU);
                    }
            }

        return needToEnqueue;
   }

   void generateFrameFromHeadADU() {
            Segment curADU = pendingADUFrames.head();

            // Output the header and side info:
            output(curADU.header);
            output(curADU.sideInfo);

            // Begin by zeroing out the rest of the frame, in case the
            // ADU data doesn't fill it in completely:
            int endOfHeadFrame = curADU.mp3FrameSize
                    - curADU.headerSize - curADU.sideInfoSize;
            output("endOfHeadFrame" zero bytes);

            // Fill in the frame with appropriate ADU data from this and
            // subsequent ADUs:
            int frameOffset = 0;
            int toOffset = 0;

            while (toOffset < endOfHeadFrame) {
                   int startOfData = frameOffset - curADU.backpointer;
                   if (startOfData > endOfHeadFrame) {
                           break; // no more ADUs are needed
                   }
                   int endOfData = startOfData + curADU.aduDataSize;
                   if (endOfData > endOfHeadFrame) {
                           endOfData = endOfHeadFrame;

                   }

                   int fromOffset;
                   if (startOfData <= toOffset) {
                           fromOffset = toOffset - startOfData;
                           startOfData = toOffset;
                           if (endOfData < startOfData) {
                                   endOfData = startOfData;
                           }
                   } else {
                           fromOffset = 0;

                           // leave some zero bytes beforehand:
                           toOffset = startOfData;
                   }

                   int bytesUsedHere = endOfData - startOfData;
                   output(starting at offset "toOffset", "bytesUsedHere"
                           bytes from "&curADU.frameData[fromOffset]");
                   toOffset += bytesUsedHere;

                   frameOffset += curADU.mp3FrameSize
                           - curADU.headerSize - curADU.sideInfoSize;
                   curADU = pendingADUFrames.next(curADU);
            }

            pendingADUFrames.dequeue();
   }

   void insertDummyADUsIfNecessary() {
            // The tail segment (ADU) is assumed to have been recently
            // enqueued.  If its backpointer would overlap the data
            // of the previous ADU, then we need to insert one or more
            // empty, 'dummy' ADUs ahead of it.  (This situation
            // should occur only if an intermediate ADU was missing
            // -- e.g., due to packet loss.)
            while (1) {
                   Segment tailADU = pendingADUFrames.tail();
                   int prevADUend; // relative to the start
                    of the tail ADU

                   if (pendingADUFrames.head() != tailADU) {
                           // there is a previous ADU
                           Segment prevADU
                                   = pendingADUFrames.previous(tailADU);
                           prevADUend
                                   = prevADU.mp3FrameSize +
                                     prevADU.backpointer
                                     - prevADU.headerSize
                                     - prevADU.sideInfoSize;
                           if (prevADU.aduDataSize > prevADUend) {
                                   // this shouldn't happen if the
                                   // previous ADU was well-formed
                                   prevADUend = 0;
                           } else {
                                   prevADUend -= prevADU.aduDataSize;
                           }
                   } else {
                           prevADUend = 0;
                   }

                   if (tailADU.backpointer > prevADUend) {
                      // Insert a 'dummy' ADU in front of the tail.
                      // This ADU can have the same "header" (and thus,
                      // "mp3FrameSize") as the tail ADU, but should
                      // have a "backpointer" of "prevADUend", and
                      // an "aduDataSize" of zero.  The simplest
                      // way to do this is to copy the "sideInfo" from
                      // the tail ADU, replace the value of
                      // "main_data_begin" with "prevADUend", and set
                      // all of the "part2_3_length" fields to zero.
                   } else {
                           break; // no more dummy ADUs need to be
                                  // inserted
                   }
            }
   }

Appendix B. Interleaving and Deinterleaving πŸ¦‹

Here's the setup for the next bit πŸ’œ The following 'pseudo code' describes how a sender can reorder a sequence of "ADU Frames" according to an interleaving pattern (step 2), and how a receiver can perform the reverse reordering (step 6).

B.1. Interleaving a Sequence of "ADU Frames" πŸŽ€

okay so first we set up our little toolkit of data structures. these are the things the interleaver keeps track of while it's working πŸ’…

  • interleaveCycleSize: an integer in the range [1..256]
  • interleaveCycle: an array, of size interleaveCycleSize, containing some permutation of the integers from the set [0 .. interleaveCycleSize-1]. e.g., if interleaveCycleSize == 8, interleaveCycle might contain: 1,3,5,7,0,2,4,6 πŸ¦‹
  • inverseInterleaveCycle: an array containing the inverse of the permutation in interleaveCycle. it's the reverse mapping, so that interleaveCycle[inverseInterleaveCycle[i]] == i ✨
  • ii: the current Interleave Index (initially 0)
  • icc: the current Interleave Cycle Count (initially 0)
  • aduFrameBuffer: an array, of size interleaveCycleSize, of ADU Frames that are awaiting packetization 🧚

here's the loop itself. it's giving very "shuffle the deck, then deal it out" πŸƒ

   while (1) {
            int positionOfNextFrame = inverseInterleaveCycle[ii];
            aduFrameBuffer[positionOfNextFrame] = the next ADU frame;
            replace the high-order 11 bits of this frame's MPEG header
                with (ii,icc);
                    // Note: Be sure to leave the remaining 21 bits
                    // as is
            if (++ii == interleaveCycleSize) {
                    // We've finished this cycle, so pass all
                    // pending frames to the packetizing step
                    for (int i = 0; i < interleaveCycleSize; ++i) {
                         pass aduFrameBuffer[i] to the packetizing step;
                    }

                    ii = 0;
                    icc = (icc+1)%8;
            }
   }

so every time a cycle wraps up, the frames get passed along in order, icc ticks forward, and it all starts again. the high-order 11 bits get stamped with (ii,icc) and the other 21 bits stay exactly as they were 🌸

B.2. Deinterleaving a Sequence of (Interleaved) "ADU Frames" 🫢

now the reverse, the deinterleaver's side of the story 😭 here are its data structures:

  • ii: the Interleave Index from the current incoming ADU frame
  • icc: the Interleave Cycle Count from the current incoming ADU frame
  • iiLastSeen: the most recently seen Interleave Index (initially, some integer not in the range [0..255])
  • iccLastSeen: the most recently seen Interleave Cycle Count (initially, some integer not in the range [0..7])
  • aduFrameBuffer: an array, of size 256, of (pointers to) ADU Frames that have just been depacketized (initially, all entries are NULL) πŸ’œ

and the loop, which is lowkey the cleverest bit of the whole thing πŸ™ˆ

   while (1) {
            aduFrame = the next ADU frame from the depacketizing step;
            (ii,icc) = "the high-order 11 bits of aduFrame's MPEG
            header"; "the high-order 11 bits of aduFrame's MPEG
            header" = 0xFFE;
                    // Note: Be sure to leave the remaining 21 bits
                    // as is

            if (icc != iccLastSeen || ii == iiLastSeen) {
                    // We've started a new interleave cycle
                    // (or interleaving was not used).  Release all
                    // pending ADU frames to the ADU->MP3 conversion
                    // step:
                    for (int i = 0; i < 256; ++i) {
                            if (aduFrameBuffer[i] != NULL) {
                                    release aduFrameBuffer[i];
                                    aduFrameBuffer[i] = NULL;
                            }
                    }
            }

            iiLastSeen = ii;
            iccLastSeen = icc;
            aduFrameBuffer[ii] = aduFrame;
   }

so when a new cycle starts (or interleaving wasn't used at all), everything still pending gets released to the ADU->MP3 conversion step. otherwise each frame just parks itself in the slot its Interleave Index points to πŸ“


Appendix C. Changes from RFC 3119 ✨

the big change from RFC 3119 is fixing the encoding name in the "SDP usage" section. the correct one is "mpa-robust" πŸŽ€ also, "media type" takes over from "mime type", and a few minor bug fixes and clarifications got sprinkled into the (non-normative) pseudo code in Appendix A and Appendix B. πŸ’…

Author's Address πŸ–ŠοΈ

Ross Finlayson, Live Networks, Inc. 650 Castro St., suite 120-196 Mountain View, CA 94041 USA

EMail: finlayson@live555.com URI: http://www.live555.com/

Copyright (C) The IETF Trust (2008).

this doc is subject to the rights, licenses and restrictions in BCP 78, and except as set forth there, the authors keep all their rights 🌈

This document and the information contained herein are provided on an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. πŸ’€

Intellectual Property πŸ”₯

the IETF doesn't take a position on whether any Intellectual Property Rights (or other rights) that might be claimed for the tech in this doc are valid or in scope, or whether any license under those rights might be available. it also doesn't claim to have independently tried to find any such rights. info about how rights work for RFC docs is in BCP 78 and BCP 79 πŸ“š

copies of IPR disclosures sent to the IETF Secretariat, any assurances of licenses that will be made available, and the results of any attempt to get a general license or permission for implementers or users to use those proprietary rights can be found in the IETF on-line IPR repository at http://www.ietf.org/ipr πŸ”

and the IETF invites anyone to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that might cover technology needed to implement this standard. send that info to ietf-ipr@ietf.org πŸ«ΆπŸ‘‘

πŸ“¦ where this page comes from