diff options
author | Gerald Begumisa <ben_g@users.sourceforge.net> | 2008-02-27 19:21:41 +0000 |
---|---|---|
committer | Gerald Begumisa <ben_g@users.sourceforge.net> | 2008-02-27 19:21:41 +0000 |
commit | 0937742d2f5689c93efca3a5a56e8b36f81152c7 (patch) | |
tree | a0c817c8b45c888c88d020583a45a5ec65a225dd | |
parent | 9483156b326918005bb42751cc0e37a16b3e3e41 (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.cpp | 409 | ||||
-rw-r--r-- | orkbasecxx/AudioCapture.cpp | 118 | ||||
-rw-r--r-- | orkbasecxx/AudioCapture.h | 19 | ||||
-rw-r--r-- | orkbasecxx/Config.cpp | 10 | ||||
-rw-r--r-- | orkbasecxx/Config.h | 6 | ||||
-rw-r--r-- | orkbasecxx/audiofile/LibSndFileFile.cpp | 115 |
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++; |