/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.audio;
import java.util.Set;
/**
* An audio format definition
*
* @author Harald Kuhn - Initial API
* @author Kelly Davis - Modified to match discussion in #584
* @author Kai Kreuzer - Moved class, included constants, added toString
*/
public class AudioFormat {
// generic mp3 format without any further constraints
public static AudioFormat MP3 = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, null, null,
null);
// generic wav format without any further constraints
public static AudioFormat WAV = new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null,
null, null, null);
// generic OGG format without any further constraints
public static AudioFormat OGG = new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, null,
null, null);
// generic AAC format without any further constraints
public static AudioFormat AAC = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, null, null,
null);
/**
* {@link AudioCodec} encoded data without any container header or footer,
* e.g. MP3 is a non-container format
*/
public static final String CONTAINER_NONE = "NONE";
/**
* Microsofts wave container format
*
* @see <a href="http://bit.ly/1TUW93t">WAV Format</a>
* @see <a href="http://bit.ly/1oRMKOt">Supported codecs</a>
* @see <a href="http://bit.ly/1TUWSlk">RIFF container format</a>
*/
public static final String CONTAINER_WAVE = "WAVE";
/**
* OGG container format
*
* @see <a href="http://bit.ly/1oRMWNE">OGG</a>
*/
public static final String CONTAINER_OGG = "OGG";
/**
* PCM Signed
*
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
*/
public static final String CODEC_PCM_SIGNED = "PCM_SIGNED";
/**
* PCM Unsigned
*
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
*/
public static final String CODEC_PCM_UNSIGNED = "PCM_UNSIGNED";
/**
* PCM A-law
*
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
*/
public static final String CODEC_PCM_ALAW = "ALAW";
/**
* PCM u-law
*
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
*/
public static final String CODEC_PCM_ULAW = "ULAW";
/**
* MP3 Codec
*
* @see <a href="http://wiki.multimedia.cx/index.php?title=MP3">MP3 Codec</a>
*/
public static final String CODEC_MP3 = "MP3";
/**
* Vorbis Codec
*
* @see <a href="http://xiph.org/vorbis/doc/">Vorbis</a>
*/
public static final String CODEC_VORBIS = "VORBIS";
/**
* AAC Codec
*/
public static final String CODEC_AAC = "AAC";
/**
* Codec
*/
private final String codec;
/**
* Container
*/
private final String container;
/**
* Big endian or little endian
*/
private final Boolean bigEndian;
/**
* Bit depth
*
* @see <a href="http://bit.ly/1OTydad">Bit Depth</a>
*/
private final Integer bitDepth;
/**
* Bit rate
*
* @see <a href="http://bit.ly/1OTy5rk">Bit Rate</a>
*/
private final Integer bitRate;
/**
* Sample frequency
*/
private final Long frequency;
/**
* Constructs an instance with the specified properties.
*
* Note that any properties that are null indicate that
* the corresponding AudioFormat allows any value for
* the property.
*
* Concretely this implies that if, for example, one
* passed null for the value of frequency, this would
* mean the created AudioFormat allowed for any valid
* frequency.
*
* @param container The container for the audio
* @param codec The audio codec
* @param bigEndian If the audo data is big endian
* @param bitDepth The bit depth of the audo data
* @param bitRate The bit rate of the audio
* @param frequency The frequency at which the audio was sampled
*/
public AudioFormat(String container, String codec, Boolean bigEndian, Integer bitDepth, Integer bitRate,
Long frequency) {
this.container = container;
this.codec = codec;
this.bigEndian = bigEndian;
this.bitDepth = bitDepth;
this.bitRate = bitRate;
this.frequency = frequency;
}
/**
* Gets codec
*
* @return The codec
*/
public String getCodec() {
return codec;
}
/**
* Gets container
*
* @return The container
*/
public String getContainer() {
return container;
}
/**
* Is big endian?
*
* @return If format is big endian
*/
public Boolean isBigEndian() {
return bigEndian;
}
/**
* Gets bit depth
*
* @see <a href="http://bit.ly/1OTydad">Bit Depth</a>
* @return Bit depth
*/
public Integer getBitDepth() {
return bitDepth;
}
/**
* Gets bit rate
*
* @see <a href="http://bit.ly/1OTy5rk">Bit Rate</a>
* @return Bit rate
*/
public Integer getBitRate() {
return bitRate;
}
/**
* Gets frequency
*
* @return The frequency
*/
public Long getFrequency() {
return frequency;
}
/**
* Determines if the passed AudioFormat is compatible with this AudioFormat.
*
* This AudioFormat is compatible with the passed AudioFormat if both have
* the same value for all non-null members of this instance.
*/
public boolean isCompatible(AudioFormat audioFormat) {
if (audioFormat == null) {
return false;
}
if ((null != getContainer()) && (!getContainer().equals(audioFormat.getContainer()))) {
return false;
}
if ((null != getCodec()) && (!getCodec().equals(audioFormat.getCodec()))) {
return false;
}
if ((null != isBigEndian()) && (!isBigEndian().equals(audioFormat.isBigEndian()))) {
return false;
}
if ((null != getBitDepth()) && (!getBitDepth().equals(audioFormat.getBitDepth()))) {
return false;
}
if ((null != getBitRate()) && (!getBitRate().equals(audioFormat.getBitRate()))) {
return false;
}
if ((null != getFrequency()) && (!getFrequency().equals(audioFormat.getFrequency()))) {
return false;
}
return true;
}
/**
* Determines the best match between a list of audio formats supported by a source and a sink.
*
* @param inputs the supported audio formats of an audio source
* @param outputs the supported audio formats of an audio sink
* @return the best matching format or null, if source and sink are incompatible
*/
public static AudioFormat getBestMatch(Set<AudioFormat> inputs, Set<AudioFormat> outputs) {
AudioFormat preferredFormat = getPreferredFormat(inputs);
if (preferredFormat != null) {
for (AudioFormat output : outputs) {
if (output.isCompatible(preferredFormat)) {
return preferredFormat;
} else {
for (AudioFormat input : inputs) {
if (output.isCompatible(input)) {
return input;
}
}
}
}
}
return null;
}
/**
* Gets the first concrete AudioFormat in the passed set or a preferred one
* based on 16bit, 16KHz, big endian default
*
* @param audioFormats The AudioFormats from which to choose
* @return The preferred AudioFormat or null if none could be determined. A passed concrete format is preferred
* adding default values to an abstract AudioFormat in the passed set.
*/
public static AudioFormat getPreferredFormat(Set<AudioFormat> audioFormats) {
// Return the first concrete AudioFormat found
for (AudioFormat currentAudioFormat : audioFormats) {
// Check if currentAudioFormat is abstract
if (null == currentAudioFormat.getCodec()) {
continue;
}
if (null == currentAudioFormat.getContainer()) {
continue;
}
if (null == currentAudioFormat.isBigEndian()) {
continue;
}
if (null == currentAudioFormat.getBitDepth()) {
continue;
}
if (null == currentAudioFormat.getBitRate()) {
continue;
}
if (null == currentAudioFormat.getFrequency()) {
continue;
}
// Prefer WAVE container
if (!currentAudioFormat.getContainer().equals("WAVE")) {
continue;
}
// As currentAudioFormat is concrete, use it
return currentAudioFormat;
}
// There's no concrete AudioFormat so we must create one
for (AudioFormat currentAudioFormat : audioFormats) {
// Define AudioFormat to return
AudioFormat format = currentAudioFormat;
// Not all Codecs and containers can be supported
if (null == format.getCodec()) {
continue;
}
if (null == format.getContainer()) {
continue;
}
// Prefer WAVE container
if (!format.getContainer().equals(AudioFormat.CONTAINER_WAVE)) {
continue;
}
// If required set BigEndian, BitDepth, BitRate, and Frequency to default values
if (null == format.isBigEndian()) {
format = new AudioFormat(format.getContainer(), format.getCodec(), new Boolean(true),
format.getBitDepth(), format.getBitRate(), format.getFrequency());
}
if (null == format.getBitDepth() || null == format.getBitRate() || null == format.getFrequency()) {
// Define default values
int defaultBitDepth = 16;
long defaultFrequency = 16384;
// Obtain current values
Integer bitRate = format.getBitRate();
Long frequency = format.getFrequency();
Integer bitDepth = format.getBitDepth();
// These values must be interdependent (bitRate = bitDepth * frequency)
if (null == bitRate) {
if (null == bitDepth) {
bitDepth = new Integer(defaultBitDepth);
}
if (null == frequency) {
frequency = new Long(defaultFrequency);
}
bitRate = new Integer(bitDepth.intValue() * frequency.intValue());
} else if (null == bitDepth) {
if (null == frequency) {
frequency = new Long(defaultFrequency);
}
bitDepth = new Integer(bitRate.intValue() / frequency.intValue());
} else if (null == frequency) {
frequency = new Long(bitRate.longValue() / bitDepth.longValue());
}
format = new AudioFormat(format.getContainer(), format.getCodec(), format.isBigEndian(), bitDepth,
bitRate, frequency);
}
// Return preferred AudioFormat
return format;
}
// Return null indicating failure
return null;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AudioFormat) {
AudioFormat format = (AudioFormat) obj;
if (!(null == getCodec() ? null == format.getCodec() : getCodec().equals(format.getCodec()))) {
return false;
}
if (!(null == getContainer() ? null == format.getContainer()
: getContainer().equals(format.getContainer()))) {
return false;
}
if (!(null == isBigEndian() ? null == format.isBigEndian() : isBigEndian().equals(format.isBigEndian()))) {
return false;
}
if (!(null == getBitDepth() ? null == format.getBitDepth() : getBitDepth().equals(format.getBitDepth()))) {
return false;
}
if (!(null == getBitRate() ? null == format.getBitRate() : getBitRate().equals(format.getBitRate()))) {
return false;
}
if (!(null == getFrequency() ? null == format.getFrequency()
: getFrequency().equals(format.getFrequency()))) {
return false;
}
return true;
}
return super.equals(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bigEndian == null) ? 0 : bigEndian.hashCode());
result = prime * result + ((bitDepth == null) ? 0 : bitDepth.hashCode());
result = prime * result + ((bitRate == null) ? 0 : bitRate.hashCode());
result = prime * result + ((codec == null) ? 0 : codec.hashCode());
result = prime * result + ((container == null) ? 0 : container.hashCode());
result = prime * result + ((frequency == null) ? 0 : frequency.hashCode());
return result;
}
@Override
public String toString() {
return "AudioFormat [" + (codec != null ? "codec=" + codec + ", " : "")
+ (container != null ? "container=" + container + ", " : "")
+ (bigEndian != null ? "bigEndian=" + bigEndian + ", " : "")
+ (bitDepth != null ? "bitDepth=" + bitDepth + ", " : "")
+ (bitRate != null ? "bitRate=" + bitRate + ", " : "")
+ (frequency != null ? "frequency=" + frequency : "") + "]";
}
}