1717
1818import androidx .annotation .Nullable ;
1919import androidx .media3 .common .C ;
20+ import androidx .media3 .common .Metadata ;
2021import androidx .media3 .common .util .ParsableByteArray ;
2122import androidx .media3 .common .util .Util ;
2223import androidx .media3 .extractor .MpegAudioUtil ;
3536 */
3637 public final long dataSize ;
3738
39+ /** Whether this frame is the LAME variant of a Xing frame, and hence has ReplayGain data. */
40+ public final boolean hasReplayGain ;
41+
42+ /**
43+ * @see Mp3InfoReplayGain#peak
44+ */
45+ public final float replayGainPeak ;
46+
47+ /**
48+ * @see Mp3InfoReplayGain#field1Name
49+ */
50+ public final byte replayGainField1Name ;
51+
52+ /**
53+ * @see Mp3InfoReplayGain#field1Originator
54+ */
55+ public final byte replayGainField1Originator ;
56+
57+ /**
58+ * @see Mp3InfoReplayGain#field1Value
59+ */
60+ public final float replayGainField1Value ;
61+
62+ /**
63+ * @see Mp3InfoReplayGain#field2Name
64+ */
65+ public final byte replayGainField2Name ;
66+
67+ /**
68+ * @see Mp3InfoReplayGain#field2Originator
69+ */
70+ public final byte replayGainField2Originator ;
71+
72+ /**
73+ * @see Mp3InfoReplayGain#field2Value
74+ */
75+ public final float replayGainField2Value ;
76+
3877 /**
3978 * The number of samples to skip at the start of the stream, or {@link C#LENGTH_UNSET} if not
4079 * present in the header.
@@ -58,12 +97,28 @@ private XingFrame(
5897 long frameCount ,
5998 long dataSize ,
6099 @ Nullable long [] tableOfContents ,
100+ boolean hasReplayGain ,
101+ float replayGainPeak ,
102+ byte replayGainField1Name ,
103+ byte replayGainField1Originator ,
104+ float replayGainField1Value ,
105+ byte replayGainField2Name ,
106+ byte replayGainField2Originator ,
107+ float replayGainField2Value ,
61108 int encoderDelay ,
62109 int encoderPadding ) {
63110 this .header = new MpegAudioUtil .Header (header );
64111 this .frameCount = frameCount ;
65112 this .dataSize = dataSize ;
66113 this .tableOfContents = tableOfContents ;
114+ this .hasReplayGain = hasReplayGain ;
115+ this .replayGainPeak = replayGainPeak ;
116+ this .replayGainField1Name = replayGainField1Name ;
117+ this .replayGainField1Originator = replayGainField1Originator ;
118+ this .replayGainField1Value = replayGainField1Value ;
119+ this .replayGainField2Name = replayGainField2Name ;
120+ this .replayGainField2Originator = replayGainField2Originator ;
121+ this .replayGainField2Value = replayGainField2Value ;
67122 this .encoderDelay = encoderDelay ;
68123 this .encoderPadding = encoderPadding ;
69124 }
@@ -98,23 +153,56 @@ public static XingFrame parse(MpegAudioUtil.Header mpegAudioHeader, ParsableByte
98153 frame .skipBytes (4 ); // Quality indicator
99154 }
100155
101- int encoderDelay ;
102- int encoderPadding ;
103- // Skip: version string (9), revision & VBR method (1), lowpass filter (1), replay gain (8),
104- // encoding flags & ATH type (1), bitrate (1).
105- int bytesToSkipBeforeEncoderDelayAndPadding = 9 + 1 + 1 + 8 + 1 + 1 ;
106- if (frame .bytesLeft () >= bytesToSkipBeforeEncoderDelayAndPadding + 3 ) {
107- frame .skipBytes (bytesToSkipBeforeEncoderDelayAndPadding );
108- int encoderDelayAndPadding = frame .readUnsignedInt24 ();
109- encoderDelay = (encoderDelayAndPadding & 0xFFF000 ) >> 12 ;
110- encoderPadding = (encoderDelayAndPadding & 0xFFF );
111- } else {
112- encoderDelay = C .LENGTH_UNSET ;
113- encoderPadding = C .LENGTH_UNSET ;
156+ boolean hasReplayGain = false ;
157+ float replayGainPeak = 0f ;
158+ byte replayGainField1Name = 0 ;
159+ byte replayGainField1Originator = 0 ;
160+ float replayGainField1Value = 0 ;
161+ byte replayGainField2Name = 0 ;
162+ byte replayGainField2Originator = 0 ;
163+ float replayGainField2Value = 0 ;
164+ int encoderDelay = C .LENGTH_UNSET ;
165+ int encoderPadding = C .LENGTH_UNSET ;
166+ // Skip: version string (9), revision & VBR method (1), lowpass filter (1).
167+ int bytesToSkipBeforeReplayGain = 9 + 1 + 1 ;
168+ if (frame .bytesLeft () >= bytesToSkipBeforeReplayGain + 8 ) {
169+ frame .skipBytes (bytesToSkipBeforeReplayGain );
170+ hasReplayGain = true ;
171+ replayGainPeak = frame .readFloat ();
172+ short field1 = frame .readShort ();
173+ replayGainField1Name = (byte ) ((field1 >> 13 ) & 7 );
174+ replayGainField1Originator = (byte ) ((field1 >> 10 ) & 7 );
175+ replayGainField1Value = ((field1 & 0x1ff ) * ((field1 & 0x200 ) != 0 ? -1 : 1 )) / 10f ;
176+ short field2 = frame .readShort ();
177+ replayGainField2Name = (byte ) ((field2 >> 13 ) & 7 );
178+ replayGainField2Originator = (byte ) ((field2 >> 10 ) & 7 );
179+ replayGainField2Value = ((field2 & 0x1ff ) * ((field2 & 0x200 ) != 0 ? -1 : 1 )) / 10f ;
180+
181+ // Skip: encoding flags & ATH type (1), bitrate (1).
182+ int bytesToSkipBeforeEncoderDelayAndPadding = 1 + 1 ;
183+ if (frame .bytesLeft () >= bytesToSkipBeforeEncoderDelayAndPadding + 3 ) {
184+ frame .skipBytes (bytesToSkipBeforeEncoderDelayAndPadding );
185+ int encoderDelayAndPadding = frame .readUnsignedInt24 ();
186+ encoderDelay = (encoderDelayAndPadding & 0xFFF000 ) >> 12 ;
187+ encoderPadding = (encoderDelayAndPadding & 0xFFF );
188+ }
114189 }
115190
116191 return new XingFrame (
117- mpegAudioHeader , frameCount , dataSize , tableOfContents , encoderDelay , encoderPadding );
192+ mpegAudioHeader ,
193+ frameCount ,
194+ dataSize ,
195+ tableOfContents ,
196+ hasReplayGain ,
197+ replayGainPeak ,
198+ replayGainField1Name ,
199+ replayGainField1Originator ,
200+ replayGainField1Value ,
201+ replayGainField2Name ,
202+ replayGainField2Originator ,
203+ replayGainField2Value ,
204+ encoderDelay ,
205+ encoderPadding );
118206 }
119207
120208 /**
@@ -132,4 +220,12 @@ public long computeDurationUs() {
132220 return Util .sampleCountToDurationUs (
133221 (frameCount * header .samplesPerFrame ) - 1 , header .sampleRate );
134222 }
223+
224+ /** Provide the metadata derived from this Xing frame, such as ReplayGain data. */
225+ public @ Nullable Metadata getMetadata () {
226+ if (hasReplayGain ) {
227+ return new Metadata (new Mp3InfoReplayGain (this ));
228+ }
229+ return null ;
230+ }
135231}
0 commit comments