Skip to content

Commit fa33b21

Browse files
dsward2dsward2
authored andcommitted
The processing pipeline is slightly revised in this commit. The AudioMonitor object uses the same stdin data from rtl_fm_localradio, but the pass-through data is now always resampled to 48000 Hz before it is written to stdout. Before this change, the next stage in the pipeline, a Sox process, was used to resample the data. Sox no longer does the resampling since AudioMonitor has already performed that task. However, the Sox process remains in place to apply other filters, like deemphasis.
A few audio packets are still getting dropped periodically, like in a batch of several dozen SInt16 samples every second, but the audio seems to sound okay anyway. More good news: moving the 48000 Hz resampling process to an earlier stage seems to fix the MP3 player stream stall problem with the <audio> element with Safari and Cocoa WebView on Mac and iOS. Most features seem to be running smoother now. The version was incremented to 1.12-alpha.
1 parent 80a6d01 commit fa33b21

4 files changed

Lines changed: 73 additions & 41 deletions

File tree

AudioMonitor/AudioMonitor/AudioMonitor.mm

Lines changed: 66 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ - (void)runInputBufferOnThread
8585
NSTimeInterval lastReadTime = [NSDate timeIntervalSinceReferenceDate] + 20;
8686
NSTimeInterval nextTimeoutReportInterval = 5;
8787

88+
//int32_t circularBufferLength = 512 * 1024;
8889
int32_t circularBufferLength = 256 * 1024;
8990
TPCircularBufferInit(&inputCircularBuffer, circularBufferLength);
9091

@@ -148,11 +149,13 @@ - (void)runInputBufferOnThread
148149

149150
if (produceBytesResult == false)
150151
{
152+
TPCircularBufferClear(&inputCircularBuffer);
153+
151154
NSLog(@"AudioMonitor runInputBufferOnThread Produce, bytesAvailableCount=%u, space = %d, head = %p", bytesAvailableCount, space, ptr);
152155
NSLog(@"AudioMonitor runInputBufferOnThread - produce bytes failed, bytesAvailableCount = %d", bytesAvailableCount);
153156
}
154157

155-
fwrite(rtlsdrBuffer, 1, bytesAvailableCount, stdout); // also write to stdout at original sampling rate, for sox, etc.
158+
//fwrite(rtlsdrBuffer, 1, bytesAvailableCount, stdout); // also write to stdout at original sampling rate, for sox, etc.
156159
}
157160

158161
free(rtlsdrBuffer);
@@ -200,7 +203,8 @@ - (void)runAudioConverterOnThread
200203
NSTimeInterval lastReadTime = [NSDate timeIntervalSinceReferenceDate] + 20;
201204
NSTimeInterval nextTimeoutReportInterval = 5;
202205

203-
int32_t circularBufferLength = 384 * 1024;
206+
//int32_t circularBufferLength = 256 * 1024;
207+
int32_t circularBufferLength = 128 * 1024;
204208
TPCircularBufferInit(&audioConverterCircularBuffer, circularBufferLength);
205209

206210
[self startAudioConverter]; // resample PCM data to 48000 Hz
@@ -224,6 +228,9 @@ - (void)runAudioConverterOnThread
224228
if( bytesAvailableCount <= 0)
225229
{
226230
//[NSThread sleepForTimeInterval:0.01f];
231+
232+
//TPCircularBufferClear(&inputCircularBuffer);
233+
227234
usleep(1000);
228235
}
229236
else
@@ -235,16 +242,18 @@ - (void)runAudioConverterOnThread
235242

236243
//NSLog(@"AudioMonitor sending data, length=%ld", bytesAvailableCount);
237244

238-
if (self.volume > 0.0f)
239-
{
240-
[self convertBuffer:circularBufferDataPtr length:bytesAvailableCount];
241-
}
245+
//if (self.volume > 0.0f)
246+
//{
247+
// [self convertBuffer:circularBufferDataPtr length:bytesAvailableCount];
248+
//}
249+
250+
[self convertBuffer:circularBufferDataPtr length:bytesAvailableCount];
242251

