Mercurial > hg > beaglert
view core/RTAudio.cpp @ 0:8a575ba3ab52
Initial commit.
author | andrewm |
---|---|
date | Fri, 31 Oct 2014 19:10:17 +0100 |
parents | |
children | 09f03ac40fcc |
line wrap: on
line source
/* * RTAudio.cpp * * Central control code for hard real-time audio on BeagleBone Black * using PRU and Xenomai Linux extensions. This code began as part * of the Hackable Instruments project (EPSRC) at Queen Mary University * of London, 2013-14. * * (c) 2014 Victor Zappi and Andrew McPherson * Queen Mary University of London */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <math.h> #include <iostream> #include <assert.h> #include <vector> // Xenomai-specific includes #include <sys/mman.h> #include <native/task.h> #include <native/timer.h> #include <rtdk.h> #include "../include/RTAudio.h" #include "../include/PRU.h" #include "../include/I2c_Codec.h" #include "../include/render.h" #include "../include/GPIOcontrol.h" using namespace std; // Data structure to keep track of auxiliary tasks we // can schedule typedef struct { RT_TASK task; void (*function)(void); char *name; int priority; } InternalAuxiliaryTask; const char gRTAudioThreadName[] = "beaglert-audio"; const char gRTCalculationThreadNameMedium[] = "dbox-calculation-medium"; const char gRTCalculationThreadNameLow[] = "dbox-calculation-low"; // Real-time tasks and objects RT_TASK gRTAudioThread; PRU *gPRU = 0; I2c_Codec *gAudioCodec = 0; vector<InternalAuxiliaryTask*> gAuxTasks; // Flag which tells the audio task to stop bool gShouldStop = false; // general settings int gRTAudioVerbose = 0; // Verbosity level for debugging char gPRUFilename[256] = "pru_rtaudio.bin"; // path to PRU binary file int gAmplifierMutePin = -1; // initAudio() prepares the infrastructure for running PRU-based real-time // audio, but does not actually start the calculations. // periodSize indicates the number of _sensor_ frames per period: the audio period size // is twice this value. In total, the audio latency in frames will be 4*periodSize, // plus any latency inherent in the ADCs and DACs themselves. // useMatrix indicates whether to use the ADC and DAC or just the audio codec. // userData is an opaque pointer which will be passed through to the initialise_render() // function for application-specific use // // Returns 0 on success. int initAudio(int periodSize, int useMatrix, void *userData, int codecI2CAddress, int ampMutePin) { rt_print_auto_init(1); if(gRTAudioVerbose == 1) rt_printf("Running with Xenomai\n"); if(gRTAudioVerbose == 1) cout << "---------------->Init Audio Thread" << endl; // Prepare GPIO pins for amplifier mute and status LED if(ampMutePin >= 0) { gAmplifierMutePin = ampMutePin; if(gpio_export(ampMutePin)) { if(gRTAudioVerbose) cout << "Warning: couldn't export amplifier mute pin\n"; } if(gpio_set_dir(ampMutePin, OUTPUT_PIN)) { if(gRTAudioVerbose) cout << "Couldn't set direction on amplifier mute pin\n"; return -1; } if(gpio_set_value(ampMutePin, LOW)) { if(gRTAudioVerbose) cout << "Couldn't set value on amplifier mute pin\n"; return -1; } } // Use PRU for audio gPRU = new PRU(); gAudioCodec = new I2c_Codec(); if(gPRU->prepareGPIO(useMatrix, 1, 1)) { cout << "Error: unable to prepare GPIO for PRU audio\n"; return 1; } if(gPRU->initialise(0, periodSize, true)) { cout << "Error: unable to initialise PRU\n"; return 1; } if(gAudioCodec->initI2C_RW(2, codecI2CAddress, -1)) { cout << "Unable to open codec I2C\n"; return 1; } if(gAudioCodec->initCodec()) { cout << "Error: unable to initialise audio codec\n"; return 1; } gAudioCodec->setDACVolume(0); // Set the DAC volume to full-scale gAudioCodec->setHPVolume(-12); // Headphones 6dB down gAudioCodec->setADCVolume(-12); // Set the ADC volume to 6dB down if(!initialise_render(2, useMatrix ? periodSize : 0, periodSize * 2, 22050.0, 44100.0, userData)) { cout << "Couldn't initialise audio rendering\n"; return 1; } return 0; } // audioLoop() is the main function which starts the PRU audio code // and then transfers control to the PRU object. The PRU object in // turn will call the audio render() callback function every time // there is new data to process. void audioLoop(void *) { if(gRTAudioVerbose==1) rt_printf("_________________Audio Thread!\n"); // PRU audio assert(gAudioCodec != 0 && gPRU != 0); if(gAudioCodec->startAudio(0)) { rt_printf("Error: unable to start I2C audio codec\n"); gShouldStop = 1; } else { if(gPRU->start(gPRUFilename)) { rt_printf("Error: unable to start PRU from file %s\n", gPRUFilename); gShouldStop = 1; } else { // All systems go. Run the loop; it will end when gShouldStop is set to 1 // First unmute the amplifier if(gpio_set_value(gAmplifierMutePin, HIGH)) { if(gRTAudioVerbose) rt_printf("Warning: couldn't set value (high) on amplifier mute pin\n"); } gPRU->loop(); // Now clean up // gPRU->waitForFinish(); gPRU->disable(); gAudioCodec->stopAudio(); gPRU->cleanupGPIO(); } } if(gRTAudioVerbose == 1) rt_printf("audio thread ended\n"); } // Create a calculation loop which can run independently of the audio, at a different // (equal or lower) priority. Audio priority is 99; priority should be generally be less than this. // Returns an (opaque) pointer to the created task on success; 0 on failure AuxiliaryTask createAuxiliaryTaskLoop(void (*functionToCall)(void), int priority, const char *name) { InternalAuxiliaryTask *newTask = (InternalAuxiliaryTask*)malloc(sizeof(InternalAuxiliaryTask)); // Attempt to create the task if(rt_task_create(&(newTask->task), name, 0, priority, T_JOINABLE | T_FPU)) { cout << "Error: unable to create auxiliary task " << name << endl; free(newTask); return 0; } // Populate the rest of the data structure and store it in the vector newTask->function = functionToCall; newTask->name = strdup(name); newTask->priority = priority; gAuxTasks.push_back(newTask); return (AuxiliaryTask)newTask; } // Schedule a previously created auxiliary task. It will run when the priority rules next // allow it to be scheduled. void scheduleAuxiliaryTask(AuxiliaryTask task) { InternalAuxiliaryTask *taskToSchedule = (InternalAuxiliaryTask *)task; rt_task_resume(&taskToSchedule->task); } // Calculation loop that can be used for other tasks running at a lower // priority than the audio thread. Simple wrapper for Xenomai calls. // Treat the argument as containing the task structure void auxiliaryTaskLoop(void *taskStruct) { // Get function to call from the argument void (*auxiliary_function)(void) = ((InternalAuxiliaryTask *)taskStruct)->function; const char *name = ((InternalAuxiliaryTask *)taskStruct)->name; // Wait for a notification rt_task_suspend(NULL); while(!gShouldStop) { // Then run the calculations auxiliary_function(); // Wait for a notification rt_task_suspend(NULL); } if(gRTAudioVerbose == 1) rt_printf("auxiliary task %s ended\n", name); } // startAudio() should be called only after initAudio() successfully completes. // It launches the real-time Xenomai task which runs the audio loop. Returns 0 // on success. int startAudio() { // Create audio thread with the highest priority if(rt_task_create(&gRTAudioThread, gRTAudioThreadName, 0, 99, T_JOINABLE | T_FPU)) { cout << "Error: unable to create Xenomai audio thread" << endl; return -1; } // Start all RT threads if(rt_task_start(&gRTAudioThread, &audioLoop, 0)) { cout << "Error: unable to start Xenomai audio thread" << endl; return -1; } // The user may have created other tasks. Start those also. vector<InternalAuxiliaryTask*>::iterator it; for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) { InternalAuxiliaryTask *taskStruct = *it; if(rt_task_start(&(taskStruct->task), &auxiliaryTaskLoop, taskStruct)) { cerr << "Error: unable to start Xenomai task " << taskStruct->name << endl; return -1; } } return 0; } // Stop the PRU-based audio from running and wait // for the tasks to complete before returning. void stopAudio() { // Tell audio thread to stop (if this hasn't been done already) gShouldStop = true; // Now wait for threads to respond and actually stop... rt_task_join(&gRTAudioThread); // Stop all the auxiliary threads too vector<InternalAuxiliaryTask*>::iterator it; for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) { InternalAuxiliaryTask *taskStruct = *it; // Wake up each thread and join it rt_task_resume(&(taskStruct->task)); rt_task_join(&(taskStruct->task)); } } // Free any resources associated with PRU real-time audio void cleanupAudio() { cleanup_render(); // Clean up the auxiliary tasks vector<InternalAuxiliaryTask*>::iterator it; for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) { InternalAuxiliaryTask *taskStruct = *it; // Free the name string and the struct itself free(taskStruct->name); free(taskStruct); } gAuxTasks.clear(); if(gPRU != 0) delete gPRU; if(gAudioCodec != 0) delete gAudioCodec; if(gAmplifierMutePin >= 0) gpio_unexport(gAmplifierMutePin); gAmplifierMutePin = -1; } // Set the verbosity level void setVerboseLevel(int level) { gRTAudioVerbose = level; }