Mercurial > hg > beaglert
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/RTAudio.cpp Fri Oct 31 19:10:17 2014 +0100 @@ -0,0 +1,325 @@ +/* + * 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; +}