Skip to content

Commit

Permalink
Merge pull request #6676 from ant-media/fixHLSHeaderSize
Browse files Browse the repository at this point in the history
Fix frame size format in ID3 header
  • Loading branch information
mekya authored Sep 29, 2024
2 parents 2431f31 + d5c42e2 commit c189ff9
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 18 deletions.
33 changes: 22 additions & 11 deletions src/main/java/io/antmedia/muxer/HLSMuxer.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,26 +277,37 @@ public synchronized void writeMetaData(String data, long dts) {
addID3Data(data);
}


public static byte[] convertIntToID3v2TagSize(int size) {
byte[] tagSizeBytes = new byte[4];
tagSizeBytes[0] = (byte) ((size >> 21) & 0x7F);
tagSizeBytes[1] = (byte) ((size >> 14) & 0x7F);
tagSizeBytes[2] = (byte) ((size >> 7) & 0x7F);
tagSizeBytes[3] = (byte) (size & 0x7F);
return tagSizeBytes;
}

public synchronized void addID3Data(String data) {


int id3TagSize = data.length() + 3; // TXXX frame size (excluding 10 byte header)
int tagSize = id3TagSize + 10;
int frameSizeWithoutFrameHeader = data.length() + 3; // TXXX frame size, 3 is for encoding (1), description (1) and end of string (1) (https://id3.org/id3v2.3.0#User_defined_text_information_frame)
int tagSize = frameSizeWithoutFrameHeader + 10; // 10 is for frame header which is "TXXX" frame id (4), frame size info(4) and frame flags (2) (https://id3.org/id3v2.3.0#ID3v2_frame_overview)
int id3ContentSize = tagSize + 10; // 10 is for ID3 header which is "ID3" (3), version (2), flags (1) and size info(4) (https://id3.org/id3v2.3.0#ID3v2_header)

ByteBuffer byteBuffer = ByteBuffer.allocate(tagSize + 10);
ByteBuffer byteBuffer = ByteBuffer.allocate(id3ContentSize);

logger.info("Adding ID3 data: {} lenght:{} byte length:{} buffer capacacity:{}", data, data.length(), data.getBytes().length, byteBuffer.capacity());


// ID3 header (https://id3.org/id3v2.3.0#ID3v2_header)
byteBuffer.put("ID3".getBytes());
byteBuffer.put(new byte[]{0x03, 0x00}); // version
byteBuffer.put((byte) 0x00); // flags
byteBuffer.putInt(tagSize); // size
byteBuffer.put(convertIntToID3v2TagSize(tagSize)); // size

// TXXX frame header (https://id3.org/id3v2.3.0#ID3v2_frame_overview)
byteBuffer.put("TXXX".getBytes()); // frame id
byteBuffer.putInt(frameSizeWithoutFrameHeader); // frame size without frame header
byteBuffer.put(new byte[]{0x00, 0x00}); // frame flags

// TXXX frame
byteBuffer.put("TXXX".getBytes());
byteBuffer.putInt(id3TagSize); // size
byteBuffer.put(new byte[]{0x00, 0x00}); // flags
//TXXX frame content (https://id3.org/id3v2.3.0#User_defined_text_information_frame)
byteBuffer.put((byte) 0x03); // encoding
byteBuffer.put((byte) 0x00); // description 00
byteBuffer.put(data.getBytes()); // description
Expand Down
110 changes: 103 additions & 7 deletions src/test/java/io/antmedia/test/MuxerUnitTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@
import static org.bytedeco.ffmpeg.global.avutil.AV_SAMPLE_FMT_FLTP;
import static org.bytedeco.ffmpeg.global.avutil.av_channel_layout_default;
import static org.bytedeco.ffmpeg.global.avutil.av_dict_get;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
Expand Down Expand Up @@ -5681,4 +5675,106 @@ public void testRtmpDtsOverflow() {
0, 0, false, 0, lastVideoDts + (long) overFlowCount * Integer.MAX_VALUE);
}


