Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: C12832_lcd crossCorrelation mbed-rtos mbed
main.cpp@0:025037057a21, 2014-03-26 (annotated)
- Committer:
- nleoni
- Date:
- Wed Mar 26 05:44:21 2014 +0000
- Revision:
- 0:025037057a21
MBED RTOS Microphone array processing; Currently operating with two microphones; Correlataion calculation not yet fully functional
Who changed what in which revision?
| User | Revision | Line number | New contents of line |
|---|---|---|---|
| nleoni | 0:025037057a21 | 1 | #include "mbed.h" |
| nleoni | 0:025037057a21 | 2 | #include "rtos.h" |
| nleoni | 0:025037057a21 | 3 | #include "C12832_lcd.h" |
| nleoni | 0:025037057a21 | 4 | #include "stdint.h" |
| nleoni | 0:025037057a21 | 5 | #include <string.h> |
| nleoni | 0:025037057a21 | 6 | #include <math.h> |
| nleoni | 0:025037057a21 | 7 | #include <stdio.h> |
| nleoni | 0:025037057a21 | 8 | |
| nleoni | 0:025037057a21 | 9 | #define DEBUG |
| nleoni | 0:025037057a21 | 10 | |
| nleoni | 0:025037057a21 | 11 | |
| nleoni | 0:025037057a21 | 12 | #define SAMPLINGPERIOD 60 //us, 20 us would correspond to a 50 kHz sampling rate |
| nleoni | 0:025037057a21 | 13 | #define EVENTLENGTH 1000//number of sample to collect a full event |
| nleoni | 0:025037057a21 | 14 | #define EVENTLOGSIZE 20 //Events are stored in this log, eventually read and cleared |
| nleoni | 0:025037057a21 | 15 | #define TRIGGERHIGH 0xC000 |
| nleoni | 0:025037057a21 | 16 | #define TRIGGERLOW 0x4000 |
| nleoni | 0:025037057a21 | 17 | #define PRETRIGSAMPLES 20 //samples to keep prior to trigger time. |
| nleoni | 0:025037057a21 | 18 | #define SAMPLEDELAY 20 |
| nleoni | 0:025037057a21 | 19 | #define BUFFER 20 //serial terminal input command buffer |
| nleoni | 0:025037057a21 | 20 | |
| nleoni | 0:025037057a21 | 21 | |
| nleoni | 0:025037057a21 | 22 | //define signals |
| nleoni | 0:025037057a21 | 23 | #define COMPARESIGNAL 0x01//use this signal to signal the onDAQ thread to check for a trigger. |
| nleoni | 0:025037057a21 | 24 | #define PROCESSINGSIGNAL 0x10 //use this signal to start the processing thread |
| nleoni | 0:025037057a21 | 25 | |
| nleoni | 0:025037057a21 | 26 | //There will be a thread checking on error conditions |
| nleoni | 0:025037057a21 | 27 | //this Thread will print to a terminal causes of error conditions and the exit |
| nleoni | 0:025037057a21 | 28 | //the program |
| nleoni | 0:025037057a21 | 29 | //signals related to error conditions |
| nleoni | 0:025037057a21 | 30 | #define NULLPOINTER 0xE1 |
| nleoni | 0:025037057a21 | 31 | |
| nleoni | 0:025037057a21 | 32 | |
| nleoni | 0:025037057a21 | 33 | AnalogIn FLMic(p20),FRMic(p19); |
| nleoni | 0:025037057a21 | 34 | AnalogOut FLMout(p18); |
| nleoni | 0:025037057a21 | 35 | DigitalOut ISRcheck(p26); |
| nleoni | 0:025037057a21 | 36 | DigitalOut circularCheck(p24); |
| nleoni | 0:025037057a21 | 37 | LocalFileSystem local("local"); //use the MBED file system to store debug files |
| nleoni | 0:025037057a21 | 38 | |
| nleoni | 0:025037057a21 | 39 | uint16_t FLMbuffer[EVENTLENGTH],FRMbuffer[EVENTLENGTH],flmLastValue,frmLastValue; |
| nleoni | 0:025037057a21 | 40 | float eventLog[2][EVENTLOGSIZE]; //down the line make an event class, and have an array of events. |
| nleoni | 0:025037057a21 | 41 | uint16_t *ptrflm,*ptrflmStart,*ptrfrm,*ptrfrmStart; //These pointers are used to implement a dual circular buffer architecture |
| nleoni | 0:025037057a21 | 42 | int indexMax,delayMax; |
| nleoni | 0:025037057a21 | 43 | int Max; |
| nleoni | 0:025037057a21 | 44 | |
| nleoni | 0:025037057a21 | 45 | Ticker audioDAQ; |
| nleoni | 0:025037057a21 | 46 | |
| nleoni | 0:025037057a21 | 47 | void acquireISR(void);//ISR routine for acquiring the microphone audio data |
| nleoni | 0:025037057a21 | 48 | Thread *onDAQTrigger; |
| nleoni | 0:025037057a21 | 49 | Thread *processingCrossCorr; |
| nleoni | 0:025037057a21 | 50 | Serial pc(USBTX, USBRX); //serial communication terminal |
| nleoni | 0:025037057a21 | 51 | |
| nleoni | 0:025037057a21 | 52 | //define DirMic state type |
| nleoni | 0:025037057a21 | 53 | typedef enum { |
| nleoni | 0:025037057a21 | 54 | ACQUIRING, |
| nleoni | 0:025037057a21 | 55 | TRIGGERED, |
| nleoni | 0:025037057a21 | 56 | PROCESSING, |
| nleoni | 0:025037057a21 | 57 | IDLE //state to enter for debugginr purposes |
| nleoni | 0:025037057a21 | 58 | } DicMicStateType; |
| nleoni | 0:025037057a21 | 59 | |
| nleoni | 0:025037057a21 | 60 | DicMicStateType dicMicState; |
| nleoni | 0:025037057a21 | 61 | |
| nleoni | 0:025037057a21 | 62 | /****************************************************************** |
| nleoni | 0:025037057a21 | 63 | * |
| nleoni | 0:025037057a21 | 64 | *This |
| nleoni | 0:025037057a21 | 65 | * |
| nleoni | 0:025037057a21 | 66 | *******************************************************************/ |
| nleoni | 0:025037057a21 | 67 | void readTerminal(void const *args){ |
| nleoni | 0:025037057a21 | 68 | int tempLFM,tempRFM,i; |
| nleoni | 0:025037057a21 | 69 | char buffer[BUFFER]; |
| nleoni | 0:025037057a21 | 70 | int filecounter; |
| nleoni | 0:025037057a21 | 71 | char *ptrChar; |
| nleoni | 0:025037057a21 | 72 | ptrChar=buffer; |
| nleoni | 0:025037057a21 | 73 | FILE *fp; |
| nleoni | 0:025037057a21 | 74 | char filename[16],outputstr[16]; |
| nleoni | 0:025037057a21 | 75 | //Test pointers |
| nleoni | 0:025037057a21 | 76 | uint16_t testbuffer[10]; |
| nleoni | 0:025037057a21 | 77 | pc.printf(">Enter a command: (a..arm acquisition; t..trigger acquisition; s..state; r..results)\n>"); |
| nleoni | 0:025037057a21 | 78 | filecounter=0; |
| nleoni | 0:025037057a21 | 79 | while(1){ |
| nleoni | 0:025037057a21 | 80 | //Note the choice of using getc instead of scanf to read the fortune cookie, |
| nleoni | 0:025037057a21 | 81 | //this is a non-blocking call and allows the rest of our threads to continue operating |
| nleoni | 0:025037057a21 | 82 | //only when a new character is typed this thread executes its body otherwise |
| nleoni | 0:025037057a21 | 83 | //it immediately yields to other threads. |
| nleoni | 0:025037057a21 | 84 | if( pc.readable() ){ |
| nleoni | 0:025037057a21 | 85 | *ptrChar=pc.getc(); |
| nleoni | 0:025037057a21 | 86 | pc.putc(*ptrChar); |
| nleoni | 0:025037057a21 | 87 | if((*ptrChar=='\n') || ((ptrChar-buffer)>=(BUFFER-1)) ){ |
| nleoni | 0:025037057a21 | 88 | if((ptrChar-buffer)>=(BUFFER-1)) *++ptrChar='\n'; |
| nleoni | 0:025037057a21 | 89 | *ptrChar='\0'; |
| nleoni | 0:025037057a21 | 90 | //Check the entered command |
| nleoni | 0:025037057a21 | 91 | switch(buffer[0]){ |
| nleoni | 0:025037057a21 | 92 | case 'a': //arm command, to be used after a trigger event |
| nleoni | 0:025037057a21 | 93 | dicMicState=ACQUIRING; |
| nleoni | 0:025037057a21 | 94 | audioDAQ.attach_us(&acquireISR,200); |
| nleoni | 0:025037057a21 | 95 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 96 | pc.printf("State is %i\n",dicMicState); |
| nleoni | 0:025037057a21 | 97 | #endif |
| nleoni | 0:025037057a21 | 98 | break; |
| nleoni | 0:025037057a21 | 99 | case 's': //check current state command |
| nleoni | 0:025037057a21 | 100 | pc.printf("State is %i\n>",dicMicState); |
| nleoni | 0:025037057a21 | 101 | break; |
| nleoni | 0:025037057a21 | 102 | case 'r': //check current state command |
| nleoni | 0:025037057a21 | 103 | pc.printf("Last Correlation results: delay=%d\n>",delayMax); |
| nleoni | 0:025037057a21 | 104 | break; |
| nleoni | 0:025037057a21 | 105 | case 'f': //pointer test command |
| nleoni | 0:025037057a21 | 106 | fp=fopen("/local/out.txt", "w"); |
| nleoni | 0:025037057a21 | 107 | fprintf(fp, "Hello World!"); |
| nleoni | 0:025037057a21 | 108 | fclose(fp); |
| nleoni | 0:025037057a21 | 109 | break; |
| nleoni | 0:025037057a21 | 110 | case 'o': //output file |
| nleoni | 0:025037057a21 | 111 | filecounter++; |
| nleoni | 0:025037057a21 | 112 | sprintf(filename,"/local/data%d.txt",filecounter); |
| nleoni | 0:025037057a21 | 113 | pc.printf("Data file:%s\n>",filename); |
| nleoni | 0:025037057a21 | 114 | fp=fopen(filename, "w"); // Open filename on the local file system for writing |
| nleoni | 0:025037057a21 | 115 | if(fp!=NULL){ |
| nleoni | 0:025037057a21 | 116 | for(i=0;i<EVENTLENGTH;i++){ |
| nleoni | 0:025037057a21 | 117 | tempLFM=(int)FLMbuffer[i]-0x8000; |
| nleoni | 0:025037057a21 | 118 | tempRFM=(int)FRMbuffer[i]-0x8000; |
| nleoni | 0:025037057a21 | 119 | sprintf(outputstr,"%d,%d%c",tempLFM,tempRFM,13); |
| nleoni | 0:025037057a21 | 120 | pc.printf("%s",outputstr); |
| nleoni | 0:025037057a21 | 121 | fprintf(fp,outputstr); |
| nleoni | 0:025037057a21 | 122 | }//for i loop |
| nleoni | 0:025037057a21 | 123 | fclose(fp); |
| nleoni | 0:025037057a21 | 124 | } else { |
| nleoni | 0:025037057a21 | 125 | pc.printf("Error Opening File\n>"); |
| nleoni | 0:025037057a21 | 126 | }//if fp!=null |
| nleoni | 0:025037057a21 | 127 | |
| nleoni | 0:025037057a21 | 128 | }//end of switch |
| nleoni | 0:025037057a21 | 129 | pc.printf(">Enter a command: (a..arm acquisition; t..trigger acquisition)\n>"); |
| nleoni | 0:025037057a21 | 130 | ptrChar=buffer; |
| nleoni | 0:025037057a21 | 131 | } else { |
| nleoni | 0:025037057a21 | 132 | ptrChar++; |
| nleoni | 0:025037057a21 | 133 | }//if ptrchar... check for buffer overflow |
| nleoni | 0:025037057a21 | 134 | }//if pc readable |
| nleoni | 0:025037057a21 | 135 | //A 100 ms wait seems like reasonable delay which allows operation of the remaining threads. |
| nleoni | 0:025037057a21 | 136 | Thread::wait(100); |
| nleoni | 0:025037057a21 | 137 | }//while(1) |
| nleoni | 0:025037057a21 | 138 | }//thread function |
| nleoni | 0:025037057a21 | 139 | |
| nleoni | 0:025037057a21 | 140 | //***************************************************************************************** |
| nleoni | 0:025037057a21 | 141 | //Comparison thread |
| nleoni | 0:025037057a21 | 142 | //This thread is called with a signal from the timer interrupt |
| nleoni | 0:025037057a21 | 143 | //it checks whether a trigger level is exceeded and if so chanegs the state |
| nleoni | 0:025037057a21 | 144 | //to storage |
| nleoni | 0:025037057a21 | 145 | void onDAQ(void const *args){ |
| nleoni | 0:025037057a21 | 146 | int sampleCounter=0; |
| nleoni | 0:025037057a21 | 147 | while(1){ |
| nleoni | 0:025037057a21 | 148 | Thread::signal_wait(COMPARESIGNAL); |
| nleoni | 0:025037057a21 | 149 | //Here we check if signal exceeded trigger, currently only triggering on one mic |
| nleoni | 0:025037057a21 | 150 | switch(dicMicState){ |
| nleoni | 0:025037057a21 | 151 | case ACQUIRING: |
| nleoni | 0:025037057a21 | 152 | if( ( (flmLastValue) > TRIGGERHIGH ) || ( (flmLastValue) < TRIGGERLOW ) ){//signal exceeded trigger |
| nleoni | 0:025037057a21 | 153 | |
| nleoni | 0:025037057a21 | 154 | dicMicState=TRIGGERED; |
| nleoni | 0:025037057a21 | 155 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 156 | pc.printf("State is %i\n>",dicMicState); |
| nleoni | 0:025037057a21 | 157 | #endif |
| nleoni | 0:025037057a21 | 158 | } |
| nleoni | 0:025037057a21 | 159 | sampleCounter=0; |
| nleoni | 0:025037057a21 | 160 | break; |
| nleoni | 0:025037057a21 | 161 | case TRIGGERED: |
| nleoni | 0:025037057a21 | 162 | if(sampleCounter==0) pc.printf("Entered countdown trigger phase\n>"); |
| nleoni | 0:025037057a21 | 163 | sampleCounter++; |
| nleoni | 0:025037057a21 | 164 | if(sampleCounter>(EVENTLENGTH-PRETRIGSAMPLES)){ |
| nleoni | 0:025037057a21 | 165 | //if(sampleCounter>100){ |
| nleoni | 0:025037057a21 | 166 | dicMicState=PROCESSING; //switching states should be done prior to detaching the interrup... |
| nleoni | 0:025037057a21 | 167 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 168 | pc.printf("State is %i\n>",dicMicState); |
| nleoni | 0:025037057a21 | 169 | Thread::wait(10); |
| nleoni | 0:025037057a21 | 170 | #endif |
| nleoni | 0:025037057a21 | 171 | processingCrossCorr->signal_set(PROCESSINGSIGNAL); |
| nleoni | 0:025037057a21 | 172 | audioDAQ.detach();//detach interrupt, this allows gathering data after the trigger yet leaving PRETRIGSAMPLES before trigger time |
| nleoni | 0:025037057a21 | 173 | } |
| nleoni | 0:025037057a21 | 174 | break; |
| nleoni | 0:025037057a21 | 175 | } |
| nleoni | 0:025037057a21 | 176 | } |
| nleoni | 0:025037057a21 | 177 | } |
| nleoni | 0:025037057a21 | 178 | |
| nleoni | 0:025037057a21 | 179 | |
| nleoni | 0:025037057a21 | 180 | //Processing thread |
| nleoni | 0:025037057a21 | 181 | void crossCorrelationThread(void const *args){ |
| nleoni | 0:025037057a21 | 182 | int i,j,k,ndelays,delay; |
| nleoni | 0:025037057a21 | 183 | |
| nleoni | 0:025037057a21 | 184 | ndelays=2*SAMPLEDELAY+1; |
| nleoni | 0:025037057a21 | 185 | int correlationArray[ndelays],tempLFM,tempRFM; |
| nleoni | 0:025037057a21 | 186 | //This thread waits for the processing signal and then perform one correlation calculation |
| nleoni | 0:025037057a21 | 187 | //This very simple cross correlation will for the time being have no Thread::wait....as it runs alone |
| nleoni | 0:025037057a21 | 188 | while(1){ |
| nleoni | 0:025037057a21 | 189 | Thread::signal_wait(PROCESSINGSIGNAL); |
| nleoni | 0:025037057a21 | 190 | Max=0; //Store max computed value of correlation |
| nleoni | 0:025037057a21 | 191 | indexMax=0; |
| nleoni | 0:025037057a21 | 192 | delayMax=2*SAMPLEDELAY;//safe value to know if it did not run |
| nleoni | 0:025037057a21 | 193 | for(j=0;j<ndelays;j++){ |
| nleoni | 0:025037057a21 | 194 | correlationArray[j]=0; |
| nleoni | 0:025037057a21 | 195 | delay=-SAMPLEDELAY+j; |
| nleoni | 0:025037057a21 | 196 | for(i=0;i<EVENTLENGTH;i++){ |
| nleoni | 0:025037057a21 | 197 | //compute k |
| nleoni | 0:025037057a21 | 198 | k=i+delay; |
| nleoni | 0:025037057a21 | 199 | if(k<0) k=EVENTLENGTH+k; |
| nleoni | 0:025037057a21 | 200 | if(k>(EVENTLENGTH-1)) k=k-EVENTLENGTH; |
| nleoni | 0:025037057a21 | 201 | tempLFM=(int)FLMbuffer[i]-0x8000; |
| nleoni | 0:025037057a21 | 202 | tempRFM=(int)FRMbuffer[k]-0x8000; |
| nleoni | 0:025037057a21 | 203 | correlationArray[j]+=tempLFM*tempRFM; |
| nleoni | 0:025037057a21 | 204 | } //end of for i |
| nleoni | 0:025037057a21 | 205 | if(correlationArray[j]>Max){ |
| nleoni | 0:025037057a21 | 206 | Max=correlationArray[j]; |
| nleoni | 0:025037057a21 | 207 | indexMax=j; |
| nleoni | 0:025037057a21 | 208 | delayMax=delay; |
| nleoni | 0:025037057a21 | 209 | } |
| nleoni | 0:025037057a21 | 210 | #ifdef DEBUG//debugging printout of calculated correlation values |
| nleoni | 0:025037057a21 | 211 | pc.printf("Correlation[%i]=%.0f\n>",j,(double)correlationArray[j]); |
| nleoni | 0:025037057a21 | 212 | pc.printf("Max=%.0f\n>",(double)Max); |
| nleoni | 0:025037057a21 | 213 | pc.printf("delayMax=%.0f\n>",(double)delayMax); |
| nleoni | 0:025037057a21 | 214 | #endif |
| nleoni | 0:025037057a21 | 215 | }//end of for j loop |
| nleoni | 0:025037057a21 | 216 | pc.printf("Correlation results: delay=%i\n>",delayMax); |
| nleoni | 0:025037057a21 | 217 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 218 | dicMicState=IDLE;//processing is done set to idle until manually armed |
| nleoni | 0:025037057a21 | 219 | #else |
| nleoni | 0:025037057a21 | 220 | dicMicState=ACQUIRING; //the systems goes back to aqcuiring after finished processing |
| nleoni | 0:025037057a21 | 221 | audioDAQ.attach_us(&acquireISR,SAMPLINGPERIOD); |
| nleoni | 0:025037057a21 | 222 | #endif |
| nleoni | 0:025037057a21 | 223 | }//end of while loop for thread |
| nleoni | 0:025037057a21 | 224 | } |
| nleoni | 0:025037057a21 | 225 | |
| nleoni | 0:025037057a21 | 226 | |
| nleoni | 0:025037057a21 | 227 | int main() { |
| nleoni | 0:025037057a21 | 228 | pc.printf("Directional Microphone\n"); |
| nleoni | 0:025037057a21 | 229 | audioDAQ.attach_us(&acquireISR,SAMPLINGPERIOD); |
| nleoni | 0:025037057a21 | 230 | ptrflm=FLMbuffer; |
| nleoni | 0:025037057a21 | 231 | ptrfrm=FRMbuffer; |
| nleoni | 0:025037057a21 | 232 | dicMicState=IDLE; |
| nleoni | 0:025037057a21 | 233 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 234 | pc.printf("State is %i\n",dicMicState); |
| nleoni | 0:025037057a21 | 235 | #endif |
| nleoni | 0:025037057a21 | 236 | Thread thread(onDAQ); |
| nleoni | 0:025037057a21 | 237 | onDAQTrigger=&thread; |
| nleoni | 0:025037057a21 | 238 | Thread thread2(crossCorrelationThread); |
| nleoni | 0:025037057a21 | 239 | processingCrossCorr=&thread2; |
| nleoni | 0:025037057a21 | 240 | Thread thread3(readTerminal); |
| nleoni | 0:025037057a21 | 241 | |
| nleoni | 0:025037057a21 | 242 | while(1) { |
| nleoni | 0:025037057a21 | 243 | |
| nleoni | 0:025037057a21 | 244 | } |
| nleoni | 0:025037057a21 | 245 | } |
| nleoni | 0:025037057a21 | 246 | |
| nleoni | 0:025037057a21 | 247 | void acquireISR(void){ |
| nleoni | 0:025037057a21 | 248 | if(dicMicState==ACQUIRING || dicMicState==TRIGGERED ){ |
| nleoni | 0:025037057a21 | 249 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 250 | ISRcheck=1; |
| nleoni | 0:025037057a21 | 251 | #endif |
| nleoni | 0:025037057a21 | 252 | if(ptrflm && ptrfrm){//check if pointer not null |
| nleoni | 0:025037057a21 | 253 | //Note that two calls to read_u16 take overall 50 us!!! |
| nleoni | 0:025037057a21 | 254 | *ptrflm=FLMic.read_u16(); |
| nleoni | 0:025037057a21 | 255 | flmLastValue=*ptrflm; |
| nleoni | 0:025037057a21 | 256 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 257 | FLMout.write_u16(*ptrflm); |
| nleoni | 0:025037057a21 | 258 | #endif |
| nleoni | 0:025037057a21 | 259 | *ptrfrm=FRMic.read_u16(); |
| nleoni | 0:025037057a21 | 260 | frmLastValue=*ptrfrm; |
| nleoni | 0:025037057a21 | 261 | if( ((ptrflm-FLMbuffer)) >= (EVENTLENGTH-1) ){ |
| nleoni | 0:025037057a21 | 262 | ptrflm=FLMbuffer;//here we should wrap the pointer around the eventlength |
| nleoni | 0:025037057a21 | 263 | ptrfrm=FRMbuffer; |
| nleoni | 0:025037057a21 | 264 | #ifdef DEBUG//Confirm that pointer wraps to beginning of array |
| nleoni | 0:025037057a21 | 265 | //Test passed! |
| nleoni | 0:025037057a21 | 266 | circularCheck=!circularCheck; |
| nleoni | 0:025037057a21 | 267 | #endif |
| nleoni | 0:025037057a21 | 268 | } else { |
| nleoni | 0:025037057a21 | 269 | ptrflm++; |
| nleoni | 0:025037057a21 | 270 | ptrfrm++; |
| nleoni | 0:025037057a21 | 271 | } |
| nleoni | 0:025037057a21 | 272 | } else { |
| nleoni | 0:025037057a21 | 273 | onDAQTrigger->signal_set(NULLPOINTER); |
| nleoni | 0:025037057a21 | 274 | exit(1); |
| nleoni | 0:025037057a21 | 275 | } |
| nleoni | 0:025037057a21 | 276 | onDAQTrigger->signal_set(COMPARESIGNAL); |
| nleoni | 0:025037057a21 | 277 | #ifdef DEBUG |
| nleoni | 0:025037057a21 | 278 | ISRcheck=0; |
| nleoni | 0:025037057a21 | 279 | #endif |
| nleoni | 0:025037057a21 | 280 | } |
| nleoni | 0:025037057a21 | 281 | } |