nRF24/RF24Audio

Hearing audio back on PC

sriranjanr opened this issue · 22 comments

Hello (posting in the right page),

I am working with windows PC and Intel edison.I want to just transfer audio to the PC using wireless nrf24 and via USB. How do I hear the audio on the PC or edison back. I understand that you have provided MRAA support but how do I hear live audio on the windows PC or Intel edison.I see that there is a file named USB audio. What does this file do?

The USB Audio example is a very minimal example of how to use an Arduino (AVR based) to receive the digital audio and send it via USB/Serial to a PC. This generally requires an understanding of wav format files and how to handle incoming Serial data programatically.

The audio being transmitted is raw, unsigned, 8-bit mono pcm data, so you can write it to a file. It may also be possible to use an audio streaming app that can handle standard PCM/WAV format audio.

The Gallileo, unfortunately is an Intel product, so of course the 'datasheet' they provide has only the most basic information, and I am not even certain if the thing has a DAC or ADC, or how to generate a proper PWM signal. In any case, you would need to figure out how to access the timers and/or DAC to generate an audio signal using the data from RF24Audio.

PC

  1. Create a wav format header file:
    Option a: See here
    Option b: Hack - Convert any mp3 etc to a standard wav file. 8-bit unsigned, mono, at the same sample rate as u ar running RF24Audio. Delete everything but the first 44 bytes. (the wav header) and you have a file containing just the wav header, and no data.
  2. As the raw audio data is received, write it to the file, after the wav header (44 bytes).
  3. If done correctly, any audio player should be able to play the .wav file

I can generate a 250 micro second period PWM signal on the Intel edison without any loss. Thats its PWM capability. So I need to understand audio from RAW audio data on the Windows PC and Intel Edison. So what is RAW data format? Is it is the ADC values from a microphone on an arduino pin or is RAW audio data something more? If you can guide me I will develop a library for edison and Due and finally a streaming app on the windows PC for RAW audio data. I will post my doubts, progress of work and updates here to this thread.

Take a look here for a general idea of what I mean.

Audio is complicated, but I use simple methods to capture or reproduce audio in simple PCM/WAV format, which is a raw digital (standard wav) format.

To explain, it is exactly as you guessed, the raw (in this case 8 or 10-bit) values from the Arduino ADC, sampled precisely at the designated sample rate. With 16khz sample rate, that means reading the ADC 16,000 times/second and storing the data, or reproducing it via PWM etc.

The method I use on AVR devices is to configure a timer usually at the given sample rate of the designated audio. From there, raw data is fead into the compare register,say 16,000 times/second, causing the duty cycle to change every cycle. The duty cycle represents the waveform, while the timer just runs at a set frequency.

This can also be accomplished (possibly better) using a DAC, and converting the ADC reading directly to DAC output.

In short, you simply read in a voltage at a set frequency to record. To reproduce, find a way to recreate the same or very similar variations in output.

I read your timer tutorial.I am now planning to implement it on edison. So how does your RF24Audio library work. I can set a timer in edison like this "Timer1.initialize(500);" 500 microseconds is the time period and it gives me a 2Khz signal.I can change the PWM of the signal by adding delay in the timer isr function.I am comfortable with and can manage 2Khz audio. So please tell me more about how did you capture the wireless signal and generated audio.I will port the library and post it here.

Hmm, not sure what more information I can give you about how it works, although you need a minimum of 8khz and really 16khz for 'good' quality audio.

Well I have tried 2 khz and 4 khz audio and it was good enough using another codec. So how shall I start the code with? Lets say now I can generate an 8khz PWM, then how do I generate audio on it from the mic data. Can you give me a schema of the code? I am reading mic data on an analog pin.

Can you give me the algorithm or the flow chart of how you are using the timers to generate audio?

I don't use flow charts or algorithms, but I can give you commented code.

The following reads an analog input, and reproduces the audio on timer pins 9,10 with AVR/Arduino Uno/Nano (328 based) devices:

#define SAMPLE_RATE 32000
volatile unsigned int sampleData;

void setup() {

  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);

  /******* Set up timer1 ********/
  ICR1 = 10 * (1600000/SAMPLE_RATE);                 // For PWM generation. Timer TOP value.
  TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1);  //Enable the timer port/pin as output
  TCCR1A |= _BV(WGM11);                              //WGM11,12,13 all set to 1 = fast PWM/w ICR TOP
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);      //CS10 = no prescaling

  /******** Set up the ADC ******/
  ADMUX = _BV(REFS0);                                // Set analog reference to 5v
  ADCSRB |= _BV(ADTS1) | _BV(ADTS2);                 // Attach ADC to TIMER1 Overflow interrupt

  byte prescaleByte = 0;                             // Adjusts the ADC prescaler depending on sample rate for best quality audio

  if(      SAMPLE_RATE < 8900){  prescaleByte = B00000111;} //128
  else if( SAMPLE_RATE < 18000){ prescaleByte = B00000110;} //ADC division factor 64 (16MHz / 64 / 13clock cycles = 19230 Hz Max Sample Rate )
  else if( SAMPLE_RATE < 27000){ prescaleByte = B00000101;} //32  (38461Hz Max)
  else if( SAMPLE_RATE < 65000){ prescaleByte = B00000100;} //16  (76923Hz Max)
  else   {                       prescaleByte = B00000011;} //8  (fast as hell)

  ADCSRA = prescaleByte;                        // Set the prescaler
  ADCSRA |= _BV(ADEN) | _BV(ADATE);             // ADC Enable, Auto-trigger enable
  TIMSK1 = _BV(TOIE1);          //Enable the TIMER1 interrupt to begin everything
}

