mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Adds wave file ID3 metadata chunk to audio recordings.
Updates wave writer to add the metadata LIST and ID3 chunks to the end of the recording so that the file contains the final/complete set of metadata that was accumulated over the call lifecycle.
This commit is contained in:
@@ -56,6 +56,11 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Wave audio recorder for AudioPackets
|
||||
* @param filePrefix
|
||||
* @param metadata
|
||||
*/
|
||||
public AudioPacketWaveRecorder(String filePrefix, Metadata metadata)
|
||||
{
|
||||
mMetadata = metadata;
|
||||
@@ -105,11 +110,6 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
|
||||
mWriter = new WaveWriter(mAudioFormat, mFile);
|
||||
|
||||
if(mMetadata != null)
|
||||
{
|
||||
mWriter.writeMetadata(WaveMetadata.createFrom(mMetadata));
|
||||
}
|
||||
|
||||
/* Schedule the processor to run every 500 milliseconds */
|
||||
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
@@ -126,8 +126,10 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
{
|
||||
try
|
||||
{
|
||||
//Finish writing any residual audio buffers
|
||||
write();
|
||||
|
||||
//Append the LIST and ID3 metadata to the end
|
||||
if(mMetadata != null)
|
||||
{
|
||||
mWriter.writeMetadata(WaveMetadata.createFrom(mMetadata));
|
||||
@@ -147,6 +149,9 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving audio packets to enqueue for later writing to the wav file.
|
||||
*/
|
||||
@Override
|
||||
public void receive(AudioPacket audioPacket)
|
||||
{
|
||||
@@ -176,7 +181,9 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes all audio currently in the queue to the file
|
||||
* Writes all audio currently in the queue to the file. Captures any audio metadata from the packet and retains a
|
||||
* copy of the latest metadata to append to the end of the recording when stop() is invoked.
|
||||
*
|
||||
* @throws IOException if there are any errors writing the audio
|
||||
*/
|
||||
private void write() throws IOException
|
||||
@@ -185,13 +192,9 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
|
||||
while(audioPacket != null && audioPacket.getType() == AudioPacket.Type.AUDIO)
|
||||
{
|
||||
if(audioPacket.hasMetadata() && audioPacket.getMetadata().isUpdated())
|
||||
if(audioPacket.hasMetadata())
|
||||
{
|
||||
if(mMetadata == null || mMetadata != audioPacket.getMetadata())
|
||||
{
|
||||
mMetadata = audioPacket.getMetadata();
|
||||
mWriter.writeMetadata(WaveMetadata.createFrom(mMetadata));
|
||||
}
|
||||
mMetadata = audioPacket.getMetadata();
|
||||
}
|
||||
|
||||
mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(audioPacket.getAudioBuffer()));
|
||||
@@ -199,6 +202,9 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled runnable to periodically write the enqueued audio packets to the file
|
||||
*/
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
public void run()
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.github.dsheirer.properties.SystemProperties;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -18,27 +19,148 @@ import java.util.Map;
|
||||
public class WaveMetadata
|
||||
{
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
private static final byte NULL_TERMINATOR = (byte)0x00;
|
||||
private static final String LIST = "LIST";
|
||||
private static final String INFO = "INFO";
|
||||
private static final String COMMENT = "Recording created by sdrtrunk";
|
||||
private static final String LIST_CHUNK_IDENTIFIER = "LIST";
|
||||
private static final String INFO_TYPE_IDENTIFIER = "INFO";
|
||||
private static final String ID3_CHUNK_IDENTIFIER = "ID3 ";
|
||||
private static final String ID3V2_IDENTIFIER = "ID3";
|
||||
private static final byte ID3V2_MAJOR_VERSION = (byte)0x4;
|
||||
private static final byte ID3V2_MINOR_VERSION = (byte)0x0;
|
||||
private static final byte ID3V2_FLAGS = (byte)0x0;
|
||||
private static final byte ID3V2_ISO_8859_1_ENCODING = (byte)0x0;
|
||||
|
||||
private Map<WaveMetadataType, String> mMetadataMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Audio Wave File - Metadata Chunk. Supports creating a map of metadata key:value pairs and
|
||||
* converting the metadata to a .wav audio recording INFO chunk.
|
||||
* converting the metadata to WAV audio recording INFO/LIST and ID3 v2.4.0 metadata chunks.
|
||||
*/
|
||||
public WaveMetadata()
|
||||
{
|
||||
}
|
||||
|
||||
public ByteBuffer getID3Chunk()
|
||||
{
|
||||
ByteBuffer subChunks = getID3Frames();
|
||||
int subChunksLength = subChunks.capacity();
|
||||
|
||||
int overallLength = subChunksLength + 18;
|
||||
|
||||
//Pad the overall length to make it an even 32-bit boundary
|
||||
int padding = 0;
|
||||
|
||||
if(overallLength % 4 != 0)
|
||||
{
|
||||
padding = 4 - (overallLength % 4);
|
||||
}
|
||||
|
||||
ByteBuffer chunk = ByteBuffer.allocate(overallLength + padding).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
int chunkLength = subChunksLength + 10 + padding;
|
||||
|
||||
chunk.put(ID3_CHUNK_IDENTIFIER.getBytes());
|
||||
chunk.putInt(chunkLength);
|
||||
chunk.put(ID3V2_IDENTIFIER.getBytes());
|
||||
chunk.put(ID3V2_MAJOR_VERSION);
|
||||
chunk.put(ID3V2_MINOR_VERSION);
|
||||
chunk.put(ID3V2_FLAGS);
|
||||
chunk.put(getID3EncodedLength(subChunksLength));
|
||||
|
||||
chunk.put(subChunks);
|
||||
|
||||
chunk.position(0);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ID3 v2.4.0 compatible frame set from the metadata
|
||||
*/
|
||||
public ByteBuffer getID3Frames()
|
||||
{
|
||||
int length = 0;
|
||||
List<ByteBuffer> buffers = new ArrayList<>();
|
||||
|
||||
//List the primary metadata tags first
|
||||
for(Map.Entry<WaveMetadataType, String> entry: mMetadataMap.entrySet())
|
||||
{
|
||||
if(entry.getKey().isPrimaryTag())
|
||||
{
|
||||
ByteBuffer buffer = getID3Frame(entry.getKey(), entry.getValue());
|
||||
|
||||
if(buffer != null)
|
||||
{
|
||||
length += buffer.capacity();
|
||||
buffers.add(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<WaveMetadataType, String> entry: mMetadataMap.entrySet())
|
||||
{
|
||||
if(!entry.getKey().isPrimaryTag())
|
||||
{
|
||||
ByteBuffer buffer = getID3Frame(entry.getKey(), entry.getValue());
|
||||
|
||||
if(buffer != null)
|
||||
{
|
||||
length += buffer.capacity();
|
||||
buffers.add(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer concatenatedBuffer = ByteBuffer.allocate(length);
|
||||
|
||||
for(ByteBuffer buffer: buffers)
|
||||
{
|
||||
concatenatedBuffer.put(buffer);
|
||||
}
|
||||
|
||||
concatenatedBuffer.position(0);
|
||||
|
||||
return concatenatedBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ID3 v2.4.0 compatible metadata frame
|
||||
* @param type of metadata
|
||||
* @param value of the metadata
|
||||
* @return frame byte buffer or null if the value is empty or null
|
||||
*/
|
||||
public ByteBuffer getID3Frame(WaveMetadataType type, String value)
|
||||
{
|
||||
if(value != null && !value.isEmpty())
|
||||
{
|
||||
ByteBuffer encodedValue = ISO_8859_1.encode(value);
|
||||
|
||||
//Length is 4 bytes for tag ID, 4 bytes for length, value, and null terminator
|
||||
int chunkLength = encodedValue.capacity() + 11;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(chunkLength).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.put(type.getID3Tag().getBytes());
|
||||
buffer.put(getID3EncodedLength(encodedValue.capacity() + 1));
|
||||
buffer.put(ID3V2_FLAGS);
|
||||
buffer.put(ID3V2_FLAGS);
|
||||
buffer.put(ID3V2_ISO_8859_1_ENCODING);
|
||||
buffer.put(encodedValue);
|
||||
|
||||
buffer.position(0);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wave LIST chunk from the metadata tags
|
||||
*/
|
||||
public ByteBuffer getLISTChunk()
|
||||
{
|
||||
ByteBuffer metadataBuffer = getAllTags();
|
||||
ByteBuffer metadataBuffer = getLISTSubChunks();
|
||||
int tagsLength = metadataBuffer.capacity();
|
||||
|
||||
int overallLength = tagsLength + 8;
|
||||
@@ -53,7 +175,7 @@ public class WaveMetadata
|
||||
|
||||
ByteBuffer chunk = ByteBuffer.allocate(overallLength + padding).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
chunk.put(LIST.getBytes());
|
||||
chunk.put(LIST_CHUNK_IDENTIFIER.getBytes());
|
||||
chunk.putInt(tagsLength + padding);
|
||||
chunk.put(metadataBuffer);
|
||||
|
||||
@@ -65,29 +187,28 @@ public class WaveMetadata
|
||||
/**
|
||||
* Formats all metadata key:value pairs in the metadata map into a wave INFO compatible format
|
||||
*/
|
||||
private ByteBuffer getAllTags()
|
||||
private ByteBuffer getLISTSubChunks()
|
||||
{
|
||||
int length = INFO.length();
|
||||
int length = INFO_TYPE_IDENTIFIER.length();
|
||||
|
||||
List<ByteBuffer> buffers = new ArrayList<>();
|
||||
|
||||
//Do the non-custom metadata types first
|
||||
//Add the primary tags first
|
||||
for(Map.Entry<WaveMetadataType, String> entry: mMetadataMap.entrySet())
|
||||
{
|
||||
if(!entry.getKey().isCustomType())
|
||||
if(entry.getKey().isPrimaryTag())
|
||||
{
|
||||
ByteBuffer buffer = getListMetadataChunk(entry.getKey(), entry.getValue());
|
||||
ByteBuffer buffer = getLISTSubChunk(entry.getKey(), entry.getValue());
|
||||
length += buffer.capacity();
|
||||
buffers.add(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
//Do the custom metadata types second
|
||||
for(Map.Entry<WaveMetadataType, String> entry: mMetadataMap.entrySet())
|
||||
{
|
||||
if(entry.getKey().isCustomType())
|
||||
if(!entry.getKey().isPrimaryTag())
|
||||
{
|
||||
ByteBuffer buffer = getListMetadataChunk(entry.getKey(), entry.getValue());
|
||||
ByteBuffer buffer = getLISTSubChunk(entry.getKey(), entry.getValue());
|
||||
length += buffer.capacity();
|
||||
buffers.add(buffer);
|
||||
}
|
||||
@@ -95,7 +216,7 @@ public class WaveMetadata
|
||||
|
||||
ByteBuffer joinedBuffer = ByteBuffer.allocate(length);
|
||||
|
||||
joinedBuffer.put(INFO.getBytes());
|
||||
joinedBuffer.put(INFO_TYPE_IDENTIFIER.getBytes());
|
||||
|
||||
for(ByteBuffer buffer: buffers)
|
||||
{
|
||||
@@ -114,26 +235,26 @@ public class WaveMetadata
|
||||
* @param value for the metadata
|
||||
* @return metadata and value formatted for wave chunk
|
||||
*/
|
||||
private ByteBuffer getListMetadataChunk(WaveMetadataType type, String value)
|
||||
private ByteBuffer getLISTSubChunk(WaveMetadataType type, String value)
|
||||
{
|
||||
//Length is 4 bytes for tag ID, 4 bytes for length, value, and null terminator
|
||||
int chunkLength = value.length() + 9;
|
||||
if(value != null && !value.isEmpty())
|
||||
{
|
||||
//Length is 4 bytes for tag ID, 4 bytes for length, value, and null terminator
|
||||
ByteBuffer encodedValue = UTF_8.encode(value);
|
||||
int chunkLength = encodedValue.capacity() + 9;
|
||||
|
||||
// //Ensure length is an even multiple of 4 bytes/32-bit word
|
||||
// if(chunkLength % 4 != 0)
|
||||
// {
|
||||
// chunkLength += (4 - (chunkLength % 4));
|
||||
// }
|
||||
//
|
||||
ByteBuffer buffer = ByteBuffer.allocate(chunkLength).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.put(type.getTag().getBytes());
|
||||
buffer.putInt(value.length() + 1);
|
||||
buffer.put(value.getBytes());
|
||||
buffer.put(NULL_TERMINATOR);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(chunkLength).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.put(type.getLISTTag().getBytes());
|
||||
buffer.putInt(encodedValue.capacity() + 1);
|
||||
buffer.put(encodedValue);
|
||||
buffer.put(NULL_TERMINATOR);
|
||||
|
||||
buffer.position(0);
|
||||
buffer.position(0);
|
||||
|
||||
return buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,15 +280,12 @@ public class WaveMetadata
|
||||
{
|
||||
WaveMetadata waveMetadata = new WaveMetadata();
|
||||
|
||||
waveMetadata.add(WaveMetadataType.GENRE, "TEST GENRE");
|
||||
waveMetadata.add(WaveMetadataType.PRODUCT, SystemProperties.getInstance().getApplicationName());
|
||||
waveMetadata.add(WaveMetadataType.COMMENTS, COMMENT);
|
||||
waveMetadata.add(WaveMetadataType.SOFTWARE, SystemProperties.getInstance().getApplicationName());
|
||||
waveMetadata.add(WaveMetadataType.DATE_CREATED, SDF.format(new Date(audioMetadata.getTimestamp())));
|
||||
waveMetadata.add(WaveMetadataType.ARTIST_NAME, audioMetadata.getChannelConfigurationSystem());
|
||||
waveMetadata.add(WaveMetadataType.ALBUM_TITLE, audioMetadata.getChannelConfigurationSite());
|
||||
waveMetadata.add(WaveMetadataType.TRACK_TITLE, audioMetadata.getChannelConfigurationName());
|
||||
waveMetadata.add(WaveMetadataType.SOURCE_FORM, audioMetadata.getPrimaryDecoderType().getDisplayString());
|
||||
// waveMetadata.add(WaveMetadataType.ALIAS_LIST_NAME, "not implemented yet");
|
||||
waveMetadata.add(WaveMetadataType.COMMENTS, audioMetadata.getPrimaryDecoderType().getDisplayString());
|
||||
waveMetadata.add(WaveMetadataType.CHANNEL_ID, audioMetadata.getChannelFrequencyLabel());
|
||||
waveMetadata.add(WaveMetadataType.CHANNEL_FREQUENCY, String.valueOf(audioMetadata.getChannelFrequency()));
|
||||
|
||||
@@ -280,4 +398,21 @@ public class WaveMetadata
|
||||
|
||||
return waveMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the integer length to an ID3 compatible length field where each byte only uses the 7 least significant
|
||||
* bits for a maximum of 28 bits used out of the 32-bit representation.
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
public static byte[] getID3EncodedLength(int length)
|
||||
{
|
||||
byte[] value = new byte[4];
|
||||
value[0] = (byte)((length >> 21) & 0x7F);
|
||||
value[1] = (byte)((length >> 14) & 0x7F);
|
||||
value[2] = (byte)((length >> 7) & 0x7F);
|
||||
value[3] = (byte)(length & 0x7F);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,60 +20,86 @@ package io.github.dsheirer.record.wave;
|
||||
/**
|
||||
* WAVE audio metadata tags.
|
||||
*
|
||||
* INFO tags are detailed here: https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info
|
||||
* Metadata tag details:
|
||||
* LIST: https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info
|
||||
* ID3: http://id3.org/id3v2.3.0
|
||||
*
|
||||
*/
|
||||
public enum WaveMetadataType
|
||||
{
|
||||
ARTIST_NAME("IART", false), //System
|
||||
ALBUM_TITLE("TALB", false), //Site
|
||||
TRACK_TITLE("INAM", false), //Channel Name
|
||||
COMMENTS("ICMT", false),
|
||||
DATE_CREATED("ICRD", false),
|
||||
GENRE("IGNR", false), //Site
|
||||
PRODUCT("IPRD", false), //sdrtrunk
|
||||
SOURCE_FORM("ISRF", false), //protocol
|
||||
//Primary tags
|
||||
ARTIST_NAME("TPE1", "IART", true), //System
|
||||
ALBUM_TITLE("TALB", "IPRD", true), //Site
|
||||
TRACK_TITLE("TIT2", "INAM", true), //Channel Name
|
||||
COMMENTS("COMM", "ICMT", true),
|
||||
DATE_CREATED("TDRC", "ICRD", true),
|
||||
GENRE("TCON", "IGNR", true),
|
||||
SOFTWARE("TSSE", "ISFT", true), //sdrtrunk application name
|
||||
SOURCE_FORM("TMED", "ISRF", true), //protocol
|
||||
|
||||
//Custom Tags
|
||||
ALIAS_LIST_NAME("ALLN", true),
|
||||
CHANNEL_FREQUENCY("CHFQ", true),
|
||||
CHANNEL_ID("CHID", true),
|
||||
CHANNEL_TIMESLOT("CHTS", true),
|
||||
NETWORK_ID_1("NTW1", true),
|
||||
NETWORK_ID_2("NTW2", true),
|
||||
TALKGROUP_PRIMARY_PATCHED_1("TPP1", true),
|
||||
TALKGROUP_PRIMARY_PATCHED_2("TPP2", true),
|
||||
TALKGROUP_PRIMARY_PATCHED_3("TPP3", true),
|
||||
TALKGROUP_PRIMARY_PATCHED_4("TPP4", true),
|
||||
TALKGROUP_PRIMARY_PATCHED_5("TPP5", true),
|
||||
TALKGROUP_PRIMARY_FROM("TPFM", true),
|
||||
TALKGROUP_PRIMARY_FROM_ALIAS("TPFA", true),
|
||||
TALKGROUP_PRIMARY_FROM_ICON("TPFI", true),
|
||||
TALKGROUP_PRIMARY_TO("TPTO", true),
|
||||
TALKGROUP_PRIMARY_TO_ALIAS("TPTA", true),
|
||||
TALKGROUP_PRIMARY_TO_ICON("TPTI", true),
|
||||
TALKGROUP_SECONDARY_FROM("TSFM", true),
|
||||
TALKGROUP_SECONDARY_FROM_ALIAS("TSFA", true),
|
||||
TALKGROUP_SECONDARY_FROM_ICON("TSFI", true),
|
||||
TALKGROUP_SECONDARY_TO("TSTO", true),
|
||||
TALKGROUP_SECONDARY_TO_ALIAS("TSTA", true),
|
||||
TALKGROUP_SECONDARY_TO_ICON("TSTI", true);
|
||||
//Secondary tags
|
||||
ALIAS_LIST_NAME("TALN", "ALLN", false),
|
||||
CHANNEL_FREQUENCY("TCHF", "CHFQ", false),
|
||||
CHANNEL_ID("TCHI", "CHID", false),
|
||||
CHANNEL_TIMESLOT("TCHT", "CHTS", false),
|
||||
NETWORK_ID_1("TNT1", "NTW1", false),
|
||||
NETWORK_ID_2("TNT2", "NTW2", false),
|
||||
TALKGROUP_PRIMARY_PATCHED_1("TPP1", "TPP1", false),
|
||||
TALKGROUP_PRIMARY_PATCHED_2("TPP2", "TPP2", false),
|
||||
TALKGROUP_PRIMARY_PATCHED_3("TPP3", "TPP3", false),
|
||||
TALKGROUP_PRIMARY_PATCHED_4("TPP4", "TPP4", false),
|
||||
TALKGROUP_PRIMARY_PATCHED_5("TPP5", "TPP5", false),
|
||||
TALKGROUP_PRIMARY_FROM("TPFM", "TPFM", false),
|
||||
TALKGROUP_PRIMARY_FROM_ALIAS("TPFA", "TPFA", false),
|
||||
TALKGROUP_PRIMARY_FROM_ICON("TPFI", "TPFI", false),
|
||||
TALKGROUP_PRIMARY_TO("TPTO", "TPTO", false),
|
||||
TALKGROUP_PRIMARY_TO_ALIAS("TOTA", "TPTA", false),
|
||||
TALKGROUP_PRIMARY_TO_ICON("TPTI", "TPTI", false),
|
||||
TALKGROUP_SECONDARY_FROM("TSFM", "TSFM", false),
|
||||
TALKGROUP_SECONDARY_FROM_ALIAS("TSFA", "TSFA", false),
|
||||
TALKGROUP_SECONDARY_FROM_ICON("TSFI", "TSFI", false),
|
||||
TALKGROUP_SECONDARY_TO("TSTO", "TSTO", false),
|
||||
TALKGROUP_SECONDARY_TO_ALIAS("TSTA", "TSTA", false),
|
||||
TALKGROUP_SECONDARY_TO_ICON("TSTI", "TSTI", false);
|
||||
|
||||
private String mTag;
|
||||
private boolean mCustomType;
|
||||
private String mID3Tag;
|
||||
private String mLISTTag;
|
||||
private boolean mPrimaryTag;
|
||||
|
||||
WaveMetadataType(String tag, boolean customType)
|
||||
/**
|
||||
* Wave file LIST and ID3 chunk metadata tag types
|
||||
* @param id3Tag used in the ID3 chunk
|
||||
* @param listTag used in the LIST chunk
|
||||
* @param PrimaryTag indicates if this is a custom LIST tag (true) or standard LIST tag (false)
|
||||
*/
|
||||
WaveMetadataType(String id3Tag, String listTag, boolean PrimaryTag)
|
||||
{
|
||||
mTag = tag;
|
||||
mCustomType = customType;
|
||||
mID3Tag = id3Tag;
|
||||
mLISTTag = listTag;
|
||||
mPrimaryTag = PrimaryTag;
|
||||
}
|
||||
|
||||
public String getTag()
|
||||
/**
|
||||
* Tag to use when this metadata type is included in a WAV ID3 chunk
|
||||
*/
|
||||
public String getID3Tag()
|
||||
{
|
||||
return mTag;
|
||||
return mID3Tag;
|
||||
}
|
||||
|
||||
public boolean isCustomType()
|
||||
/**
|
||||
* Tag to use when this metadata type is included in a WAV INFO/LIST chunk
|
||||
*/
|
||||
public String getLISTTag()
|
||||
{
|
||||
return mCustomType;
|
||||
return mLISTTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a primary tag that should be listed first, before the secondary tags
|
||||
*/
|
||||
public boolean isPrimaryTag()
|
||||
{
|
||||
return mPrimaryTag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,20 +256,36 @@ public class WaveWriter implements AutoCloseable
|
||||
*/
|
||||
public void writeMetadata(WaveMetadata metadata) throws IOException
|
||||
{
|
||||
ByteBuffer metadataChunk = metadata.getLISTChunk();
|
||||
ByteBuffer listChunk = metadata.getLISTChunk();
|
||||
|
||||
if(mFileChannel.size() + metadataChunk.capacity() >= mMaxSize)
|
||||
if(mFileChannel.size() + listChunk.capacity() >= mMaxSize)
|
||||
{
|
||||
throw new IOException("Cannot write metadata chunk - insufficient file space remaining");
|
||||
throw new IOException("Cannot write LIST metadata chunk - insufficient file space remaining");
|
||||
}
|
||||
|
||||
closeDataChunk();
|
||||
|
||||
metadataChunk.position(0);
|
||||
listChunk.position(0);
|
||||
|
||||
while(metadataChunk.hasRemaining())
|
||||
while(listChunk.hasRemaining())
|
||||
{
|
||||
mFileChannel.write(metadataChunk);
|
||||
mFileChannel.write(listChunk);
|
||||
}
|
||||
|
||||
updateTotalSize();
|
||||
|
||||
ByteBuffer id3Chunk = metadata.getID3Chunk();
|
||||
|
||||
if(mFileChannel.size() + id3Chunk.capacity() >= mMaxSize)
|
||||
{
|
||||
throw new IOException("Cannot write ID3 metadata chunk - insufficient file space remaining");
|
||||
}
|
||||
|
||||
id3Chunk.position(0);
|
||||
|
||||
while(id3Chunk.hasRemaining())
|
||||
{
|
||||
mFileChannel.write(id3Chunk);
|
||||
}
|
||||
|
||||
updateTotalSize();
|
||||
@@ -332,8 +348,7 @@ public class WaveWriter implements AutoCloseable
|
||||
|
||||
/* This side-steps an issue with right shifting a signed long by 32
|
||||
* where it produces an error value. Instead, we right shift in two steps. */
|
||||
buffer.put((byte)Long.rotateRight(
|
||||
Long.rotateRight(size & 0xFF000000l, 16), 8));
|
||||
buffer.put((byte)Long.rotateRight(Long.rotateRight(size & 0xFF000000l, 16), 8));
|
||||
|
||||
buffer.position(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user