#include #include # include #include #include #include #include #include #include #include #include #include #include #include #include #include "arplayer.h" #include "Recorder.h" #include "InputPlayer.h" #include "AudioRingBuffer3.h" extern input_map inputMap; Recorder::Recorder(void) { errCode = 0; UID = 0; TimeStamp.mSampleTime = 0; readTime = -1; t_limit = 0.0; stopTime = 0.0; ratio = 1.0; RecordTime = 0.0; runThread = false; converter = NULL; output_instance = NULL; outputData.mBuffers[0].mData = NULL; record_thread = 0; sourceBuffDur = 0.0; inputTime = 0.0; delay = 0.0; blockSize = 256; // processing block: samples per channel wakeMutex = NULL; wakeSemaphore = NULL; SourceBuffer = NULL; sourceAU = NULL; mInputBuffer = NULL; source = "none"; } Recorder::~Recorder(void) { UnInit(); if(output_instance) delete output_instance; deleteMetaRecord(UID); } void Recorder::UnInit(void) { stop(); if(converter){ AudioUnitUninitialize(converter); CloseComponent(converter); converter = NULL; } if(outputData.mBuffers[0].mData) free(outputData.mBuffers[0].mData); shutdownSource(); } unsigned char Recorder::init(void) { string typeStr; int lMap; unsigned long size; AURenderCallbackStruct rcbs; ComponentDescription desc; Component comp; OSErr err; if(output_instance) return false; // can't re-init t_limit = atof(GetMetaData(UID, "Limit").c_str()); source = GetMetaData(UID, "Source"); lMap = atoi(GetMetaData(UID, "Bus").c_str()); lMap = lMap * 2; if(source == "mixer"){ //mixer output bus is the source if((lMap < 0) || (lMap >= outChNum-1)){ return false; }else{ Bus[0] = lMap; Bus[1] = lMap+1; } }else{ // an input is the specified source lMap = 0; // inputs only have one stereo bus Bus[0] = lMap; Bus[1] = lMap+1; } typeStr = GetMetaData(UID, "Type"); if(typeStr == "aiff"){ output_instance = new aiffOut(); if(!output_instance->init(this)){ delete output_instance; output_instance = NULL; return false; } }else if(typeStr == "wave"){ output_instance = new waveOut(); if(!output_instance->init(this)){ delete output_instance; output_instance = NULL; return false; } }else if(typeStr == "lame/mp3"){ output_instance = new mp3FileOut(); if(!output_instance->init(this)){ delete output_instance; output_instance = NULL; return false; } }else if(typeStr == "shoutcast"){ output_instance = new shoutcastOut(); if(!output_instance->init(this)){ delete output_instance; output_instance = NULL; return false; } }else return false; // try to set up a new source if(!initSource()) return false; // get the source output audio data format int element = 1; if(sourceAU == &mixer) element = 0; size = sizeof(sourceFormat); err = AudioUnitGetProperty( *sourceAU, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, element, &sourceFormat, &size ); if (err) goto Fail; // we will only be using 2 output buses sourceFormat.mChannelsPerFrame = 2; // out going channel buffer size in bytes, interleaved channels size = (unsigned long)(blockSize * fileStreamFormat.mBytesPerPacket); // size is a scaled version of the input data block size based on the sample rate conversion +10% margin outputData.mNumberBuffers = 1; // single interleaved buffer outputData.mBuffers[0].mNumberChannels = fileStreamFormat.mChannelsPerFrame; outputData.mBuffers[0].mDataByteSize = size; outputData.mBuffers[0].mData = malloc(size); // create the audio converter to convert the mixer output format to the desired file format desc.componentType = kAudioUnitType_FormatConverter; desc.componentSubType = kAudioUnitSubType_AUConverter; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; if(comp = FindNextComponent(0, &desc)){ err = OpenAComponent(comp, &converter); if (err) goto Fail; }else{ goto Fail; } // set converter input format to the format of the mixer output buffers err = AudioUnitSetProperty( converter, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &sourceFormat, sizeof(sourceFormat) ); if (err) goto Fail; // set converter output format to desired file (or stream) err = AudioUnitSetProperty( converter, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &fileStreamFormat, sizeof(fileStreamFormat) ); if (err) goto Fail; ratio = sourceFormat.mSampleRate/fileStreamFormat.mSampleRate; // set converter input to our buffer reader callback rcbs.inputProc = &Recorder::ReadBuffer; rcbs.inputProcRefCon = this; err = AudioUnitSetProperty( converter, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &rcbs, sizeof(rcbs) ); if (err) goto Fail; // initialize the sample rate convereter err = AudioUnitInitialize(converter); if (err) goto Fail; errCode = 0; return true; Fail: if(output_instance){ delete output_instance; output_instance = NULL; } if(converter){ AudioUnitUninitialize(converter); CloseComponent(converter); converter = NULL; } shutdownSource(); return false; } unsigned char Recorder::initSource(void) { input_map_ptr dp; if(source == "mixer"){ // mixer output buses... special case sourceAU = &mixer; SourceBuffer = BusBuffer; wakeMutex = &pushMutex; wakeSemaphore = &pushSemaphore; sourceOutputTime = &MasterOutputTime; sourceBuffDur = bufDuration; return true; }else{ // get input definition by name pthread_mutex_lock( &iMutex ); // find the pair given the key dp = inputMap.find(source); if (dp == inputMap.end()){ // not found... exit pthread_mutex_unlock( &iMutex ); return false; } sourceBuffDur = bufDuration; inputTime = 0.0; pthread_mutex_unlock( &iMutex ); if(InputSetUp(GetDeviceID((*dp).second.deviceUID.c_str()), (*dp).second.channelMap)){ sourceAU = &mInputUnit; wakeMutex = &inputMutex; wakeSemaphore = &inputSemaphore; sourceOutputTime = &inputTime; return true; } return false; } } unsigned char Recorder::InputSetUp(AudioDeviceID in, long *OutputMap) { OSStatus err; AURenderCallbackStruct rcbs; unsigned long size; unsigned long enableIO; unsigned long inbufferSizeFrames; ComponentDescription desc; Component comp; float sampleRate; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_HALOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; if(comp = FindNextComponent(0, &desc)){ err = OpenAComponent(comp, &mInputUnit); if (err) return false; }else{ return false; } //ENABLE IO (INPUT) //You must enable the Audio Unit (AUHAL) for input and disable output //BEFORE setting the AUHAL's current device. //Enable input on the AUHAL enableIO = 1; err = AudioUnitSetProperty(mInputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, // input element &enableIO, sizeof(enableIO)); if(err) return false; //disable Output on the AUHAL enableIO = 0; err = AudioUnitSetProperty( mInputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, //output element &enableIO, sizeof(enableIO)); if(err) return false; size = sizeof(AudioDeviceID); //Set the in Device to the AUHAL. //this should be done only after IO has been enabled on the AUHAL. err = AudioUnitSetProperty( mInputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &in, size); if(err) return false; // get the input audio data format and save the sample rate size = sizeof(sourceFormat); err = AudioUnitGetProperty( mInputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &sourceFormat, &size ); if (err) return false; sampleRate = sourceFormat.mSampleRate; // use the first mixer output as a template for the desired audio format size = sizeof(sourceFormat); err = AudioUnitGetProperty( mixer, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &sourceFormat, &size ); if (err) return false; // set AUHAL output to desired output format (same as mixer) with the sample rate portion of the format set to the input device rate and 2 channels sourceFormat.mSampleRate = sampleRate; sourceFormat.mChannelsPerFrame = 2; //Set new stream format to AUHAL err = AudioUnitSetProperty( mInputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &sourceFormat, sizeof(sourceFormat)); if (err) return false; //Setup the input to output channel maping. if(OutputMap == NULL) return false; size = sourceFormat.mChannelsPerFrame * sizeof(long); err = AudioUnitSetProperty(mInputUnit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Output, 1, OutputMap, size); if(err) return false; rcbs.inputProc = &Recorder::FillSourceBuffer; rcbs.inputProcRefCon = this; //Setup the input callback to our ring buffer filler routine. err = AudioUnitSetProperty( mInputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &rcbs, sizeof(rcbs)); if(err) return false; // initialize the input AUHAL err = AudioUnitInitialize(mInputUnit); if (err) return false; //Get the size of the In device buffer size = sizeof(inbufferSizeFrames); err = AudioUnitGetProperty( mInputUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 1, &inbufferSizeFrames, &size); if(err) return false; if(SetupBuffers(inbufferSizeFrames)){ AudioOutputUnitStart(mInputUnit); pthread_mutex_init(&inputMutex, NULL); pthread_cond_init(&inputSemaphore, NULL); return true; } return false; } //Allocate Audio Buffer List(s) to hold the data from input. unsigned char Recorder::SetupBuffers(unsigned long inbufferSizeFrames) { unsigned long bufferSizeBytes, propsize; bufferSizeBytes = inbufferSizeFrames * sizeof(float); //calculate number of buffers from channels propsize = offsetof(AudioBufferList, mBuffers[sourceFormat.mChannelsPerFrame]); //malloc buffer lists mInputBuffer = (AudioBufferList *)malloc(propsize); mInputBuffer->mNumberBuffers = sourceFormat.mChannelsPerFrame; //pre-malloc buffers for AudioBufferLists for(unsigned long i =0; i< mInputBuffer->mNumberBuffers; i++) { mInputBuffer->mBuffers[i].mNumberChannels = 1; mInputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes; mInputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes); } //Alloc ring buffer that will hold data between the two audio devices SourceBuffer = new AudioRingBuffer(); SourceBuffer->Allocate(sourceFormat.mChannelsPerFrame, sourceFormat.mBytesPerFrame, (long unsigned int)(sourceFormat.mSampleRate * sourceBuffDur)); return true; } void Recorder::shutdownSource(void) { if(wakeMutex != &pushMutex) if(wakeMutex != NULL) pthread_mutex_destroy(wakeMutex); if(wakeSemaphore != &pushSemaphore) if(wakeSemaphore != NULL) pthread_cond_destroy(wakeSemaphore); if((sourceAU != &mixer) && (sourceAU != NULL)){ AudioOutputUnitStop(*sourceAU); AudioUnitUninitialize(*sourceAU); CloseComponent(*sourceAU); } if(mInputBuffer){ for(unsigned long i = 0; imNumberBuffers; i++){ free(mInputBuffer->mBuffers[i].mData); } free(mInputBuffer); mInputBuffer = NULL; } if(SourceBuffer){ if(SourceBuffer != BusBuffer){ delete SourceBuffer; SourceBuffer = NULL; } } sourceAU = NULL; wakeMutex = NULL; wakeSemaphore = NULL; SourceBuffer = NULL; } void Recorder::start(void) { if(converter){ stopTime = 0.0; if (!record_thread){ // create the recording thread pthread_create(&record_thread, NULL, &Recorder::RecordingThread, this); } } } void Recorder::stop(void) { if (record_thread){ stopTime = *sourceOutputTime; pthread_join(record_thread, NULL); record_thread = 0; errCode = 0; } } void Recorder::post(ProgramLogStruct* rec) { if(record_thread){ if(output_instance){ output_instance->post(rec); } } } unsigned char Recorder::SetDelay(float sec) { double locDelay; if(sec < 0) return false; if(sec > 10) return false; locDelay = sec * sourceFormat.mSampleRate; if(locDelay > readTime) return false; delay = locDelay; return true; } float Recorder::GetDelay(void) { return delay / sourceFormat.mSampleRate; } void *Recorder::RecordingThread(void* refCon) { Recorder *parent = (Recorder *)(refCon); unsigned long sampleCount, size; double localOutTime; OSStatus err = noErr; AudioUnitRenderActionFlags ioActionFlags; parent->runThread = true; parent->errCode = 7; // status = starting if(!parent->output_instance->Start()){ ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": failed to start"); parent->errCode = 2; parent->runThread = false; pthread_exit(0); } parent->errCode = 0; parent->readTime = *parent->sourceOutputTime; while (parent->runThread){ if((parent->t_limit > 0) && (parent->RecordTime >= parent->t_limit)){ parent->runThread = false; parent->errCode = 1; } sampleCount = parent->blockSize; localOutTime = *parent->sourceOutputTime; while (localOutTime > (parent->readTime + 16 + (parent->ratio * parent->blockSize))){ if((parent->t_limit > 0) && (parent->RecordTime >= parent->t_limit)){ parent->runThread = false; parent->errCode = 1; } if((parent->stopTime != 0) && (parent->stopTime < parent->readTime)){ parent->runThread = false; goto bail; } //Get the new audio data err= AudioUnitRender(parent->converter, &ioActionFlags, &parent->TimeStamp, 0, sampleCount, //# of frames requested &parent->outputData); parent->TimeStamp.mSampleTime = parent->TimeStamp.mSampleTime + sampleCount; if(err){ // handle error reading master buffer if(!parent->output_instance->CantKeepUp()){ parent->errCode = 3; parent->runThread = false; goto bail; } } // get size in bytes of output data: sample counter now has the output rate converted sample count size = sampleCount * parent->fileStreamFormat.mBytesPerPacket; // pass the flat sample buffer on to the output instance if(err = parent->output_instance->passSampleBufferIn(parent->outputData.mBuffers[0].mData, size)){ // handle error passing samples to the output instance parent->output_instance->CantKeepUp(); if(err > 0){ parent->errCode = err; parent->runThread = false; } } // update record time parent->RecordTime = parent->TimeStamp.mSampleTime / parent->fileStreamFormat.mSampleRate; // reset sample count to reference soure block sampleCount = parent->blockSize; } // wait for more samples to be pulled in from the mixer to the master output pthread_mutex_lock( parent->wakeMutex ); pthread_cond_wait( parent->wakeSemaphore, parent->wakeMutex ); pthread_mutex_unlock( parent->wakeMutex ); } bail: if(atoi(GetMetaData(parent->UID, "Close").c_str())){ // close self after stopping TaskItem *task; char *passIn; string name; passIn = (char*)malloc(32); // will be freed aftre Task completes snprintf(passIn, sizeof(passIn), "closerec %08lx", parent->UID); name = string(passIn); snprintf(passIn, sizeof(passIn), "closerec %08lx\n", parent->UID); task = new TaskItem(name, ExecuteCommand, (void *)passIn, 0L, 0L, false); } parent->output_instance->Stop(); pthread_exit(0); } OSStatus Recorder::ReadBuffer(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *TimeStamp, unsigned long inBusNumber, unsigned long inNumberFrames, AudioBufferList * ioData) { AudioRingBufferError err; Recorder *parent = (Recorder *)inRefCon; unsigned int i; //copy the data from the buffer starting at ReadPointer time if(err = parent->SourceBuffer->Fetch(ioData, inNumberFrames, (long long int)(parent->readTime - parent->delay), 0, parent->Bus)){ return err; } if(parent->fileStreamFormat.mChannelsPerFrame == 1){ // mono mode.... sum right into left channel float *LBuff, *RBuff; LBuff = (float*)(ioData->mBuffers[0].mData); RBuff = (float*)(ioData->mBuffers[1].mData); for(i=0; i < inNumberFrames; i++) LBuff[i] = LBuff[i] + RBuff[i]; } // advance source buffer read pointer parent->readTime = parent->readTime + inNumberFrames; return noErr; } OSStatus Recorder::FillSourceBuffer(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, unsigned long inBusNumber, unsigned long inNumberFrames, AudioBufferList * ioData) { OSStatus err = noErr; Recorder *parent = (Recorder *)inRefCon; //Get the new audio data err= AudioUnitRender(parent->mInputUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, //# of frames requested parent->mInputBuffer); // Audio Buffer List to hold data parent->SourceBuffer->Store(parent->mInputBuffer, inNumberFrames, (long long int)inTimeStamp->mSampleTime); parent->inputTime = inTimeStamp->mSampleTime + inNumberFrames; pthread_cond_broadcast(parent->wakeSemaphore); return err; } aiffOut::aiffOut(void) { audioFileID = NULL; } aiffOut::~aiffOut(void) { if(audioFileID) AudioFileClose(audioFileID); } unsigned char aiffOut::init(Recorder* inParent) { time_t now; string destStr; struct tm loc_t; struct stat fileInfo; char baseName[32]; CFStringRef CFSFileName; FSRef dirRef; int statErr; OSErr err; parent = inParent; if(GetMetaData(parent->UID, "Path").length() == 0) return false; if(statErr = stat(GetMetaData(parent->UID, string("Path")).c_str(), &fileInfo)){ // an error occured trying to get file information return false; }else{ // no error... look at the file info to see if it is a directory if(fileInfo.st_mode & S_IFDIR){ // It's a directory... if(GetMetaData(parent->UID, "Name").length() == 0){ // make a file named with current date/time. now = time(NULL); localtime_r(&now, &loc_t); // Use date/time for file name strftime(baseName, sizeof baseName, "%Y-%m-%d-%HH%MM%SS", &loc_t); destStr = string(baseName) + ".aif"; SetMetaData(parent->UID, "Name", string(baseName)); }else{ // name already specified... use it! destStr = GetMetaData(parent->UID, "Name") + ".aif"; } }else{ // Not a directory and it already exists... don't write over it! return false; } } // all is well. Proceeed! //An interleaved stereo file AudioStreamBasicDescription: parent->fileStreamFormat.mSampleRate = atof(GetMetaData(parent->UID, string("SampleRate")).c_str()); parent->fileStreamFormat.mFormatID = kAudioFormatLinearPCM; parent->fileStreamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsBigEndian; if(atoi(GetMetaData(parent->UID, "Mode").c_str())){ parent->fileStreamFormat.mChannelsPerFrame = 2; }else{ parent->fileStreamFormat.mChannelsPerFrame = 1; } parent->fileStreamFormat.mBitsPerChannel = atoi(GetMetaData(parent->UID, "SampleSize").c_str()); parent->fileStreamFormat.mBytesPerFrame = parent->fileStreamFormat.mBitsPerChannel * parent->fileStreamFormat.mChannelsPerFrame / 8; parent->fileStreamFormat.mBytesPerPacket = parent->fileStreamFormat.mBytesPerFrame; parent->fileStreamFormat.mFramesPerPacket = 1; // Convert our file name to an FSRef if (FSPathMakeRef((const unsigned char *)(GetMetaData(parent->UID, "Path").c_str()), &dirRef, NULL) != noErr) return false; CFSFileName = CFStringCreateWithCString(NULL, destStr.c_str(), kCFStringEncodingMacRoman); err = AudioFileCreate (&dirRef, CFSFileName, kAudioFileAIFFType, &parent->fileStreamFormat, 0, &fileRef, &audioFileID); CFRelease(CFSFileName); if (err) { audioFileID = NULL; return false; } return true; } unsigned char aiffOut::Start(void) { return true; } void aiffOut::Stop(void) { return; } void aiffOut::post(ProgramLogStruct* rec) { return; } OSStatus aiffOut::passSampleBufferIn(void* buffer, size_t bufSize) { OSErr err; size_t size; size = bufSize; if(audioFileID){ // write bytes to file err = AudioFileWriteBytes ( audioFileID, false, (long long int)(parent->TimeStamp.mSampleTime * parent->fileStreamFormat.mBytesPerPacket), &size, buffer ); if(err) return 4; }else{ return 4; } return 0; } unsigned char aiffOut::CantKeepUp(void) { ServerLoger->MakeEntry("Recorder- "+ GetMetaData(parent->UID, string("Name")) + ": writing to file too far behind; stopping."); return false; } waveOut::waveOut(void) { audioFileID = NULL; } waveOut::~waveOut(void) { if(audioFileID) AudioFileClose(audioFileID); } unsigned char waveOut::init(Recorder* inParent) { time_t now; string destStr; struct tm loc_t; struct stat fileInfo; char baseName[32]; CFStringRef CFSFileName; FSRef dirRef; int statErr; OSErr err; parent = inParent; if(GetMetaData(parent->UID, "Path").length() == 0) return false; if(statErr = stat(GetMetaData(parent->UID, string("Path")).c_str(), &fileInfo)){ // an error occured trying to get file information return false; }else{ // no error... look at the file info to see if it is a directory if(fileInfo.st_mode & S_IFDIR){ // It's a directory... if(GetMetaData(parent->UID, "Name").length() == 0){ // make a file named with current date/time. now = time(NULL); localtime_r(&now, &loc_t); // Use date/time for file name strftime(baseName, sizeof baseName, "%Y-%m-%d-%HH%MM%SS", &loc_t); destStr = string(baseName) + ".wav"; SetMetaData(parent->UID, "Name", string(baseName)); }else{ // name already specified... use it! destStr = GetMetaData(parent->UID, "Name") + ".wav"; } }else{ // Not a directory and it already exists... don't write over it! return false; } } // all is well. Proceeed! //An interleaved stereo file AudioStreamBasicDescription: parent->fileStreamFormat.mSampleRate = atof(GetMetaData(parent->UID, string("SampleRate")).c_str()); parent->fileStreamFormat.mFormatID = kAudioFormatLinearPCM; parent->fileStreamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; if(atoi(GetMetaData(parent->UID, "Mode").c_str())){ parent->fileStreamFormat.mChannelsPerFrame = 2; }else{ parent->fileStreamFormat.mChannelsPerFrame = 1; } parent->fileStreamFormat.mBitsPerChannel = atoi(GetMetaData(parent->UID, "SampleSize").c_str()); parent->fileStreamFormat.mBytesPerFrame = parent->fileStreamFormat.mBitsPerChannel * parent->fileStreamFormat.mChannelsPerFrame / 8; parent->fileStreamFormat.mBytesPerPacket = parent->fileStreamFormat.mBytesPerFrame; parent->fileStreamFormat.mFramesPerPacket = 1; // Convert our file name to an FSRef if (FSPathMakeRef((const unsigned char *)(GetMetaData(parent->UID, "Path").c_str()), &dirRef, NULL) != noErr) return false; CFSFileName = CFStringCreateWithCString(NULL, destStr.c_str(), kCFStringEncodingMacRoman); err = AudioFileCreate (&dirRef, CFSFileName, kAudioFileWAVEType, &parent->fileStreamFormat, 0, &fileRef, &audioFileID); CFRelease(CFSFileName); if (err) { audioFileID = NULL; return false; } return true; } unsigned char waveOut::Start(void) { return true; } void waveOut::Stop(void) { return; } void waveOut::post(ProgramLogStruct* rec) { return; } OSStatus waveOut::passSampleBufferIn(void* buffer, size_t bufSize) { OSErr err; size_t size; size = bufSize; if(audioFileID){ // write bytes to file err = AudioFileWriteBytes( audioFileID, false, (long long int)(parent->TimeStamp.mSampleTime * parent->fileStreamFormat.mBytesPerPacket), &size, buffer ); if(err) return 4; }else{ return 4; } return 0; } unsigned char waveOut::CantKeepUp(void) { ServerLoger->MakeEntry("Recorder- "+ GetMetaData(parent->UID, string("Name")) + ": writing to file too far behind; stopping."); return false; } mp3FileOut::mp3FileOut(void) { write_thread = 0; fp = NULL; encoder = 0; sockset[0] = 0; sockset[1] = 0; } mp3FileOut::~mp3FileOut(void) { Stop(); if(fp) fclose(fp); } unsigned char mp3FileOut::init(Recorder* inParent) { time_t now; string destStr; string outFilePath; struct tm loc_t; struct stat fileInfo; char baseName[32]; int statErr; parent = inParent; if(GetMetaData(parent->UID, "Path").length() == 0) return false; if(statErr = stat(GetMetaData(parent->UID, string("Path")).c_str(), &fileInfo)){ // an error occured trying to get file information return false; }else{ // no error... look at the file info to see if it is a directory if(fileInfo.st_mode & S_IFDIR){ // It's a directory... if(GetMetaData(parent->UID, "Name").length() == 0){ // make a file named with current date/time. now = time(NULL); localtime_r(&now, &loc_t); // Use date/time for file name strftime(baseName, sizeof baseName, "%Y-%m-%d-%HH%MM%SS", &loc_t); destStr = string(baseName) + ".mp3"; SetMetaData(parent->UID, "Name", string(baseName)); }else{ // name already specified... use it! destStr = GetMetaData(parent->UID, "Name") + ".mp3"; } }else{ // Not a directory and it already exists... don't write over it! return false; } } outFilePath = GetMetaData(parent->UID, string("Path")).c_str(); // all is well. Proceeed! //An interleaved stereo file AudioStreamBasicDescription: parent->fileStreamFormat.mSampleRate = atof(GetMetaData(parent->UID, "SampleRate").c_str()); parent->fileStreamFormat.mFormatID = kAudioFormatLinearPCM; parent->fileStreamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsBigEndian; if(atoi(GetMetaData(parent->UID, "Mode").c_str())){ parent->fileStreamFormat.mChannelsPerFrame = 2; }else{ parent->fileStreamFormat.mChannelsPerFrame = 1; } //For the LAME converter input, we use 16 bit sample size parent->fileStreamFormat.mBitsPerChannel = 16; parent->fileStreamFormat.mBytesPerFrame = parent->fileStreamFormat.mBitsPerChannel * parent->fileStreamFormat.mChannelsPerFrame / 8; parent->fileStreamFormat.mBytesPerPacket = parent->fileStreamFormat.mBytesPerFrame; parent->fileStreamFormat.mFramesPerPacket = 1; // Create and open the output file if(outFilePath[outFilePath.length()-1] != '/'){ // add trailing slash outFilePath = outFilePath + "/" + destStr; }else{ outFilePath = outFilePath + destStr; } fp = fopen(outFilePath.c_str(), "wb"); if(fp == NULL) return false; return true; } void mp3FileOut::SetupEncoder(void) { char buf[1024], name[256], artist[256], album[256]; char *argv[32]; char *save_pointer; char *workingDir; float ksps; string shellStr; int i; size_t count; workingDir = getcwd(NULL,0); if(workingDir == NULL) return; count = 0; // LAME command: raw input, auto mode, parity out - build the command string ksps = (float)atoi(GetMetaData(parent->UID, "SampleRate").c_str())/1000; if(atoi(GetMetaData(parent->UID, "Mode").c_str()) == 0) count = count + snprintf(buf, sizeof(buf), "lame -m m "); else count = count + snprintf(buf, sizeof(buf), "lame -m a "); if(count == sizeof(buf)-1) return; count = count + snprintf(&buf[count], sizeof(buf) - count, "-r -p -q %d -b %d -s %f --resample %f - -", atoi(GetMetaData(parent->UID, "Quality").c_str()), atoi(GetMetaData(parent->UID, "BitRate").c_str()), ksps, ksps); if(count == sizeof(buf)-1) return; // pars parameter sting into argv array i = 0; argv[i] = strtok_r(buf, " ", &save_pointer); while(argv[i]){ if(i++ > 31) return; argv[i] = strtok_r(NULL, " ", &save_pointer); } argv[i] = "--tt"; if(i++ > 31) return; argv[i] = name; if(i++ > 31) return; if(snprintf(name, sizeof(name), "%s", GetMetaData(parent->UID, "Name").c_str()) == sizeof(name)-1) return; if(GetMetaData(parent->UID, "Artist").length() > 0){ argv[i] = "--ta"; if(i++ > 31) return; argv[i] = artist; if(i++ > 31) return; if(snprintf(artist, sizeof(artist), "%s", GetMetaData(parent->UID, "Artist").c_str()) == sizeof(artist)-1) return; } if(GetMetaData(parent->UID, "Album").length() > 0){ argv[i] = "--tl"; if(i++ > 31) return; argv[i] = album; if(i++ > 31) return; if(snprintf(album, sizeof(album), "%s", GetMetaData(parent->UID, "Album").c_str()) == sizeof(album)-1) return; } argv[i] = NULL; close(STDERR_FILENO); if(sockset[1] != STDIN_FILENO) { // Redirect standard input to socketpair if(dup2(sockset[1], STDIN_FILENO) != STDIN_FILENO) return; } if(sockset[1] != STDOUT_FILENO) { // Redirect standard output to socketpair if(dup2(sockset[1], STDOUT_FILENO) != STDOUT_FILENO) return; } workingDir = getcwd(NULL,0); if(workingDir == NULL) return; string fullPath = string(workingDir) + "/encoders/lame"; free(workingDir); // run the new process! execv(fullPath.c_str(), argv); } unsigned char mp3FileOut::Start(void) { pid_t child; if(sockset[0]) return false; // sockset[0] for the parent to access, sockset[1] for the child if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockset) < 0) return false; child = fork(); if(child < 0) return false; if(child == 0){ // executed if we are the forked child close(sockset[0]); SetupEncoder(); parent->UnInit(); return false; }else{ // executed otherwise (parent) encoder = child; close(sockset[1]); sockset[1] = 0; // set socket write buffer to 10 seconds int buffSize = (int)(10 * parent->fileStreamFormat.mSampleRate * parent->fileStreamFormat.mBytesPerFrame); setsockopt(sockset[0], SOL_SOCKET, SO_SNDBUF, &buffSize, sizeof(buffSize)); // set socket read buffer to 10 seconds buffSize = (int)(10 * parent->fileStreamFormat.mSampleRate * parent->fileStreamFormat.mBytesPerFrame); setsockopt(sockset[0], SOL_SOCKET, SO_RCVBUF, &buffSize, sizeof(buffSize)); // set socket send timeout to 60 seconds struct timeval timeout; timeout.tv_sec = 10; /* seconds */ timeout.tv_usec = 0; /* and microseconds */ setsockopt(sockset[0], SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); if (!write_thread){ // create a thread that sends the mp3 packets to the sc server pthread_create(&write_thread, NULL, &mp3FileOut::WriteThread, this); } return true; } } void mp3FileOut::Stop(void) { if(encoder){ shutdown(sockset[0], 1); // encoder gets a read EOF, write still OK waitpid(encoder, NULL, 0); encoder = 0; } if(sockset[0]) close(sockset[0]); sockset[0] = 0; if(write_thread){ pthread_join(write_thread, NULL); write_thread = 0; } } void mp3FileOut::post(ProgramLogStruct* rec) { return; } OSStatus mp3FileOut::passSampleBufferIn(void* buffer, size_t bufSize) { size_t size; int sent; size = bufSize; if(sockset[0]){ // write bytes to encoder input socket sent = write(sockset[0], buffer, bufSize); if(sent != (signed int)bufSize) return 5; }else{ return 5; } return 0; } void *mp3FileOut::WriteThread(void* refCon) { mp3FileOut *parent = (mp3FileOut *)(refCon); int count; char dataBuff[1024]; do{ count = read(parent->sockset[0], dataBuff, sizeof(dataBuff)); if(count > 0) fwrite(dataBuff, count, 1, parent->fp); else break; }while(ferror(parent->fp) == 0); pthread_exit(0); } unsigned char mp3FileOut::CantKeepUp(void) { ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": encoder can't keep up with audio; stopping."); return false; } shoutcastOut::shoutcastOut(void) { transmit_thread = 0; tcp_socket = 0; encoder = 0; sockset[0] = 0; sockset[1] = 0; connected = false; retry = false; } shoutcastOut::~shoutcastOut(void) { Stop(); } unsigned char shoutcastOut::init(Recorder *inParent) { parent = inParent; if(GetMetaData(parent->UID, "Name").size() == 0) return false; if(GetMetaData(parent->UID, "Server").size() == 0) return false; if(GetMetaData(parent->UID, "Port").size() == 0) return false; if(GetMetaData(parent->UID, "Password").size() == 0) return false; //An interleaved stereo file AudioStreamBasicDescription: parent->fileStreamFormat.mSampleRate = atof(GetMetaData(parent->UID, "SampleRate").c_str()); parent->fileStreamFormat.mFormatID = kAudioFormatLinearPCM; parent->fileStreamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsBigEndian; if(atoi(GetMetaData(parent->UID, "Mode").c_str())){ parent->fileStreamFormat.mChannelsPerFrame = 2; }else{ parent->fileStreamFormat.mChannelsPerFrame = 1; } //For the LAME converter input, we use 16 bit sample size parent->fileStreamFormat.mBitsPerChannel = 16; parent->fileStreamFormat.mBytesPerFrame = parent->fileStreamFormat.mBitsPerChannel * parent->fileStreamFormat.mChannelsPerFrame / 8; parent->fileStreamFormat.mBytesPerPacket = parent->fileStreamFormat.mBytesPerFrame; parent->fileStreamFormat.mFramesPerPacket = 1; return true; } void shoutcastOut::SetupEncoder(void) { char buf[1024], name[256], artist[256], album[256]; char *argv[32]; char *save_pointer; char *workingDir; float ksps; string shellStr; int i; size_t count; workingDir = getcwd(NULL,0); if(workingDir == NULL) return; count = 0; // LAME command: raw input, auto mode, parity out - build the command string ksps = (float)atoi(GetMetaData(parent->UID, "SampleRate").c_str())/1000; if(atoi(GetMetaData(parent->UID, "Mode").c_str()) == 0) count = count + snprintf(buf, sizeof(buf), "lame -m m "); else count = count + snprintf(buf, sizeof(buf), "lame -m a "); if(count == sizeof(buf)-1) return; count = count + snprintf(&buf[count], sizeof(buf) - count, "-r -p -q %d -b %d -s %f --resample %f - -", atoi(GetMetaData(parent->UID, "Quality").c_str()), atoi(GetMetaData(parent->UID, "BitRate").c_str()), ksps, ksps); if(count == sizeof(buf)-1) return; // pars parameter sting into argv array i = 0; argv[i] = strtok_r(buf, " ", &save_pointer); while(argv[i]){ if(i++ > 31) return; argv[i] = strtok_r(NULL, " ", &save_pointer); } argv[i] = "--tt"; if(i++ > 31) return; argv[i] = name; if(i++ > 31) return; if(snprintf(name, sizeof(name), "%s", GetMetaData(parent->UID, "Name").c_str()) == sizeof(name)-1) return; if(GetMetaData(parent->UID, "Artist").length() > 0){ argv[i] = "--ta"; if(i++ > 31) return; argv[i] = artist; if(i++ > 31) return; if(snprintf(artist, sizeof(artist), "%s", GetMetaData(parent->UID, "Artist").c_str()) == sizeof(artist)-1) return; } if(GetMetaData(parent->UID, "Album").length() > 0){ argv[i] = "--tl"; if(i++ > 31) return; argv[i] = album; if(i++ > 31) return; if(snprintf(album, sizeof(album), "%s", GetMetaData(parent->UID, "Album").c_str()) == sizeof(album)-1) return; } argv[i] = NULL; close(STDERR_FILENO); if(sockset[1] != STDIN_FILENO) { // Redirect standard input to socketpair if(dup2(sockset[1], STDIN_FILENO) != STDIN_FILENO) return; } if(sockset[1] != STDOUT_FILENO) { // Redirect standard output to socketpair if(dup2(sockset[1], STDOUT_FILENO) != STDOUT_FILENO) return; } workingDir = getcwd(NULL,0); if(workingDir == NULL) return; string fullPath = string(workingDir) + "/encoders/lame"; free(workingDir); // run the new process! execv(fullPath.c_str(), argv); } void shoutcastOut::Stop(void) { if(encoder){ shutdown(sockset[0], 1); // encoder gets a read EOF, write still OK waitpid(encoder, NULL, 0); encoder = 0; } if(sockset[0]){ close(sockset[0]); sockset[0] = 0; } retry = false; if(tcp_socket){ close(tcp_socket); tcp_socket = 0; } if(transmit_thread){ pthread_join(transmit_thread, NULL); transmit_thread = 0; } connected = false; } void shoutcastOut::post(ProgramLogStruct* rec) { string httpReq; string Name, Artist, Album; struct sockaddr_in adrRec; // Internet address struct struct hostent* host; // host/IP translation int post_tcp_socket; string server = GetMetaData(parent->UID, "Server"); unsigned short port = atoi(GetMetaData(parent->UID, "Port").c_str()); string pw = GetMetaData(parent->UID, "Password"); if(escapeString(Name, rec->name)) if(escapeString(Artist, rec->artist)) if(escapeString(Album, rec->album)){ httpReq = "GET /admin.cgi?pass="+pw+"&mode=updinfo&song="+Artist+"%20-%20"+Name+"%20-%20"+Album+" HTTP/1.0\n"; httpReq = httpReq+"User-Agent: Mozilla/4.0\n"; httpReq = httpReq+"Content-Type: application/x-www-form-urlencoded\n\n"; // First try the address as doted-quad format adrRec.sin_addr.s_addr = inet_addr(server.c_str()); if (adrRec.sin_addr.s_addr == INADDR_NONE){ // Otherwise, look up name host = gethostbyname(server.c_str()); if (host == NULL) return; // We have a resolved address! Clear out the struct, to avoid garbage and copy memset(&adrRec, 0, sizeof(adrRec)); memcpy(&adrRec.sin_addr.s_addr, host->h_addr_list[0], host->h_length); } adrRec.sin_family = AF_INET; adrRec.sin_port = htons(port-1); // stream send port - 1 post_tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (post_tcp_socket < 0){ ServerLoger->MakeEntry("Track Posting- Shoutcast server "+ server + ": couldn't open tcp socket"); return; } if(connect(post_tcp_socket, (struct sockaddr *)&adrRec, sizeof(adrRec))){ ServerLoger->MakeEntry("Track Posting- Shoutcast server "+ server + ": connection to shoutcast server failed"); return; } // send track posting if(send(post_tcp_socket, httpReq.c_str(), httpReq.size(), 0) <= 0){ close(post_tcp_socket); return; }else{ close(post_tcp_socket); return; } } } unsigned char shoutcastOut::escapeString(string &result, string source) { char str[1024]; CFStringRef originalStr, escapedStr; if(originalStr = CFStringCreateWithCString(NULL, source.c_str(), CFStringGetSystemEncoding())){ if(escapedStr = CFURLCreateStringByAddingPercentEscapes (NULL, originalStr, NULL, CFSTR("=&"), kCFStringEncodingUTF8)){ CFStringGetCString(escapedStr, str, sizeof str, CFStringGetSystemEncoding()); result = string(str); CFRelease(escapedStr); CFRelease(originalStr); return true; } CFRelease(originalStr); } return false; } void shoutcastOut::HandleConnectionLoss(void) { // close the connection and set the corrisponding status/error flags close(tcp_socket); tcp_socket = 0; if(connected){ ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": lost contact with server- trying to reconnect."); connected = false; } retry = true; parent->errCode = 6; } unsigned char shoutcastOut::Connect(void) { struct timeval timeout; struct sockaddr_in adrRec; // Internet address struct struct hostent* host; // host/IP translation int count, size; string param, reply; char buf[1024]; if(connected) return true; if(tcp_socket) close(tcp_socket); string address = GetMetaData(parent->UID, "Server"); unsigned short Port = atoi(GetMetaData(parent->UID, "Port").c_str()); // First try the address as doted-quad format adrRec.sin_addr.s_addr = inet_addr(address.c_str()); if (adrRec.sin_addr.s_addr == INADDR_NONE){ // Otherwise, look up name host = gethostbyname(address.c_str()); if (host == NULL) return false; // We have a resolved address! Clear out the struct, to avoid garbage and copy memset(&adrRec, 0, sizeof(adrRec)); memcpy(&adrRec.sin_addr.s_addr, host->h_addr_list[0], host->h_length); } adrRec.sin_family = AF_INET; adrRec.sin_port = htons(Port); tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (tcp_socket < 0){ if(!retry) ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": couldn't open tcp socket"); return false; } if(connect(tcp_socket, (struct sockaddr *)&adrRec, sizeof(adrRec))){ if(!retry) ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": connection to server failed"); return false; } // set send timeout to 10 seconds TX, 30 seconds RX timeout.tv_sec = 10; /* seconds */ timeout.tv_usec = 0; /* and microseconds */ setsockopt(tcp_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); timeout.tv_sec = 30; /* seconds */ timeout.tv_usec = 0; /* and microseconds */ setsockopt(tcp_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); param = GetMetaData(parent->UID, "Password"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; // read reply buf[0] = '\0'; size = 0; do{ count = read(tcp_socket, buf + size, sizeof(buf) - (size + 1)); if(count <= 0){ goto fail; } size = size + count; buf[size] = '\0'; }while(strchr(buf, '\n') == 0); if(strstr(buf, "OK") == NULL){ // password rejected! if(!retry) ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": password rejected"); return false; } // password OK... send all the setup info param = "icy-name:"; param = param + GetMetaData(parent->UID, "Name"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-genre:"; param = param + GetMetaData(parent->UID, "Genre"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; if(GetMetaData(parent->UID, "Public").size() > 0) param = "icy-pub:1\n"; else param = "icy-pub:0\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-br:"; param = param + GetMetaData(parent->UID, "BitRate"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-url:"; param = param + GetMetaData(parent->UID, "url"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-irc:"; param = param + GetMetaData(parent->UID, "irc"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-icq:"; param = param + GetMetaData(parent->UID, "icq"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "icy-aim:"; param = param + GetMetaData(parent->UID, "aim"); param = param + "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; param = "\n"; if(send(tcp_socket, param.c_str(), param.size(), 0) <= 0) goto fail; connected = true; retry = false; parent->errCode = 0; return true; fail: if(!retry) ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": lost connection to server"); if(tcp_socket){ close(tcp_socket); tcp_socket = 0; } connected = false; return false; } unsigned char shoutcastOut::Start(void) { pid_t child; if(!Connect()) goto bail; if(sockset[0]) goto bail; // sockset[0] for the parent, sockset[1] for the child if(socketpair( AF_UNIX, SOCK_STREAM, 0, sockset) < 0) goto bail; child = fork(); if(child < 0) goto bail; if(child == 0){ // executed if we are the forked child close(sockset[0]); SetupEncoder(); parent->UnInit(); goto bail; }else{ // executed otherwise (parent) encoder = child; close(sockset[1]); sockset[1] = 0; // set socket write buffer to 5 seconds int buffSize = (int)(5 * parent->fileStreamFormat.mSampleRate * parent->fileStreamFormat.mBytesPerFrame); setsockopt(sockset[0], SOL_SOCKET, SO_SNDBUF, &buffSize, sizeof(buffSize)); // set socket read buffer to 5 seconds buffSize = (int)(5 * parent->fileStreamFormat.mSampleRate * parent->fileStreamFormat.mBytesPerFrame); setsockopt(sockset[0], SOL_SOCKET, SO_RCVBUF, &buffSize, sizeof(buffSize)); // set socket send timeout to 10 seconds struct timeval timeout; timeout.tv_sec = 10; /* seconds */ timeout.tv_usec = 0; /* and microseconds */ setsockopt(sockset[0], SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); if (!transmit_thread){ // create a thread that sends the mp3 packets to the sc server pthread_create(&transmit_thread, NULL, &shoutcastOut::TransmitThread, this); } ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": has started"); return true; } return true; bail: if(tcp_socket) close(tcp_socket); connected = false; return false; } void *shoutcastOut::TransmitThread(void* refCon) { shoutcastOut *parent = (shoutcastOut *)(refCon); int count, sent; unsigned int idx; char dataBuff[1024]; float behind; count = read(parent->sockset[0], dataBuff, sizeof(dataBuff)); while(count > 0){ // data has come in from the endcoder pipe... send it out the socket idx = 0; while(idx < (unsigned int)count){ sent = send(parent->tcp_socket, &dataBuff[idx], count-idx, 0); if(sent <= 0){ // problem sending the data! parent->HandleConnectionLoss(); while(parent->retry){ // check how far behind we are behind = parent->parent->sourceBuffDur - (parent->parent->delay + *parent->parent->sourceOutputTime - parent->parent->readTime)/parent->parent->sourceFormat.mSampleRate; if(behind < 2.0){ // the buffer has run out (less than two second left) // move the read pointer to NOW parent->parent->readTime = *parent->parent->sourceOutputTime; } // try to reconnect here! if(parent->Connect()){ parent->parent->errCode = 0; parent->retry = false; }else{ // wait five seconds and try again sleep(5); } } if(!parent->connected){ ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->parent->UID, string("Name")) + ": retry canceled"); parent->parent->errCode = 2; parent->parent->runThread = false; pthread_exit(0); }else{ ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->parent->UID, string("Name")) + ": reconnected to server"); } } idx = idx + sent; } // get next chunk count = read(parent->sockset[0], dataBuff, sizeof(dataBuff)); } pthread_exit(0); } OSStatus shoutcastOut::passSampleBufferIn(void* buffer, size_t bufSize) { size_t size; int sent; size = bufSize; if(sockset[0]){ // write bytes to encoder input socket sent = write(sockset[0], buffer, bufSize); if(sent != (signed int)bufSize){ // can't write to lame encoder input - buffer full. // proceed and adjust time! This is a realtime stream, so the audio will skip return -5; } }else{ return 5; } return 0; } unsigned char shoutcastOut::CantKeepUp(void) { // skip ahead on the read buffer parent->readTime = *parent->sourceOutputTime; if(!retry) ServerLoger->MakeEntry("Encoder- "+ GetMetaData(parent->UID, string("Name")) + ": encoder can't keep up with audio; recording position adjusted."); return true; }