mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Interim checkin for call recording.
This commit is contained in:
251
src/record/wave/WaveWriter.java
Normal file
251
src/record/wave/WaveWriter.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package record.wave;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class WaveWriter implements AutoCloseable
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class );
|
||||
|
||||
private static final Pattern FILENAME_PATTERN =
|
||||
Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
|
||||
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
|
||||
|
||||
private AudioFormat mAudioFormat;
|
||||
private int mFileRolloverCounter = 1;
|
||||
private long mMaxSize;
|
||||
private Path mFile;
|
||||
private FileChannel mFileChannel;
|
||||
|
||||
/**
|
||||
* Constructs a new wave writer that is open with a complete header, ready
|
||||
* for writing buffers of PCM sample data.
|
||||
*
|
||||
* Each time the maximum file size is reached, a new file is created with a
|
||||
* series suffix appended to the file name.
|
||||
*
|
||||
* @param format - audio format (channels, sample size, sample rate)
|
||||
* @param file - wave file to write
|
||||
* @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes )
|
||||
* @throws IOException - if there are any IO issues
|
||||
*/
|
||||
public WaveWriter( AudioFormat format, Path file, long maxSize )
|
||||
throws IOException
|
||||
{
|
||||
assert( format != null );
|
||||
assert( file != null );
|
||||
|
||||
mAudioFormat = format;
|
||||
mFile = file;
|
||||
|
||||
if( 0 < maxSize && maxSize <= MAX_WAVE_SIZE )
|
||||
{
|
||||
mMaxSize = maxSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
mMaxSize = MAX_WAVE_SIZE;
|
||||
}
|
||||
|
||||
open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new wave writer that is open with a complete header, ready
|
||||
* for writing buffers of PCM sample data. The maximum file size is limited
|
||||
* to the max size specified in the wave file format: max unsigned integer
|
||||
*
|
||||
* @param format - audio format (channels, sample size, sample rate)
|
||||
* @param file - wave file to write
|
||||
* @throws IOException - if there are any IO issues
|
||||
*/
|
||||
public WaveWriter( AudioFormat format, Path file ) throws IOException
|
||||
{
|
||||
this( format, file, Integer.MAX_VALUE * 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file and writes a wave header.
|
||||
*/
|
||||
private void open() throws IOException
|
||||
{
|
||||
Files.createFile( mFile );
|
||||
|
||||
mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) );
|
||||
|
||||
ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat );
|
||||
|
||||
header.flip();
|
||||
|
||||
while( header.hasRemaining() )
|
||||
{
|
||||
mFileChannel.write( header );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the file
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
mFileChannel.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the buffer contents to the file. Assumes that the buffer is full
|
||||
* and the first byte of data is at position 0.
|
||||
*/
|
||||
public void write( ByteBuffer buffer ) throws IOException
|
||||
{
|
||||
buffer.position( 0 );
|
||||
|
||||
/* Write the full buffer if there is room, respecting the max file size */
|
||||
if( mFileChannel.size() + buffer.capacity() < mMaxSize )
|
||||
{
|
||||
while( buffer.hasRemaining() )
|
||||
{
|
||||
mFileChannel.write( buffer );
|
||||
}
|
||||
|
||||
updateWaveFileSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Split the buffer to finish filling the current file and then put
|
||||
* the leftover into a new file */
|
||||
int remaining = (int)( mMaxSize - mFileChannel.size() );
|
||||
|
||||
/* Ensure we write full frames to fill up the remaining size */
|
||||
remaining -= (int)( remaining % mAudioFormat.getFrameSize() );
|
||||
|
||||
byte[] bytes = buffer.array();
|
||||
|
||||
ByteBuffer current = ByteBuffer.wrap( Arrays.copyOf( bytes, remaining ) );
|
||||
|
||||
ByteBuffer next = ByteBuffer.wrap( Arrays.copyOfRange( bytes,
|
||||
remaining, bytes.length ) );
|
||||
|
||||
while( current.hasRemaining() )
|
||||
{
|
||||
mFileChannel.write( current );
|
||||
}
|
||||
|
||||
updateWaveFileSize();
|
||||
|
||||
rollover();
|
||||
|
||||
while( next.hasRemaining() )
|
||||
{
|
||||
mFileChannel.write( next );
|
||||
}
|
||||
|
||||
updateWaveFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes out the current file, appends an incremented sequence number to
|
||||
* the file name and opens up a new file.
|
||||
*/
|
||||
private void rollover() throws IOException
|
||||
{
|
||||
close();
|
||||
|
||||
mFileRolloverCounter++;
|
||||
|
||||
updateFileName();
|
||||
|
||||
open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the overall and the chunk2 sizes
|
||||
*/
|
||||
private void updateWaveFileSize() throws IOException
|
||||
{
|
||||
/* Update overall wave size (total size - 8 bytes) */
|
||||
mFileChannel.write( getUnsignedIntegerBuffer( mFileChannel.size() - 8 ), 4 );
|
||||
|
||||
/* Update chunk 2 size (total size - 44 bytes) */
|
||||
mFileChannel.write( getUnsignedIntegerBuffer( mFileChannel.size() - 44 ), 40 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a little-endian 4-byte buffer containing an unsigned 32-bit
|
||||
* integer value derived from the 4 least significant bytes of the argument.
|
||||
*
|
||||
* The buffer's position is set to 0 to prepare it for writing to a channel.
|
||||
*/
|
||||
private static ByteBuffer getUnsignedIntegerBuffer( long size )
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate( 4 );
|
||||
|
||||
buffer.put( (byte)( size & 0xFFl ) );
|
||||
buffer.put( (byte)( ( size & 0xFF00l ) >> 8 ) );
|
||||
buffer.put( (byte)( ( size & 0xFF0000l ) >> 16 ) );
|
||||
buffer.put( (byte)( ( size & 0xFF000000l ) >> 32 ) );
|
||||
|
||||
buffer.position( 0 );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current file name with the rollover counter series suffix
|
||||
*/
|
||||
private void updateFileName()
|
||||
{
|
||||
String filename = mFile.toString();
|
||||
|
||||
if( mFileRolloverCounter == 2 )
|
||||
{
|
||||
filename = filename.replace( ".wav", "_2.wav" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Matcher m = FILENAME_PATTERN.matcher( filename );
|
||||
|
||||
if( m.find() )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( m.group( 1 ) );
|
||||
sb.append( mFileRolloverCounter );
|
||||
sb.append( m.group( 3 ) );
|
||||
|
||||
filename = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
mFile = Paths.get( filename );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user