Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
8a89abcb
authored
Oct 27, 2016
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge branch 'dev-v2-id3' into dev-v2
parents
2c543632
8caaf0b5
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
1446 additions
and
518 deletions
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java
library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java
library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
library/src/main/java/com/google/android/exoplayer2/Format.java
library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java
View file @
8a89abcb
...
...
@@ -27,8 +27,10 @@ import com.google.android.exoplayer2.Timeline;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.GeobFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
...
...
@@ -46,7 +48,6 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import
com.google.android.exoplayer2.video.VideoRendererEventListener
;
import
java.io.IOException
;
import
java.text.NumberFormat
;
import
java.util.List
;
import
java.util.Locale
;
/**
...
...
@@ -55,7 +56,7 @@ import java.util.Locale;
/* package */
final
class
EventLogger
implements
ExoPlayer
.
EventListener
,
AudioRendererEventListener
,
VideoRendererEventListener
,
AdaptiveMediaSourceEventListener
,
ExtractorMediaSource
.
EventListener
,
StreamingDrmSessionManager
.
EventListener
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
{
MetadataRenderer
.
Output
{
private
static
final
String
TAG
=
"EventLogger"
;
private
static
final
int
MAX_TIMELINE_ITEM_LINES
=
3
;
...
...
@@ -157,6 +158,18 @@ import java.util.Locale;
}
Log
.
d
(
TAG
,
" ]"
);
}
// Log metadata for at most one of the tracks selected for the renderer.
if
(
trackSelection
!=
null
)
{
for
(
int
selectionIndex
=
0
;
selectionIndex
<
trackSelection
.
length
();
selectionIndex
++)
{
Metadata
metadata
=
trackSelection
.
getFormat
(
selectionIndex
).
metadata
;
if
(
metadata
!=
null
)
{
Log
.
d
(
TAG
,
" Metadata ["
);
printMetadata
(
metadata
,
" "
);
Log
.
d
(
TAG
,
" ]"
);
break
;
}
}
}
Log
.
d
(
TAG
,
" ]"
);
}
}
...
...
@@ -182,34 +195,13 @@ import java.util.Locale;
Log
.
d
(
TAG
,
"]"
);
}
// MetadataRenderer.Output
<List<Id3Frame>>
// MetadataRenderer.Output
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
for
(
Id3Frame
id3Frame
:
id3Frames
)
{
if
(
id3Frame
instanceof
TxxxFrame
)
{
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s, value=%s"
,
txxxFrame
.
id
,
txxxFrame
.
description
,
txxxFrame
.
value
));
}
else
if
(
id3Frame
instanceof
PrivFrame
)
{
PrivFrame
privFrame
=
(
PrivFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: owner=%s"
,
privFrame
.
id
,
privFrame
.
owner
));
}
else
if
(
id3Frame
instanceof
GeobFrame
)
{
GeobFrame
geobFrame
=
(
GeobFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s"
,
geobFrame
.
id
,
geobFrame
.
mimeType
,
geobFrame
.
filename
,
geobFrame
.
description
));
}
else
if
(
id3Frame
instanceof
ApicFrame
)
{
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: mimeType=%s, description=%s"
,
apicFrame
.
id
,
apicFrame
.
mimeType
,
apicFrame
.
description
));
}
else
if
(
id3Frame
instanceof
TextInformationFrame
)
{
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s"
,
textInformationFrame
.
id
,
textInformationFrame
.
description
));
}
else
{
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s"
,
id3Frame
.
id
));
}
}
public
void
onMetadata
(
Metadata
metadata
)
{
Log
.
d
(
TAG
,
"onMetadata ["
);
printMetadata
(
metadata
,
" "
);
Log
.
d
(
TAG
,
"]"
);
}
// AudioRendererEventListener
...
...
@@ -354,6 +346,39 @@ import java.util.Locale;
Log
.
e
(
TAG
,
"internalError ["
+
getSessionTimeString
()
+
", "
+
type
+
"]"
,
e
);
}
private
void
printMetadata
(
Metadata
metadata
,
String
prefix
)
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
TxxxFrame
)
{
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: description=%s, value=%s"
,
txxxFrame
.
id
,
txxxFrame
.
description
,
txxxFrame
.
value
));
}
else
if
(
entry
instanceof
PrivFrame
)
{
PrivFrame
privFrame
=
(
PrivFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: owner=%s"
,
privFrame
.
id
,
privFrame
.
owner
));
}
else
if
(
entry
instanceof
GeobFrame
)
{
GeobFrame
geobFrame
=
(
GeobFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: mimeType=%s, filename=%s, description=%s"
,
geobFrame
.
id
,
geobFrame
.
mimeType
,
geobFrame
.
filename
,
geobFrame
.
description
));
}
else
if
(
entry
instanceof
ApicFrame
)
{
ApicFrame
apicFrame
=
(
ApicFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: mimeType=%s, description=%s"
,
apicFrame
.
id
,
apicFrame
.
mimeType
,
apicFrame
.
description
));
}
else
if
(
entry
instanceof
TextInformationFrame
)
{
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: description=%s"
,
textInformationFrame
.
id
,
textInformationFrame
.
description
));
}
else
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: language=%s description=%s"
,
commentFrame
.
id
,
commentFrame
.
language
,
commentFrame
.
description
,
commentFrame
.
text
));
}
else
if
(
entry
instanceof
Id3Frame
)
{
Id3Frame
id3Frame
=
(
Id3Frame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s"
,
id3Frame
.
id
));
}
}
}
private
String
getSessionTimeString
()
{
return
getTimeString
(
SystemClock
.
elapsedRealtime
()
-
startTimeMs
);
}
...
...
library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java
View file @
8a89abcb
...
...
@@ -24,6 +24,8 @@ import android.annotation.TargetApi;
import
android.media.MediaFormat
;
import
android.os.Parcel
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
...
...
@@ -56,11 +58,14 @@ public final class FormatTest extends TestCase {
TestUtil
.
buildTestData
(
128
,
1
/* data seed */
));
DrmInitData
drmInitData
=
new
DrmInitData
(
DRM_DATA_1
,
DRM_DATA_2
);
byte
[]
projectionData
=
new
byte
[]
{
1
,
2
,
3
};
Metadata
metadata
=
new
Metadata
(
new
TextInformationFrame
(
"id1"
,
"description1"
),
new
TextInformationFrame
(
"id2"
,
"description2"
));
Format
formatToParcel
=
new
Format
(
"id"
,
MimeTypes
.
VIDEO_MP4
,
MimeTypes
.
VIDEO_H264
,
null
,
1024
,
2048
,
1920
,
1080
,
24
,
90
,
2
,
projectionData
,
C
.
STEREO_MODE_TOP_BOTTOM
,
6
,
44100
,
C
.
ENCODING_PCM_24BIT
,
1001
,
1002
,
0
,
"und"
,
Format
.
OFFSET_SAMPLE_RELATIVE
,
INIT_DATA
,
drmInitData
);
drmInitData
,
metadata
);
Parcel
parcel
=
Parcel
.
obtain
();
formatToParcel
.
writeToParcel
(
parcel
,
0
);
...
...
library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
View file @
8a89abcb
...
...
@@ -16,8 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.test.MoreAsserts
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
java.util.List
;
import
junit.framework.TestCase
;
/**
...
...
@@ -30,9 +30,9 @@ public class Id3DecoderTest extends TestCase {
3
,
0
,
109
,
100
,
105
,
97
,
108
,
111
,
103
,
95
,
86
,
73
,
78
,
68
,
73
,
67
,
79
,
49
,
53
,
50
,
55
,
54
,
54
,
52
,
95
,
115
,
116
,
97
,
114
,
116
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
metadata
.
get
(
0
);
assertEquals
(
""
,
txxxFrame
.
description
);
assertEquals
(
"mdialog_VINDICO1527664_start"
,
txxxFrame
.
value
);
}
...
...
@@ -42,9 +42,9 @@ public class Id3DecoderTest extends TestCase {
3
,
105
,
109
,
97
,
103
,
101
,
47
,
106
,
112
,
101
,
103
,
0
,
16
,
72
,
101
,
108
,
108
,
111
,
32
,
87
,
111
,
114
,
108
,
100
,
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
ApicFrame
apicFrame
=
(
ApicFrame
)
metadata
.
get
(
0
);
assertEquals
(
"image/jpeg"
,
apicFrame
.
mimeType
);
assertEquals
(
16
,
apicFrame
.
pictureType
);
assertEquals
(
"Hello World"
,
apicFrame
.
description
);
...
...
@@ -56,9 +56,9 @@ public class Id3DecoderTest extends TestCase {
byte
[]
rawId3
=
new
byte
[]
{
73
,
68
,
51
,
4
,
0
,
0
,
0
,
0
,
0
,
23
,
84
,
73
,
84
,
50
,
0
,
0
,
0
,
13
,
0
,
0
,
3
,
72
,
101
,
108
,
108
,
111
,
32
,
87
,
111
,
114
,
108
,
100
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
metadata
.
get
(
0
);
assertEquals
(
"TIT2"
,
textInformationFrame
.
id
);
assertEquals
(
"Hello World"
,
textInformationFrame
.
description
);
}
...
...
library/src/main/java/com/google/android/exoplayer2/Format.java
View file @
8a89abcb
...
...
@@ -21,6 +21,7 @@ import android.media.MediaFormat;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.nio.ByteBuffer
;
...
...
@@ -57,6 +58,10 @@ public final class Format implements Parcelable {
* Codecs of the format as described in RFC 6381, or null if unknown or not applicable.
*/
public
final
String
codecs
;
/**
* Metadata, or null if unknown or not applicable.
*/
public
final
Metadata
metadata
;
// Container specific.
...
...
@@ -185,7 +190,7 @@ public final class Format implements Parcelable {
float
frameRate
,
List
<
byte
[]>
initializationData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
width
,
height
,
frameRate
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
);
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
,
null
);
}
public
static
Format
createVideoSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -211,7 +216,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
drmInitData
,
null
);
}
// Audio.
...
...
@@ -222,7 +227,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
);
null
,
null
);
}
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -239,18 +244,18 @@ public final class Format implements Parcelable {
@C
.
SelectionFlags
int
selectionFlags
,
String
language
)
{
return
createAudioSampleFormat
(
id
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
channelCount
,
sampleRate
,
pcmEncoding
,
NO_VALUE
,
NO_VALUE
,
initializationData
,
drmInitData
,
selectionFlags
,
language
);
selectionFlags
,
language
,
null
);
}
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
int
maxInputSize
,
int
channelCount
,
int
sampleRate
,
@C
.
PcmEncoding
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
,
@C
.
SelectionFlags
int
selectionFlags
,
String
language
)
{
@C
.
SelectionFlags
int
selectionFlags
,
String
language
,
Metadata
metadata
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
initializationData
,
drmInitData
,
metadata
);
}
// Text.
...
...
@@ -260,7 +265,7 @@ public final class Format implements Parcelable {
String
language
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
);
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
,
null
);
}
public
static
Format
createTextSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -274,7 +279,7 @@ public final class Format implements Parcelable {
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
subsampleOffsetUs
,
null
,
drmInitData
);
NO_VALUE
,
selectionFlags
,
language
,
subsampleOffsetUs
,
null
,
drmInitData
,
null
);
}
// Image.
...
...
@@ -283,7 +288,7 @@ public final class Format implements Parcelable {
int
bitrate
,
List
<
byte
[]>
initializationData
,
String
language
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
NO_VALUE
,
0
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
,
null
);
}
// Generic.
...
...
@@ -292,14 +297,14 @@ public final class Format implements Parcelable {
String
sampleMimeType
,
int
bitrate
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
);
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
,
null
);
}
public
static
Format
createSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
drmInitData
);
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
drmInitData
,
null
);
}
/* package */
Format
(
String
id
,
String
containerMimeType
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -307,7 +312,8 @@ public final class Format implements Parcelable {
float
pixelWidthHeightRatio
,
byte
[]
projectionData
,
@C
.
StereoMode
int
stereoMode
,
int
channelCount
,
int
sampleRate
,
@C
.
PcmEncoding
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
@C
.
SelectionFlags
int
selectionFlags
,
String
language
,
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
)
{
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
,
Metadata
metadata
)
{
this
.
id
=
id
;
this
.
containerMimeType
=
containerMimeType
;
this
.
sampleMimeType
=
sampleMimeType
;
...
...
@@ -332,6 +338,7 @@ public final class Format implements Parcelable {
this
.
initializationData
=
initializationData
==
null
?
Collections
.<
byte
[]>
emptyList
()
:
initializationData
;
this
.
drmInitData
=
drmInitData
;
this
.
metadata
=
metadata
;
}
@SuppressWarnings
(
"ResourceType"
)
...
...
@@ -364,20 +371,21 @@ public final class Format implements Parcelable {
initializationData
.
add
(
in
.
createByteArray
());
}
drmInitData
=
in
.
readParcelable
(
DrmInitData
.
class
.
getClassLoader
());
metadata
=
in
.
readParcelable
(
Metadata
.
class
.
getClassLoader
());
}
public
Format
copyWithMaxInputSize
(
int
maxInputSize
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithSubsampleOffsetUs
(
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithContainerInfo
(
String
id
,
String
codecs
,
int
bitrate
,
int
width
,
int
height
,
...
...
@@ -385,7 +393,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithManifestFormatInfo
(
Format
manifestFormat
,
...
...
@@ -401,21 +409,28 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithGaplessInfo
(
int
encoderDelay
,
int
encoderPadding
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithDrmInitData
(
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithMetadata
(
Metadata
metadata
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
/**
...
...
@@ -475,6 +490,7 @@ public final class Format implements Parcelable {
result
=
31
*
result
+
sampleRate
;
result
=
31
*
result
+
(
language
==
null
?
0
:
language
.
hashCode
());
result
=
31
*
result
+
(
drmInitData
==
null
?
0
:
drmInitData
.
hashCode
());
result
=
31
*
result
+
(
metadata
==
null
?
0
:
metadata
.
hashCode
());
hashCode
=
result
;
}
return
hashCode
;
...
...
@@ -502,6 +518,7 @@ public final class Format implements Parcelable {
||
!
Util
.
areEqual
(
sampleMimeType
,
other
.
sampleMimeType
)
||
!
Util
.
areEqual
(
codecs
,
other
.
codecs
)
||
!
Util
.
areEqual
(
drmInitData
,
other
.
drmInitData
)
||
!
Util
.
areEqual
(
metadata
,
other
.
metadata
)
||
!
Arrays
.
equals
(
projectionData
,
other
.
projectionData
)
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
return
false
;
...
...
@@ -574,6 +591,7 @@ public final class Format implements Parcelable {
dest
.
writeByteArray
(
initializationData
.
get
(
i
));
}
dest
.
writeParcelable
(
drmInitData
,
0
);
dest
.
writeParcelable
(
metadata
,
0
);
}
/**
...
...
library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
View file @
8a89abcb
...
...
@@ -35,9 +35,9 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.drm.FrameworkMediaCrypto
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.text.Cue
;
...
...
@@ -107,7 +107,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private
SurfaceHolder
surfaceHolder
;
private
TextureView
textureView
;
private
TextRenderer
.
Output
textOutput
;
private
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
id3
Output
;
private
MetadataRenderer
.
Output
metadata
Output
;
private
VideoListener
videoListener
;
private
AudioRendererEventListener
audioDebugListener
;
private
VideoRendererEventListener
videoDebugListener
;
...
...
@@ -364,12 +364,21 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
/**
* Sets a listener to receive ID3 metadata events.
* @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead.
* @param output The output.
*/
@Deprecated
public
void
setId3Output
(
MetadataRenderer
.
Output
output
)
{
setMetadataOutput
(
output
);
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public
void
set
Id3Output
(
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
output
)
{
id3
Output
=
output
;
public
void
set
MetadataOutput
(
MetadataRenderer
.
Output
output
)
{
metadata
Output
=
output
;
}
// ExoPlayer implementation
...
...
@@ -540,9 +549,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
Renderer
textRenderer
=
new
TextRenderer
(
componentListener
,
mainHandler
.
getLooper
());
renderersList
.
add
(
textRenderer
);
MetadataRenderer
<
List
<
Id3Frame
>>
id3Renderer
=
new
MetadataRenderer
<>
(
componentListener
,
MetadataRenderer
metadataRenderer
=
new
MetadataRenderer
(
componentListener
,
mainHandler
.
getLooper
(),
new
Id3Decoder
());
renderersList
.
add
(
id3
Renderer
);
renderersList
.
add
(
metadata
Renderer
);
}
private
void
buildExtensionRenderers
(
ArrayList
<
Renderer
>
renderersList
,
...
...
@@ -644,7 +653,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
private
final
class
ComponentListener
implements
VideoRendererEventListener
,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
,
SurfaceHolder
.
Callback
,
TextureView
.
SurfaceTextureListener
{
// VideoRendererEventListener implementation
...
...
@@ -775,12 +784,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
}
// MetadataRenderer.Output
<List<Id3Frame>>
implementation
// MetadataRenderer.Output implementation
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
if
(
id3
Output
!=
null
)
{
id3Output
.
onMetadata
(
id3Frames
);
public
void
onMetadata
(
Metadata
metadata
)
{
if
(
metadata
Output
!=
null
)
{
metadataOutput
.
onMetadata
(
metadata
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
View file @
8a89abcb
...
...
@@ -16,6 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -66,6 +68,25 @@ public final class GaplessInfoHolder {
}
/**
* Populates the holder with data parsed from ID3 {@link Metadata}.
*
* @param metadata The metadata from which to parse the gapless information.
* @return Whether the holder was populated.
*/
public
boolean
setFromMetadata
(
Metadata
metadata
)
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
if
(
setFromComment
(
commentFrame
.
description
,
commentFrame
.
text
))
{
return
true
;
}
}
}
return
false
;
}
/**
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
*
...
...
@@ -73,7 +94,7 @@ public final class GaplessInfoHolder {
* @param data The comment's payload data.
* @return Whether the holder was populated.
*/
p
ublic
boolean
setFromComment
(
String
name
,
String
data
)
{
p
rivate
boolean
setFromComment
(
String
name
,
String
data
)
{
if
(!
GAPLESS_COMMENT_ID
.
equals
(
name
))
{
return
false
;
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
deleted
100644 → 0
View file @
2c543632
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.nio.charset.Charset
;
/**
* Utility for parsing ID3 version 2 metadata in MP3 files.
*/
/* package */
final
class
Id3Util
{
/**
* The maximum valid length for metadata in bytes.
*/
private
static
final
int
MAXIMUM_METADATA_SIZE
=
3
*
1024
*
1024
;
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
private
static
final
Charset
[]
CHARSET_BY_ENCODING
=
new
Charset
[]
{
Charset
.
forName
(
"ISO-8859-1"
),
Charset
.
forName
(
"UTF-16LE"
),
Charset
.
forName
(
"UTF-16BE"
),
Charset
.
forName
(
"UTF-8"
)};
/**
* Peeks data from the input and parses ID3 metadata.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @param out The {@link GaplessInfoHolder} to populate.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
public
static
void
parseId3
(
ExtractorInput
input
,
GaplessInfoHolder
out
)
throws
IOException
,
InterruptedException
{
ParsableByteArray
scratch
=
new
ParsableByteArray
(
10
);
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
10
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
ID3_TAG
)
{
break
;
}
int
majorVersion
=
scratch
.
readUnsignedByte
();
int
minorVersion
=
scratch
.
readUnsignedByte
();
int
flags
=
scratch
.
readUnsignedByte
();
int
length
=
scratch
.
readSynchSafeInt
();
if
(!
out
.
hasGaplessInfo
()
&&
canParseMetadata
(
majorVersion
,
minorVersion
,
flags
,
length
))
{
byte
[]
frame
=
new
byte
[
length
];
input
.
peekFully
(
frame
,
0
,
length
);
parseGaplessInfo
(
new
ParsableByteArray
(
frame
),
majorVersion
,
flags
,
out
);
}
else
{
input
.
advancePeekPosition
(
length
);
}
peekedId3Bytes
+=
10
+
length
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
}
private
static
boolean
canParseMetadata
(
int
majorVersion
,
int
minorVersion
,
int
flags
,
int
length
)
{
return
minorVersion
!=
0xFF
&&
majorVersion
>=
2
&&
majorVersion
<=
4
&&
length
<=
MAXIMUM_METADATA_SIZE
&&
!(
majorVersion
==
2
&&
((
flags
&
0x3F
)
!=
0
||
(
flags
&
0x40
)
!=
0
))
&&
!(
majorVersion
==
3
&&
(
flags
&
0x1F
)
!=
0
)
&&
!(
majorVersion
==
4
&&
(
flags
&
0x0F
)
!=
0
);
}
private
static
void
parseGaplessInfo
(
ParsableByteArray
frame
,
int
version
,
int
flags
,
GaplessInfoHolder
out
)
{
unescape
(
frame
,
version
,
flags
);
// Skip any extended header.
frame
.
setPosition
(
0
);
if
(
version
==
3
&&
(
flags
&
0x40
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
;
}
int
extendedHeaderSize
=
frame
.
readUnsignedIntToInt
();
if
(
extendedHeaderSize
>
frame
.
bytesLeft
())
{
return
;
}
int
paddingSize
;
if
(
extendedHeaderSize
>=
6
)
{
frame
.
skipBytes
(
2
);
// extended flags
paddingSize
=
frame
.
readUnsignedIntToInt
();
frame
.
setPosition
(
4
);
frame
.
setLimit
(
frame
.
limit
()
-
paddingSize
);
if
(
frame
.
bytesLeft
()
<
extendedHeaderSize
)
{
return
;
}
}
frame
.
skipBytes
(
extendedHeaderSize
);
}
else
if
(
version
==
4
&&
(
flags
&
0x40
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
;
}
int
extendedHeaderSize
=
frame
.
readSynchSafeInt
();
if
(
extendedHeaderSize
<
6
||
extendedHeaderSize
>
frame
.
bytesLeft
()
+
4
)
{
return
;
}
frame
.
setPosition
(
extendedHeaderSize
);
}
// Extract gapless playback metadata stored in comments.
Pair
<
String
,
String
>
comment
;
while
((
comment
=
findNextComment
(
version
,
frame
))
!=
null
)
{
if
(
comment
.
first
.
length
()
>
3
)
{
if
(
out
.
setFromComment
(
comment
.
first
.
substring
(
3
),
comment
.
second
))
{
break
;
}
}
}
}
private
static
Pair
<
String
,
String
>
findNextComment
(
int
majorVersion
,
ParsableByteArray
data
)
{
int
frameSize
;
while
(
true
)
{
if
(
majorVersion
==
2
)
{
if
(
data
.
bytesLeft
()
<
6
)
{
return
null
;
}
String
id
=
data
.
readString
(
3
,
Charset
.
forName
(
"US-ASCII"
));
if
(
id
.
equals
(
"\0\0\0"
))
{
return
null
;
}
frameSize
=
data
.
readUnsignedInt24
();
if
(
frameSize
==
0
||
frameSize
>
data
.
bytesLeft
())
{
return
null
;
}
if
(
id
.
equals
(
"COM"
))
{
break
;
}
}
else
/* major == 3 || major == 4 */
{
if
(
data
.
bytesLeft
()
<
10
)
{
return
null
;
}
String
id
=
data
.
readString
(
4
,
Charset
.
forName
(
"US-ASCII"
));
if
(
id
.
equals
(
"\0\0\0\0"
))
{
return
null
;
}
frameSize
=
majorVersion
==
4
?
data
.
readSynchSafeInt
()
:
data
.
readUnsignedIntToInt
();
if
(
frameSize
==
0
||
frameSize
>
data
.
bytesLeft
()
-
2
)
{
return
null
;
}
int
flags
=
data
.
readUnsignedShort
();
boolean
compressedOrEncrypted
=
(
majorVersion
==
4
&&
(
flags
&
0x0C
)
!=
0
)
||
(
majorVersion
==
3
&&
(
flags
&
0xC0
)
!=
0
);
if
(!
compressedOrEncrypted
&&
id
.
equals
(
"COMM"
))
{
break
;
}
}
data
.
skipBytes
(
frameSize
);
}
// The comment tag is at the reading position in data.
int
encoding
=
data
.
readUnsignedByte
();
if
(
encoding
<
0
||
encoding
>=
CHARSET_BY_ENCODING
.
length
)
{
return
null
;
}
Charset
charset
=
CHARSET_BY_ENCODING
[
encoding
];
String
[]
commentFields
=
data
.
readString
(
frameSize
-
1
,
charset
).
split
(
"\0"
);
return
commentFields
.
length
==
2
?
Pair
.
create
(
commentFields
[
0
],
commentFields
[
1
])
:
null
;
}
private
static
boolean
unescape
(
ParsableByteArray
frame
,
int
version
,
int
flags
)
{
if
(
version
!=
4
)
{
if
((
flags
&
0x80
)
!=
0
)
{
// Remove unsynchronization on ID3 version < 2.4.0.
byte
[]
bytes
=
frame
.
data
;
int
newLength
=
bytes
.
length
;
for
(
int
i
=
0
;
i
+
1
<
newLength
;
i
++)
{
if
((
bytes
[
i
]
&
0xFF
)
==
0xFF
&&
bytes
[
i
+
1
]
==
0x00
)
{
System
.
arraycopy
(
bytes
,
i
+
2
,
bytes
,
i
+
1
,
newLength
-
i
-
2
);
newLength
--;
}
}
frame
.
setLimit
(
newLength
);
}
}
else
{
// Remove unsynchronization on ID3 version 2.4.0.
if
(
canUnescapeVersion4
(
frame
,
false
))
{
unescapeVersion4
(
frame
,
false
);
}
else
if
(
canUnescapeVersion4
(
frame
,
true
))
{
unescapeVersion4
(
frame
,
true
);
}
else
{
return
false
;
}
}
return
true
;
}
private
static
boolean
canUnescapeVersion4
(
ParsableByteArray
frame
,
boolean
unsignedIntDataSizeHack
)
{
frame
.
setPosition
(
0
);
while
(
frame
.
bytesLeft
()
>=
10
)
{
if
(
frame
.
readInt
()
==
0
)
{
return
true
;
}
long
dataSize
=
frame
.
readUnsignedInt
();
if
(!
unsignedIntDataSizeHack
)
{
// Parse the data size as a syncsafe integer.
if
((
dataSize
&
0x808080
L
)
!=
0
)
{
return
false
;
}
dataSize
=
(
dataSize
&
0x7F
)
|
(((
dataSize
>>
8
)
&
0x7F
)
<<
7
)
|
(((
dataSize
>>
16
)
&
0x7F
)
<<
14
)
|
(((
dataSize
>>
24
)
&
0x7F
)
<<
21
);
}
if
(
dataSize
>
frame
.
bytesLeft
()
-
2
)
{
return
false
;
}
int
flags
=
frame
.
readUnsignedShort
();
if
((
flags
&
1
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
false
;
}
}
frame
.
skipBytes
((
int
)
dataSize
);
}
return
true
;
}
private
static
void
unescapeVersion4
(
ParsableByteArray
frame
,
boolean
unsignedIntDataSizeHack
)
{
frame
.
setPosition
(
0
);
byte
[]
bytes
=
frame
.
data
;
while
(
frame
.
bytesLeft
()
>=
10
)
{
if
(
frame
.
readInt
()
==
0
)
{
return
;
}
int
dataSize
=
unsignedIntDataSizeHack
?
frame
.
readUnsignedIntToInt
()
:
frame
.
readSynchSafeInt
();
int
flags
=
frame
.
readUnsignedShort
();
int
previousFlags
=
flags
;
if
((
flags
&
1
)
!=
0
)
{
// Strip data length indicator.
int
offset
=
frame
.
getPosition
();
System
.
arraycopy
(
bytes
,
offset
+
4
,
bytes
,
offset
,
frame
.
bytesLeft
()
-
4
);
dataSize
-=
4
;
flags
&=
~
1
;
frame
.
setLimit
(
frame
.
limit
()
-
4
);
}
if
((
flags
&
2
)
!=
0
)
{
// Unescape 0xFF00 to 0xFF in the next dataSize bytes.
int
readOffset
=
frame
.
getPosition
()
+
1
;
int
writeOffset
=
readOffset
;
for
(
int
i
=
0
;
i
+
1
<
dataSize
;
i
++)
{
if
((
bytes
[
readOffset
-
1
]
&
0xFF
)
==
0xFF
&&
bytes
[
readOffset
]
==
0
)
{
readOffset
++;
dataSize
--;
}
bytes
[
writeOffset
++]
=
bytes
[
readOffset
++];
}
frame
.
setLimit
(
frame
.
limit
()
-
(
readOffset
-
writeOffset
));
System
.
arraycopy
(
bytes
,
readOffset
,
bytes
,
writeOffset
,
frame
.
bytesLeft
()
-
readOffset
);
flags
&=
~
2
;
}
if
(
flags
!=
previousFlags
||
unsignedIntDataSizeHack
)
{
int
dataSizeOffset
=
frame
.
getPosition
()
-
6
;
writeSyncSafeInteger
(
bytes
,
dataSizeOffset
,
dataSize
);
bytes
[
dataSizeOffset
+
4
]
=
(
byte
)
(
flags
>>
8
);
bytes
[
dataSizeOffset
+
5
]
=
(
byte
)
(
flags
&
0xFF
);
}
frame
.
skipBytes
(
dataSize
);
}
}
private
static
void
writeSyncSafeInteger
(
byte
[]
bytes
,
int
offset
,
int
value
)
{
bytes
[
offset
]
=
(
byte
)
((
value
>>
21
)
&
0x7F
);
bytes
[
offset
+
1
]
=
(
byte
)
((
value
>>
14
)
&
0x7F
);
bytes
[
offset
+
2
]
=
(
byte
)
((
value
>>
7
)
&
0x7F
);
bytes
[
offset
+
3
]
=
(
byte
)
(
value
&
0x7F
);
}
private
Id3Util
()
{}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
View file @
8a89abcb
...
...
@@ -27,6 +27,8 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.EOFException
;
...
...
@@ -57,6 +59,10 @@ public final class Mp3Extractor implements Extractor {
* The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up.
*/
private
static
final
int
MAX_SNIFF_BYTES
=
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
;
/**
* Maximum length of data read into {@link #scratch}.
*/
private
static
final
int
SCRATCH_LENGTH
=
10
;
/**
* Mask that includes the audio header values that must match between frames.
...
...
@@ -77,6 +83,7 @@ public final class Mp3Extractor implements Extractor {
private
int
synchronizedHeaderData
;
private
Metadata
metadata
;
private
Seeker
seeker
;
private
long
basisTimeUs
;
private
long
samplesRead
;
...
...
@@ -97,7 +104,7 @@ public final class Mp3Extractor implements Extractor {
*/
public
Mp3Extractor
(
long
forcedFirstSampleTimestampUs
)
{
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
scratch
=
new
ParsableByteArray
(
4
);
scratch
=
new
ParsableByteArray
(
SCRATCH_LENGTH
);
synchronizedHeader
=
new
MpegAudioHeader
();
gaplessInfoHolder
=
new
GaplessInfoHolder
();
basisTimeUs
=
C
.
TIME_UNSET
;
...
...
@@ -144,7 +151,7 @@ public final class Mp3Extractor implements Extractor {
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
));
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
,
metadata
));
}
return
readSample
(
input
);
}
...
...
@@ -199,7 +206,7 @@ public final class Mp3Extractor implements Extractor {
int
searchLimitBytes
=
sniffing
?
MAX_SNIFF_BYTES
:
MAX_SYNC_BYTES
;
input
.
resetPeekPosition
();
if
(
input
.
getPosition
()
==
0
)
{
Id3Util
.
parseId3
(
input
,
gaplessInfoHolder
);
peekId3Data
(
input
);
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
if
(!
sniffing
)
{
input
.
skipFully
(
peekedId3Bytes
);
...
...
@@ -254,6 +261,45 @@ public final class Mp3Extractor implements Extractor {
}
/**
* Peeks ID3 data from the input, including gapless playback information.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
private
void
peekId3Data
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
Id3Decoder
.
ID3_HEADER_LENGTH
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
Id3Decoder
.
ID3_TAG
)
{
// Not an ID3 tag.
break
;
}
scratch
.
skipBytes
(
3
);
// Skip major version, minor version and flags.
int
framesLength
=
scratch
.
readSynchSafeInt
();
int
tagLength
=
Id3Decoder
.
ID3_HEADER_LENGTH
+
framesLength
;
if
(
metadata
==
null
)
{
byte
[]
id3Data
=
new
byte
[
tagLength
];
System
.
arraycopy
(
scratch
.
data
,
0
,
id3Data
,
0
,
Id3Decoder
.
ID3_HEADER_LENGTH
);
input
.
peekFully
(
id3Data
,
Id3Decoder
.
ID3_HEADER_LENGTH
,
framesLength
);
metadata
=
new
Id3Decoder
().
decode
(
id3Data
,
tagLength
);
if
(
metadata
!=
null
)
{
gaplessInfoHolder
.
setFromMetadata
(
metadata
);
}
}
else
{
input
.
advancePeekPosition
(
framesLength
);
}
peekedId3Bytes
+=
tagLength
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
}
/**
* Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide
* data from the start of the first frame in the stream. On returning, the input's position will
* be set to the start of the first frame of audio.
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java
View file @
8a89abcb
...
...
@@ -132,7 +132,6 @@ import java.util.List;
public
static
final
int
TYPE_vp08
=
Util
.
getIntegerCodeForString
(
"vp08"
);
public
static
final
int
TYPE_vp09
=
Util
.
getIntegerCodeForString
(
"vp09"
);
public
static
final
int
TYPE_vpcC
=
Util
.
getIntegerCodeForString
(
"vpcC"
);
public
static
final
int
TYPE_DASHES
=
Util
.
getIntegerCodeForString
(
"----"
);
public
final
int
type
;
...
...
@@ -299,7 +298,7 @@ import java.util.List;
* @return The corresponding four character string.
*/
public
static
String
getAtomTypeString
(
int
type
)
{
return
""
+
(
char
)
(
type
>>
24
)
return
""
+
(
char
)
(
(
type
>>
24
)
&
0xFF
)
+
(
char
)
((
type
>>
16
)
&
0xFF
)
+
(
char
)
((
type
>>
8
)
&
0xFF
)
+
(
char
)
(
type
&
0xFF
);
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
View file @
8a89abcb
...
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ParserException;
import
com.google.android.exoplayer2.audio.Ac3Util
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer2.util.MimeTypes
;
...
...
@@ -30,6 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.video.AvcConfig
;
import
com.google.android.exoplayer2.video.HevcConfig
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
...
...
@@ -400,80 +402,54 @@ import java.util.List;
*
* @param udtaAtom The udta (user data) atom to decode.
* @param isQuickTime True for QuickTime media. False otherwise.
* @
param out {@link GaplessInfoHolder} to populate with gapless playback information
.
* @
return Parsed metadata, or null
.
*/
public
static
void
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
,
GaplessInfoHolder
out
)
{
public
static
Metadata
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
)
{
if
(
isQuickTime
)
{
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
// decode one.
return
;
return
null
;
}
ParsableByteArray
udtaData
=
udtaAtom
.
data
;
udtaData
.
setPosition
(
Atom
.
HEADER_SIZE
);
while
(
udtaData
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
int
atomPosition
=
udtaData
.
getPosition
();
int
atomSize
=
udtaData
.
readInt
();
int
atomType
=
udtaData
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_meta
)
{
udtaData
.
setPosition
(
udtaData
.
getPosition
()
-
Atom
.
HEADER_SIZE
);
udtaData
.
setLimit
(
udtaData
.
getPosition
()
+
atomSize
);
parseMetaAtom
(
udtaData
,
out
);
break
;
udtaData
.
setPosition
(
atomPosition
);
return
parseMetaAtom
(
udtaData
,
atomPosition
+
atomSize
);
}
udtaData
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
}
return
null
;
}
private
static
void
parseMetaAtom
(
ParsableByteArray
data
,
GaplessInfoHolder
ou
t
)
{
da
ta
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
ParsableByteArray
ilst
=
new
ParsableByteArray
();
while
(
data
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
int
payloadSize
=
data
.
readInt
()
-
Atom
.
HEADER_SIZE
;
int
atomType
=
da
ta
.
readInt
();
private
static
Metadata
parseMetaAtom
(
ParsableByteArray
meta
,
int
limi
t
)
{
me
ta
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
while
(
meta
.
getPosition
()
<
limit
)
{
int
atomPosition
=
meta
.
getPosition
();
int
atomSize
=
meta
.
readInt
()
;
int
atomType
=
me
ta
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_ilst
)
{
ilst
.
reset
(
data
.
data
,
data
.
getPosition
()
+
payloadSize
);
ilst
.
setPosition
(
data
.
getPosition
());
parseIlst
(
ilst
,
out
);
if
(
out
.
hasGaplessInfo
())
{
return
;
}
meta
.
setPosition
(
atomPosition
);
return
parseIlst
(
meta
,
atomPosition
+
atomSize
);
}
data
.
skipBytes
(
payloadSize
);
meta
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
}
return
null
;
}
private
static
void
parseIlst
(
ParsableByteArray
ilst
,
GaplessInfoHolder
out
)
{
while
(
ilst
.
bytesLeft
()
>
0
)
{
int
position
=
ilst
.
getPosition
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
if
(
type
==
Atom
.
TYPE_DASHES
)
{
String
lastCommentMean
=
null
;
String
lastCommentName
=
null
;
String
lastCommentData
=
null
;
while
(
ilst
.
getPosition
()
<
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_mean
)
{
lastCommentMean
=
ilst
.
readString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_name
)
{
lastCommentName
=
ilst
.
readString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_data
)
{
ilst
.
skipBytes
(
4
);
lastCommentData
=
ilst
.
readString
(
length
-
4
);
}
else
{
ilst
.
skipBytes
(
length
);
}
}
if
(
lastCommentName
!=
null
&&
lastCommentData
!=
null
&&
"com.apple.iTunes"
.
equals
(
lastCommentMean
))
{
out
.
setFromComment
(
lastCommentName
,
lastCommentData
);
break
;
}
}
else
{
ilst
.
setPosition
(
endPosition
);
private
static
Metadata
parseIlst
(
ParsableByteArray
ilst
,
int
limit
)
{
ilst
.
skipBytes
(
Atom
.
HEADER_SIZE
);
ArrayList
<
Metadata
.
Entry
>
entries
=
new
ArrayList
<>();
while
(
ilst
.
getPosition
()
<
limit
)
{
Metadata
.
Entry
entry
=
MetadataUtil
.
parseIlstElement
(
ilst
);
if
(
entry
!=
null
)
{
entries
.
add
(
entry
);
}
}
return
entries
.
isEmpty
()
?
null
:
new
Metadata
(
entries
);
}
/**
...
...
@@ -484,12 +460,9 @@ import java.util.List;
*/
private
static
long
parseMvhd
(
ParsableByteArray
mvhd
)
{
mvhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
mvhd
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
mvhd
.
skipBytes
(
version
==
0
?
8
:
16
);
return
mvhd
.
readUnsignedInt
();
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
0 → 100644
View file @
8a89abcb
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp4
;
import
android.util.Log
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Parses metadata items stored in ilst atoms.
*/
/* package */
final
class
MetadataUtil
{
private
static
final
String
TAG
=
"MetadataUtil"
;
// Codes that start with the copyright character (omitted) and have equivalent ID3 frames.
private
static
final
int
SHORT_TYPE_NAME_1
=
Util
.
getIntegerCodeForString
(
"nam"
);
private
static
final
int
SHORT_TYPE_NAME_2
=
Util
.
getIntegerCodeForString
(
"trk"
);
private
static
final
int
SHORT_TYPE_COMMENT
=
Util
.
getIntegerCodeForString
(
"cmt"
);
private
static
final
int
SHORT_TYPE_YEAR
=
Util
.
getIntegerCodeForString
(
"day"
);
private
static
final
int
SHORT_TYPE_ARTIST
=
Util
.
getIntegerCodeForString
(
"ART"
);
private
static
final
int
SHORT_TYPE_ENCODER
=
Util
.
getIntegerCodeForString
(
"too"
);
private
static
final
int
SHORT_TYPE_ALBUM
=
Util
.
getIntegerCodeForString
(
"alb"
);
private
static
final
int
SHORT_TYPE_COMPOSER_1
=
Util
.
getIntegerCodeForString
(
"com"
);
private
static
final
int
SHORT_TYPE_COMPOSER_2
=
Util
.
getIntegerCodeForString
(
"wrt"
);
private
static
final
int
SHORT_TYPE_LYRICS
=
Util
.
getIntegerCodeForString
(
"lyr"
);
private
static
final
int
SHORT_TYPE_GENRE
=
Util
.
getIntegerCodeForString
(
"gen"
);
// Codes that have equivalent ID3 frames.
private
static
final
int
TYPE_COVER_ART
=
Util
.
getIntegerCodeForString
(
"covr"
);
private
static
final
int
TYPE_GENRE
=
Util
.
getIntegerCodeForString
(
"gnre"
);
private
static
final
int
TYPE_GROUPING
=
Util
.
getIntegerCodeForString
(
"grp"
);
private
static
final
int
TYPE_DISK_NUMBER
=
Util
.
getIntegerCodeForString
(
"disk"
);
private
static
final
int
TYPE_TRACK_NUMBER
=
Util
.
getIntegerCodeForString
(
"trkn"
);
private
static
final
int
TYPE_TEMPO
=
Util
.
getIntegerCodeForString
(
"tmpo"
);
private
static
final
int
TYPE_COMPILATION
=
Util
.
getIntegerCodeForString
(
"cpil"
);
private
static
final
int
TYPE_ALBUM_ARTIST
=
Util
.
getIntegerCodeForString
(
"aART"
);
private
static
final
int
TYPE_SORT_TRACK_NAME
=
Util
.
getIntegerCodeForString
(
"sonm"
);
private
static
final
int
TYPE_SORT_ALBUM
=
Util
.
getIntegerCodeForString
(
"soal"
);
private
static
final
int
TYPE_SORT_ARTIST
=
Util
.
getIntegerCodeForString
(
"soar"
);
private
static
final
int
TYPE_SORT_ALBUM_ARTIST
=
Util
.
getIntegerCodeForString
(
"soaa"
);
private
static
final
int
TYPE_SORT_COMPOSER
=
Util
.
getIntegerCodeForString
(
"soco"
);
// Types that do not have equivalent ID3 frames.
private
static
final
int
TYPE_RATING
=
Util
.
getIntegerCodeForString
(
"rtng"
);
private
static
final
int
TYPE_GAPLESS_ALBUM
=
Util
.
getIntegerCodeForString
(
"pgap"
);
private
static
final
int
TYPE_TV_SORT_SHOW
=
Util
.
getIntegerCodeForString
(
"sosn"
);
private
static
final
int
TYPE_TV_SHOW
=
Util
.
getIntegerCodeForString
(
"tvsh"
);
// Type for items that are intended for internal use by the player.
private
static
final
int
TYPE_INTERNAL
=
Util
.
getIntegerCodeForString
(
"----"
);
// Standard genres.
private
static
final
String
[]
STANDARD_GENRES
=
new
String
[]
{
// These are the official ID3v1 genres.
"Blues"
,
"Classic Rock"
,
"Country"
,
"Dance"
,
"Disco"
,
"Funk"
,
"Grunge"
,
"Hip-Hop"
,
"Jazz"
,
"Metal"
,
"New Age"
,
"Oldies"
,
"Other"
,
"Pop"
,
"R&B"
,
"Rap"
,
"Reggae"
,
"Rock"
,
"Techno"
,
"Industrial"
,
"Alternative"
,
"Ska"
,
"Death Metal"
,
"Pranks"
,
"Soundtrack"
,
"Euro-Techno"
,
"Ambient"
,
"Trip-Hop"
,
"Vocal"
,
"Jazz+Funk"
,
"Fusion"
,
"Trance"
,
"Classical"
,
"Instrumental"
,
"Acid"
,
"House"
,
"Game"
,
"Sound Clip"
,
"Gospel"
,
"Noise"
,
"AlternRock"
,
"Bass"
,
"Soul"
,
"Punk"
,
"Space"
,
"Meditative"
,
"Instrumental Pop"
,
"Instrumental Rock"
,
"Ethnic"
,
"Gothic"
,
"Darkwave"
,
"Techno-Industrial"
,
"Electronic"
,
"Pop-Folk"
,
"Eurodance"
,
"Dream"
,
"Southern Rock"
,
"Comedy"
,
"Cult"
,
"Gangsta"
,
"Top 40"
,
"Christian Rap"
,
"Pop/Funk"
,
"Jungle"
,
"Native American"
,
"Cabaret"
,
"New Wave"
,
"Psychadelic"
,
"Rave"
,
"Showtunes"
,
"Trailer"
,
"Lo-Fi"
,
"Tribal"
,
"Acid Punk"
,
"Acid Jazz"
,
"Polka"
,
"Retro"
,
"Musical"
,
"Rock & Roll"
,
"Hard Rock"
,
// These were made up by the authors of Winamp but backported into the ID3 spec.
"Folk"
,
"Folk-Rock"
,
"National Folk"
,
"Swing"
,
"Fast Fusion"
,
"Bebob"
,
"Latin"
,
"Revival"
,
"Celtic"
,
"Bluegrass"
,
"Avantgarde"
,
"Gothic Rock"
,
"Progressive Rock"
,
"Psychedelic Rock"
,
"Symphonic Rock"
,
"Slow Rock"
,
"Big Band"
,
"Chorus"
,
"Easy Listening"
,
"Acoustic"
,
"Humour"
,
"Speech"
,
"Chanson"
,
"Opera"
,
"Chamber Music"
,
"Sonata"
,
"Symphony"
,
"Booty Bass"
,
"Primus"
,
"Porn Groove"
,
"Satire"
,
"Slow Jam"
,
"Club"
,
"Tango"
,
"Samba"
,
"Folklore"
,
"Ballad"
,
"Power Ballad"
,
"Rhythmic Soul"
,
"Freestyle"
,
"Duet"
,
"Punk Rock"
,
"Drum Solo"
,
"A capella"
,
"Euro-House"
,
"Dance Hall"
,
// These were also invented by the Winamp folks but ignored by the ID3 authors.
"Goa"
,
"Drum & Bass"
,
"Club-House"
,
"Hardcore"
,
"Terror"
,
"Indie"
,
"BritPop"
,
"Negerpunk"
,
"Polsk Punk"
,
"Beat"
,
"Christian Gangsta Rap"
,
"Heavy Metal"
,
"Black Metal"
,
"Crossover"
,
"Contemporary Christian"
,
"Christian Rock"
,
"Merengue"
,
"Salsa"
,
"Thrash Metal"
,
"Anime"
,
"Jpop"
,
"Synthpop"
};
private
static
final
String
LANGUAGE_UNDEFINED
=
"und"
;
private
MetadataUtil
()
{}
/**
* Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting
* from the current position of the {@link ParsableByteArray}, and the position is advanced by
* the size of the element. The position is advanced even if the element's type is unrecognized.
*
* @param ilst Holds the data to be parsed.
* @return The parsed element, or null if the element's type was not recognized.
*/
public
static
Metadata
.
Entry
parseIlstElement
(
ParsableByteArray
ilst
)
{
int
position
=
ilst
.
getPosition
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
int
typeTopByte
=
(
type
>>
24
)
&
0xFF
;
try
{
if
(
typeTopByte
==
'\
u00A9
'
/* Copyright char */
||
typeTopByte
==
'\
uFFFD
'
/* Replacement char */
)
{
int
shortType
=
type
&
0x00FFFFFF
;
if
(
shortType
==
SHORT_TYPE_COMMENT
)
{
return
parseCommentAttribute
(
type
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_NAME_1
||
shortType
==
SHORT_TYPE_NAME_2
)
{
return
parseTextAttribute
(
type
,
"TIT2"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_COMPOSER_1
||
shortType
==
SHORT_TYPE_COMPOSER_2
)
{
return
parseTextAttribute
(
type
,
"TCOM"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_YEAR
)
{
return
parseTextAttribute
(
type
,
"TDRC"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TPE1"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ENCODER
)
{
return
parseTextAttribute
(
type
,
"TSSE"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ALBUM
)
{
return
parseTextAttribute
(
type
,
"TALB"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_LYRICS
)
{
return
parseTextAttribute
(
type
,
"USLT"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_GENRE
)
{
return
parseTextAttribute
(
type
,
"TCON"
,
ilst
);
}
else
if
(
shortType
==
TYPE_GROUPING
)
{
return
parseTextAttribute
(
type
,
"TIT1"
,
ilst
);
}
}
else
if
(
type
==
TYPE_GENRE
)
{
return
parseStandardGenreAttribute
(
ilst
);
}
else
if
(
type
==
TYPE_DISK_NUMBER
)
{
return
parseIndexAndCountAttribute
(
type
,
"TPOS"
,
ilst
);
}
else
if
(
type
==
TYPE_TRACK_NUMBER
)
{
return
parseIndexAndCountAttribute
(
type
,
"TRCK"
,
ilst
);
}
else
if
(
type
==
TYPE_TEMPO
)
{
return
parseUint8Attribute
(
type
,
"TBPM"
,
ilst
,
true
,
false
);
}
else
if
(
type
==
TYPE_COMPILATION
)
{
return
parseUint8Attribute
(
type
,
"TCMP"
,
ilst
,
true
,
true
);
}
else
if
(
type
==
TYPE_COVER_ART
)
{
return
parseCoverArt
(
ilst
);
}
else
if
(
type
==
TYPE_ALBUM_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TPE2"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_TRACK_NAME
)
{
return
parseTextAttribute
(
type
,
"TSOT"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ALBUM
)
{
return
parseTextAttribute
(
type
,
"TSO2"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TSOA"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ALBUM_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TSOP"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_COMPOSER
)
{
return
parseTextAttribute
(
type
,
"TSOC"
,
ilst
);
}
else
if
(
type
==
TYPE_RATING
)
{
return
parseUint8Attribute
(
type
,
"ITUNESADVISORY"
,
ilst
,
false
,
false
);
}
else
if
(
type
==
TYPE_GAPLESS_ALBUM
)
{
return
parseUint8Attribute
(
type
,
"ITUNESGAPLESS"
,
ilst
,
false
,
true
);
}
else
if
(
type
==
TYPE_TV_SORT_SHOW
)
{
return
parseTextAttribute
(
type
,
"TVSHOWSORT"
,
ilst
);
}
else
if
(
type
==
TYPE_TV_SHOW
)
{
return
parseTextAttribute
(
type
,
"TVSHOW"
,
ilst
);
}
else
if
(
type
==
TYPE_INTERNAL
)
{
return
parseInternalAttribute
(
ilst
,
endPosition
);
}
Log
.
d
(
TAG
,
"Skipped unknown metadata entry: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
finally
{
ilst
.
setPosition
(
endPosition
);
}
}
private
static
TextInformationFrame
parseTextAttribute
(
int
type
,
String
id
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
atomSize
-
16
);
return
new
TextInformationFrame
(
id
,
value
);
}
Log
.
w
(
TAG
,
"Failed to parse text attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
CommentFrame
parseCommentAttribute
(
int
type
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
atomSize
-
16
);
return
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
value
,
value
);
}
Log
.
w
(
TAG
,
"Failed to parse comment attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
Id3Frame
parseUint8Attribute
(
int
type
,
String
id
,
ParsableByteArray
data
,
boolean
isTextInformationFrame
,
boolean
isBoolean
)
{
int
value
=
parseUint8AttributeValue
(
data
);
if
(
isBoolean
)
{
value
=
Math
.
min
(
1
,
value
);
}
if
(
value
>=
0
)
{
return
isTextInformationFrame
?
new
TextInformationFrame
(
id
,
Integer
.
toString
(
value
))
:
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
id
,
Integer
.
toString
(
value
));
}
Log
.
w
(
TAG
,
"Failed to parse uint8 attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
TextInformationFrame
parseIndexAndCountAttribute
(
int
type
,
String
attributeName
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
&&
atomSize
>=
22
)
{
data
.
skipBytes
(
10
);
// version (1), flags (3), empty (4), empty (2)
int
index
=
data
.
readUnsignedShort
();
if
(
index
>
0
)
{
String
description
=
""
+
index
;
int
count
=
data
.
readUnsignedShort
();
if
(
count
>
0
)
{
description
+=
"/"
+
count
;
}
return
new
TextInformationFrame
(
attributeName
,
description
);
}
}
Log
.
w
(
TAG
,
"Failed to parse index/count attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
TextInformationFrame
parseStandardGenreAttribute
(
ParsableByteArray
data
)
{
int
genreCode
=
parseUint8AttributeValue
(
data
);
String
genreString
=
(
0
<
genreCode
&&
genreCode
<=
STANDARD_GENRES
.
length
)
?
STANDARD_GENRES
[
genreCode
-
1
]
:
null
;
if
(
genreString
!=
null
)
{
return
new
TextInformationFrame
(
"TCON"
,
genreString
);
}
Log
.
w
(
TAG
,
"Failed to parse standard genre code"
);
return
null
;
}
private
static
ApicFrame
parseCoverArt
(
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
int
fullVersionInt
=
data
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullVersionInt
);
String
mimeType
=
flags
==
13
?
"image/jpeg"
:
flags
==
14
?
"image/png"
:
null
;
if
(
mimeType
==
null
)
{
Log
.
w
(
TAG
,
"Unrecognized cover art flags: "
+
flags
);
return
null
;
}
data
.
skipBytes
(
4
);
// empty (4)
byte
[]
pictureData
=
new
byte
[
atomSize
-
16
];
data
.
readBytes
(
pictureData
,
0
,
pictureData
.
length
);
return
new
ApicFrame
(
mimeType
,
null
,
3
/* Cover (front) */
,
pictureData
);
}
Log
.
w
(
TAG
,
"Failed to parse cover art attribute"
);
return
null
;
}
private
static
Id3Frame
parseInternalAttribute
(
ParsableByteArray
data
,
int
endPosition
)
{
String
domain
=
null
;
String
name
=
null
;
int
dataAtomPosition
=
-
1
;
int
dataAtomSize
=
-
1
;
while
(
data
.
getPosition
()
<
endPosition
)
{
int
atomPosition
=
data
.
getPosition
();
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
data
.
skipBytes
(
4
);
// version (1), flags (3)
if
(
atomType
==
Atom
.
TYPE_mean
)
{
domain
=
data
.
readNullTerminatedString
(
atomSize
-
12
);
}
else
if
(
atomType
==
Atom
.
TYPE_name
)
{
name
=
data
.
readNullTerminatedString
(
atomSize
-
12
);
}
else
{
if
(
atomType
==
Atom
.
TYPE_data
)
{
dataAtomPosition
=
atomPosition
;
dataAtomSize
=
atomSize
;
}
data
.
skipBytes
(
atomSize
-
12
);
}
}
if
(!
"com.apple.iTunes"
.
equals
(
domain
)
||
!
"iTunSMPB"
.
equals
(
name
)
||
dataAtomPosition
==
-
1
)
{
// We're only interested in iTunSMPB.
return
null
;
}
data
.
setPosition
(
dataAtomPosition
);
data
.
skipBytes
(
16
);
// size (4), type (4), version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
dataAtomSize
-
16
);
return
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
name
,
value
);
}
private
static
int
parseUint8AttributeValue
(
ParsableByteArray
data
)
{
data
.
skipBytes
(
4
);
// atomSize
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
return
data
.
readUnsignedByte
();
}
Log
.
w
(
TAG
,
"Failed to parse uint8 attribute value"
);
return
-
1
;
}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
View file @
8a89abcb
...
...
@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.NalUnitUtil
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
...
...
@@ -310,10 +311,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
List
<
Mp4Track
>
tracks
=
new
ArrayList
<>();
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
Metadata
metadata
=
null
;
GaplessInfoHolder
gaplessInfoHolder
=
new
GaplessInfoHolder
();
Atom
.
LeafAtom
udta
=
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_udta
);
if
(
udta
!=
null
)
{
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
,
gaplessInfoHolder
);
metadata
=
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
);
if
(
metadata
!=
null
)
{
gaplessInfoHolder
.
setFromMetadata
(
metadata
);
}
}
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
...
...
@@ -340,9 +345,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Allow ten source samples per output sample, like the platform extractor.
int
maxInputSize
=
trackSampleTable
.
maximumSize
+
3
*
10
;
Format
format
=
track
.
format
.
copyWithMaxInputSize
(
maxInputSize
);
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
&&
gaplessInfoHolder
.
hasGaplessInfo
())
{
format
=
format
.
copyWithGaplessInfo
(
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
);
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
)
{
if
(
gaplessInfoHolder
.
hasGaplessInfo
())
{
format
=
format
.
copyWithGaplessInfo
(
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
);
}
if
(
metadata
!=
null
)
{
format
=
format
.
copyWithMetadata
(
metadata
);
}
}
mp4Track
.
trackOutput
.
format
(
format
);
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java
0 → 100644
View file @
8a89abcb
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* A collection of metadata entries.
*/
public
final
class
Metadata
implements
Parcelable
{
/**
* A metadata entry.
*/
public
interface
Entry
extends
Parcelable
{}
private
final
Entry
[]
entries
;
/**
* @param entries The metadata entries.
*/
public
Metadata
(
Entry
...
entries
)
{
this
.
entries
=
entries
==
null
?
new
Entry
[
0
]
:
entries
;
}
/**
* @param entries The metadata entries.
*/
public
Metadata
(
List
<?
extends
Entry
>
entries
)
{
if
(
entries
!=
null
)
{
this
.
entries
=
new
Entry
[
entries
.
size
()];
entries
.
toArray
(
this
.
entries
);
}
else
{
this
.
entries
=
new
Entry
[
0
];
}
}
/* package */
Metadata
(
Parcel
in
)
{
entries
=
new
Metadata
.
Entry
[
in
.
readInt
()];
for
(
int
i
=
0
;
i
<
entries
.
length
;
i
++)
{
entries
[
i
]
=
in
.
readParcelable
(
Entry
.
class
.
getClassLoader
());
}
}
/**
* Returns the number of metadata entries.
*/
public
int
length
()
{
return
entries
.
length
;
}
/**
* Returns the entry at the specified index.
*
* @param index The index of the entry.
* @return The entry at the specified index.
*/
public
Metadata
.
Entry
get
(
int
index
)
{
return
entries
[
index
];
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
Metadata
other
=
(
Metadata
)
obj
;
return
Arrays
.
equals
(
entries
,
other
.
entries
);
}
@Override
public
int
hashCode
()
{
return
Arrays
.
hashCode
(
entries
);
}
@Override
public
int
describeContents
()
{
return
0
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeInt
(
entries
.
length
);
for
(
Entry
entry
:
entries
)
{
dest
.
writeParcelable
(
entry
,
0
);
}
}
public
static
final
Parcelable
.
Creator
<
Metadata
>
CREATOR
=
new
Parcelable
.
Creator
<
Metadata
>()
{
@Override
public
Metadata
createFromParcel
(
Parcel
in
)
{
return
new
Metadata
(
in
);
}
@Override
public
Metadata
[]
newArray
(
int
size
)
{
return
new
Metadata
[
0
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
View file @
8a89abcb
...
...
@@ -17,10 +17,8 @@ package com.google.android.exoplayer2.metadata;
/**
* Decodes metadata from binary data.
*
* @param <T> The type of the metadata.
*/
public
interface
MetadataDecoder
<
T
>
{
public
interface
MetadataDecoder
{
/**
* Checks whether the decoder supports a given mime type.
...
...
@@ -38,6 +36,6 @@ public interface MetadataDecoder<T> {
* @return The decoded metadata object.
* @throws MetadataDecoderException If a problem occurred decoding the data.
*/
T
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
;
Metadata
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
;
}
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
View file @
8a89abcb
...
...
@@ -30,38 +30,34 @@ import java.nio.ByteBuffer;
/**
* A renderer for metadata.
*
* @param <T> The type of the metadata.
*/
public
final
class
MetadataRenderer
<
T
>
extends
BaseRenderer
implements
Callback
{
public
final
class
MetadataRenderer
extends
BaseRenderer
implements
Callback
{
/**
* Receives output from a {@link MetadataRenderer}.
*
* @param <T> The type of the metadata.
*/
public
interface
Output
<
T
>
{
public
interface
Output
{
/**
* Called each time there is a metadata associated with current playback time.
*
* @param metadata The metadata.
*/
void
onMetadata
(
T
metadata
);
void
onMetadata
(
Metadata
metadata
);
}
private
static
final
int
MSG_INVOKE_RENDERER
=
0
;
private
final
MetadataDecoder
<
T
>
metadataDecoder
;
private
final
Output
<
T
>
output
;
private
final
MetadataDecoder
metadataDecoder
;
private
final
Output
output
;
private
final
Handler
outputHandler
;
private
final
FormatHolder
formatHolder
;
private
final
DecoderInputBuffer
buffer
;
private
boolean
inputStreamEnded
;
private
long
pendingMetadataTimestamp
;
private
T
pendingMetadata
;
private
Metadata
pendingMetadata
;
/**
* @param output The output.
...
...
@@ -72,8 +68,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
* called directly on the player's internal rendering thread.
* @param metadataDecoder A decoder for the metadata.
*/
public
MetadataRenderer
(
Output
<
T
>
output
,
Looper
outputLooper
,
MetadataDecoder
<
T
>
metadataDecoder
)
{
public
MetadataRenderer
(
Output
output
,
Looper
outputLooper
,
MetadataDecoder
metadataDecoder
)
{
super
(
C
.
TRACK_TYPE_METADATA
);
this
.
output
=
Assertions
.
checkNotNull
(
output
);
this
.
outputHandler
=
outputLooper
==
null
?
null
:
new
Handler
(
outputLooper
,
this
);
...
...
@@ -137,7 +132,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
return
true
;
}
private
void
invokeRenderer
(
T
metadata
)
{
private
void
invokeRenderer
(
Metadata
metadata
)
{
if
(
outputHandler
!=
null
)
{
outputHandler
.
obtainMessage
(
MSG_INVOKE_RENDERER
,
metadata
).
sendToTarget
();
}
else
{
...
...
@@ -150,13 +145,13 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
MSG_INVOKE_RENDERER:
invokeRendererInternal
((
T
)
msg
.
obj
);
invokeRendererInternal
((
Metadata
)
msg
.
obj
);
return
true
;
}
return
false
;
}
private
void
invokeRendererInternal
(
T
metadata
)
{
private
void
invokeRendererInternal
(
Metadata
metadata
)
{
output
.
onMetadata
(
metadata
);
}
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* APIC (Attached Picture) ID3 frame.
*/
...
...
@@ -35,4 +40,58 @@ public final class ApicFrame extends Id3Frame {
this
.
pictureData
=
pictureData
;
}
/* package */
ApicFrame
(
Parcel
in
)
{
super
(
ID
);
mimeType
=
in
.
readString
();
description
=
in
.
readString
();
pictureType
=
in
.
readInt
();
pictureData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
ApicFrame
other
=
(
ApicFrame
)
obj
;
return
pictureType
==
other
.
pictureType
&&
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
&&
Util
.
areEqual
(
description
,
other
.
description
)
&&
Arrays
.
equals
(
pictureData
,
other
.
pictureData
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
pictureType
;
result
=
31
*
result
+
(
mimeType
!=
null
?
mimeType
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
pictureData
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
mimeType
);
dest
.
writeString
(
description
);
dest
.
writeInt
(
pictureType
);
dest
.
writeByteArray
(
pictureData
);
}
public
static
final
Parcelable
.
Creator
<
ApicFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
ApicFrame
>()
{
@Override
public
ApicFrame
createFromParcel
(
Parcel
in
)
{
return
new
ApicFrame
(
in
);
}
@Override
public
ApicFrame
[]
newArray
(
int
size
)
{
return
new
ApicFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
* Binary ID3 frame.
*/
...
...
@@ -22,9 +26,55 @@ public final class BinaryFrame extends Id3Frame {
public
final
byte
[]
data
;
public
BinaryFrame
(
String
type
,
byte
[]
data
)
{
super
(
type
);
public
BinaryFrame
(
String
id
,
byte
[]
data
)
{
super
(
id
);
this
.
data
=
data
;
}
/* package */
BinaryFrame
(
Parcel
in
)
{
super
(
in
.
readString
());
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
BinaryFrame
other
=
(
BinaryFrame
)
obj
;
return
id
.
equals
(
other
.
id
)
&&
Arrays
.
equals
(
data
,
other
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
id
.
hashCode
();
result
=
31
*
result
+
Arrays
.
hashCode
(
data
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeByteArray
(
data
);
}
public
static
final
Parcelable
.
Creator
<
BinaryFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
BinaryFrame
>()
{
@Override
public
BinaryFrame
createFromParcel
(
Parcel
in
)
{
return
new
BinaryFrame
(
in
);
}
@Override
public
BinaryFrame
[]
newArray
(
int
size
)
{
return
new
BinaryFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
0 → 100644
View file @
8a89abcb
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Comment ID3 frame.
*/
public
final
class
CommentFrame
extends
Id3Frame
{
public
static
final
String
ID
=
"COMM"
;
public
final
String
language
;
public
final
String
description
;
public
final
String
text
;
public
CommentFrame
(
String
language
,
String
description
,
String
text
)
{
super
(
ID
);
this
.
language
=
language
;
this
.
description
=
description
;
this
.
text
=
text
;
}
/* package */
CommentFrame
(
Parcel
in
)
{
super
(
ID
);
language
=
in
.
readString
();
description
=
in
.
readString
();
text
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
CommentFrame
other
=
(
CommentFrame
)
obj
;
return
Util
.
areEqual
(
description
,
other
.
description
)
&&
Util
.
areEqual
(
language
,
other
.
language
)
&&
Util
.
areEqual
(
text
,
other
.
text
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
language
!=
null
?
language
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
text
!=
null
?
text
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeString
(
language
);
dest
.
writeString
(
text
);
}
public
static
final
Parcelable
.
Creator
<
CommentFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
CommentFrame
>()
{
@Override
public
CommentFrame
createFromParcel
(
Parcel
in
)
{
return
new
CommentFrame
(
in
);
}
@Override
public
CommentFrame
[]
newArray
(
int
size
)
{
return
new
CommentFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* GEOB (General Encapsulated Object) ID3 frame.
*/
...
...
@@ -35,4 +40,57 @@ public final class GeobFrame extends Id3Frame {
this
.
data
=
data
;
}
/* package */
GeobFrame
(
Parcel
in
)
{
super
(
ID
);
mimeType
=
in
.
readString
();
filename
=
in
.
readString
();
description
=
in
.
readString
();
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
GeobFrame
other
=
(
GeobFrame
)
obj
;
return
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
&&
Util
.
areEqual
(
filename
,
other
.
filename
)
&&
Util
.
areEqual
(
description
,
other
.
description
)
&&
Arrays
.
equals
(
data
,
other
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
mimeType
!=
null
?
mimeType
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
filename
!=
null
?
filename
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
data
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
mimeType
);
dest
.
writeString
(
filename
);
dest
.
writeString
(
description
);
dest
.
writeByteArray
(
data
);
}
public
static
final
Parcelable
.
Creator
<
GeobFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
GeobFrame
>()
{
@Override
public
GeobFrame
createFromParcel
(
Parcel
in
)
{
return
new
GeobFrame
(
in
);
}
@Override
public
GeobFrame
[]
newArray
(
int
size
)
{
return
new
GeobFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
View file @
8a89abcb
...
...
@@ -15,21 +15,33 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.util.Log
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoder
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.UnsupportedEncodingException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Locale
;
/**
* Decodes
individual TXXX text frames from raw ID3 data
.
* Decodes
ID3 tags
.
*/
public
final
class
Id3Decoder
implements
MetadataDecoder
<
List
<
Id3Frame
>>
{
public
final
class
Id3Decoder
implements
MetadataDecoder
{
private
static
final
String
TAG
=
"Id3Decoder"
;
/**
* The first three bytes of a well formed ID3 tag header.
*/
public
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
/**
* Length of an ID3 tag header.
*/
public
static
final
int
ID3_HEADER_LENGTH
=
10
;
private
static
final
int
ID3_TEXT_ENCODING_ISO_8859_1
=
0
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16
=
1
;
...
...
@@ -42,51 +54,46 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
@Override
public
List
<
Id3Frame
>
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
{
public
Metadata
decode
(
byte
[]
data
,
int
size
)
{
List
<
Id3Frame
>
id3Frames
=
new
ArrayList
<>();
ParsableByteArray
id3Data
=
new
ParsableByteArray
(
data
,
size
);
int
id3Size
=
decodeId3Header
(
id3Data
);
while
(
id3Size
>
0
)
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
id3Data
.
readUnsignedByte
();
int
frameSize
=
id3Data
.
readSynchSafeInt
();
if
(
frameSize
<=
1
)
{
break
;
}
// Skip frame flags.
id3Data
.
skipBytes
(
2
);
try
{
Id3Frame
frame
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
frameId3
==
'B'
)
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
)
{
frame
=
decodeApicFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
Id3Header
id3Header
=
decodeHeader
(
id3Data
);
if
(
id3Header
==
null
)
{
return
null
;
}
int
startPosition
=
id3Data
.
getPosition
();
int
framesSize
=
id3Header
.
framesSize
;
if
(
id3Header
.
isUnsynchronized
)
{
framesSize
=
removeUnsynchronization
(
id3Data
,
id3Header
.
framesSize
);
}
id3Data
.
setLimit
(
startPosition
+
framesSize
);
boolean
unsignedIntFrameSizeHack
=
false
;
if
(
id3Header
.
majorVersion
==
4
)
{
if
(!
validateV4Frames
(
id3Data
,
false
))
{
if
(
validateV4Frames
(
id3Data
,
true
))
{
unsignedIntFrameSizeHack
=
true
;
}
else
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
)
;
Log
.
w
(
TAG
,
"Failed to validate V4 ID3 tag"
);
return
null
;
}
}
}
int
frameHeaderSize
=
id3Header
.
majorVersion
==
2
?
6
:
10
;
while
(
id3Data
.
bytesLeft
()
>=
frameHeaderSize
)
{
Id3Frame
frame
=
decodeFrame
(
id3Header
.
majorVersion
,
id3Data
,
unsignedIntFrameSizeHack
);
if
(
frame
!=
null
)
{
id3Frames
.
add
(
frame
);
id3Size
-=
frameSize
+
10
/* header size */
;
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
MetadataDecoderException
(
"Unsupported encoding"
,
e
);
}
}
return
Collections
.
unmodifiableList
(
id3Frames
);
return
new
Metadata
(
id3Frames
);
}
// TODO: Move the following three methods nearer to the bottom of the file.
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
int
terminationPos
=
indexOfZeroByte
(
data
,
fromIndex
);
...
...
@@ -95,7 +102,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
terminationPos
;
}
// Otherwise look for a second zero byte.
// Otherwise
ensure an even index and
look for a second zero byte.
while
(
terminationPos
<
data
.
length
-
1
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
return
terminationPos
;
...
...
@@ -121,38 +128,207 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
/**
* @param id3Buffer A {@link ParsableByteArray} from which data should be read.
* @return The size of ID3 frames in bytes, excluding the header and footer.
* @throws MetadataDecoderException If ID3 file identifier != "ID3".
* @param data A {@link ParsableByteArray} from which the header should be read.
* @return The parsed header, or null if the ID3 tag is unsupported.
*/
private
static
int
decodeId3Header
(
ParsableByteArray
id3Buffer
)
throws
MetadataDecoderException
{
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
if
(
id1
!=
'I'
||
id2
!=
'D'
||
id3
!=
'3'
)
{
throw
new
MetadataDecoderException
(
String
.
format
(
Locale
.
US
,
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
private
static
Id3Header
decodeHeader
(
ParsableByteArray
data
)
{
if
(
data
.
bytesLeft
()
<
ID3_HEADER_LENGTH
)
{
Log
.
w
(
TAG
,
"Data too short to be an ID3 tag"
);
return
null
;
}
int
id
=
data
.
readUnsignedInt24
();
if
(
id
!=
ID3_TAG
)
{
Log
.
w
(
TAG
,
"Unexpected first three bytes of ID3 tag header: "
+
id
);
return
null
;
}
int
majorVersion
=
data
.
readUnsignedByte
();
data
.
skipBytes
(
1
);
// Skip minor version.
int
flags
=
data
.
readUnsignedByte
();
int
framesSize
=
data
.
readSynchSafeInt
();
if
(
majorVersion
==
2
)
{
boolean
isCompressed
=
(
flags
&
0x40
)
!=
0
;
if
(
isCompressed
)
{
Log
.
w
(
TAG
,
"Skipped ID3 tag with majorVersion=2 and undefined compression scheme"
);
return
null
;
}
}
else
if
(
majorVersion
==
3
)
{
boolean
hasExtendedHeader
=
(
flags
&
0x40
)
!=
0
;
if
(
hasExtendedHeader
)
{
int
extendedHeaderSize
=
data
.
readInt
();
// Size excluding size field.
data
.
skipBytes
(
extendedHeaderSize
);
framesSize
-=
(
extendedHeaderSize
+
4
);
}
}
else
if
(
majorVersion
==
4
)
{
boolean
hasExtendedHeader
=
(
flags
&
0x40
)
!=
0
;
if
(
hasExtendedHeader
)
{
int
extendedHeaderSize
=
data
.
readSynchSafeInt
();
// Size including size field.
data
.
skipBytes
(
extendedHeaderSize
-
4
);
framesSize
-=
extendedHeaderSize
;
}
boolean
hasFooter
=
(
flags
&
0x10
)
!=
0
;
if
(
hasFooter
)
{
framesSize
-=
10
;
}
}
else
{
Log
.
w
(
TAG
,
"Skipped ID3 tag with unsupported majorVersion="
+
majorVersion
);
return
null
;
}
id3Buffer
.
skipBytes
(
2
);
// Skip version.
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
// isUnsynchronized is advisory only in version 4. Frame level flags are used instead.
boolean
isUnsynchronized
=
majorVersion
<
4
&&
(
flags
&
0x80
)
!=
0
;
return
new
Id3Header
(
majorVersion
,
isUnsynchronized
,
framesSize
);
}
private
static
boolean
validateV4Frames
(
ParsableByteArray
id3Data
,
boolean
unsignedIntFrameSizeHack
)
{
int
startPosition
=
id3Data
.
getPosition
();
try
{
while
(
id3Data
.
bytesLeft
()
>=
10
)
{
int
id
=
id3Data
.
readInt
();
int
frameSize
=
id3Data
.
readUnsignedIntToInt
();
int
flags
=
id3Data
.
readUnsignedShort
();
if
(
id
==
0
&&
frameSize
==
0
&&
flags
==
0
)
{
return
true
;
}
else
{
if
(!
unsignedIntFrameSizeHack
)
{
// Parse the data size as a synchsafe integer, as per the spec.
if
((
frameSize
&
0x808080
L
)
!=
0
)
{
return
false
;
}
frameSize
=
(
frameSize
&
0xFF
)
|
(((
frameSize
>>
8
)
&
0xFF
)
<<
7
)
|
(((
frameSize
>>
16
)
&
0xFF
)
<<
14
)
|
(((
frameSize
>>
24
)
&
0xFF
)
<<
21
);
}
int
minimumFrameSize
=
0
;
if
((
flags
&
0x0040
)
!=
0
/* hasGroupIdentifier */
)
{
minimumFrameSize
++;
}
if
((
flags
&
0x0001
)
!=
0
/* hasDataLength */
)
{
minimumFrameSize
+=
4
;
}
if
(
frameSize
<
minimumFrameSize
)
{
return
false
;
}
if
(
id3Data
.
bytesLeft
()
<
frameSize
)
{
return
false
;
}
id3Data
.
skipBytes
(
frameSize
);
// flags
}
}
return
true
;
}
finally
{
id3Data
.
setPosition
(
startPosition
);
}
}
// Check if extended header presents.
if
((
flags
&
0x2
)
!=
0
)
{
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
if
(
extendedHeaderSize
>
4
)
{
id3Buffer
.
skipBytes
(
extendedHeaderSize
-
4
);
private
static
Id3Frame
decodeFrame
(
int
majorVersion
,
ParsableByteArray
id3Data
,
boolean
unsignedIntFrameSizeHack
)
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
majorVersion
>=
3
?
id3Data
.
readUnsignedByte
()
:
0
;
int
frameSize
;
if
(
majorVersion
==
4
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
if
(!
unsignedIntFrameSizeHack
)
{
frameSize
=
(
frameSize
&
0xFF
)
|
(((
frameSize
>>
8
)
&
0xFF
)
<<
7
)
|
(((
frameSize
>>
16
)
&
0xFF
)
<<
14
)
|
(((
frameSize
>>
24
)
&
0xFF
)
<<
21
);
}
id3Size
-=
extendedHeaderSize
;
}
else
if
(
majorVersion
==
3
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
}
else
/* id3Header.majorVersion == 2 */
{
frameSize
=
id3Data
.
readUnsignedInt24
();
}
int
flags
=
majorVersion
>=
3
?
id3Data
.
readUnsignedShort
()
:
0
;
if
(
frameId0
==
0
&&
frameId1
==
0
&&
frameId2
==
0
&&
frameId3
==
0
&&
frameSize
==
0
&&
flags
==
0
)
{
// We must be reading zero padding at the end of the tag.
id3Data
.
setPosition
(
id3Data
.
limit
());
return
null
;
}
int
nextFramePosition
=
id3Data
.
getPosition
()
+
frameSize
;
if
(
nextFramePosition
>
id3Data
.
limit
())
{
Log
.
w
(
TAG
,
"Frame size exceeds remaining tag data"
);
id3Data
.
setPosition
(
id3Data
.
limit
());
return
null
;
}
// Frame flags.
boolean
isCompressed
=
false
;
boolean
isEncrypted
=
false
;
boolean
isUnsynchronized
=
false
;
boolean
hasDataLength
=
false
;
boolean
hasGroupIdentifier
=
false
;
if
(
majorVersion
==
3
)
{
isCompressed
=
(
flags
&
0x0080
)
!=
0
;
isEncrypted
=
(
flags
&
0x0040
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0020
)
!=
0
;
hasDataLength
=
isCompressed
;
}
else
if
(
majorVersion
==
4
)
{
hasGroupIdentifier
=
(
flags
&
0x0040
)
!=
0
;
isCompressed
=
(
flags
&
0x0008
)
!=
0
;
isEncrypted
=
(
flags
&
0x0004
)
!=
0
;
isUnsynchronized
=
(
flags
&
0x0002
)
!=
0
;
hasDataLength
=
(
flags
&
0x0001
)
!=
0
;
}
// Check if footer presents.
if
((
flags
&
0x8
)
!=
0
)
{
id3Size
-=
10
;
if
(
isCompressed
||
isEncrypted
)
{
Log
.
w
(
TAG
,
"Skipping unsupported compressed or encrypted frame"
);
id3Data
.
setPosition
(
nextFramePosition
);
return
null
;
}
return
id3Size
;
if
(
hasGroupIdentifier
)
{
frameSize
--;
id3Data
.
skipBytes
(
1
);
}
if
(
hasDataLength
)
{
frameSize
-=
4
;
id3Data
.
skipBytes
(
4
);
}
if
(
isUnsynchronized
)
{
frameSize
=
removeUnsynchronization
(
id3Data
,
frameSize
);
}
try
{
Id3Frame
frame
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
(
majorVersion
==
2
||
frameId3
==
'X'
))
{
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
(
frameId3
==
'B'
||
majorVersion
==
2
))
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
}
else
if
(
majorVersion
==
2
?
(
frameId0
==
'P'
&&
frameId1
==
'I'
&&
frameId2
==
'C'
)
:
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
))
{
frame
=
decodeApicFrame
(
id3Data
,
frameSize
,
majorVersion
);
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
majorVersion
==
2
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
}
else
if
(
frameId0
==
'C'
&&
frameId1
==
'O'
&&
frameId2
==
'M'
&&
(
frameId3
==
'M'
||
majorVersion
==
2
))
{
frame
=
decodeCommentFrame
(
id3Data
,
frameSize
);
}
else
{
String
id
=
majorVersion
==
2
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
}
return
frame
;
}
catch
(
UnsupportedEncodingException
e
)
{
Log
.
w
(
TAG
,
"Unsupported character encoding"
);
return
null
;
}
finally
{
id3Data
.
setPosition
(
nextFramePosition
);
}
}
private
static
TxxxFrame
decodeTxxxFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
...
...
@@ -214,16 +390,29 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
new
GeobFrame
(
mimeType
,
filename
,
description
,
objectData
);
}
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
throws
UnsupportedEncodingException
{
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
int
majorVersion
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
frameSize
-
1
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
int
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
String
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
);
String
mimeType
;
int
mimeTypeEndIndex
;
if
(
majorVersion
==
2
)
{
mimeTypeEndIndex
=
2
;
mimeType
=
"image/"
+
new
String
(
data
,
0
,
3
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
equals
(
"image/jpg"
))
{
mimeType
=
"image/jpeg"
;
}
}
else
{
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
indexOf
(
'/'
)
==
-
1
)
{
mimeType
=
"image/"
+
mimeType
;
}
}
int
pictureType
=
data
[
mimeTypeEndIndex
+
1
]
&
0xFF
;
...
...
@@ -238,6 +427,28 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
new
ApicFrame
(
mimeType
,
description
,
pictureType
,
pictureData
);
}
private
static
CommentFrame
decodeCommentFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
3
];
id3Data
.
readBytes
(
data
,
0
,
3
);
String
language
=
new
String
(
data
,
0
,
3
);
data
=
new
byte
[
frameSize
-
4
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
4
);
int
descriptionEndIndex
=
indexOfEos
(
data
,
0
,
encoding
);
String
description
=
new
String
(
data
,
0
,
descriptionEndIndex
,
charset
);
int
textStartIndex
=
descriptionEndIndex
+
delimiterLength
(
encoding
);
int
textEndIndex
=
indexOfEos
(
data
,
textStartIndex
,
encoding
);
String
text
=
new
String
(
data
,
textStartIndex
,
textEndIndex
-
textStartIndex
,
charset
);
return
new
CommentFrame
(
language
,
description
,
text
);
}
private
static
TextInformationFrame
decodeTextInformationFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
...
...
@@ -261,6 +472,25 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
/**
* Performs in-place removal of unsynchronization for {@code length} bytes starting from
* {@link ParsableByteArray#getPosition()}
*
* @param data Contains the data to be processed.
* @param length The length of the data to be processed.
* @return The length of the data after processing.
*/
private
static
int
removeUnsynchronization
(
ParsableByteArray
data
,
int
length
)
{
byte
[]
bytes
=
data
.
data
;
for
(
int
i
=
data
.
getPosition
();
i
+
1
<
length
;
i
++)
{
if
((
bytes
[
i
]
&
0xFF
)
==
0xFF
&&
bytes
[
i
+
1
]
==
0x00
)
{
System
.
arraycopy
(
bytes
,
i
+
2
,
bytes
,
i
+
1
,
length
-
i
-
2
);
length
--;
}
}
return
length
;
}
/**
* Maps encoding byte from ID3v2 frame to a Charset.
* @param encodingByte The value of encoding byte from ID3v2 frame.
* @return Charset name.
...
...
@@ -280,4 +510,18 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
}
private
static
final
class
Id3Header
{
private
final
int
majorVersion
;
private
final
boolean
isUnsynchronized
;
private
final
int
framesSize
;
public
Id3Header
(
int
majorVersion
,
boolean
isUnsynchronized
,
int
framesSize
)
{
this
.
majorVersion
=
majorVersion
;
this
.
isUnsynchronized
=
isUnsynchronized
;
this
.
framesSize
=
framesSize
;
}
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
View file @
8a89abcb
...
...
@@ -15,10 +15,13 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
/**
* Base class for ID3 frames.
*/
public
abstract
class
Id3Frame
{
public
abstract
class
Id3Frame
implements
Metadata
.
Entry
{
/**
* The frame ID.
...
...
@@ -26,7 +29,12 @@ public abstract class Id3Frame {
public
final
String
id
;
public
Id3Frame
(
String
id
)
{
this
.
id
=
id
;
this
.
id
=
Assertions
.
checkNotNull
(
id
);
}
@Override
public
int
describeContents
()
{
return
0
;
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* PRIV (Private) ID3 frame.
*/
...
...
@@ -31,4 +36,50 @@ public final class PrivFrame extends Id3Frame {
this
.
privateData
=
privateData
;
}
/* package */
PrivFrame
(
Parcel
in
)
{
super
(
ID
);
owner
=
in
.
readString
();
privateData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
PrivFrame
other
=
(
PrivFrame
)
obj
;
return
Util
.
areEqual
(
owner
,
other
.
owner
)
&&
Arrays
.
equals
(
privateData
,
other
.
privateData
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
owner
!=
null
?
owner
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
privateData
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
owner
);
dest
.
writeByteArray
(
privateData
);
}
public
static
final
Parcelable
.
Creator
<
PrivFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
PrivFrame
>()
{
@Override
public
PrivFrame
createFromParcel
(
Parcel
in
)
{
return
new
PrivFrame
(
in
);
}
@Override
public
PrivFrame
[]
newArray
(
int
size
)
{
return
new
PrivFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
*/
...
...
@@ -27,4 +31,50 @@ public final class TextInformationFrame extends Id3Frame {
this
.
description
=
description
;
}
/* package */
TextInformationFrame
(
Parcel
in
)
{
super
(
in
.
readString
());
description
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
TextInformationFrame
other
=
(
TextInformationFrame
)
obj
;
return
id
.
equals
(
other
.
id
)
&&
Util
.
areEqual
(
description
,
other
.
description
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
id
.
hashCode
();
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeString
(
description
);
}
public
static
final
Parcelable
.
Creator
<
TextInformationFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
TextInformationFrame
>()
{
@Override
public
TextInformationFrame
createFromParcel
(
Parcel
in
)
{
return
new
TextInformationFrame
(
in
);
}
@Override
public
TextInformationFrame
[]
newArray
(
int
size
)
{
return
new
TextInformationFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java
View file @
8a89abcb
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* TXXX (User defined text information) ID3 frame.
*/
...
...
@@ -31,4 +35,50 @@ public final class TxxxFrame extends Id3Frame {
this
.
value
=
value
;
}
/* package */
TxxxFrame
(
Parcel
in
)
{
super
(
ID
);
description
=
in
.
readString
();
value
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
TxxxFrame
other
=
(
TxxxFrame
)
obj
;
return
Util
.
areEqual
(
description
,
other
.
description
)
&&
Util
.
areEqual
(
value
,
other
.
value
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
value
!=
null
?
value
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
description
);
dest
.
writeString
(
value
);
}
public
static
final
Parcelable
.
Creator
<
TxxxFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
TxxxFrame
>()
{
@Override
public
TxxxFrame
createFromParcel
(
Parcel
in
)
{
return
new
TxxxFrame
(
in
);
}
@Override
public
TxxxFrame
[]
newArray
(
int
size
)
{
return
new
TxxxFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
View file @
8a89abcb
...
...
@@ -300,9 +300,9 @@ public final class ParsableByteArray {
*/
public
int
readLittleEndianInt
()
{
return
(
data
[
position
++]
&
0xFF
)
|
(
data
[
position
++]
&
0xFF
)
<<
8
|
(
data
[
position
++]
&
0xFF
)
<<
16
|
(
data
[
position
++]
&
0xFF
)
<<
24
;
|
(
data
[
position
++]
&
0xFF
)
<<
8
|
(
data
[
position
++]
&
0xFF
)
<<
16
|
(
data
[
position
++]
&
0xFF
)
<<
24
;
}
/**
...
...
@@ -424,6 +424,24 @@ public final class ParsableByteArray {
}
/**
* Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is ignored,
* if present.
*
* @param length The number of bytes to read.
* @return The string encoded by the bytes.
*/
public
String
readNullTerminatedString
(
int
length
)
{
int
stringLength
=
length
;
int
lastIndex
=
position
+
length
-
1
;
if
(
lastIndex
<
limit
&&
data
[
lastIndex
]
==
0
)
{
stringLength
--;
}
String
result
=
new
String
(
data
,
position
,
stringLength
,
Charset
.
defaultCharset
());
position
+=
length
;
return
result
;
}
/**
* Reads the next {@code length} bytes as characters in the specified {@link Charset}.
*
* @param length The number of bytes to read.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment