mirror of
https://github.com/androidx/media.git
synced 2025-05-04 14:10:40 +08:00
Fix VBRI and XING seekers
- Remove skipping of the VBRI/XING frame before calculating position offsets. This was incorrect. Instead, a constraint is used to ensure we don't return positions within these frames, the difference being that the constraint adjusts only positions that would fall within the frames, where-as the previous approach shifted positions through the whole stream. - Excluded last entry in the VBRI table because it has an invalid position (the length of the stream). - Give variables in XingSeeker descriptive names. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177451295
This commit is contained in:
parent
022b85a625
commit
0ea8c8bfa0
@ -25,309 +25,313 @@ track 0:
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 76
|
||||
sample count = 77
|
||||
sample 0:
|
||||
time = 945782
|
||||
time = 928567
|
||||
flags = 1
|
||||
data = length 384, hash F7E344F4
|
||||
sample 1:
|
||||
time = 952567
|
||||
flags = 1
|
||||
data = length 384, hash 14EF6AFD
|
||||
sample 1:
|
||||
time = 969782
|
||||
sample 2:
|
||||
time = 976567
|
||||
flags = 1
|
||||
data = length 384, hash 61C9B92C
|
||||
sample 2:
|
||||
time = 993782
|
||||
sample 3:
|
||||
time = 1000567
|
||||
flags = 1
|
||||
data = length 384, hash ABE1368
|
||||
sample 3:
|
||||
time = 1017782
|
||||
sample 4:
|
||||
time = 1024567
|
||||
flags = 1
|
||||
data = length 384, hash 6A3B8547
|
||||
sample 4:
|
||||
time = 1041782
|
||||
sample 5:
|
||||
time = 1048567
|
||||
flags = 1
|
||||
data = length 384, hash 30E905FA
|
||||
sample 5:
|
||||
time = 1065782
|
||||
sample 6:
|
||||
time = 1072567
|
||||
flags = 1
|
||||
data = length 384, hash 21A267CD
|
||||
sample 6:
|
||||
time = 1089782
|
||||
sample 7:
|
||||
time = 1096567
|
||||
flags = 1
|
||||
data = length 384, hash D96A2651
|
||||
sample 7:
|
||||
time = 1113782
|
||||
sample 8:
|
||||
time = 1120567
|
||||
flags = 1
|
||||
data = length 384, hash 72340177
|
||||
sample 8:
|
||||
time = 1137782
|
||||
sample 9:
|
||||
time = 1144567
|
||||
flags = 1
|
||||
data = length 384, hash 9345E744
|
||||
sample 9:
|
||||
time = 1161782
|
||||
sample 10:
|
||||
time = 1168567
|
||||
flags = 1
|
||||
data = length 384, hash FDE39E3A
|
||||
sample 10:
|
||||
time = 1185782
|
||||
sample 11:
|
||||
time = 1192567
|
||||
flags = 1
|
||||
data = length 384, hash F0B7465
|
||||
sample 11:
|
||||
time = 1209782
|
||||
sample 12:
|
||||
time = 1216567
|
||||
flags = 1
|
||||
data = length 384, hash 3693AB86
|
||||
sample 12:
|
||||
time = 1233782
|
||||
sample 13:
|
||||
time = 1240567
|
||||
flags = 1
|
||||
data = length 384, hash F39719B1
|
||||
sample 13:
|
||||
time = 1257782
|
||||
sample 14:
|
||||
time = 1264567
|
||||
flags = 1
|
||||
data = length 384, hash DA3958DC
|
||||
sample 14:
|
||||
time = 1281782
|
||||
sample 15:
|
||||
time = 1288567
|
||||
flags = 1
|
||||
data = length 384, hash FDC7599F
|
||||
sample 15:
|
||||
time = 1305782
|
||||
sample 16:
|
||||
time = 1312567
|
||||
flags = 1
|
||||
data = length 384, hash AEFF8471
|
||||
sample 16:
|
||||
time = 1329782
|
||||
sample 17:
|
||||
time = 1336567
|
||||
flags = 1
|
||||
data = length 384, hash 89C92C19
|
||||
sample 17:
|
||||
time = 1353782
|
||||
sample 18:
|
||||
time = 1360567
|
||||
flags = 1
|
||||
data = length 384, hash 5C786A4B
|
||||
sample 18:
|
||||
time = 1377782
|
||||
sample 19:
|
||||
time = 1384567
|
||||
flags = 1
|
||||
data = length 384, hash 5ACA8B
|
||||
sample 19:
|
||||
time = 1401782
|
||||
sample 20:
|
||||
time = 1408567
|
||||
flags = 1
|
||||
data = length 384, hash 7755974C
|
||||
sample 20:
|
||||
time = 1425782
|
||||
sample 21:
|
||||
time = 1432567
|
||||
flags = 1
|
||||
data = length 384, hash 3934B73C
|
||||
sample 21:
|
||||
time = 1449782
|
||||
sample 22:
|
||||
time = 1456567
|
||||
flags = 1
|
||||
data = length 384, hash DDD70A2F
|
||||
sample 22:
|
||||
time = 1473782
|
||||
sample 23:
|
||||
time = 1480567
|
||||
flags = 1
|
||||
data = length 384, hash 8FACE2EF
|
||||
sample 23:
|
||||
time = 1497782
|
||||
sample 24:
|
||||
time = 1504567
|
||||
flags = 1
|
||||
data = length 384, hash 4A602591
|
||||
sample 24:
|
||||
time = 1521782
|
||||
sample 25:
|
||||
time = 1528567
|
||||
flags = 1
|
||||
data = length 384, hash D019AA2D
|
||||
sample 25:
|
||||
time = 1545782
|
||||
sample 26:
|
||||
time = 1552567
|
||||
flags = 1
|
||||
data = length 384, hash 8A680B9D
|
||||
sample 26:
|
||||
time = 1569782
|
||||
sample 27:
|
||||
time = 1576567
|
||||
flags = 1
|
||||
data = length 384, hash B655C959
|
||||
sample 27:
|
||||
time = 1593782
|
||||
sample 28:
|
||||
time = 1600567
|
||||
flags = 1
|
||||
data = length 384, hash 2168336B
|
||||
sample 28:
|
||||
time = 1617782
|
||||
sample 29:
|
||||
time = 1624567
|
||||
flags = 1
|
||||
data = length 384, hash D77F6D31
|
||||
sample 29:
|
||||
time = 1641782
|
||||
sample 30:
|
||||
time = 1648567
|
||||
flags = 1
|
||||
data = length 384, hash 524B4B2F
|
||||
sample 30:
|
||||
time = 1665782
|
||||
sample 31:
|
||||
time = 1672567
|
||||
flags = 1
|
||||
data = length 384, hash 4752DDFC
|
||||
sample 31:
|
||||
time = 1689782
|
||||
sample 32:
|
||||
time = 1696567
|
||||
flags = 1
|
||||
data = length 384, hash E786727F
|
||||
sample 32:
|
||||
time = 1713782
|
||||
sample 33:
|
||||
time = 1720567
|
||||
flags = 1
|
||||
data = length 384, hash 5DA6FB8C
|
||||
sample 33:
|
||||
time = 1737782
|
||||
sample 34:
|
||||
time = 1744567
|
||||
flags = 1
|
||||
data = length 384, hash 92F24269
|
||||
sample 34:
|
||||
time = 1761782
|
||||
sample 35:
|
||||
time = 1768567
|
||||
flags = 1
|
||||
data = length 384, hash CD0A3BA1
|
||||
sample 35:
|
||||
time = 1785782
|
||||
sample 36:
|
||||
time = 1792567
|
||||
flags = 1
|
||||
data = length 384, hash 7D00409F
|
||||
sample 36:
|
||||
time = 1809782
|
||||
sample 37:
|
||||
time = 1816567
|
||||
flags = 1
|
||||
data = length 384, hash D7ADB5FA
|
||||
sample 37:
|
||||
time = 1833782
|
||||
sample 38:
|
||||
time = 1840567
|
||||
flags = 1
|
||||
data = length 384, hash 4A140209
|
||||
sample 38:
|
||||
time = 1857782
|
||||
sample 39:
|
||||
time = 1864567
|
||||
flags = 1
|
||||
data = length 384, hash E801184A
|
||||
sample 39:
|
||||
time = 1881782
|
||||
sample 40:
|
||||
time = 1888567
|
||||
flags = 1
|
||||
data = length 384, hash 53C6CF9C
|
||||
sample 40:
|
||||
time = 1905782
|
||||
sample 41:
|
||||
time = 1912567
|
||||
flags = 1
|
||||
data = length 384, hash 19A8D99F
|
||||
sample 41:
|
||||
time = 1929782
|
||||
sample 42:
|
||||
time = 1936567
|
||||
flags = 1
|
||||
data = length 384, hash E47EB43F
|
||||
sample 42:
|
||||
time = 1953782
|
||||
sample 43:
|
||||
time = 1960567
|
||||
flags = 1
|
||||
data = length 384, hash 4EA329E7
|
||||
sample 43:
|
||||
time = 1977782
|
||||
sample 44:
|
||||
time = 1984567
|
||||
flags = 1
|
||||
data = length 384, hash 1CCAAE62
|
||||
sample 44:
|
||||
time = 2001782
|
||||
sample 45:
|
||||
time = 2008567
|
||||
flags = 1
|
||||
data = length 384, hash ED3F8C66
|
||||
sample 45:
|
||||
time = 2025782
|
||||
sample 46:
|
||||
time = 2032567
|
||||
flags = 1
|
||||
data = length 384, hash D3D646B6
|
||||
sample 46:
|
||||
time = 2049782
|
||||
sample 47:
|
||||
time = 2056567
|
||||
flags = 1
|
||||
data = length 384, hash 68CD1574
|
||||
sample 47:
|
||||
time = 2073782
|
||||
sample 48:
|
||||
time = 2080567
|
||||
flags = 1
|
||||
data = length 384, hash 8CEAB382
|
||||
sample 48:
|
||||
time = 2097782
|
||||
sample 49:
|
||||
time = 2104567
|
||||
flags = 1
|
||||
data = length 384, hash D54B1C48
|
||||
sample 49:
|
||||
time = 2121782
|
||||
sample 50:
|
||||
time = 2128567
|
||||
flags = 1
|
||||
data = length 384, hash FFE2EE90
|
||||
sample 50:
|
||||
time = 2145782
|
||||
sample 51:
|
||||
time = 2152567
|
||||
flags = 1
|
||||
data = length 384, hash BFE8A673
|
||||
sample 51:
|
||||
time = 2169782
|
||||
sample 52:
|
||||
time = 2176567
|
||||
flags = 1
|
||||
data = length 384, hash 978B1C92
|
||||
sample 52:
|
||||
time = 2193782
|
||||
sample 53:
|
||||
time = 2200567
|
||||
flags = 1
|
||||
data = length 384, hash 810CC71E
|
||||
sample 53:
|
||||
time = 2217782
|
||||
sample 54:
|
||||
time = 2224567
|
||||
flags = 1
|
||||
data = length 384, hash 44FE42D9
|
||||
sample 54:
|
||||
time = 2241782
|
||||
sample 55:
|
||||
time = 2248567
|
||||
flags = 1
|
||||
data = length 384, hash 2F5BB02C
|
||||
sample 55:
|
||||
time = 2265782
|
||||
sample 56:
|
||||
time = 2272567
|
||||
flags = 1
|
||||
data = length 384, hash 77DDB90
|
||||
sample 56:
|
||||
time = 2289782
|
||||
sample 57:
|
||||
time = 2296567
|
||||
flags = 1
|
||||
data = length 384, hash 24FB5EDA
|
||||
sample 57:
|
||||
time = 2313782
|
||||
sample 58:
|
||||
time = 2320567
|
||||
flags = 1
|
||||
data = length 384, hash E73203C6
|
||||
sample 58:
|
||||
time = 2337782
|
||||
sample 59:
|
||||
time = 2344567
|
||||
flags = 1
|
||||
data = length 384, hash 14B525F1
|
||||
sample 59:
|
||||
time = 2361782
|
||||
sample 60:
|
||||
time = 2368567
|
||||
flags = 1
|
||||
data = length 384, hash 5E0F4E2E
|
||||
sample 60:
|
||||
time = 2385782
|
||||
sample 61:
|
||||
time = 2392567
|
||||
flags = 1
|
||||
data = length 384, hash 67EE4E31
|
||||
sample 61:
|
||||
time = 2409782
|
||||
sample 62:
|
||||
time = 2416567
|
||||
flags = 1
|
||||
data = length 384, hash 2E04EC4C
|
||||
sample 62:
|
||||
time = 2433782
|
||||
sample 63:
|
||||
time = 2440567
|
||||
flags = 1
|
||||
data = length 384, hash 852CABA7
|
||||
sample 63:
|
||||
time = 2457782
|
||||
sample 64:
|
||||
time = 2464567
|
||||
flags = 1
|
||||
data = length 384, hash 19928903
|
||||
sample 64:
|
||||
time = 2481782
|
||||
sample 65:
|
||||
time = 2488567
|
||||
flags = 1
|
||||
data = length 384, hash 5DA42021
|
||||
sample 65:
|
||||
time = 2505782
|
||||
sample 66:
|
||||
time = 2512567
|
||||
flags = 1
|
||||
data = length 384, hash 45B20B7C
|
||||
sample 66:
|
||||
time = 2529782
|
||||
sample 67:
|
||||
time = 2536567
|
||||
flags = 1
|
||||
data = length 384, hash D108A215
|
||||
sample 67:
|
||||
time = 2553782
|
||||
sample 68:
|
||||
time = 2560567
|
||||
flags = 1
|
||||
data = length 384, hash BD25DB7C
|
||||
sample 68:
|
||||
time = 2577782
|
||||
sample 69:
|
||||
time = 2584567
|
||||
flags = 1
|
||||
data = length 384, hash DA7F9861
|
||||
sample 69:
|
||||
time = 2601782
|
||||
sample 70:
|
||||
time = 2608567
|
||||
flags = 1
|
||||
data = length 384, hash CCD576F
|
||||
sample 70:
|
||||
time = 2625782
|
||||
sample 71:
|
||||
time = 2632567
|
||||
flags = 1
|
||||
data = length 384, hash 405C1EB5
|
||||
sample 71:
|
||||
time = 2649782
|
||||
sample 72:
|
||||
time = 2656567
|
||||
flags = 1
|
||||
data = length 384, hash 6640B74E
|
||||
sample 72:
|
||||
time = 2673782
|
||||
sample 73:
|
||||
time = 2680567
|
||||
flags = 1
|
||||
data = length 384, hash B4E5937A
|
||||
sample 73:
|
||||
time = 2697782
|
||||
sample 74:
|
||||
time = 2704567
|
||||
flags = 1
|
||||
data = length 384, hash CEE17733
|
||||
sample 74:
|
||||
time = 2721782
|
||||
sample 75:
|
||||
time = 2728567
|
||||
flags = 1
|
||||
data = length 384, hash 2A0DA733
|
||||
sample 75:
|
||||
time = 2745782
|
||||
sample 76:
|
||||
time = 2752567
|
||||
flags = 1
|
||||
data = length 384, hash 97F4129B
|
||||
tracksEnded = true
|
||||
|
@ -27,155 +27,155 @@ track 0:
|
||||
initializationData:
|
||||
sample count = 38
|
||||
sample 0:
|
||||
time = 1858196
|
||||
time = 1871586
|
||||
flags = 1
|
||||
data = length 384, hash E801184A
|
||||
sample 1:
|
||||
time = 1882196
|
||||
time = 1895586
|
||||
flags = 1
|
||||
data = length 384, hash 53C6CF9C
|
||||
sample 2:
|
||||
time = 1906196
|
||||
time = 1919586
|
||||
flags = 1
|
||||
data = length 384, hash 19A8D99F
|
||||
sample 3:
|
||||
time = 1930196
|
||||
time = 1943586
|
||||
flags = 1
|
||||
data = length 384, hash E47EB43F
|
||||
sample 4:
|
||||
time = 1954196
|
||||
time = 1967586
|
||||
flags = 1
|
||||
data = length 384, hash 4EA329E7
|
||||
sample 5:
|
||||
time = 1978196
|
||||
time = 1991586
|
||||
flags = 1
|
||||
data = length 384, hash 1CCAAE62
|
||||
sample 6:
|
||||
time = 2002196
|
||||
time = 2015586
|
||||
flags = 1
|
||||
data = length 384, hash ED3F8C66
|
||||
sample 7:
|
||||
time = 2026196
|
||||
time = 2039586
|
||||
flags = 1
|
||||
data = length 384, hash D3D646B6
|
||||
sample 8:
|
||||
time = 2050196
|
||||
time = 2063586
|
||||
flags = 1
|
||||
data = length 384, hash 68CD1574
|
||||
sample 9:
|
||||
time = 2074196
|
||||
time = 2087586
|
||||
flags = 1
|
||||
data = length 384, hash 8CEAB382
|
||||
sample 10:
|
||||
time = 2098196
|
||||
time = 2111586
|
||||
flags = 1
|
||||
data = length 384, hash D54B1C48
|
||||
sample 11:
|
||||
time = 2122196
|
||||
time = 2135586
|
||||
flags = 1
|
||||
data = length 384, hash FFE2EE90
|
||||
sample 12:
|
||||
time = 2146196
|
||||
time = 2159586
|
||||
flags = 1
|
||||
data = length 384, hash BFE8A673
|
||||
sample 13:
|
||||
time = 2170196
|
||||
time = 2183586
|
||||
flags = 1
|
||||
data = length 384, hash 978B1C92
|
||||
sample 14:
|
||||
time = 2194196
|
||||
time = 2207586
|
||||
flags = 1
|
||||
data = length 384, hash 810CC71E
|
||||
sample 15:
|
||||
time = 2218196
|
||||
time = 2231586
|
||||
flags = 1
|
||||
data = length 384, hash 44FE42D9
|
||||
sample 16:
|
||||
time = 2242196
|
||||
time = 2255586
|
||||
flags = 1
|
||||
data = length 384, hash 2F5BB02C
|
||||
sample 17:
|
||||
time = 2266196
|
||||
time = 2279586
|
||||
flags = 1
|
||||
data = length 384, hash 77DDB90
|
||||
sample 18:
|
||||
time = 2290196
|
||||
time = 2303586
|
||||
flags = 1
|
||||
data = length 384, hash 24FB5EDA
|
||||
sample 19:
|
||||
time = 2314196
|
||||
time = 2327586
|
||||
flags = 1
|
||||
data = length 384, hash E73203C6
|
||||
sample 20:
|
||||
time = 2338196
|
||||
time = 2351586
|
||||
flags = 1
|
||||
data = length 384, hash 14B525F1
|
||||
sample 21:
|
||||
time = 2362196
|
||||
time = 2375586
|
||||
flags = 1
|
||||
data = length 384, hash 5E0F4E2E
|
||||
sample 22:
|
||||
time = 2386196
|
||||
time = 2399586
|
||||
flags = 1
|
||||
data = length 384, hash 67EE4E31
|
||||
sample 23:
|
||||
time = 2410196
|
||||
time = 2423586
|
||||
flags = 1
|
||||
data = length 384, hash 2E04EC4C
|
||||
sample 24:
|
||||
time = 2434196
|
||||
time = 2447586
|
||||
flags = 1
|
||||
data = length 384, hash 852CABA7
|
||||
sample 25:
|
||||
time = 2458196
|
||||
time = 2471586
|
||||
flags = 1
|
||||
data = length 384, hash 19928903
|
||||
sample 26:
|
||||
time = 2482196
|
||||
time = 2495586
|
||||
flags = 1
|
||||
data = length 384, hash 5DA42021
|
||||
sample 27:
|
||||
time = 2506196
|
||||
time = 2519586
|
||||
flags = 1
|
||||
data = length 384, hash 45B20B7C
|
||||
sample 28:
|
||||
time = 2530196
|
||||
time = 2543586
|
||||
flags = 1
|
||||
data = length 384, hash D108A215
|
||||
sample 29:
|
||||
time = 2554196
|
||||
time = 2567586
|
||||
flags = 1
|
||||
data = length 384, hash BD25DB7C
|
||||
sample 30:
|
||||
time = 2578196
|
||||
time = 2591586
|
||||
flags = 1
|
||||
data = length 384, hash DA7F9861
|
||||
sample 31:
|
||||
time = 2602196
|
||||
time = 2615586
|
||||
flags = 1
|
||||
data = length 384, hash CCD576F
|
||||
sample 32:
|
||||
time = 2626196
|
||||
time = 2639586
|
||||
flags = 1
|
||||
data = length 384, hash 405C1EB5
|
||||
sample 33:
|
||||
time = 2650196
|
||||
time = 2663586
|
||||
flags = 1
|
||||
data = length 384, hash 6640B74E
|
||||
sample 34:
|
||||
time = 2674196
|
||||
time = 2687586
|
||||
flags = 1
|
||||
data = length 384, hash B4E5937A
|
||||
sample 35:
|
||||
time = 2698196
|
||||
time = 2711586
|
||||
flags = 1
|
||||
data = length 384, hash CEE17733
|
||||
sample 36:
|
||||
time = 2722196
|
||||
time = 2735586
|
||||
flags = 1
|
||||
data = length 384, hash 2A0DA733
|
||||
sample 37:
|
||||
time = 2746196
|
||||
time = 2759586
|
||||
flags = 1
|
||||
data = length 384, hash 97F4129B
|
||||
tracksEnded = true
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
@ -26,22 +27,21 @@ import com.google.android.exoplayer2.util.Util;
|
||||
private static final int BITS_PER_BYTE = 8;
|
||||
|
||||
private final long firstFramePosition;
|
||||
private final long dataSize;
|
||||
private final int frameSize;
|
||||
private final long dataSize;
|
||||
private final int bitrate;
|
||||
private final long durationUs;
|
||||
|
||||
/**
|
||||
* @param firstFramePosition The position (byte offset) of the first frame.
|
||||
* @param inputLength The length of the stream.
|
||||
* @param frameSize The size of a single frame in the stream.
|
||||
* @param bitrate The stream's bitrate.
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param firstFramePosition The position of the first frame in the stream.
|
||||
* @param mpegAudioHeader The MPEG audio header associated with the first frame.
|
||||
*/
|
||||
public ConstantBitrateSeeker(long firstFramePosition, long inputLength, int frameSize,
|
||||
int bitrate) {
|
||||
public ConstantBitrateSeeker(long inputLength, long firstFramePosition,
|
||||
MpegAudioHeader mpegAudioHeader) {
|
||||
this.firstFramePosition = firstFramePosition;
|
||||
this.frameSize = frameSize;
|
||||
this.bitrate = bitrate;
|
||||
this.frameSize = mpegAudioHeader.frameSize;
|
||||
this.bitrate = mpegAudioHeader.bitrate;
|
||||
if (inputLength == C.LENGTH_UNSET) {
|
||||
dataSize = C.LENGTH_UNSET;
|
||||
durationUs = C.TIME_UNSET;
|
||||
|
@ -360,7 +360,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
int seekHeader = getSeekFrameHeader(frame, xingBase);
|
||||
Seeker seeker;
|
||||
if (seekHeader == SEEK_HEADER_XING || seekHeader == SEEK_HEADER_INFO) {
|
||||
seeker = XingSeeker.create(synchronizedHeader, frame, input.getPosition(), input.getLength());
|
||||
seeker = XingSeeker.create(input.getLength(), input.getPosition(), synchronizedHeader, frame);
|
||||
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
|
||||
// If there is a Xing header, read gapless playback metadata at a fixed offset.
|
||||
input.resetPeekPosition();
|
||||
@ -375,7 +375,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
return getConstantBitrateSeeker(input);
|
||||
}
|
||||
} else if (seekHeader == SEEK_HEADER_VBRI) {
|
||||
seeker = VbriSeeker.create(synchronizedHeader, frame, input.getPosition(), input.getLength());
|
||||
seeker = VbriSeeker.create(input.getLength(), input.getPosition(), synchronizedHeader, frame);
|
||||
input.skipFully(synchronizedHeader.frameSize);
|
||||
} else { // seekerHeader == SEEK_HEADER_UNSET
|
||||
// This frame doesn't contain seeking information, so reset the peek position.
|
||||
@ -393,8 +393,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
input.peekFully(scratch.data, 0, 4);
|
||||
scratch.setPosition(0);
|
||||
MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader);
|
||||
return new ConstantBitrateSeeker(input.getPosition(), input.getLength(),
|
||||
synchronizedHeader.frameSize, synchronizedHeader.bitrate);
|
||||
return new ConstantBitrateSeeker(input.getLength(), input.getPosition(), synchronizedHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
@ -25,21 +26,23 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
/* package */ final class VbriSeeker implements Mp3Extractor.Seeker {
|
||||
|
||||
private static final String TAG = "VbriSeeker";
|
||||
|
||||
/**
|
||||
* Returns a {@link VbriSeeker} for seeking in the stream, if required information is present.
|
||||
* Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the
|
||||
* caller should reset it.
|
||||
*
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param position The position of the start of this frame in the stream.
|
||||
* @param mpegAudioHeader The MPEG audio header associated with the frame.
|
||||
* @param frame The data in this audio frame, with its position set to immediately after the
|
||||
* 'VBRI' tag.
|
||||
* @param position The position (byte offset) of the start of this frame in the stream.
|
||||
* @param inputLength The length of the stream in bytes.
|
||||
* @return A {@link VbriSeeker} for seeking in the stream, or {@code null} if the required
|
||||
* information is not present.
|
||||
*/
|
||||
public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame,
|
||||
long position, long inputLength) {
|
||||
public static VbriSeeker create(long inputLength, long position, MpegAudioHeader mpegAudioHeader,
|
||||
ParsableByteArray frame) {
|
||||
frame.skipBytes(10);
|
||||
int numFrames = frame.readInt();
|
||||
if (numFrames <= 0) {
|
||||
@ -53,15 +56,15 @@ import com.google.android.exoplayer2.util.Util;
|
||||
int entrySize = frame.readUnsignedShort();
|
||||
frame.skipBytes(2);
|
||||
|
||||
// Skip the frame containing the VBRI header.
|
||||
position += mpegAudioHeader.frameSize;
|
||||
|
||||
long minPosition = position + mpegAudioHeader.frameSize;
|
||||
// Read table of contents entries.
|
||||
long[] timesUs = new long[entryCount + 1];
|
||||
long[] positions = new long[entryCount + 1];
|
||||
timesUs[0] = 0L;
|
||||
positions[0] = position;
|
||||
for (int index = 1; index < timesUs.length; index++) {
|
||||
long[] timesUs = new long[entryCount];
|
||||
long[] positions = new long[entryCount];
|
||||
for (int index = 0; index < entryCount; index++) {
|
||||
timesUs[index] = (index * durationUs) / entryCount;
|
||||
// Ensure positions do not fall within the frame containing the VBRI header. This constraint
|
||||
// will normally only apply to the first entry in the table.
|
||||
positions[index] = Math.max(position, minPosition);
|
||||
int segmentSize;
|
||||
switch (entrySize) {
|
||||
case 1:
|
||||
@ -80,9 +83,9 @@ import com.google.android.exoplayer2.util.Util;
|
||||
return null;
|
||||
}
|
||||
position += segmentSize * scale;
|
||||
timesUs[index] = index * durationUs / entryCount;
|
||||
positions[index] =
|
||||
inputLength == C.LENGTH_UNSET ? position : Math.min(inputLength, position);
|
||||
}
|
||||
if (inputLength != C.LENGTH_UNSET && inputLength != position) {
|
||||
Log.w(TAG, "VBRI data size mismatch: " + inputLength + ", " + position);
|
||||
}
|
||||
return new VbriSeeker(timesUs, positions, durationUs);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
@ -25,24 +26,25 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
/* package */ final class XingSeeker implements Mp3Extractor.Seeker {
|
||||
|
||||
private static final String TAG = "XingSeeker";
|
||||
|
||||
/**
|
||||
* Returns a {@link XingSeeker} for seeking in the stream, if required information is present.
|
||||
* Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the
|
||||
* caller should reset it.
|
||||
*
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param position The position of the start of this frame in the stream.
|
||||
* @param mpegAudioHeader The MPEG audio header associated with the frame.
|
||||
* @param frame The data in this audio frame, with its position set to immediately after the
|
||||
* 'Xing' or 'Info' tag.
|
||||
* @param position The position (byte offset) of the start of this frame in the stream.
|
||||
* @param inputLength The length of the stream in bytes.
|
||||
* @return A {@link XingSeeker} for seeking in the stream, or {@code null} if the required
|
||||
* information is not present.
|
||||
*/
|
||||
public static XingSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame,
|
||||
long position, long inputLength) {
|
||||
public static XingSeeker create(long inputLength, long position, MpegAudioHeader mpegAudioHeader,
|
||||
ParsableByteArray frame) {
|
||||
int samplesPerFrame = mpegAudioHeader.samplesPerFrame;
|
||||
int sampleRate = mpegAudioHeader.sampleRate;
|
||||
long firstFramePosition = position + mpegAudioHeader.frameSize;
|
||||
|
||||
int flags = frame.readInt();
|
||||
int frameCount;
|
||||
@ -54,10 +56,10 @@ import com.google.android.exoplayer2.util.Util;
|
||||
sampleRate);
|
||||
if ((flags & 0x06) != 0x06) {
|
||||
// If the size in bytes or table of contents is missing, the stream is not seekable.
|
||||
return new XingSeeker(firstFramePosition, durationUs, inputLength);
|
||||
return new XingSeeker(position, mpegAudioHeader.frameSize, durationUs);
|
||||
}
|
||||
|
||||
long sizeBytes = frame.readUnsignedIntToInt();
|
||||
long dataSize = frame.readUnsignedIntToInt();
|
||||
long[] tableOfContents = new long[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
tableOfContents[i] = frame.readUnsignedByte();
|
||||
@ -66,32 +68,37 @@ import com.google.android.exoplayer2.util.Util;
|
||||
// TODO: Handle encoder delay and padding in 3 bytes offset by xingBase + 213 bytes:
|
||||
// delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4);
|
||||
// padding = ((frame.readUnsignedByte() & 0x0F) << 8) + frame.readUnsignedByte();
|
||||
return new XingSeeker(firstFramePosition, durationUs, inputLength, tableOfContents,
|
||||
sizeBytes, mpegAudioHeader.frameSize);
|
||||
|
||||
if (inputLength != C.LENGTH_UNSET && inputLength != position + dataSize) {
|
||||
Log.w(TAG, "XING data size mismatch: " + inputLength + ", " + (position + dataSize));
|
||||
}
|
||||
return new XingSeeker(position, mpegAudioHeader.frameSize, durationUs, dataSize,
|
||||
tableOfContents);
|
||||
}
|
||||
|
||||
private final long firstFramePosition;
|
||||
private final long dataStartPosition;
|
||||
private final int xingFrameSize;
|
||||
private final long durationUs;
|
||||
private final long inputLength;
|
||||
/**
|
||||
* Data size, including the XING frame.
|
||||
*/
|
||||
private final long dataSize;
|
||||
/**
|
||||
* Entries are in the range [0, 255], but are stored as long integers for convenience.
|
||||
*/
|
||||
private final long[] tableOfContents;
|
||||
private final long sizeBytes;
|
||||
private final int headerSize;
|
||||
|
||||
private XingSeeker(long firstFramePosition, long durationUs, long inputLength) {
|
||||
this(firstFramePosition, durationUs, inputLength, null, 0, 0);
|
||||
private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs) {
|
||||
this(dataStartPosition, xingFrameSize, durationUs, C.LENGTH_UNSET, null);
|
||||
}
|
||||
|
||||
private XingSeeker(long firstFramePosition, long durationUs, long inputLength,
|
||||
long[] tableOfContents, long sizeBytes, int headerSize) {
|
||||
this.firstFramePosition = firstFramePosition;
|
||||
private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs, long dataSize,
|
||||
long[] tableOfContents) {
|
||||
this.dataStartPosition = dataStartPosition;
|
||||
this.xingFrameSize = xingFrameSize;
|
||||
this.durationUs = durationUs;
|
||||
this.inputLength = inputLength;
|
||||
this.dataSize = dataSize;
|
||||
this.tableOfContents = tableOfContents;
|
||||
this.sizeBytes = sizeBytes;
|
||||
this.headerSize = headerSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,44 +109,45 @@ import com.google.android.exoplayer2.util.Util;
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
if (!isSeekable()) {
|
||||
return firstFramePosition;
|
||||
return dataStartPosition + xingFrameSize;
|
||||
}
|
||||
double percent = (timeUs * 100d) / durationUs;
|
||||
double fx;
|
||||
double scaledPosition;
|
||||
if (percent <= 0) {
|
||||
fx = 0;
|
||||
scaledPosition = 0;
|
||||
} else if (percent >= 100) {
|
||||
fx = 256;
|
||||
scaledPosition = 256;
|
||||
} else {
|
||||
int a = (int) percent;
|
||||
float fa = tableOfContents[a];
|
||||
float fb = a == 99 ? 256 : tableOfContents[a + 1];
|
||||
fx = fa + (fb - fa) * (percent - a);
|
||||
int prevTableIndex = (int) percent;
|
||||
double prevScaledPosition = tableOfContents[prevTableIndex];
|
||||
double nextScaledPosition = prevTableIndex == 99 ? 256 : tableOfContents[prevTableIndex + 1];
|
||||
// Linearly interpolate between the two scaled positions.
|
||||
double interpolateFraction = percent - prevTableIndex;
|
||||
scaledPosition = prevScaledPosition
|
||||
+ (interpolateFraction * (nextScaledPosition - prevScaledPosition));
|
||||
}
|
||||
|
||||
long position = Math.round((fx / 256) * sizeBytes) + firstFramePosition;
|
||||
long maximumPosition = inputLength != C.LENGTH_UNSET ? inputLength - 1
|
||||
: firstFramePosition - headerSize + sizeBytes - 1;
|
||||
return Math.min(position, maximumPosition);
|
||||
long positionOffset = Math.round((scaledPosition / 256) * dataSize);
|
||||
// Ensure returned positions skip the frame containing the XING header.
|
||||
positionOffset = Util.constrainValue(positionOffset, xingFrameSize, dataSize - 1);
|
||||
return dataStartPosition + positionOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeUs(long position) {
|
||||
if (!isSeekable() || position < firstFramePosition) {
|
||||
long positionOffset = position - dataStartPosition;
|
||||
if (!isSeekable() || positionOffset <= xingFrameSize) {
|
||||
return 0L;
|
||||
}
|
||||
double offsetByte = (256d * (position - firstFramePosition)) / sizeBytes;
|
||||
int previousTocPosition =
|
||||
Util.binarySearchFloor(tableOfContents, (long) offsetByte, true, true);
|
||||
long previousTime = getTimeUsForTocPosition(previousTocPosition);
|
||||
|
||||
// Linearly interpolate the time taking into account the next entry.
|
||||
long previousByte = tableOfContents[previousTocPosition];
|
||||
long nextByte = previousTocPosition == 99 ? 256 : tableOfContents[previousTocPosition + 1];
|
||||
long nextTime = getTimeUsForTocPosition(previousTocPosition + 1);
|
||||
long timeOffset = nextByte == previousByte ? 0 : (long) ((nextTime - previousTime)
|
||||
* (offsetByte - previousByte) / (nextByte - previousByte));
|
||||
return previousTime + timeOffset;
|
||||
double scaledPosition = (positionOffset * 256d) / dataSize;
|
||||
int prevTableIndex = Util.binarySearchFloor(tableOfContents, (long) scaledPosition, true, true);
|
||||
long prevTimeUs = getTimeUsForTableIndex(prevTableIndex);
|
||||
long prevScaledPosition = tableOfContents[prevTableIndex];
|
||||
long nextTimeUs = getTimeUsForTableIndex(prevTableIndex + 1);
|
||||
long nextScaledPosition = prevTableIndex == 99 ? 256 : tableOfContents[prevTableIndex + 1];
|
||||
// Linearly interpolate between the two table entries.
|
||||
double interpolateFraction = prevScaledPosition == nextScaledPosition ? 0
|
||||
: ((scaledPosition - prevScaledPosition) / (nextScaledPosition - prevScaledPosition));
|
||||
return prevTimeUs + Math.round(interpolateFraction * (nextTimeUs - prevTimeUs));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -148,11 +156,13 @@ import com.google.android.exoplayer2.util.Util;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time in microseconds corresponding to a table of contents position, which is
|
||||
* interpreted as a percentage of the stream's duration between 0 and 100.
|
||||
* Returns the time in microseconds for a given table index.
|
||||
*
|
||||
* @param tableIndex A table index in the range [0, 100].
|
||||
* @return The corresponding time in microseconds.
|
||||
*/
|
||||
private long getTimeUsForTocPosition(int tocPosition) {
|
||||
return (durationUs * tocPosition) / 100;
|
||||
private long getTimeUsForTableIndex(int tableIndex) {
|
||||
return (durationUs * tableIndex) / 100;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,17 +43,17 @@ public final class XingSeekerTest {
|
||||
private static final int XING_FRAME_POSITION = 157;
|
||||
|
||||
/**
|
||||
* Size of the audio stream, encoded in {@link #XING_FRAME_PAYLOAD}.
|
||||
* Data size, as encoded in {@link #XING_FRAME_PAYLOAD}.
|
||||
*/
|
||||
private static final int STREAM_SIZE_BYTES = 948505;
|
||||
private static final int DATA_SIZE_BYTES = 948505;
|
||||
/**
|
||||
* Duration of the audio stream in microseconds, encoded in {@link #XING_FRAME_PAYLOAD}.
|
||||
*/
|
||||
private static final int STREAM_DURATION_US = 59271836;
|
||||
/**
|
||||
* The length of the file in bytes.
|
||||
* The length of the stream in bytes.
|
||||
*/
|
||||
private static final int INPUT_LENGTH = 948662;
|
||||
private static final int STREAM_LENGTH = XING_FRAME_POSITION + DATA_SIZE_BYTES;
|
||||
|
||||
private XingSeeker seeker;
|
||||
private XingSeeker seekerWithInputLength;
|
||||
@ -63,10 +63,10 @@ public final class XingSeekerTest {
|
||||
public void setUp() throws Exception {
|
||||
MpegAudioHeader xingFrameHeader = new MpegAudioHeader();
|
||||
MpegAudioHeader.populateHeader(XING_FRAME_HEADER_DATA, xingFrameHeader);
|
||||
seeker = XingSeeker.create(xingFrameHeader, new ParsableByteArray(XING_FRAME_PAYLOAD),
|
||||
XING_FRAME_POSITION, C.LENGTH_UNSET);
|
||||
seekerWithInputLength = XingSeeker.create(xingFrameHeader,
|
||||
new ParsableByteArray(XING_FRAME_PAYLOAD), XING_FRAME_POSITION, INPUT_LENGTH);
|
||||
seeker = XingSeeker.create(C.LENGTH_UNSET, XING_FRAME_POSITION, xingFrameHeader,
|
||||
new ParsableByteArray(XING_FRAME_PAYLOAD));
|
||||
seekerWithInputLength = XingSeeker.create(STREAM_LENGTH,
|
||||
XING_FRAME_POSITION, xingFrameHeader, new ParsableByteArray(XING_FRAME_PAYLOAD));
|
||||
xingFrameSize = xingFrameHeader.frameSize;
|
||||
}
|
||||
|
||||
@ -84,10 +84,10 @@ public final class XingSeekerTest {
|
||||
|
||||
@Test
|
||||
public void testGetTimeUsAtEndOfStream() {
|
||||
assertThat(seeker.getTimeUs(XING_FRAME_POSITION + xingFrameSize + STREAM_SIZE_BYTES))
|
||||
assertThat(seeker.getTimeUs(STREAM_LENGTH))
|
||||
.isEqualTo(STREAM_DURATION_US);
|
||||
assertThat(
|
||||
seekerWithInputLength.getTimeUs(XING_FRAME_POSITION + xingFrameSize + STREAM_SIZE_BYTES))
|
||||
seekerWithInputLength.getTimeUs(STREAM_LENGTH))
|
||||
.isEqualTo(STREAM_DURATION_US);
|
||||
}
|
||||
|
||||
@ -100,14 +100,14 @@ public final class XingSeekerTest {
|
||||
@Test
|
||||
public void testGetPositionAtEndOfStream() {
|
||||
assertThat(seeker.getPosition(STREAM_DURATION_US))
|
||||
.isEqualTo(XING_FRAME_POSITION + STREAM_SIZE_BYTES - 1);
|
||||
.isEqualTo(STREAM_LENGTH - 1);
|
||||
assertThat(seekerWithInputLength.getPosition(STREAM_DURATION_US))
|
||||
.isEqualTo(XING_FRAME_POSITION + STREAM_SIZE_BYTES - 1);
|
||||
.isEqualTo(STREAM_LENGTH - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeForAllPositions() {
|
||||
for (int offset = xingFrameSize; offset < STREAM_SIZE_BYTES; offset++) {
|
||||
for (int offset = xingFrameSize; offset < DATA_SIZE_BYTES; offset++) {
|
||||
int position = XING_FRAME_POSITION + offset;
|
||||
long timeUs = seeker.getTimeUs(position);
|
||||
assertThat(seeker.getPosition(timeUs)).isEqualTo(position);
|
||||
|
Loading…
x
Reference in New Issue
Block a user