243252
//NSLog(@"AudioMonitor runAudioConverterOnThread - Consume bytesAvailableCount = %d, circularBufferDataPtr = %p", bytesAvailableCount, circularBufferDataPtr);
244253

245254
TPCircularBufferConsume(&inputCircularBuffer, bytesAvailableCount);
246255

247-
//fwrite(&inputCircularBuffer, 1, bytesAvailableCount, stdout); // also write to stdout at original sampling rate, for sox, etc.
256+
////fwrite(&inputCircularBuffer, 1, bytesAvailableCount, stdout); // also write to stdout at original sampling rate, for sox, etc.
248257
}
249258
}
250259

@@ -305,6 +314,8 @@ - (void)runAudioQueueOnThread
305314
{
306315
[NSThread sleepForTimeInterval:0.2f];
307316

317+
if (self.volume > 0.0f)
318+
{
308319
// configure AudioQueue for rendering PCM audio data to default output device (i.e., speakers).
309320

310321
audioQueueIndex = 0;
@@ -313,7 +324,7 @@ - (void)runAudioQueueOnThread
313324

314325
audioQueueDescription.mSampleRate = kAudioQueueSampleRate;
315326
audioQueueDescription.mFormatID = kAudioFormatLinearPCM;
316-
audioQueueDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
327+
audioQueueDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
317328
audioQueueDescription.mBitsPerChannel = 8 * sizeof(SInt16);
318329
audioQueueDescription.mChannelsPerFrame = kAudioQueueChannelsCount;
319330
audioQueueDescription.mBytesPerFrame = sizeof(SInt16) * kAudioQueueChannelsCount;
@@ -345,18 +356,20 @@ - (void)runAudioQueueOnThread
345356

346357
UInt32 inNumberOfFramesToPrepare = 0; // decode all enqueued buffers
347358
UInt32 outNumberOfFramesPrepared = 0;
348-
OSStatus queuePrimeStatus = AudioQueuePrime(audioQueue, inNumberOfFramesToPrepare, &outNumberOfFramesPrepared);
349-
if (queuePrimeStatus != noErr)
350-
{
351-
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queuePrimeStatus userInfo:NULL];
352-
NSLog(@"AudioMonitor runAudioQueueOnThread queuePrimeStatus %@", error);
353-
}
354-
355-
OSStatus queueStartStatus = AudioQueueStart(audioQueue, NULL);
356-
if (queueStartStatus != noErr)
357-
{
358-
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueStartStatus userInfo:NULL];
359-
NSLog(@"AudioMonitor runAudioQueueOnThread queueStartStatus %@", error);
359+
360+
OSStatus queuePrimeStatus = AudioQueuePrime(audioQueue, inNumberOfFramesToPrepare, &outNumberOfFramesPrepared);
361+
if (queuePrimeStatus != noErr)
362+
{
363+
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queuePrimeStatus userInfo:NULL];
364+
NSLog(@"AudioMonitor runAudioQueueOnThread queuePrimeStatus %@", error);
365+
}
366+
367+
OSStatus queueStartStatus = AudioQueueStart(audioQueue, NULL);
368+
if (queueStartStatus != noErr)
369+
{
370+
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueStartStatus userInfo:NULL];
371+
NSLog(@"AudioMonitor runAudioQueueOnThread queueStartStatus %@", error);
372+
}
360373
}
361374

