summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGerald Begumisa <ben_g@users.sourceforge.net>2008-02-27 19:21:41 +0000
committerGerald Begumisa <ben_g@users.sourceforge.net>2008-02-27 19:21:41 +0000
commit0937742d2f5689c93efca3a5a56e8b36f81152c7 (patch)
treea0c817c8b45c888c88d020583a45a5ec65a225dd
parent9483156b326918005bb42751cc0e37a16b3e3e41 (diff)
Added support for storage of stereo files - typically files containing 2 separate channels. Added the config.xml variable StereoRecording which needs to be set to true for this to work. Added another config.xml variable TapeNumChannels which should be set to 2 to test this. If TapeNumChannels is set to 1, default behaviour happens. Currently only 1 and 2 are the only supported values for TapeNumChannels.
git-svn-id: https://oreka.svn.sourceforge.net/svnroot/oreka/trunk@526 09dcff7a-b715-0410-9601-b79a96267cd0
-rw-r--r--orkaudio/filters/rtpmixer/RtpMixer.cpp409
-rw-r--r--orkbasecxx/AudioCapture.cpp118
-rw-r--r--orkbasecxx/AudioCapture.h19
-rw-r--r--orkbasecxx/Config.cpp10
-rw-r--r--orkbasecxx/Config.h6
-rw-r--r--orkbasecxx/audiofile/LibSndFileFile.cpp115
6 files changed, 645 insertions, 32 deletions
diff --git a/orkaudio/filters/rtpmixer/RtpMixer.cpp b/orkaudio/filters/rtpmixer/RtpMixer.cpp
index e3a52af..991f8c1 100644
--- a/orkaudio/filters/rtpmixer/RtpMixer.cpp
+++ b/orkaudio/filters/rtpmixer/RtpMixer.cpp
@@ -14,9 +14,11 @@
#include "dll.h"
#include <queue>
+#include <vector>
#include "Filter.h"
#include "AudioCapture.h"
#include <log4cxx/logger.h>
+#include "ConfigManager.h"
extern "C"
{
#include "g711.h"
@@ -26,6 +28,24 @@ extern "C"
#define NUM_SAMPLES_TRIGGER 12000 // when we have this number of available samples make a shipment
#define NUM_SAMPLES_SHIPMENT_HOLDOFF 11000 // when shipping, ship everything but this number of samples
+class RtpMixerChannel
+{
+public:
+ RtpMixerChannel();
+ ~RtpMixerChannel();
+
+ int m_channel;
+ double m_timestampCorrectiveDelta;
+ short m_buffer[NUM_SAMPLES_CIRCULAR_BUFFER];
+
+ // Statistics related to channel
+ AudioChunkRef m_lastChunk;
+ int m_seqNumMisses;
+ int m_seqMaxGap;
+ int m_seqNumOutOfOrder;
+ int m_seqNumDiscontinuities;
+};
+typedef boost::shared_ptr<RtpMixerChannel> RtpMixerChannelRef;
class RtpMixer : public Filter
{
@@ -48,6 +68,7 @@ private:
void StoreRtpPacket(AudioChunkRef& chunk, unsigned int correctedTimestamp);
void ManageOutOfRangeTimestamp(AudioChunkRef& chunk);
+ void HandleMixedOutput(AudioChunkRef &chunk, AudioChunkDetails& details, short *readPtr);
void CreateShipment(size_t silenceSize = 0, bool force = false);
void Reset(unsigned int timestamp);
unsigned int FreeSpace();
@@ -59,6 +80,7 @@ private:
void DoStats( AudioChunkDetails* details, AudioChunkDetails* lastDetails,
int& seqNumMisses, int& seqMaxGap, int& seqNumOutOfOrder,
int& seqNumDiscontinuities);
+ void CreateChannels(int channelNumber);
short* m_writePtr; // pointer after newest RTP data we've received
short* m_readPtr; // where to read from next
@@ -84,8 +106,36 @@ private:
int m_seqNumOutOfOrderS2;
int m_seqNumDiscontinuitiesS1;
int m_seqNumDiscontinuitiesS2;
+
+ //==========================================================
+ // Multi-channel separated output
+
+ int m_numChannels; // Number of channels
+ std::vector<RtpMixerChannelRef> m_rtpMixerChannels; // Channel information
};
+//==========================================================
+RtpMixerChannel::RtpMixerChannel()
+{
+ m_timestampCorrectiveDelta = 0.0;
+ m_seqNumMisses = 0;
+ m_seqMaxGap = 0;
+ m_seqNumOutOfOrder = 0;
+ m_seqNumDiscontinuities = 0;
+ m_channel = 0;
+ for(int i = 0; i < NUM_SAMPLES_CIRCULAR_BUFFER; i++)
+ {
+ m_buffer[i] = 0;
+ }
+}
+
+RtpMixerChannel::~RtpMixerChannel()
+{
+ //printf("\n\n\n\n\n\n\nRtpMixerChannel%d being destroyed\n\n\n\n\n", m_channel);
+}
+
+//==========================================================
+
RtpMixer::RtpMixer()
{
m_writePtr = m_buffer;
@@ -107,6 +157,44 @@ RtpMixer::RtpMixer()
m_seqNumOutOfOrderS2 = 0;
m_seqNumDiscontinuitiesS1 = 0;
m_seqNumDiscontinuitiesS2 = 0;
+ m_numChannels = 0;
+ m_rtpMixerChannels.clear();
+}
+
+void RtpMixer::CreateChannels(int channelNumber)
+{
+ if(CONFIG.m_stereoRecording == false)
+ {
+ return;
+ }
+
+ if(CONFIG.m_tapeNumChannels <= 1)
+ {
+ return;
+ }
+
+ if(channelNumber <= m_numChannels)
+ {
+ // Channel already created
+ return;
+ }
+
+ RtpMixerChannelRef rtpMixerChannel;
+
+ int i;
+
+ i = m_numChannels;
+
+ // Always create channels until this number
+ for(; i < channelNumber; i++)
+ {
+ rtpMixerChannel.reset(new RtpMixerChannel());
+ rtpMixerChannel->m_channel = i+1;
+ m_rtpMixerChannels.push_back(rtpMixerChannel);
+ m_numChannels += 1;
+ }
+
+ return;
}
FilterRef RtpMixer::Instanciate()
@@ -167,11 +255,42 @@ void RtpMixer::AudioChunkIn(AudioChunkRef& chunk)
if(details->m_marker == MEDIA_CHUNK_EOS_MARKER)
{
- logMsg.Format("EOS s1: misses:%d maxgap:%d oo:%d disc:%d s2: misses:%d maxgap:%d oo:%d disc:%d",
- m_seqNumMissesS1, m_seqMaxGapS1, m_seqNumOutOfOrderS1, m_seqNumDiscontinuitiesS1,
- m_seqNumMissesS2, m_seqMaxGapS2, m_seqNumOutOfOrderS2, m_seqNumDiscontinuitiesS2);
+ if(m_numChannels)
+ {
+ for(int i = 0; i < m_numChannels; i++)
+ {
+ CStdString statsChan;
+
+ statsChan.Format("EOS s%d: misses:%d maxgap:%d oo:%d disc:%d", i+1,
+ m_rtpMixerChannels[i]->m_seqNumMisses, m_rtpMixerChannels[i]->m_seqMaxGap,
+ m_rtpMixerChannels[i]->m_seqNumOutOfOrder, m_rtpMixerChannels[i]->m_seqNumDiscontinuities);
+
+ if(logMsg.size())
+ {
+ logMsg = logMsg + " " + statsChan;
+ }
+ else
+ {
+ logMsg = statsChan;
+ }
+ }
+ }
+ else
+ {
+ logMsg.Format("EOS s1: misses:%d maxgap:%d oo:%d disc:%d s2: misses:%d maxgap:%d oo:%d disc:%d",
+ m_seqNumMissesS1, m_seqMaxGapS1, m_seqNumOutOfOrderS1, m_seqNumDiscontinuitiesS1,
+ m_seqNumMissesS2, m_seqMaxGapS2, m_seqNumOutOfOrderS2, m_seqNumDiscontinuitiesS2);
+
+ }
+
LOG4CXX_INFO(m_log, logMsg);
+ //logMsg.Format("COMPARISON: EOS s1: misses:%d maxgap:%d oo:%d disc:%d s2: misses:%d maxgap:%d oo:%d disc:%d",
+ // m_seqNumMissesS1, m_seqMaxGapS1, m_seqNumOutOfOrderS1, m_seqNumDiscontinuitiesS1,
+ // m_seqNumMissesS2, m_seqMaxGapS2, m_seqNumOutOfOrderS2, m_seqNumDiscontinuitiesS2);
+
+ //LOG4CXX_INFO(m_log, logMsg);
+
CreateShipment(0, true); // flush the buffer
return;
}
@@ -193,12 +312,31 @@ void RtpMixer::AudioChunkIn(AudioChunkRef& chunk)
unsigned int correctedTimestamp = 0;
+ if(CONFIG.m_stereoRecording == true)
+ {
+ CreateChannels(details->m_channel);
+ }
+
if(details->m_channel == 1)
{
+ if(m_numChannels)
+ {
+ int chanIdx = 0;
+ AudioChunkRef lastChunk = m_rtpMixerChannels[chanIdx]->m_lastChunk;
+
+ if(lastChunk.get())
+ {
+ DoStats(details, lastChunk->GetDetails(), m_rtpMixerChannels[chanIdx]->m_seqNumMisses,
+ m_rtpMixerChannels[chanIdx]->m_seqMaxGap, m_rtpMixerChannels[chanIdx]->m_seqNumOutOfOrder,
+ m_rtpMixerChannels[chanIdx]->m_seqNumDiscontinuities);
+ }
+ m_rtpMixerChannels[chanIdx]->m_lastChunk = chunk;
+ }
+
if(m_lastChunkS1.get())
{
DoStats(details, m_lastChunkS1->GetDetails(), m_seqNumMissesS1, m_seqMaxGapS1,
- m_seqNumOutOfOrderS1, m_seqNumDiscontinuitiesS1);
+ m_seqNumOutOfOrderS1, m_seqNumDiscontinuitiesS1);
}
m_lastChunkS1 = chunk;
correctedTimestamp = details->m_timestamp;
@@ -212,6 +350,20 @@ void RtpMixer::AudioChunkIn(AudioChunkRef& chunk)
}
else if(details->m_channel == 2)
{
+ if(m_numChannels)
+ {
+ int chanIdx = 1;
+ AudioChunkRef lastChunk = m_rtpMixerChannels[chanIdx]->m_lastChunk;
+
+ if(lastChunk.get())
+ {
+ DoStats(details, lastChunk->GetDetails(), m_rtpMixerChannels[chanIdx]->m_seqNumMisses,
+ m_rtpMixerChannels[chanIdx]->m_seqMaxGap, m_rtpMixerChannels[chanIdx]->m_seqNumOutOfOrder,
+ m_rtpMixerChannels[chanIdx]->m_seqNumDiscontinuities);
+ }
+ m_rtpMixerChannels[chanIdx]->m_lastChunk = chunk;
+ }
+
if(m_lastChunkS2.get())
{
DoStats(details, m_lastChunkS2->GetDetails(), m_seqNumMissesS2, m_seqMaxGapS2,
@@ -232,11 +384,40 @@ void RtpMixer::AudioChunkIn(AudioChunkRef& chunk)
}
else
{
- if(m_invalidChannelReported == false)
+ // Support for channel 3, 4, 5, ...
+ if(m_numChannels)
+ {
+ int chanIdx = (details->m_channel)-1;
+ AudioChunkRef lastChunk = m_rtpMixerChannels[chanIdx]->m_lastChunk;
+
+ if(lastChunk.get())
+ {
+ DoStats(details, lastChunk->GetDetails(), m_rtpMixerChannels[chanIdx]->m_seqNumMisses,
+ m_rtpMixerChannels[chanIdx]->m_seqMaxGap, m_rtpMixerChannels[chanIdx]->m_seqNumOutOfOrder,
+ m_rtpMixerChannels[chanIdx]->m_seqNumDiscontinuities);
+ }
+ m_rtpMixerChannels[chanIdx]->m_lastChunk = chunk;
+
+ // Corrective delta always only applied to side 2 and other sides.
+ double tmp = (double)details->m_timestamp - m_rtpMixerChannels[chanIdx]->m_timestampCorrectiveDelta;
+ if(tmp < 0.0)
+ {
+ // Unsuccessful correction, do not correct.
+ correctedTimestamp = details->m_timestamp;
+ }
+ else
+ {
+ correctedTimestamp = (unsigned int)tmp;
+ }
+ }
+ else
{
- m_invalidChannelReported = true;
- logMsg.Format("Invalid Channel:%d", details->m_channel);
- LOG4CXX_ERROR(m_log, logMsg);
+ if(m_invalidChannelReported == false)
+ {
+ m_invalidChannelReported = true;
+ logMsg.Format("Invalid Channel:%d", details->m_channel);
+ LOG4CXX_ERROR(m_log, logMsg);
+ }
}
}
unsigned int rtpEndTimestamp = correctedTimestamp + chunk->GetNumSamples();
@@ -333,6 +514,17 @@ void RtpMixer::ManageOutOfRangeTimestamp(AudioChunkRef& chunk)
// will be in the circular buffer timestamp window.
m_timestampCorrectiveDelta = (double)details->m_timestamp - (double)m_writeTimestamp;
}
+ else
+ {
+ if(m_numChannels)
+ {
+ int chanIdx = details->m_channel - 1;
+
+ // Calculate timestamp corrective delta so that next channel-x chunk
+ // will be in the circular buffer timestamp window.
+ m_rtpMixerChannels[chanIdx]->m_timestampCorrectiveDelta = (double)details->m_timestamp - (double)m_writeTimestamp;
+ }
+ }
}
void RtpMixer::Reset(unsigned int timestamp)
@@ -382,14 +574,37 @@ void RtpMixer::StoreRtpPacket(AudioChunkRef& audioChunk, unsigned int correctedT
{
CStdString debug;
AudioChunkDetails* details = audioChunk->GetDetails();
+ RtpMixerChannelRef myChannel;
+
+ if(m_numChannels)
+ {
+ // Define this once and for all
+ myChannel = m_rtpMixerChannels[details->m_channel - 1];
+ }
// 1. Silence from write pointer until end of RTP packet
+ // Doing this also will help us determine the offset at the channel's circular
+ // buffer at which we should write - for stereo recording
unsigned int endRtpTimestamp = correctedTimestamp + audioChunk->GetNumSamples();
if (endRtpTimestamp > m_writeTimestamp)
{
for(int i=0; i<(endRtpTimestamp - m_writeTimestamp); i++)
{
*m_writePtr = 0;
+
+ if(m_numChannels)
+ {
+ for(int x = 0; x < m_numChannels; x++)
+ {
+ // We follow in step and zero the bytes in the buffers of all the
+ // RtpMixer channels, in preparation for the write - this means that
+ // if there is no data for this timestamp in any one buffer,
+ // then we have silence there
+ short *myZeroPtr = m_rtpMixerChannels[x]->m_buffer + (m_writePtr - m_buffer);
+ *myZeroPtr = 0;
+ }
+ }
+
m_writePtr++;
if(m_writePtr >= m_bufferEnd)
{
@@ -420,6 +635,14 @@ void RtpMixer::StoreRtpPacket(AudioChunkRef& audioChunk, unsigned int correctedT
sample = -32768;
}
*tempWritePtr = (short)sample;
+
+ // Follow in step and save the payload in the respective channel buffer
+ if(m_numChannels)
+ {
+ short* myChannelTempWritePtr = myChannel->m_buffer + (tempWritePtr - m_buffer);
+ *myChannelTempWritePtr = (short)payload[i];
+ }
+
tempWritePtr++;
if(tempWritePtr >= m_bufferEnd)
{
@@ -454,6 +677,118 @@ short* RtpMixer::CicularPointerSubtractOffset(short *ptr, size_t offset)
}
}
+void RtpMixer::HandleMixedOutput(AudioChunkRef &chunk, AudioChunkDetails& details, short *readPtr)
+{
+ if(CONFIG.m_stereoRecording == false)
+ {
+ return;
+ }
+ if(CONFIG.m_tapeNumChannels <= 1)
+ {
+ return;
+ }
+
+ // If we're required to output a different number of channels than we currently
+ // know about in the RtpMixer, we will need to adjust the chunk such that it has
+ // the exact number of channel buffers we're required to output i.e so that
+ // the number of channel buffers in the chunk == CONFIG.m_tapeNumChannels
+ if(CONFIG.m_tapeNumChannels != m_numChannels)
+ {
+ chunk.reset(new AudioChunk(CONFIG.m_tapeNumChannels));
+ chunk->SetBuffer((void*)readPtr, details);
+ }
+
+ // 2 is hardcoded here because as per instructions we are currently to support
+ // only output of 2 channels. So tapeChannels[0] will contain a mixed
+ // output of all even channels in the system (2, 4, 6...) and
+ // tapeChannels[1] will contain a mixed output of all odd channels
+ // in the system (1, 3, 5...). Now, the rest of the chunks for the rest of
+ // the output channels i.e output channel 3, 4, 5... will be zeroed
+ short *tapeChannels[2];
+
+ tapeChannels[0] = (short*)malloc(details.m_numBytes); // Even channels mixed into here
+ tapeChannels[1] = (short*)malloc(details.m_numBytes); // Odd channels mixed into here
+
+ if(!tapeChannels[0] || !tapeChannels[1])
+ {
+ CStdString logMsg;
+
+ logMsg.Format("Out of memory in RtpMixer::AddMixedChannels while allocating %d bytes", details.m_numBytes);
+ LOG4CXX_ERROR(m_log, logMsg);
+
+ if(tapeChannels[0])
+ {
+ free(tapeChannels[0]);
+ }
+ if(tapeChannels[1])
+ {
+ free(tapeChannels[1]);
+ }
+
+ return;
+ }
+
+ memset(tapeChannels[0], 0, details.m_numBytes);
+ memset(tapeChannels[1], 0, details.m_numBytes);
+
+ int chanNo = 0;
+ int chanIdx = 0;
+
+ for(int i = 0; i < m_numChannels; i++)
+ {
+ chanNo = i+1;
+ chanIdx = i;
+
+ for(int j = 0; j < (details.m_numBytes/2); j++)
+ {
+ int sample;
+ int saveIdx = 0;
+
+ if(!(chanNo % 2))
+ {
+ saveIdx = 0;
+ }
+ else
+ {
+ saveIdx = 1;
+ }
+
+ short *chanReadPtr = (short*)m_rtpMixerChannels[chanIdx]->m_buffer + ((readPtr - m_buffer) + j);
+
+ sample = tapeChannels[saveIdx][j] + *chanReadPtr;
+ if (sample > 32767)
+ {
+ sample = 32767;
+ }
+ if(sample < -32768)
+ {
+ sample = -32768;
+ }
+
+ tapeChannels[saveIdx][j] = (short)sample;
+ }
+ }
+
+ // Set the two supported output channels
+ chunk->SetBuffer((void*)tapeChannels[0], details, 1);
+ chunk->SetBuffer((void*)tapeChannels[1], details, 2);
+
+ memset(tapeChannels[0], 0, details.m_numBytes);
+
+ // If we're required to output more than two channels then we need to
+ // zero the rest of the output channels i.e 3, 4, 5...
+ if(CONFIG.m_tapeNumChannels > 2)
+ {
+ for(int i = 2; i < CONFIG.m_tapeNumChannels; i++)
+ {
+ chunk->SetBuffer((void*)tapeChannels[0], details, (i+1));
+ }
+ }
+
+ free(tapeChannels[0]);
+ free(tapeChannels[1]);
+}
+
void RtpMixer::CreateShipment(size_t silenceSize, bool force)
{
// 1. ship from readPtr until stop pointer or until end of buffer if wrapped
@@ -478,13 +813,28 @@ void RtpMixer::CreateShipment(size_t silenceSize, bool force)
}
size_t shortSize = stopPtr-m_readPtr;
size_t byteSize = shortSize*2;
- AudioChunkRef chunk(new AudioChunk());
+ AudioChunkRef chunk;
AudioChunkDetails details;
+
+ if(m_numChannels)
+ {
+ chunk.reset(new AudioChunk(m_numChannels));
+ details.m_channel = 100;
+ }
+ else
+ {
+ chunk.reset(new AudioChunk());
+ }
+
details.m_encoding = PcmAudio;
details.m_numBytes = byteSize;
if(CheckChunkDetails(details))
{
chunk->SetBuffer((void*)m_readPtr, details);
+ if(CONFIG.m_stereoRecording == true)
+ {
+ HandleMixedOutput(chunk, details, m_readPtr);
+ }
m_outputQueue.push(chunk);
}
m_shippedSamples += shortSize;
@@ -499,17 +849,34 @@ void RtpMixer::CreateShipment(size_t silenceSize, bool force)
// 2. ship from beginning of buffer until stop ptr
if(bufferWrapped)
{
+ AudioChunkDetails details;
+
shortSize = wrappedStopPtr - m_buffer;
byteSize = shortSize*2;
- chunk.reset(new AudioChunk());
- AudioChunkDetails details;
+
+ if(m_numChannels)
+ {
+ chunk.reset(new AudioChunk(m_numChannels));
+ details.m_channel = 100;
+ }
+ else
+ {
+ chunk.reset(new AudioChunk());
+ }
+
details.m_encoding = PcmAudio;
details.m_numBytes = byteSize;
+
if(CheckChunkDetails(details))
{
chunk->SetBuffer((void*)m_buffer, details);
+ if(CONFIG.m_stereoRecording == true)
+ {
+ HandleMixedOutput(chunk, details, m_buffer);
+ }
m_outputQueue.push(chunk);
}
+
m_shippedSamples += shortSize;
m_readPtr = CircularPointerAddOffset(m_readPtr ,shortSize);
m_readTimestamp += shortSize;
@@ -521,8 +888,26 @@ void RtpMixer::CreateShipment(size_t silenceSize, bool force)
if (silenceSize)
{
byteSize = silenceSize*2;
- AudioChunkRef chunk(new AudioChunk());
+ AudioChunkRef chunk;
AudioChunkDetails details;
+
+ if(CONFIG.m_stereoRecording == true)
+ {
+ if(CONFIG.m_tapeNumChannels > 1)
+ {
+ chunk.reset(new AudioChunk(CONFIG.m_tapeNumChannels));
+ details.m_channel = 100;
+ }
+ else
+ {
+ chunk.reset(new AudioChunk());
+ }
+ }
+ else
+ {
+ chunk.reset(new AudioChunk());
+ }
+
details.m_encoding = PcmAudio;
details.m_numBytes = byteSize;
if(CheckChunkDetails(details))
diff --git a/orkbasecxx/AudioCapture.cpp b/orkbasecxx/AudioCapture.cpp
index 706c657..77b9e95 100644
--- a/orkbasecxx/AudioCapture.cpp
+++ b/orkbasecxx/AudioCapture.cpp
@@ -22,33 +22,105 @@ AudioChunk::AudioChunk()
{
m_details.Clear();
m_pBuffer = NULL;
+ m_numChannels = 0;
+ m_pChannelAudio = NULL;
}
-AudioChunk::~AudioChunk()
+AudioChunk::AudioChunk(int numChannels)
{
- if(m_pBuffer)
+ m_details.Clear();
+ m_pBuffer = NULL;
+ m_pChannelAudio = NULL;
+ if(numChannels <= 0)
{
- free(m_pBuffer);
+ // Force at least one channel to resolve this possible
+ // error condition
+ numChannels = 1;
+ }
+
+ m_numChannels = numChannels;
+ m_pChannelAudio = (void**)calloc(m_numChannels, sizeof(void*));
+ m_details.m_channel = 100;
+
+ if(!m_pChannelAudio)
+ {
+ CStdString numBytesString;
+
+ numBytesString.Format("%d", m_numChannels*sizeof(void*));
+ throw("AudioChunk::AudioChunk(numChannels) could not allocate a buffer of size " + numBytesString);
}
}
+AudioChunk::~AudioChunk()
+{
+ FreeAll();
+}
+
void AudioChunk::ToString(CStdString& string)
{
- string.Format("encoding:%d numBytes:%u ts:%u ats:%u seq:%u rtp-pt:%d ch:%u",
- m_details.m_encoding, m_details.m_numBytes, m_details.m_timestamp, m_details.m_arrivalTimestamp,
- m_details.m_sequenceNumber, m_details.m_rtpPayloadType, m_details.m_channel);
+ if(!m_numChannels)
+ {
+ string.Format("encoding:%d numBytes:%u ts:%u ats:%u seq:%u rtp-pt:%d ch:%u",
+ m_details.m_encoding, m_details.m_numBytes, m_details.m_timestamp, m_details.m_arrivalTimestamp,
+ m_details.m_sequenceNumber, m_details.m_rtpPayloadType, m_details.m_channel);
+ }
+ else
+ {
+ string.Format("encoding:%d numBytesPerChannel:%u numChannels:%d ts:%u ats:%u seq:%u rtp-pt:%d",
+ m_details.m_encoding, m_details.m_numBytes, m_numChannels, m_details.m_timestamp,
+ m_details.m_arrivalTimestamp, m_details.m_sequenceNumber, m_details.m_rtpPayloadType);
+ }
}
-void* AudioChunk::CreateBuffer(AudioChunkDetails& details)
+void AudioChunk::FreeAll()
{
if(m_pBuffer)
{
free(m_pBuffer);
m_pBuffer = NULL;
}
+ if(m_numChannels)
+ {
+ for(int i = 0; i < m_numChannels; i++)
+ {
+ if(m_pChannelAudio[i])
+ {
+ free(m_pChannelAudio[i]);
+ }
+ }
+
+ free(m_pChannelAudio);
+ m_pChannelAudio = NULL;
+ }
+}
+
+void AudioChunk::CreateMultiChannelBuffers(AudioChunkDetails& details)
+{
+ if(!m_numChannels)
+ {
+ return;
+ }
+
+ for(int i = 0; i < m_numChannels; i++)
+ {
+ m_pChannelAudio[i] = calloc(details.m_numBytes, 1);
+ if(!m_pChannelAudio[i])
+ {
+ CStdString exception;
+
+ exception.Format("AudioChunk::CreateMultiChannelBuffers failed to calloc buffer of size:%d for channel:%d", details.m_numBytes, i);
+ throw(exception);
+ }
+ }
+}
+
+void* AudioChunk::CreateBuffer(AudioChunkDetails& details)
+{
+ FreeAll();
if(details.m_numBytes)
{
m_pBuffer = calloc(details.m_numBytes, 1);
+ CreateMultiChannelBuffers(details);
}
if (!m_pBuffer)
{
@@ -86,6 +158,38 @@ void AudioChunk::SetBuffer(void* pBuffer, AudioChunkDetails& details)
}
}
+void AudioChunk::SetBuffer(void* pBuffer, AudioChunkDetails& details, int chan)
+{
+ CStdString exception;
+ int chanIdx = 0;
+
+ if(chan > m_numChannels || chan < 1)
+ {
+ exception.Format("AudioChunk::SetBuffer: invalid channel %d", chan);
+ throw(exception);
+ }
+
+ chanIdx = chan - 1;
+ if(m_pChannelAudio[chanIdx])
+ {
+ free(m_pChannelAudio[chanIdx]);
+ m_pChannelAudio[chanIdx] = NULL;
+ }
+ if(details.m_numBytes && pBuffer)
+ {
+ m_pChannelAudio[chanIdx] = malloc(details.m_numBytes);
+ if(!m_pChannelAudio[chanIdx])
+ {
+ exception.Format("AudioChunk::SetBuffer: failed to allocate buffer of size:%d for channel:%d channelidx:%d", details.m_numBytes, chan, chanIdx);
+ throw(exception);
+ }
+ else
+ {
+ memcpy(m_pChannelAudio[chanIdx], pBuffer, details.m_numBytes);
+ }
+ }
+}
+
int AudioChunk::GetNumSamples()
{
switch(m_details.m_encoding)
diff --git a/orkbasecxx/AudioCapture.h b/orkbasecxx/AudioCapture.h
index 1d6ae78..972e428 100644
--- a/orkbasecxx/AudioCapture.h
+++ b/orkbasecxx/AudioCapture.h
@@ -55,7 +55,8 @@ public:
unsigned int m_sequenceNumber;
unsigned int m_sampleRate;
char m_rtpPayloadType; // -1 if none
- unsigned char m_channel; // 0 if mono, 1 or 2 if stereo
+ unsigned char m_channel; // 0 if mono, 1 or 2 if stereo, 100 if we have
+ // separated multiple channels
};
/**
@@ -65,21 +66,31 @@ class DLL_IMPORT_EXPORT_ORKBASE AudioChunk
{
public:
AudioChunk();
+ AudioChunk(int numChannels);
~AudioChunk();
void ToString(CStdString&);
+ /** Creates n zeroed buffers where n=m_numChannels */
+ void CreateMultiChannelBuffers(AudioChunkDetails& details);
+
/** Allocate a new empty buffer (zeroed) */
void* CreateBuffer(AudioChunkDetails& details);
/** Copy external buffer to internal buffer. Create internal buffer if necessary */
void SetBuffer(void* pBuffer, AudioChunkDetails& details);
+ /** Copy external buffer to internal buffer. Create internal buffer if necessary */
+ void SetBuffer(void* pBuffer, AudioChunkDetails& details, int chan);
+
/** Computes the Root-Mean-Square power value of the buffer */
double ComputeRms();
/** Compute the RMS decibel value of the buffer with a 0 dB reference being the maximum theoretical RMS power of the buffer (2^15) */
double ComputeRmsDb();
+ /** Free's all memory allocated */
+ void FreeAll();
+
int GetNumSamples();
int GetNumBytes();
int GetSampleRate();
@@ -90,6 +101,12 @@ public:
void * m_pBuffer;
+ //==========================================================
+ // Additions to support separate audio for multiple channels
+
+ int m_numChannels;
+ void ** m_pChannelAudio;
+
private:
AudioChunkDetails m_details;
};
diff --git a/orkbasecxx/Config.cpp b/orkbasecxx/Config.cpp
index 7699525..2da7141 100644
--- a/orkbasecxx/Config.cpp
+++ b/orkbasecxx/Config.cpp
@@ -68,6 +68,8 @@ Config::Config()
m_allowAutomaticRecording = ALLOW_AUTOMATIC_RECORDING_DEFAULT;
m_captureFileSizeLimitKb = CAPTURE_FILE_SIZE_LIMIT_KB_DEFAULT;
m_partyFilter.clear();
+ m_stereoRecording = STEREO_RECORDING_DEFAULT;
+ m_tapeNumChannels = TAPE_NUM_CHANNELS_DEFAULT;
m_tapeDurationMinimumSec = TAPE_DURATION_MINIMUM_SEC_DEFAULT;
}
@@ -141,6 +143,8 @@ void Config::Define(Serializer* s)
s->BoolValue(ALLOW_AUTOMATIC_RECORDING_PARAM, m_allowAutomaticRecording); // only valid in non-lookback mode
s->IntValue(CAPTURE_FILE_SIZE_LIMIT_KB_PARAM, m_captureFileSizeLimitKb);
s->CsvValue(PARTY_FILTER_PARAM, m_partyFilter);
+ s->BoolValue(STEREO_RECORDING_PARAM, m_stereoRecording);
+ s->IntValue(TAPE_NUM_CHANNELS_PARAM, m_tapeNumChannels);
s->IntValue(TAPE_DURATION_MINIMUM_SEC_PARAM, m_tapeDurationMinimumSec);
}
@@ -185,6 +189,12 @@ void Config::Validate()
throw(exc);
}
}
+ if(m_tapeNumChannels < 0)
+ {
+ CStdString exception;
+ exception.Format("Config::Validate: please set a valid number for TapeNumChannels - currently:%d", m_tapeNumChannels);
+ throw(exception);
+ }
}
CStdString Config::GetClassName()
diff --git a/orkbasecxx/Config.h b/orkbasecxx/Config.h
index 0713c47..561e238 100644
--- a/orkbasecxx/Config.h
+++ b/orkbasecxx/Config.h
@@ -104,6 +104,10 @@
#define CAPTURE_FILE_SIZE_LIMIT_KB_DEFAULT 300000
#define PARTY_FILTER_PARAM "PartyFilter"
#define PARTY_FILTER_DEFAULT ""
+#define STEREO_RECORDING_PARAM "StereoRecording"
+#define STEREO_RECORDING_DEFAULT false
+#define TAPE_NUM_CHANNELS_PARAM "TapeNumChannels"
+#define TAPE_NUM_CHANNELS_DEFAULT 2
#define TAPE_DURATION_MINIMUM_SEC_PARAM "TapeDurationMinimumSec"
#define TAPE_DURATION_MINIMUM_SEC_DEFAULT 0
@@ -166,6 +170,8 @@ public:
bool m_allowAutomaticRecording;
int m_captureFileSizeLimitKb;
std::list<CStdString> m_partyFilter;
+ bool m_stereoRecording;
+ int m_tapeNumChannels;
int m_tapeDurationMinimumSec;
private:
diff --git a/orkbasecxx/audiofile/LibSndFileFile.cpp b/orkbasecxx/audiofile/LibSndFileFile.cpp
index c7377fd..6250057 100644
--- a/orkbasecxx/audiofile/LibSndFileFile.cpp
+++ b/orkbasecxx/audiofile/LibSndFileFile.cpp
@@ -19,12 +19,12 @@
LibSndFileFile::LibSndFileFile(int fileFormat)
{
- m_fileInfo.format = fileFormat;
+ m_fileInfo.format = fileFormat;
m_fileInfo.frames = 0;
- m_fileInfo.samplerate = 0;
- m_fileInfo.channels = 0;
- m_fileInfo.sections = 0;
- m_fileInfo.seekable = 0;
+ m_fileInfo.samplerate = 0;
+ m_fileInfo.channels = 0;
+ m_fileInfo.sections = 0;
+ m_fileInfo.seekable = 0;
m_pFile = NULL;
m_numChunksWritten = 0;
m_mode = READ;
@@ -43,7 +43,21 @@ void LibSndFileFile::Open(CStdString& filename, fileOpenModeEnum mode, bool ster
m_filename = filename + ".wav";
}
m_mode = mode;
- stereo ? m_fileInfo.channels = 2 : m_fileInfo.channels = 1;
+ if(CONFIG.m_stereoRecording == true)
+ {
+ if(CONFIG.m_tapeNumChannels > 1)
+ {
+ m_fileInfo.channels = CONFIG.m_tapeNumChannels;
+ }
+ else
+ {
+ m_fileInfo.channels = 1;
+ }
+ }
+ else
+ {
+ m_fileInfo.channels = 1;
+ }
if(m_sampleRate == 0)
{
m_sampleRate = sampleRate;
@@ -82,18 +96,95 @@ void LibSndFileFile::WriteChunk(AudioChunkRef chunkRef)
{
if( chunkRef->GetEncoding() == AlawAudio || chunkRef->GetEncoding() == UlawAudio)
{
- if(sf_write_raw(m_pFile, chunkRef->m_pBuffer, chunkRef->GetNumSamples()) != chunkRef->GetNumSamples())
+ // We have faith that whoever is producing these chunks created them
+ // with the same number of channels that we have opened the soundfile
+ // with above - this is enforced in the RtpMixer
+ if(chunkRef->m_numChannels > 0 && CONFIG.m_stereoRecording == true)
+ {
+ int numBytes = 0;
+ unsigned char *muxAudio = NULL;
+ unsigned char *wrPtr = NULL;
+
+ numBytes = chunkRef->GetNumSamples() * chunkRef->m_numChannels;
+ muxAudio = (unsigned char*)malloc(numBytes);
+ wrPtr = muxAudio;
+
+ if(!muxAudio)
+ {
+ CStdString exception;
+
+ exception.Format("sf_write_raw failed for stereo write: could not allocate %d bytes", numBytes);
+ throw(exception);
+ }
+
+ for(int x = 0; x < chunkRef->GetNumSamples(); x++)
+ {
+ for(int i = 0; i < chunkRef->m_numChannels; i++)
+ {
+ *wrPtr++ = (unsigned char)*((unsigned char*)(chunkRef->m_pChannelAudio[i])+x);
+ }
+ }
+
+ if(sf_write_raw(m_pFile, muxAudio, numBytes) != numBytes)
+ {
+ CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
+ free(muxAudio);
+ throw(CStdString("sf_write_raw failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ }
+ free(muxAudio);
+ }
+ else
{
- CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
- throw(CStdString("sf_write_raw failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ if(sf_write_raw(m_pFile, chunkRef->m_pBuffer, chunkRef->GetNumSamples()) != chunkRef->GetNumSamples())
+ {
+ CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
+ throw(CStdString("sf_write_raw failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ }
}
}
else if (chunkRef->GetEncoding() == PcmAudio)
{
- if(sf_write_short(m_pFile, (short*)chunkRef->m_pBuffer, chunkRef->GetNumSamples()) != chunkRef->GetNumSamples())
+ // We have faith that whoever is producing these chunks created them
+ // with the same number of channels that we have opened the soundfile
+ // with above - this is enforced in the RtpMixer
+ if(chunkRef->m_numChannels > 0 && CONFIG.m_stereoRecording == true)
+ {
+ int numShorts = 0;
+ short *muxAudio = NULL;
+ short *wrPtr = NULL;
+
+ numShorts = chunkRef->GetNumSamples()*chunkRef->m_numChannels;
+ muxAudio = (short*)calloc(numShorts, sizeof(short));
+ wrPtr = muxAudio;
+ if(!muxAudio)
+ {
+ CStdString exception;
+
+ exception.Format("sf_write_raw failed for stereo write: could not allocate %d bytes", numShorts*sizeof(short));
+ throw(exception);
+ }
+ for(int x = 0; x < chunkRef->GetNumSamples(); x++)
+ {
+ for(int i = 0; i < chunkRef->m_numChannels; i++)
+ {
+ *wrPtr++ = (short)*((short*)(chunkRef->m_pChannelAudio[i])+x);
+ }
+ }
+ if(sf_write_short(m_pFile, muxAudio, numShorts) != numShorts)
+ {
+ CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
+ free(muxAudio);
+ throw(CStdString("sf_write_short failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ }
+ free(muxAudio);
+ }
+ else
{
- CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
- throw(CStdString("sf_write_short failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ if(sf_write_short(m_pFile, (short*)chunkRef->m_pBuffer, chunkRef->GetNumSamples()) != chunkRef->GetNumSamples())
+ {
+ CStdString numChunksWrittenString = IntToString(m_numChunksWritten);
+ throw(CStdString("sf_write_short failed, audio file " + m_filename + " could not be written after " + numChunksWrittenString + " chunks written"));
+ }
}
}
m_numChunksWritten++;