@Test
public void testID3HeaderTagSize() {
int size = 257;
byte[] tagSizeBytes = HLSMuxer.convertIntToID3v2TagSize(size);
for (byte b : tagSizeBytes) {
System.out.printf("%02X ", b); // Print bytes in hexadecimal format
}

assertEquals(0x00, tagSizeBytes[0]);
assertEquals(0x00, tagSizeBytes[1]);
assertEquals(0x02, tagSizeBytes[2]);
assertEquals(0x01, tagSizeBytes[3]);


tagSizeBytes = HLSMuxer.convertIntToID3v2TagSize((int) Math.pow(2, 7));

assertEquals(0x00, tagSizeBytes[0]);
assertEquals(0x00, tagSizeBytes[1]);
assertEquals(0x01, tagSizeBytes[2]);
assertEquals(0x00, tagSizeBytes[3]);

tagSizeBytes = HLSMuxer.convertIntToID3v2TagSize((int) Math.pow(2, 14));

assertEquals(0x00, tagSizeBytes[0]);
assertEquals(0x01, tagSizeBytes[1]);
assertEquals(0x00, tagSizeBytes[2]);
assertEquals(0x00, tagSizeBytes[3]);

tagSizeBytes = HLSMuxer.convertIntToID3v2TagSize((int) Math.pow(2, 21));

assertEquals(0x01, tagSizeBytes[0]);
assertEquals(0x00, tagSizeBytes[1]);
assertEquals(0x00, tagSizeBytes[2]);
assertEquals(0x00, tagSizeBytes[3]);
}

@Test
public void testAddID3Data() {
HLSMuxer hlsMuxer = spy(new HLSMuxer(vertx, Mockito.mock(StorageClient.class),
"streams", 0, "http://example.com", false));
hlsMuxer.setId3Enabled(true);
hlsMuxer.createID3StreamIfRequired();
long lastPts = RandomUtils.nextLong();
doReturn(lastPts).when(hlsMuxer).getLastPts();
doNothing().when(hlsMuxer).writeDataFrame(any(), any());

int dataSize = 257 - 10 - 3;
String data = "a".repeat(dataSize); // Create a string with 247 'a' characters

hlsMuxer.addID3Data(data);

// Capture the parameter passed to writeID3Packet
ArgumentCaptor<ByteBuffer> captor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(hlsMuxer).writeID3Packet(captor.capture());

ByteBuffer capturedBuffer = captor.getValue();

// Extract values from the captured buffer
byte[] id3Header = new byte[3];
capturedBuffer.get(id3Header);
assertArrayEquals("ID3".getBytes(), id3Header);

byte[] version = new byte[2];
capturedBuffer.get(version);
assertArrayEquals(new byte[]{0x03, 0x00}, version);

byte flags = capturedBuffer.get();
assertEquals(0x00, flags);

byte[] size = new byte[4];
capturedBuffer.get(size);
assertEquals(0x00, size[0]);
assertEquals(0x00, size[1]);
assertEquals(0x02, size[2]);
assertEquals(0x01, size[3]);

byte[] frameId = new byte[4];
capturedBuffer.get(frameId);
assertArrayEquals("TXXX".getBytes(), frameId);

int frameSize = capturedBuffer.getInt();
assertEquals(dataSize + 3, frameSize);

byte[] frameFlags = new byte[2];
capturedBuffer.get(frameFlags);
assertArrayEquals(new byte[]{0x00, 0x00}, frameFlags);

byte encoding = capturedBuffer.get();
assertEquals(0x03, encoding);

byte descriptionTerminator = capturedBuffer.get();
assertEquals(0x00, descriptionTerminator);

byte[] description = new byte[dataSize];
capturedBuffer.get(description);
assertArrayEquals(data.getBytes(), description);

byte endOfString = capturedBuffer.get();
assertEquals(0x00, endOfString);
}

}

0 comments on commit c189ff9

Please sign in to comment.