org.openimaj.audio.SampleChunk Maven / Gradle / Ivy
/**
* Copyright (c) 2011, The University of Southampton and the individual contributors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the University of Southampton nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
*/
package org.openimaj.audio;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.openimaj.audio.samples.SampleBuffer;
import org.openimaj.audio.samples.SampleBufferFactory;
import org.openimaj.audio.timecode.AudioTimecode;
/**
* Represents a chunk of an audio file and stores the raw audio data.
* The data is unnormalised - that is, it is stored in this class in its
* original format in the form of a byte array. This is for speed during
* audio playback.
*
* If you require normalised data (data as an integer array for example)
* use the method {@link #getSamplesAsByteBuffer()} and use the
* {@link ByteBuffer}'s methods asXXXBuffer (e.g. ByteBuffer#asShortBuffer)
* to get the samples in a normalised form.
*
* @author David Dupplaw ([email protected])
* @created 8 Jun 2011
*
*/
public class SampleChunk extends Audio
{
/** The samples in the chunk */
private byte[] samples = new byte[1];
/** The timecode of the start of the sample chunk */
private AudioTimecode startTimecode = new AudioTimecode(0);
/**
* Create a new SampleChunk buffer with the given
* audio format, but do not initialise the samples.
*
* @param af The audio format of the samples
*/
public SampleChunk( final AudioFormat af )
{
this( new byte[1], af );
}
/**
* Create a new sample chunk using the given samples
* and the given audio format.
*
* @param samples The samples to initialise with
* @param af The audio format of the samples
*/
public SampleChunk( final byte[] samples, final AudioFormat af )
{
this.setSamples( samples );
super.format = af;
}
/**
* Create a new sample chunk using the given samples
* and the given audio format.
*
* @param samples The samples to initialise with
* @param af The audio format of the samples
* @param tc The audio timecode of these samples
*/
public SampleChunk( final byte[] samples, final AudioFormat af, final AudioTimecode tc )
{
this.setSamples( samples );
this.startTimecode = tc;
super.format = af;
}
/**
* Set the samples in this sample chunk.
* @param samples the samples in this sample chunk.
*/
public void setSamples( final byte[] samples )
{
synchronized( this.samples )
{
this.samples = samples;
}
}
/**
* Get the samples in this sample chunk
* @return the samples in this sample chunk
*/
public byte[] getSamples()
{
return this.samples;
}
/**
* Returns the number of samples in this sample chunk. If there are 128
* stereo samples, this method will return 256. That is, it does not
* normalise for the number of channels. However, it does normalise
* for the size of each sample. So if this is a 16-bit buffer of 256
* bytes length, this method will return 128.
*
* @return the number of samples in this sample chunk.
*/
public int getNumberOfSamples()
{
return this.samples.length / (this.format.getNBits()/8);
}
/**
* Returns a {@link ByteBuffer} that can be used to create
* views of the samples in the object. For example, to get short
* integers, you can get {@link #getSamplesAsByteBuffer()}.asShortBuffer()
*
* @return A {@link ByteBuffer}
*/
public ByteBuffer getSamplesAsByteBuffer()
{
if( this.samples == null ) return null;
ByteOrder bo = null;
if( this.format.isBigEndian() )
bo = ByteOrder.BIG_ENDIAN;
else bo = ByteOrder.LITTLE_ENDIAN;
return ByteBuffer.wrap( this.samples ).order( bo );
}
/**
* Returns an appropriate sample buffer for this
* sample chunk. If an appropriate sample buffer
* cannot be found, null will be returned. This will
* wrap the samples array from the underlying chunk,
* so you should only side-affect the samples if you're
* sure they will not be reused.
*
* @return An appropriate {@link SampleBuffer}
*/
public SampleBuffer getSampleBuffer()
{
return SampleBufferFactory.createSampleBuffer( this, this.format );
}
/**
* Set the timecode at the start of this audio chunk.
* @param startTimecode the timecode at the start of the chunk.
*/
public void setStartTimecode( final AudioTimecode startTimecode )
{
this.startTimecode = startTimecode;
}
/**
* Get the timecode at the start of this audio chunk.
* @return the timecode at the start of the chunk.
*/
public AudioTimecode getStartTimecode()
{
return this.startTimecode;
}
/**
* Return a slice of data from the sample array. The indices are based
* on the samples in the byte array, not the bytes themselves.
*
* The assumption is that samples are whole numbers of bytes. So, if the sample
* size was 16-bits, then passing in 2 for the start index would actually
* index the byte at index 4 in the underlying sample array. The order
* of the bytes is unchanged.
*
* @param start The start index of the sample.
* @param length The length of the samples get.
* @return The sample slice as a new {@link SampleChunk}
*/
public SampleChunk getSampleSlice( final int start, final int length )
{
final int nBytesPerSample = this.format.getNBits()/8;
final int startSampleByteIndex = start * nBytesPerSample;
final byte[] newSamples = new byte[length * nBytesPerSample];
synchronized( this.samples )
{
System.arraycopy( this.samples, startSampleByteIndex,
newSamples, 0, length*nBytesPerSample );
}
final SampleChunk s = new SampleChunk( this.format );
s.setSamples( newSamples );
// Set the timestamp to the start of this new slice
final double samplesPerChannelPerMillisec = this.format.getSampleRateKHz();
s.setStartTimecode( new AudioTimecode(
this.getStartTimecode().getTimecodeInMilliseconds() +
(long)(start / samplesPerChannelPerMillisec) ) );
return s;
}
/**
* Prepends the given samples to the front of this sample chunk. It is
* expected that the given samples are in the same format as this
* sample chunk; if they are not an exception is thrown.
* Side-affects this sample chunk and will return a reference to
* this sample chunk.
*
* @param sample the samples to add
* @return This sample chunk with the bytes prepended
*/
public SampleChunk prepend( final SampleChunk sample )
{
// Check the sample formats are the same
if( !sample.getFormat().equals( this.format ) )
throw new IllegalArgumentException("Sample types are not equivalent");
// Get the samples from the given chunk
final byte[] x1 = sample.getSamplesAsByteBuffer().array();
// Create an array for the concatenated pair
final byte[] newSamples = new byte[ this.samples.length + x1.length ];
// Loop through adding the new samples
System.arraycopy( x1, 0, newSamples, 0, x1.length );
synchronized( this.samples )
{
System.arraycopy( this.samples, 0, newSamples, x1.length, this.samples.length );
}
// Update this object
this.samples = newSamples;
this.setStartTimecode( sample.getStartTimecode().clone() );
return this;
}
/**
* Appends the given samples to the end of this sample chunk. It is
* expected that the given samples are in the same format as this
* sample chunk; if they are not an exception is thrown.
* Side-affects this sample chunk and will return a reference to
* this sample chunk.
*
* @param sample the samples to add
* @return This sample chunk with the bytes appended
*/
public SampleChunk append( final SampleChunk sample )
{
// Check the sample formats are the same
if( !sample.getFormat().equals( this.format ) )
throw new IllegalArgumentException("Sample types are not equivalent");
// Get the samples from the given chunk
final byte[] x1 = sample.getSamplesAsByteBuffer().array();
// Create an array for the concatenated pair
final byte[] newSamples = new byte[ this.samples.length + x1.length ];
synchronized( this.samples )
{
System.arraycopy( this.samples, 0, newSamples, 0, this.samples.length );
}
System.arraycopy( x1, 0, newSamples, this.samples.length, x1.length );
// Update this object
this.samples = newSamples;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public SampleChunk clone()
{
return new SampleChunk( this.samples.clone(), this.format.clone(), this.startTimecode.clone() );
}
/**
* Pads the sample chunk to the given size with zeros.
* @param requiredSampleSetSize The required sample size
*/
public void pad( final int requiredSampleSetSize )
{
final byte[] samples = new byte[requiredSampleSetSize*(this.format.getNBits()/8)];
System.arraycopy( this.samples, 0, samples, 0, this.samples.length );
this.samples = samples;
}
}