362375
/*
@@ -393,6 +406,7 @@ void audioQueueCallback(void *custom_data, AudioQueueRef queue, AudioQueueBuffer
393406

394407
AudioMonitor * self = (__bridge AudioMonitor *)custom_data;
395408

409+
// get pointer to resampled LPCM data for reading
396410
int32_t availableBytes = 0;
397411
void * circularBufferDataPtr = TPCircularBufferTail(&(self->audioConverterCircularBuffer), &availableBytes);
398412

@@ -424,21 +438,24 @@ void audioQueueCallback(void *custom_data, AudioQueueRef queue, AudioQueueBuffer
424438
}
425439
}
426440

427-
//NSLog(@"AudioMonitor audioQueueCallback - Consume availableBytes = %d, circularBufferDataPtr = %p", availableBytes, circularBufferDataPtr);
428-
429-
TPCircularBufferConsume(&(self->audioConverterCircularBuffer), availableBytes);
430-
431-
//fwrite(circularBufferDataPtr, 1, availableBytes, stdout); // also write resampled audio to stdout, perhaps for sox, etc.
441+
//fwrite(circularBufferDataPtr, sizeof(SInt16), samplesCount, stdout); // also write resampled audio to stdout, perhaps for sox, etc.
432442

433443
buffer->mAudioDataByteSize = channelOutputBytes;
434444

435-
// output resampled audio to the current system device
436-
OSStatus queueEnqueueStatus = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
437-
if (queueEnqueueStatus != noErr)
445+
if (self.volume > 0.0f)
438446
{
439-
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueEnqueueStatus userInfo:NULL];
440-
NSLog(@"AudioMonitor runAudioQueueOnThread data queueEnqueueStatus %@", error);
447+
// output resampled audio to the current system device
448+
OSStatus queueEnqueueStatus = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
449+
if (queueEnqueueStatus != noErr)
450+
{
451+
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueEnqueueStatus userInfo:NULL];
452+
NSLog(@"AudioMonitor runAudioQueueOnThread data queueEnqueueStatus %@", error);
453+
}
441454
}
455+
456+
//NSLog(@"AudioMonitor audioQueueCallback - Consume availableBytes = %d, circularBufferDataPtr = %p", availableBytes, circularBufferDataPtr);
457+
458+
TPCircularBufferConsume(&(self->audioConverterCircularBuffer), availableBytes);
442459
}
443460
else
444461
{
@@ -449,13 +466,18 @@ void audioQueueCallback(void *custom_data, AudioQueueRef queue, AudioQueueBuffer
449466
buffer->mAudioDataByteSize = sizeof(SInt16) * 256; // 64 frames * 2 bytes per packet * 2 packets per frame
450467

451468
memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize);
469+
470+
//fwrite(buffer->mAudioData, 1, buffer->mAudioDataByteSize, stdout); // also write silent audio to stdout, perhaps for sox, etc.
452471

453-
// output silent audio to the current system device
454-
OSStatus queueEnqueueStatus = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
455-
if (queueEnqueueStatus != noErr)
472+
if (self.volume > 0.0f)
456473
{
457-
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueEnqueueStatus userInfo:NULL];
458-
NSLog(@"AudioMonitor runAudioQueueOnThread silence queueEnqueueStatus %@", error);
474+
// output silent audio to the current system device
475+
OSStatus queueEnqueueStatus = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
476+
if (queueEnqueueStatus != noErr)
477+
{
478+
NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:queueEnqueueStatus userInfo:NULL];
479+
NSLog(@"AudioMonitor runAudioQueueOnThread silence queueEnqueueStatus %@", error);
480+
}
459481
}
460482
}
461483
}
@@ -514,6 +536,8 @@ - (void)convertBuffer:(void *)inputBufferPtr length:(UInt32)dataLength
514536

515537
if (outputDataPacketSize > 0) // number of packets converted
516538
{
539+
// produce resampled audio to second circular buffer
540+
517541
int32_t convertedDataLength = outputDataPacketSize * sizeof(SInt16);
518542

519543
void * convertedDataPtr = audioConverterOutputBufferList.mBuffers[0].mData;
@@ -524,8 +548,14 @@ - (void)convertBuffer:(void *)inputBufferPtr length:(UInt32)dataLength
524548

525549
bool produceBytesResult = TPCircularBufferProduceBytes(&audioConverterCircularBuffer, convertedDataPtr, convertedDataLength);
526550

551+
fwrite(convertedDataPtr, 1, convertedDataLength, stdout); // also write resampled audio to stdout, perhaps for sox, etc.
552+
527553
if (produceBytesResult == false)
528554
{
555+
// TODO: We are dropping packets here to avoid buffer overrun, is audioConverterCircularBuffer missing consume operations?
556+
557+
TPCircularBufferClear(&(self->audioConverterCircularBuffer));
558+
529559
NSLog(@"AudioMonitor convertBuffer Produce convertedDataLength = %d, space = %d, head = %p", convertedDataLength, space, ptr);
530560
NSLog(@"AudioMonitor convertBuffer - produce bytes failed, convertedDataLength = %d", convertedDataLength);
531561
}
@@ -662,7 +692,7 @@ - (void)logDescription:(AudioStreamBasicDescription *)asbd withName:(NSString *)
662692
NSLog(@"AudioStreamBasicDescription %@", name);
663693
NSLog(@" %@.mSampleRate=%f", name, asbd->mSampleRate);
664694
NSLog(@" %@.mFormatID=%c4", name, asbd->mFormatID);
665-
NSLog(@" %@.AudioFormatFlags=%u", name, (unsigned int)asbd->mFormatFlags);
695+
NSLog(@" %@.mFormatFlags=%u", name, (unsigned int)asbd->mFormatFlags);
666696
NSLog(@" %@.mBytesPerPacket=%u", name, (unsigned int)asbd->mBytesPerPacket);
667697
NSLog(@" %@.mFramesPerPacket=%u", name, (unsigned int)asbd->mFramesPerPacket);
668698
NSLog(@" %@.mBytesPerFrame=%u", name, (unsigned int)asbd->mBytesPerFrame);

LocalRadio/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<key>CFBundlePackageType</key>
1818
<string>APPL</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>1.11</string>
20+
<string>1.12</string>
2121
<key>CFBundleVersion</key>
22-
<string>12</string>
22+
<string>13</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.entertainment</string>
2525
<key>LSMinimumSystemVersion</key>

LocalRadio/SDRController.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@ - (void)dispatchedStartRtlsdrTasksForFrequencies:(NSArray *)frequenciesArray cat
545545
// configure rtl_fm_localradio for output to UDPSender (then to EZStream/Icecast)
546546

547547
[soxArgsArray addObject:@"-r"];
548-
[soxArgsArray addObject:tunerSampleRateNumber];
548+
//[soxArgsArray addObject:tunerSampleRateNumber];
549+
[soxArgsArray addObject:@"48000"]; // assume input from AudioMonitor already resampled to 48000 Hz
549550

550551
[soxArgsArray addObject:@"-e"];
551552
[soxArgsArray addObject:@"signed-integer"];
@@ -606,7 +607,8 @@ - (void)dispatchedStartRtlsdrTasksForFrequencies:(NSArray *)frequenciesArray cat
606607
// input args
607608

608609
[soxArgsArray addObject:@"-r"];
609-
[soxArgsArray addObject:tunerSampleRateNumber];
610+
//[soxArgsArray addObject:tunerSampleRateNumber];
611+
[soxArgsArray addObject:@"48000"]; // assume input from AudioMonitor already resampled to 48000 Hz
610612

611613
[soxArgsArray addObject:@"-e"];
612614
[soxArgsArray addObject:@"signed-integer"];

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<hr>
99

10-
### The latest pre-release alpha versions of the LocalRadio app are [avaiable for download at this link](https://github.com/dsward2/LocalRadio/releases).
10+
### The latest pre-release alpha versions of the LocalRadio app are [available for download at this link](https://github.com/dsward2/LocalRadio/releases).
1111

1212
<hr>
1313

0 commit comments

Comments
 (0)