ISR(TIMER1_OVF_vect){ // This is called at the frequency of our sample rate 

  OCR1A = OCR1B = ADCL | ADCH << 8;  // Read the ADC values directly into the timer compare register to modify the duty cycle.

}

void loop() {

}

I want to make your existing RF24 library work with Edison with the audio interface on it bought the NRF transceivers and if you can guide me step by step I will write the library.

(added)Ok I will try the above code and get back to you after I test it.

I'm thinking it would just be easier for me to write the library if you want me to figure out how to do it on the Edison as well...

You tell me how you did it on the Arduino uno and I will do the same on Edison. I think the code you posted above should yield some useful results. I will get back to you after I try it.

So in this code you are setting the timer top to match the sampling frequency and you are reading ADC at that frequency? And are you also generating the PWM output (for the sound on the speaker) at the same frequency . So electrically where do I wire the headphone jack (I think it should be on the pin which generates the PWM). But how are you using the Mic ADC data to generate the PWM? What should be the pulse width of the PWM.Is this width in accordance to the analog value from the ADC?

Why are you using these two pins as output? One should be PWM out and the Mic should be attached to one analog pin.?

If I do this "analogWrite(9,analogRead(0)>>8);" at 8khz in an ISR will I get a good quality output?

So in this code you are setting the timer top to match the sampling frequency and you are reading ADC at that frequency?

Yes

And are you also generating the PWM output (for the sound on the speaker) at the same frequency.

Yes

So electrically where do I wire the headphone jack (I think it should be on the pin which generates the PWM). But how are you using the Mic ADC data to generate the PWM? What should be the pulse width of the PWM.Is this width in accordance to the analog value from the ADC?

Yes, pwm pins.

Why are you using these two pins as output? One should be PWM out and the Mic should be attached to one analog pin.?

Push/Pull instead of Push/Null output

If I do this "analogWrite(9,analogRead(0)>>8);" at 8khz in an ISR will I get a good quality output?

Probably not.

I have a question. What is the methood with which you are transferring the audio.I tried to send audio over wired serial connection at 3mbps but all I get is broken audio that is noisy and grainy. So how are you buffering the audio ?How are you maintaining audio sync at 8khz or whatever at the receiving end. Will it better if I use the RF24 wireless chip (is it taking care of the buffering?). Please let me knnow.

I post my code, and I add comments and documentation so people can learn from it if they want, but this level of detail requires to much time that I would rather spend on development. In this case, the development sketch is available, as well as the existing library, along with comments and blog posts. All of the information you require is there.

You have used the RX( ) and TX( ) functions , reading and writing pipes and some regsiters are also being manipulated.I will dig deep into your code to understand how the serial transfer is taking place. I want edison to receive the audio and Due to transmit audio. Please guide me as I ask more concrete questions.

As I read you did use buffer with interrupts for Rx and Tx. Some how I need to replicate this for edison and Due.

Where are these functions defined?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
radio.setChannel(1); // Set RF channel to 1
radio.setAutoAck(0); // Disable ACKnowledgement packets
radio.setDataRate(RF_SPEED); // Set data rate as specified in user options
radio.setCRCLength(RF24_CRC_8);
radio.openWritingPipe(pipes[0]); // Set up reading and writing pipes. All of the radios write via multicast on the same pipe
radio.openReadingPipe(1,pipes[1]); // All of the radios listen by default to the same multicast pipe
radio.openReadingPipe(2,pipes[radioIdentifier + 2]); // Every radio also has its own private listening pipe
radio.startListening();
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

I am able to play audio on edison using this library https://github.com/llatta/edison-audio/blob/master/src/audioTest.c
So tell me this if I capture the mic value on ADC can I just use the ADC value to generate audio or should I convert it to PCM or RAW format.If yes could you please give me a code snippet to convert mic ADC value to PCM or RAW format?

One thing is clear that the intel edison is able to generate audio using PWM as in the link above and my question is how do I use mic ADC data to generate audio. The above link uses samples stored in a file as:
const unsigned char audioData[41984] = {
0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f,
0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f,
0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, .....}

I do know what that format is and how do I convert the ADC data from the mic to such format. So I am asking for your help in understanding this.