Adding BASTP TagParser
This is required for ReplayGain - a POC version of the code is already implemented (no album gain, no config option yet)
This commit is contained in:
parent
75976e298a
commit
d4c4498068
@ -66,6 +66,10 @@ import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
import ch.blinkenlights.bastp.Bastp;
|
||||
|
||||
|
||||
/**
|
||||
* Handles music playback and pretty much all the other work.
|
||||
@ -555,6 +559,31 @@ public final class PlaybackService extends Service
|
||||
return mp;
|
||||
}
|
||||
|
||||
public void prepareMediaPlayer(MediaPlayer mp, String path) throws IOException{
|
||||
|
||||
mp.setDataSource(path);
|
||||
|
||||
Hashtable tags = (new Bastp()).getTags(path);
|
||||
float adjust = 1.0f;
|
||||
|
||||
if(tags.containsKey("REPLAYGAIN_TRACK_GAIN")) {
|
||||
String rg_raw = (String)((Vector)tags.get("REPLAYGAIN_TRACK_GAIN")).get(0);
|
||||
String rg_numonly = "";
|
||||
float rg_float = 0f;
|
||||
try {
|
||||
String nums = rg_raw.replaceAll("[^0-9.-]","");
|
||||
rg_float = Float.parseFloat(nums);
|
||||
} catch(Exception e) {}
|
||||
|
||||
adjust = (float)Math.pow(10, (rg_float/20) );
|
||||
Toast.makeText(this, path+"\n"+" PX "+rg_raw+" adj = "+adjust, Toast.LENGTH_LONG).show();
|
||||
|
||||
}
|
||||
|
||||
mp.setVolume(adjust, adjust);
|
||||
mp.prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys any currently prepared MediaPlayer and
|
||||
* re-creates a newone if needed.
|
||||
@ -586,8 +615,7 @@ public final class PlaybackService extends Service
|
||||
&& !mTimeline.isEndOfQueue() ) {
|
||||
try {
|
||||
mPreparedMediaPlayer = getNewMediaPlayer();
|
||||
mPreparedMediaPlayer.setDataSource(nextSong.path);
|
||||
mPreparedMediaPlayer.prepare();
|
||||
prepareMediaPlayer(mPreparedMediaPlayer, nextSong.path);
|
||||
mMediaPlayer.setNextMediaPlayer(mPreparedMediaPlayer);
|
||||
Log.d("VanillaMusic", "New media player prepared as "+mPreparedMediaPlayer+" with path "+nextSong.path);
|
||||
} catch (IOException e) {
|
||||
@ -1051,14 +1079,12 @@ public final class PlaybackService extends Service
|
||||
|
||||
if(mPreparedMediaPlayer != null &&
|
||||
mPreparedMediaPlayer.isPlaying()) {
|
||||
Log.d("VanillaMusic", "Replacing existing mediaplayer object with prepared version");
|
||||
mMediaPlayer.release();
|
||||
mMediaPlayer = mPreparedMediaPlayer;
|
||||
mPreparedMediaPlayer = null;
|
||||
}
|
||||
else {
|
||||
mMediaPlayer.setDataSource(song.path);
|
||||
mMediaPlayer.prepare();
|
||||
prepareMediaPlayer(mMediaPlayer, song.path);
|
||||
}
|
||||
|
||||
mMediaPlayerInitialized = true;
|
||||
|
50
src/ch/blinkenlights/bastp/Bastp.java
Normal file
50
src/ch/blinkenlights/bastp/Bastp.java
Normal file
@ -0,0 +1,50 @@
|
||||
package ch.blinkenlights.bastp;
|
||||
|
||||
import ch.blinkenlights.bastp.OggFile;
|
||||
import ch.blinkenlights.bastp.FlacFile;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
|
||||
|
||||
public class Bastp {
|
||||
|
||||
public Bastp() {
|
||||
}
|
||||
|
||||
public Hashtable getTags(String fname) {
|
||||
Hashtable tags = new Hashtable();
|
||||
try {
|
||||
RandomAccessFile ra = new RandomAccessFile(fname, "r");
|
||||
tags = getTags(ra);
|
||||
ra.close();
|
||||
}
|
||||
catch(Exception e) {
|
||||
/* we dont' care much: SOMETHING went wrong. d'oh! */
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
public Hashtable getTags(RandomAccessFile s) {
|
||||
Hashtable tags = new Hashtable();
|
||||
byte[] file_ff = new byte[4];
|
||||
|
||||
try {
|
||||
s.read(file_ff);
|
||||
String magic = new String(file_ff);
|
||||
if(magic.equals("fLaC")) {
|
||||
tags = (new FlacFile()).getTags(s);
|
||||
}
|
||||
else if(magic.equals("OggS")) {
|
||||
tags = (new OggFile()).getTags(s);
|
||||
}
|
||||
tags.put("_MAGIC", magic);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
}
|
||||
|
92
src/ch/blinkenlights/bastp/Common.java
Normal file
92
src/ch/blinkenlights/bastp/Common.java
Normal file
@ -0,0 +1,92 @@
|
||||
package ch.blinkenlights.bastp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
public class Common {
|
||||
private static final long MAX_PKT_SIZE = 524288;
|
||||
|
||||
public void xdie(String reason) throws IOException {
|
||||
throw new IOException(reason);
|
||||
}
|
||||
|
||||
/*
|
||||
** Returns a 32bit int from given byte offset in LE
|
||||
*/
|
||||
public int b2le32(byte[] b, int off) {
|
||||
int r = 0;
|
||||
for(int i=0; i<4; i++) {
|
||||
r |= ( b2u(b[off+i]) << (8*i) );
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public int b2be32(byte[] b, int off) {
|
||||
return swap32(b2le32(b, off));
|
||||
}
|
||||
|
||||
public int swap32(int i) {
|
||||
return((i&0xff)<<24)+((i&0xff00)<<8)+((i&0xff0000)>>8)+((i>>24)&0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
** convert 'byte' value into unsigned int
|
||||
*/
|
||||
public int b2u(byte x) {
|
||||
return (x & 0xFF);
|
||||
}
|
||||
|
||||
/*
|
||||
** Printout debug message to STDOUT
|
||||
*/
|
||||
public void debug(String s) {
|
||||
System.out.println("DBUG "+s);
|
||||
}
|
||||
|
||||
public Hashtable parse_vorbis_comment(RandomAccessFile s, long offset, long payload_len) throws IOException {
|
||||
Hashtable tags = new Hashtable();
|
||||
int comments = 0; // number of found comments
|
||||
int xoff = 0; // offset within 'scratch'
|
||||
int can_read = (int)(payload_len > MAX_PKT_SIZE ? MAX_PKT_SIZE : payload_len);
|
||||
byte[] scratch = new byte[can_read];
|
||||
|
||||
// seek to given position and slurp in the payload
|
||||
s.seek(offset);
|
||||
s.read(scratch);
|
||||
|
||||
// skip vendor string in format: [LEN][VENDOR_STRING]
|
||||
xoff += 4 + b2le32(scratch, xoff); // 4 = LEN = 32bit int
|
||||
comments = b2le32(scratch, xoff);
|
||||
xoff += 4;
|
||||
|
||||
// debug("comments count = "+comments);
|
||||
for(int i=0; i<comments; i++) {
|
||||
|
||||
int clen = (int)b2le32(scratch, xoff);
|
||||
xoff += 4+clen;
|
||||
|
||||
if(xoff > scratch.length)
|
||||
xdie("string out of bounds");
|
||||
|
||||
String tag_raw = new String(scratch, xoff-clen, clen);
|
||||
String[] tag_vec = tag_raw.split("=",2);
|
||||
String tag_key = tag_vec[0].toUpperCase();
|
||||
|
||||
/* A key can have multiple values, so we need
|
||||
** to store all child data in an array */
|
||||
if(tags.containsKey(tag_key)) {
|
||||
((Vector)tags.get(tag_key)).add(tag_vec[1]); // just add to existing vecotr
|
||||
}
|
||||
else {
|
||||
Vector vx = new Vector();
|
||||
vx.add(tag_vec[1]);
|
||||
tags.put(tag_key, vx);
|
||||
}
|
||||
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
}
|
78
src/ch/blinkenlights/bastp/FlacFile.java
Normal file
78
src/ch/blinkenlights/bastp/FlacFile.java
Normal file
@ -0,0 +1,78 @@
|
||||
/*****************************************************************
|
||||
* This file is part of 'bastp!' - the BuggyAndSloppyTagParser! *
|
||||
* *
|
||||
* (C) 2012 Adrian Ulrich *
|
||||
* *
|
||||
* Released as 'Public Domain' software *
|
||||
* *
|
||||
* *
|
||||
*****************************************************************/
|
||||
|
||||
package ch.blinkenlights.bastp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
|
||||
|
||||
public class FlacFile extends Common {
|
||||
private static final int FLAC_TYPE_COMMENT = 4; // ID of 'VorbisComment's
|
||||
|
||||
public FlacFile() {
|
||||
}
|
||||
|
||||
public Hashtable getTags(RandomAccessFile s) throws IOException {
|
||||
int xoff = 4; // skip file magic
|
||||
int retry = 64;
|
||||
int r[];
|
||||
Hashtable tags = new Hashtable();
|
||||
|
||||
for(; retry > 0; retry--) {
|
||||
r = parse_metadata_block(s, xoff);
|
||||
|
||||
if(r[2] == FLAC_TYPE_COMMENT) {
|
||||
tags = parse_vorbis_comment(s, xoff+r[0], r[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
if(r[3] != 0)
|
||||
break; // eof reached
|
||||
|
||||
// else: calculate next offset
|
||||
xoff += r[0] + r[1];
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/* Parses the metadata block at 'offset' and returns
|
||||
** [header_size, payload_size, type, stop_after]
|
||||
*/
|
||||
private int[] parse_metadata_block(RandomAccessFile s, long offset) throws IOException {
|
||||
int[] result = new int[4];
|
||||
byte[] mb_head = new byte[4];
|
||||
int stop_after = 0;
|
||||
int block_type = 0;
|
||||
int block_size = 0;
|
||||
|
||||
s.seek(offset);
|
||||
|
||||
if( s.read(mb_head) != 4 )
|
||||
xdie("failed to read metadata block header");
|
||||
|
||||
block_size = b2be32(mb_head,0); // read whole header as 32 big endian
|
||||
block_type = (block_size >> 24) & 127; // BIT 1-7 are the type
|
||||
stop_after = (((block_size >> 24) & 128) > 0 ? 1 : 0 ); // BIT 0 indicates the last-block flag
|
||||
block_size = (block_size & 0x00FFFFFF); // byte 1-7 are the size
|
||||
|
||||
// debug("size="+block_size+", type="+block_type+", is_last="+stop_after);
|
||||
|
||||
result[0] = 4; // hardcoded - only returned to be consistent with OGG parser
|
||||
result[1] = block_size;
|
||||
result[2] = block_type;
|
||||
result[3] = stop_after;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
107
src/ch/blinkenlights/bastp/OggFile.java
Normal file
107
src/ch/blinkenlights/bastp/OggFile.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*****************************************************************
|
||||
* This file is part of 'bastp!' - the BuggyAndSloppyTagParser! *
|
||||
* *
|
||||
* (C) 2012 Adrian Ulrich *
|
||||
* *
|
||||
* Released as 'Public Domain' software *
|
||||
* *
|
||||
* *
|
||||
*****************************************************************/
|
||||
|
||||
package ch.blinkenlights.bastp;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Hashtable;
|
||||
|
||||
|
||||
public class OggFile extends Common {
|
||||
|
||||
private static final int OGG_PAGE_SIZE = 27; // Static size of an OGG Page
|
||||
private static final int OGG_TYPE_COMMENT = 3; // ID of 'VorbisComment's
|
||||
|
||||
public OggFile() {
|
||||
}
|
||||
|
||||
public Hashtable getTags(RandomAccessFile s) throws IOException {
|
||||
long offset = 0;
|
||||
int retry = 64;
|
||||
Hashtable tags = new Hashtable();
|
||||
|
||||
for( ; retry > 0 ; retry-- ) {
|
||||
long res[] = parse_ogg_page(s, offset);
|
||||
if(res[2] == OGG_TYPE_COMMENT) {
|
||||
tags = parse_ogg_vorbis_comment(s, offset+res[0], res[1]);
|
||||
break;
|
||||
}
|
||||
offset += res[0] + res[1];
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
||||
/* Parses the ogg page at offset 'offset' and returns
|
||||
** [header_size, payload_size, type]
|
||||
*/
|
||||
private long[] parse_ogg_page(RandomAccessFile s, long offset) throws IOException {
|
||||
long[] result = new long[3]; // [header_size, payload_size]
|
||||
byte[] p_header = new byte[OGG_PAGE_SIZE]; // buffer for the page header
|
||||
byte[] scratch;
|
||||
int bread = 0; // number of bytes read
|
||||
int psize = 0; // payload-size
|
||||
int nsegs = 0; // Number of segments
|
||||
|
||||
s.seek(offset);
|
||||
bread = s.read(p_header);
|
||||
if(bread != OGG_PAGE_SIZE)
|
||||
xdie("Unable to read() OGG_PAGE_HEADER");
|
||||
if((new String(p_header, 0, 5)).equals("OggS\0") != true)
|
||||
xdie("Invalid magic - not an ogg file?");
|
||||
|
||||
nsegs = b2u(p_header[26]);
|
||||
// debug("> file seg: "+nsegs);
|
||||
if(nsegs > 0) {
|
||||
scratch = new byte[nsegs];
|
||||
bread = s.read(scratch);
|
||||
if(bread != nsegs)
|
||||
xdie("Failed to read segtable");
|
||||
|
||||
for(int i=0; i<nsegs; i++) {
|
||||
psize += b2u(scratch[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// populate result array
|
||||
result[0] = (s.getFilePointer() - offset);
|
||||
result[1] = psize;
|
||||
result[2] = -1;
|
||||
|
||||
/* next byte is most likely the type -> pre-read */
|
||||
if(psize >= 1 && s.read(p_header, 0, 1) == 1) {
|
||||
result[2] = b2u(p_header[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* In 'vorbiscomment' field is prefixed with \3vorbis in OGG files
|
||||
** we check that this marker is present and call the generic comment
|
||||
** parset with the correct offset (+7) */
|
||||
private Hashtable parse_ogg_vorbis_comment(RandomAccessFile s, long offset, long pl_len) throws IOException {
|
||||
final int pfx_len = 7;
|
||||
byte[] pfx = new byte[pfx_len];
|
||||
|
||||
if(pl_len < pfx_len)
|
||||
xdie("ogg vorbis comment field is too short!");
|
||||
|
||||
s.seek(offset);
|
||||
s.read(pfx);
|
||||
|
||||
if( (new String(pfx, 0, pfx_len)).equals("\3vorbis") == false )
|
||||
xdie("Damaged packet found!");
|
||||
|
||||
return parse_vorbis_comment(s, offset+pfx_len, pl_len-pfx_len);
|
||||
}
|
||||
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user