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;
+}