annotate core/RTAudio.cpp @ 12:a6beeba3a648

Initial support for higher matrix sample rates by reducing the number of channels. Input not tested yet, and not all examples updated to new format.
author andrewm
date Thu, 22 Jan 2015 19:00:22 +0000
parents 09f03ac40fcc
children 6adb088196a7
rev   line source
andrewm@0 1 /*
andrewm@0 2 * RTAudio.cpp
andrewm@0 3 *
andrewm@0 4 * Central control code for hard real-time audio on BeagleBone Black
andrewm@0 5 * using PRU and Xenomai Linux extensions. This code began as part
andrewm@0 6 * of the Hackable Instruments project (EPSRC) at Queen Mary University
andrewm@0 7 * of London, 2013-14.
andrewm@0 8 *
andrewm@0 9 * (c) 2014 Victor Zappi and Andrew McPherson
andrewm@0 10 * Queen Mary University of London
andrewm@0 11 */
andrewm@0 12
andrewm@0 13
andrewm@0 14 #include <stdio.h>
andrewm@0 15 #include <stdlib.h>
andrewm@0 16 #include <string.h>
andrewm@0 17 #include <strings.h>
andrewm@0 18 #include <math.h>
andrewm@0 19 #include <iostream>
andrewm@0 20 #include <assert.h>
andrewm@0 21 #include <vector>
andrewm@0 22
andrewm@0 23 // Xenomai-specific includes
andrewm@0 24 #include <sys/mman.h>
andrewm@0 25 #include <native/task.h>
andrewm@0 26 #include <native/timer.h>
andrewm@0 27 #include <rtdk.h>
andrewm@0 28
andrewm@0 29 #include "../include/RTAudio.h"
andrewm@0 30 #include "../include/PRU.h"
andrewm@0 31 #include "../include/I2c_Codec.h"
andrewm@0 32 #include "../include/render.h"
andrewm@0 33 #include "../include/GPIOcontrol.h"
andrewm@0 34
andrewm@0 35 using namespace std;
andrewm@0 36
andrewm@0 37 // Data structure to keep track of auxiliary tasks we
andrewm@0 38 // can schedule
andrewm@0 39 typedef struct {
andrewm@0 40 RT_TASK task;
andrewm@0 41 void (*function)(void);
andrewm@0 42 char *name;
andrewm@0 43 int priority;
andrewm@0 44 } InternalAuxiliaryTask;
andrewm@0 45
andrewm@0 46 const char gRTAudioThreadName[] = "beaglert-audio";
andrewm@0 47
andrewm@0 48 // Real-time tasks and objects
andrewm@0 49 RT_TASK gRTAudioThread;
andrewm@0 50 PRU *gPRU = 0;
andrewm@0 51 I2c_Codec *gAudioCodec = 0;
andrewm@0 52
andrewm@0 53 vector<InternalAuxiliaryTask*> gAuxTasks;
andrewm@0 54
andrewm@0 55 // Flag which tells the audio task to stop
andrewm@0 56 bool gShouldStop = false;
andrewm@0 57
andrewm@0 58 // general settings
andrewm@0 59 int gRTAudioVerbose = 0; // Verbosity level for debugging
andrewm@0 60 char gPRUFilename[256] = "pru_rtaudio.bin"; // path to PRU binary file
andrewm@0 61 int gAmplifierMutePin = -1;
andrewm@5 62 int gAmplifierShouldBeginMuted = 0;
andrewm@0 63
andrewm@0 64
andrewm@0 65 // initAudio() prepares the infrastructure for running PRU-based real-time
andrewm@0 66 // audio, but does not actually start the calculations.
andrewm@0 67 // periodSize indicates the number of _sensor_ frames per period: the audio period size
andrewm@0 68 // is twice this value. In total, the audio latency in frames will be 4*periodSize,
andrewm@0 69 // plus any latency inherent in the ADCs and DACs themselves.
andrewm@12 70 // useMatrix indicates whether to enable the ADC and DAC or just use the audio codec.
andrewm@12 71 // numMatrixChannels indicates how many ADC and DAC channels to use.
andrewm@0 72 // userData is an opaque pointer which will be passed through to the initialise_render()
andrewm@0 73 // function for application-specific use
andrewm@0 74 //
andrewm@0 75 // Returns 0 on success.
andrewm@0 76
andrewm@5 77 int BeagleRT_initAudio(RTAudioSettings *settings, void *userData)
andrewm@0 78 {
andrewm@0 79 rt_print_auto_init(1);
andrewm@5 80 setVerboseLevel(settings->verbose);
andrewm@5 81
andrewm@0 82 if(gRTAudioVerbose == 1)
andrewm@0 83 rt_printf("Running with Xenomai\n");
andrewm@0 84
andrewm@5 85 if(gRTAudioVerbose) {
andrewm@5 86 cout << "Starting with period size " << settings->periodSize << "; ";
andrewm@5 87 if(settings->useMatrix)
andrewm@5 88 cout << "matrix enabled\n";
andrewm@5 89 else
andrewm@5 90 cout << "matrix disabled\n";
andrewm@5 91 cout << "DAC level " << settings->dacLevel << "dB; ADC level " << settings->adcLevel;
andrewm@5 92 cout << "dB; headphone level " << settings->headphoneLevel << "dB\n";
andrewm@5 93 if(settings->beginMuted)
andrewm@5 94 cout << "Beginning with speaker muted\n";
andrewm@5 95 }
andrewm@0 96
andrewm@0 97 // Prepare GPIO pins for amplifier mute and status LED
andrewm@5 98 if(settings->ampMutePin >= 0) {
andrewm@5 99 gAmplifierMutePin = settings->ampMutePin;
andrewm@5 100 gAmplifierShouldBeginMuted = settings->beginMuted;
andrewm@0 101
andrewm@5 102 if(gpio_export(settings->ampMutePin)) {
andrewm@0 103 if(gRTAudioVerbose)
andrewm@0 104 cout << "Warning: couldn't export amplifier mute pin\n";
andrewm@0 105 }
andrewm@5 106 if(gpio_set_dir(settings->ampMutePin, OUTPUT_PIN)) {
andrewm@0 107 if(gRTAudioVerbose)
andrewm@0 108 cout << "Couldn't set direction on amplifier mute pin\n";
andrewm@0 109 return -1;
andrewm@0 110 }
andrewm@5 111 if(gpio_set_value(settings->ampMutePin, LOW)) {
andrewm@0 112 if(gRTAudioVerbose)
andrewm@0 113 cout << "Couldn't set value on amplifier mute pin\n";
andrewm@0 114 return -1;
andrewm@0 115 }
andrewm@0 116 }
andrewm@0 117
andrewm@12 118 // Limit the matrix channels to sane values
andrewm@12 119 if(settings->numMatrixChannels >= 8)
andrewm@12 120 settings->numMatrixChannels = 8;
andrewm@12 121 else if(settings->numMatrixChannels >= 4)
andrewm@12 122 settings->numMatrixChannels = 4;
andrewm@12 123 else
andrewm@12 124 settings->numMatrixChannels = 2;
andrewm@12 125
andrewm@12 126 // Sanity check the combination of channels and period size
andrewm@12 127 if(settings->numMatrixChannels <= 4 && settings->periodSize < 2) {
andrewm@12 128 cout << "Error: " << settings->numMatrixChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
andrewm@12 129 return 1;
andrewm@12 130 }
andrewm@12 131 if(settings->numMatrixChannels <= 2 && settings->periodSize < 4) {
andrewm@12 132 cout << "Error: " << settings->numMatrixChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
andrewm@12 133 return 1;
andrewm@12 134 }
andrewm@12 135
andrewm@0 136 // Use PRU for audio
andrewm@0 137 gPRU = new PRU();
andrewm@0 138 gAudioCodec = new I2c_Codec();
andrewm@0 139
andrewm@5 140 if(gPRU->prepareGPIO(settings->useMatrix, 1, 1)) {
andrewm@0 141 cout << "Error: unable to prepare GPIO for PRU audio\n";
andrewm@0 142 return 1;
andrewm@0 143 }
andrewm@12 144 if(gPRU->initialise(0, settings->periodSize, settings->numMatrixChannels, true)) {
andrewm@0 145 cout << "Error: unable to initialise PRU\n";
andrewm@0 146 return 1;
andrewm@0 147 }
andrewm@5 148 if(gAudioCodec->initI2C_RW(2, settings->codecI2CAddress, -1)) {
andrewm@0 149 cout << "Unable to open codec I2C\n";
andrewm@0 150 return 1;
andrewm@0 151 }
andrewm@0 152 if(gAudioCodec->initCodec()) {
andrewm@0 153 cout << "Error: unable to initialise audio codec\n";
andrewm@0 154 return 1;
andrewm@0 155 }
andrewm@0 156
andrewm@5 157 // Set default volume levels
andrewm@5 158 BeagleRT_setDACLevel(settings->dacLevel);
andrewm@5 159 BeagleRT_setADCLevel(settings->adcLevel);
andrewm@5 160 BeagleRT_setHeadphoneLevel(settings->headphoneLevel);
andrewm@5 161
andrewm@12 162 // Initialise the rendering environment: pass the number of audio and matrix
andrewm@12 163 // channels, the period size for matrix and audio, and the sample rates
andrewm@12 164
andrewm@12 165 int audioPeriodSize = settings->periodSize * 2;
andrewm@12 166 float audioSampleRate = 44100.0;
andrewm@12 167 float matrixSampleRate = 22050.0;
andrewm@12 168 if(settings->useMatrix) {
andrewm@12 169 audioPeriodSize = settings->periodSize * settings->numMatrixChannels / 4;
andrewm@12 170 matrixSampleRate = audioSampleRate * 4.0 / (float)settings->numMatrixChannels;
andrewm@12 171 }
andrewm@12 172
andrewm@12 173 if(!initialise_render(settings->useMatrix ? settings->numMatrixChannels : 0, /* matrix channels */
andrewm@12 174 2, /* audio channels */
andrewm@12 175 settings->useMatrix ? settings->periodSize : 0, /* matrix period size */
andrewm@12 176 audioPeriodSize,
andrewm@12 177 matrixSampleRate, audioSampleRate,
andrewm@12 178 userData)) {
andrewm@0 179 cout << "Couldn't initialise audio rendering\n";
andrewm@0 180 return 1;
andrewm@0 181 }
andrewm@0 182
andrewm@0 183 return 0;
andrewm@0 184 }
andrewm@0 185
andrewm@0 186 // audioLoop() is the main function which starts the PRU audio code
andrewm@0 187 // and then transfers control to the PRU object. The PRU object in
andrewm@0 188 // turn will call the audio render() callback function every time
andrewm@0 189 // there is new data to process.
andrewm@0 190
andrewm@0 191 void audioLoop(void *)
andrewm@0 192 {
andrewm@0 193 if(gRTAudioVerbose==1)
andrewm@0 194 rt_printf("_________________Audio Thread!\n");
andrewm@0 195
andrewm@0 196 // PRU audio
andrewm@0 197 assert(gAudioCodec != 0 && gPRU != 0);
andrewm@0 198
andrewm@0 199 if(gAudioCodec->startAudio(0)) {
andrewm@0 200 rt_printf("Error: unable to start I2C audio codec\n");
andrewm@0 201 gShouldStop = 1;
andrewm@0 202 }
andrewm@0 203 else {
andrewm@0 204 if(gPRU->start(gPRUFilename)) {
andrewm@0 205 rt_printf("Error: unable to start PRU from file %s\n", gPRUFilename);
andrewm@0 206 gShouldStop = 1;
andrewm@0 207 }
andrewm@0 208 else {
andrewm@0 209 // All systems go. Run the loop; it will end when gShouldStop is set to 1
andrewm@5 210
andrewm@5 211 if(!gAmplifierShouldBeginMuted) {
andrewm@5 212 // First unmute the amplifier
andrewm@5 213 if(BeagleRT_muteSpeakers(0)) {
andrewm@5 214 if(gRTAudioVerbose)
andrewm@5 215 rt_printf("Warning: couldn't set value (high) on amplifier mute pin\n");
andrewm@5 216 }
andrewm@0 217 }
andrewm@0 218
andrewm@0 219 gPRU->loop();
andrewm@0 220
andrewm@0 221 // Now clean up
andrewm@0 222 // gPRU->waitForFinish();
andrewm@0 223 gPRU->disable();
andrewm@0 224 gAudioCodec->stopAudio();
andrewm@0 225 gPRU->cleanupGPIO();
andrewm@0 226 }
andrewm@0 227 }
andrewm@0 228
andrewm@0 229 if(gRTAudioVerbose == 1)
andrewm@0 230 rt_printf("audio thread ended\n");
andrewm@0 231 }
andrewm@0 232
andrewm@0 233 // Create a calculation loop which can run independently of the audio, at a different
andrewm@0 234 // (equal or lower) priority. Audio priority is 99; priority should be generally be less than this.
andrewm@0 235 // Returns an (opaque) pointer to the created task on success; 0 on failure
andrewm@0 236 AuxiliaryTask createAuxiliaryTaskLoop(void (*functionToCall)(void), int priority, const char *name)
andrewm@0 237 {
andrewm@0 238 InternalAuxiliaryTask *newTask = (InternalAuxiliaryTask*)malloc(sizeof(InternalAuxiliaryTask));
andrewm@0 239
andrewm@0 240 // Attempt to create the task
andrewm@0 241 if(rt_task_create(&(newTask->task), name, 0, priority, T_JOINABLE | T_FPU)) {
andrewm@0 242 cout << "Error: unable to create auxiliary task " << name << endl;
andrewm@0 243 free(newTask);
andrewm@0 244 return 0;
andrewm@0 245 }
andrewm@0 246
andrewm@0 247 // Populate the rest of the data structure and store it in the vector
andrewm@0 248 newTask->function = functionToCall;
andrewm@0 249 newTask->name = strdup(name);
andrewm@0 250 newTask->priority = priority;
andrewm@0 251
andrewm@0 252 gAuxTasks.push_back(newTask);
andrewm@0 253
andrewm@0 254 return (AuxiliaryTask)newTask;
andrewm@0 255 }
andrewm@0 256
andrewm@0 257 // Schedule a previously created auxiliary task. It will run when the priority rules next
andrewm@0 258 // allow it to be scheduled.
andrewm@0 259 void scheduleAuxiliaryTask(AuxiliaryTask task)
andrewm@0 260 {
andrewm@0 261 InternalAuxiliaryTask *taskToSchedule = (InternalAuxiliaryTask *)task;
andrewm@0 262
andrewm@0 263 rt_task_resume(&taskToSchedule->task);
andrewm@0 264 }
andrewm@0 265
andrewm@0 266 // Calculation loop that can be used for other tasks running at a lower
andrewm@0 267 // priority than the audio thread. Simple wrapper for Xenomai calls.
andrewm@0 268 // Treat the argument as containing the task structure
andrewm@0 269 void auxiliaryTaskLoop(void *taskStruct)
andrewm@0 270 {
andrewm@0 271 // Get function to call from the argument
andrewm@0 272 void (*auxiliary_function)(void) = ((InternalAuxiliaryTask *)taskStruct)->function;
andrewm@0 273 const char *name = ((InternalAuxiliaryTask *)taskStruct)->name;
andrewm@0 274
andrewm@0 275 // Wait for a notification
andrewm@0 276 rt_task_suspend(NULL);
andrewm@0 277
andrewm@0 278 while(!gShouldStop) {
andrewm@0 279 // Then run the calculations
andrewm@0 280 auxiliary_function();
andrewm@0 281
andrewm@0 282 // Wait for a notification
andrewm@0 283 rt_task_suspend(NULL);
andrewm@0 284 }
andrewm@0 285
andrewm@0 286 if(gRTAudioVerbose == 1)
andrewm@0 287 rt_printf("auxiliary task %s ended\n", name);
andrewm@0 288 }
andrewm@0 289
andrewm@0 290 // startAudio() should be called only after initAudio() successfully completes.
andrewm@0 291 // It launches the real-time Xenomai task which runs the audio loop. Returns 0
andrewm@0 292 // on success.
andrewm@0 293
andrewm@5 294 int BeagleRT_startAudio()
andrewm@0 295 {
andrewm@0 296 // Create audio thread with the highest priority
andrewm@0 297 if(rt_task_create(&gRTAudioThread, gRTAudioThreadName, 0, 99, T_JOINABLE | T_FPU)) {
andrewm@0 298 cout << "Error: unable to create Xenomai audio thread" << endl;
andrewm@0 299 return -1;
andrewm@0 300 }
andrewm@0 301
andrewm@0 302 // Start all RT threads
andrewm@0 303 if(rt_task_start(&gRTAudioThread, &audioLoop, 0)) {
andrewm@0 304 cout << "Error: unable to start Xenomai audio thread" << endl;
andrewm@0 305 return -1;
andrewm@0 306 }
andrewm@0 307
andrewm@0 308 // The user may have created other tasks. Start those also.
andrewm@0 309 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 310 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 311 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 312
andrewm@0 313 if(rt_task_start(&(taskStruct->task), &auxiliaryTaskLoop, taskStruct)) {
andrewm@0 314 cerr << "Error: unable to start Xenomai task " << taskStruct->name << endl;
andrewm@0 315 return -1;
andrewm@0 316 }
andrewm@0 317 }
andrewm@0 318
andrewm@0 319 return 0;
andrewm@0 320 }
andrewm@0 321
andrewm@0 322 // Stop the PRU-based audio from running and wait
andrewm@0 323 // for the tasks to complete before returning.
andrewm@0 324
andrewm@5 325 void BeagleRT_stopAudio()
andrewm@0 326 {
andrewm@0 327 // Tell audio thread to stop (if this hasn't been done already)
andrewm@0 328 gShouldStop = true;
andrewm@0 329
andrewm@5 330 if(gRTAudioVerbose)
andrewm@5 331 cout << "Stopping audio...\n";
andrewm@5 332
andrewm@0 333 // Now wait for threads to respond and actually stop...
andrewm@0 334 rt_task_join(&gRTAudioThread);
andrewm@0 335
andrewm@0 336 // Stop all the auxiliary threads too
andrewm@0 337 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 338 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 339 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 340
andrewm@0 341 // Wake up each thread and join it
andrewm@0 342 rt_task_resume(&(taskStruct->task));
andrewm@0 343 rt_task_join(&(taskStruct->task));
andrewm@0 344 }
andrewm@0 345 }
andrewm@0 346
andrewm@0 347 // Free any resources associated with PRU real-time audio
andrewm@5 348 void BeagleRT_cleanupAudio()
andrewm@0 349 {
andrewm@0 350 cleanup_render();
andrewm@0 351
andrewm@0 352 // Clean up the auxiliary tasks
andrewm@0 353 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 354 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 355 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 356
andrewm@0 357 // Free the name string and the struct itself
andrewm@0 358 free(taskStruct->name);
andrewm@0 359 free(taskStruct);
andrewm@0 360 }
andrewm@0 361 gAuxTasks.clear();
andrewm@0 362
andrewm@0 363 if(gPRU != 0)
andrewm@0 364 delete gPRU;
andrewm@0 365 if(gAudioCodec != 0)
andrewm@0 366 delete gAudioCodec;
andrewm@0 367
andrewm@0 368 if(gAmplifierMutePin >= 0)
andrewm@0 369 gpio_unexport(gAmplifierMutePin);
andrewm@0 370 gAmplifierMutePin = -1;
andrewm@0 371 }
andrewm@0 372
andrewm@5 373 // Set the level of the DAC; affects all outputs (headphone, line, speaker)
andrewm@5 374 // 0dB is the maximum, -63.5dB is the minimum; 0.5dB steps
andrewm@5 375 int BeagleRT_setDACLevel(float decibels)
andrewm@5 376 {
andrewm@5 377 if(gAudioCodec == 0)
andrewm@5 378 return -1;
andrewm@5 379 return gAudioCodec->setDACVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 380 }
andrewm@5 381
andrewm@5 382 // Set the level of the ADC
andrewm@5 383 // 0dB is the maximum, -12dB is the minimum; 1.5dB steps
andrewm@5 384 int BeagleRT_setADCLevel(float decibels)
andrewm@5 385 {
andrewm@5 386 if(gAudioCodec == 0)
andrewm@5 387 return -1;
andrewm@5 388 return gAudioCodec->setADCVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 389 }
andrewm@5 390
andrewm@5 391 // Set the level of the onboard headphone amplifier; affects headphone
andrewm@5 392 // output only (not line out or speaker)
andrewm@5 393 // 0dB is the maximum, -63.5dB is the minimum; 0.5dB steps
andrewm@5 394 int BeagleRT_setHeadphoneLevel(float decibels)
andrewm@5 395 {
andrewm@5 396 if(gAudioCodec == 0)
andrewm@5 397 return -1;
andrewm@5 398 return gAudioCodec->setHPVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 399 }
andrewm@5 400
andrewm@5 401 // Mute or unmute the onboard speaker amplifiers
andrewm@5 402 // mute == 0 means unmute; otherwise mute
andrewm@5 403 // Returns 0 on success
andrewm@5 404 int BeagleRT_muteSpeakers(int mute)
andrewm@5 405 {
andrewm@5 406 int pinValue = mute ? LOW : HIGH;
andrewm@5 407
andrewm@5 408 // Check that we have an enabled pin for controlling the mute
andrewm@5 409 if(gAmplifierMutePin < 0)
andrewm@5 410 return -1;
andrewm@5 411
andrewm@5 412 return gpio_set_value(gAmplifierMutePin, pinValue);
andrewm@5 413 }
andrewm@5 414
andrewm@0 415 // Set the verbosity level
andrewm@0 416 void setVerboseLevel(int level)
andrewm@0 417 {
andrewm@0 418 gRTAudioVerbose = level;
andrewm@0 419 }