From e5d08722f8e1a46ba73af7a6ea7a09833799a25f Mon Sep 17 00:00:00 2001 From: Andy Date: Sun, 19 Jun 2005 03:05:53 +0000 Subject: Iriver: First attempt at recording. Use Info->Debug->PCM recording to test recording of wav-files. Seams to work fine except occasional 100 ms noise at pos 100 ms (not later) so initialization or synch problem.. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6763 a1c6a512-1295-4272-9138-f99709370657 --- firmware/pcm_record.c | 650 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 650 insertions(+) create mode 100644 firmware/pcm_record.c (limited to 'firmware/pcm_record.c') diff --git a/firmware/pcm_record.c b/firmware/pcm_record.c new file mode 100644 index 0000000000..d11458427d --- /dev/null +++ b/firmware/pcm_record.c @@ -0,0 +1,650 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 by Linus Nielsen Feltzing + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "config.h" +#include "debug.h" +#include "panic.h" +#include "thread.h" + +#include +#include +#include +#include +#include + +#include "cpu.h" +#include "i2c.h" +#include "uda1380.h" +#include "system.h" +#include "usb.h" + +#include "buffer.h" +#include "audio.h" +#include "button.h" +#include "file.h" +#include "sprintf.h" +#include "logf.h" +#include "button.h" +#include "lcd.h" +#include "lcd-remote.h" +#include "pcm_playback.h" +#include "pcm_record.h" + + +/***************************************************************************/ + +static volatile bool is_recording; /* We are recording */ +static volatile bool is_stopping; /* Are we going to stop */ +static volatile bool is_paused; /* We have paused */ + +static volatile int num_rec_bytes; +static volatile int int_count; /* Number of DMA completed interrupts */ +static volatile int error_count; /* Number of DMA errors */ + +static unsigned long record_start_time; /* Value of current_tick when recording was started */ +static unsigned long pause_start_time; /* Value of current_tick when pause was started */ + +static int rec_gain, rec_volume; +static bool show_waveform; +static int init_done = 0; +static int wav_file; +static char recording_filename[MAX_PATH]; + +/***************************************************************************/ + +/* + Some estimates: + 44100 HZ * 4 = 176400 bytes/s + Refresh LCD 10 HZ = 176400 / 10 = 17640 bytes ~=~ 1024*16 bytes + + If NUM_BUFFERS is 80 we can hold ~8 sec of data in memory + ALL_BUFFER_SIZE will be 1024*16 * 80 = 1310720 bytes +*/ + +#define NUM_BUFFERS 80 +#define EACH_BUFFER_SIZE (1024*16) /* Multiple of 4. Use small value to get responsive waveform */ +#define ALL_BUFFERS_SIZE (NUM_BUFFERS * EACH_BUFFER_SIZE) + +#define WRITE_THRESHOLD 40 /* Minimum number of buffers before write to file */ + +static unsigned char *rec_buffers[NUM_BUFFERS]; + +/* + Overrun occures when DMA needs to write a new buffer and write_index == read_index + Solution to this is to optimize pcmrec_callback, use cpu_boost somewhere or increase + the total buffer size (or WRITE_THRESHOLD) +*/ + +static int write_index; /* Which buffer the DMA is currently recording */ +static int read_index; /* The oldest buffer that the pcmrec_callback has not read */ + +/***************************************************************************/ + +static struct event_queue pcmrec_queue; +static long pcmrec_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; +static const char pcmrec_thread_name[] = "pcmrec"; + +static void pcmrec_thread(void); + +/* Event IDs */ +#define PCMREC_OPEN 1 /* Enable recording */ +#define PCMREC_CLOSE 2 /* Disable recording */ +#define PCMREC_START 3 /* Start a new recording */ +#define PCMREC_STOP 4 /* Stop the current recording */ +#define PCMREC_PAUSE 10 +#define PCMREC_RESUME 11 +#define PCMREC_NEW_FILE 12 +#define PCMREC_SET_GAIN 13 +#define PCMREC_GOT_DATA 20 /* DMA1 notifies when data has arrived */ + + +/*******************************************************************/ +/* Functions that are not executing in the pcmrec_thread first */ +/*******************************************************************/ + +void pcm_init_recording(void) +{ + int_count = 0; + error_count = 0; + + show_waveform = 0; + is_recording = 0; + is_stopping = 0; + num_rec_bytes = 0; + wav_file = -1; + read_index = 0; + write_index = 0; + + queue_init(&pcmrec_queue); + create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), pcmrec_thread_name); +} + +void pcm_open_recording(void) +{ + init_done = 0; + + logf("pcm_open_rec"); + + queue_post(&pcmrec_queue, PCMREC_OPEN, 0); + + while (init_done) + { + sleep(HZ >> 8); + } + + logf("pcm_open_rec done"); +} + +void pcm_close_recording(void) +{ + /* todo: synchronize completion with pcmrec thread */ + queue_post(&pcmrec_queue, PCMREC_CLOSE, 0); +} + + + +unsigned long pcm_status(void) +{ + unsigned long ret = 0; + + if (is_recording) + ret |= AUDIO_STATUS_RECORD; + + return ret; +} + + + +void pcm_new_file(const char *filename) +{ + /* todo */ + filename = filename; + +} + +unsigned long pcm_recorded_time(void) +{ + if (is_recording) + { + if(is_paused) + return pause_start_time - record_start_time; + else + return current_tick - record_start_time; + } + + return 0; +} + +unsigned long pcm_num_recorded_bytes(void) +{ + + if (is_recording) + { + return num_rec_bytes; + } + else + return 0; +} + +void pcm_pause_recording(void) +{ + /* todo */ +} + +void pcm_resume_recording(void) +{ + /* todo */ +} + + +/** + * Sets the audio source + * + * Side effect: This functions starts feeding the CPU with audio data over the I2S bus + * + * @param source 0=line-in, 1=mic + */ +void pcm_set_recording_options(int source, bool enable_waveform) +{ + uda1380_enable_recording(source); + + show_waveform = enable_waveform; + +} + + +/** + * + * @param gain line-in and microphone gain (0-15) + * @param volume ADC volume (0-255) + */ +void pcm_set_recording_gain(int gain, int volume) +{ + rec_gain = gain; + rec_volume = volume; + + queue_post(&pcmrec_queue, PCMREC_SET_GAIN, 0); + +} + +/** + * Start recording + * + * Use pcm_set_recording_options before calling record + */ +void pcm_record(const char *filename) +{ + strncpy(recording_filename, filename, MAX_PATH - 1); + recording_filename[MAX_PATH - 1] = 0; + + queue_post(&pcmrec_queue, PCMREC_START, 0); +} + +/** + * + */ +void pcm_stop_recording(void) +{ + if (is_recording) + is_stopping = 1; + + queue_post(&pcmrec_queue, PCMREC_STOP, 0); + + logf("pcm_stop_recording"); + + while (is_stopping) + { + sleep(HZ >> 4); + } + + logf("pcm_stop_recording done"); + + +} + + +/***************************************************************************/ +/* Functions that executes in the context of pcmrec_thread */ +/***************************************************************************/ + + +/** + * Process the buffers using read_index and write_index. + * + * DMA1 handler posts to pcmrec_queue so that pcmrec_thread calls this + * function. Also pcmrec_stop will call this function when the recording + * is stopping, and that call will have flush = true. + * + */ + +void pcmrec_callback(bool flush) __attribute__ ((section (".icode"))); +void pcmrec_callback(bool flush) +{ + int num_ready; + + num_ready = write_index - read_index; + if (num_ready < 0) + num_ready += NUM_BUFFERS; + + /* we can consume up to num_ready buffers */ + +#ifdef HAVE_REMOTE_LCD + /* Draw waveform on remote LCD */ + if (show_waveform && num_ready>0) + { + short *buf; + long x,y,offset; + int show_index; + + /* Just display the last buffer (most recent one) */ + show_index = read_index + num_ready - 1; + buf = (short*)rec_buffers[show_index]; + + lcd_remote_clear_display(); + + offset = 0; + for (x=0; x> 15; /* Divide with SHRT_MAX */ + y += LCD_REMOTE_HEIGHT/2; + + if (y < 2) y=2; + if (y >= LCD_REMOTE_HEIGHT-2) y = LCD_REMOTE_HEIGHT-2; + + REMOTE_DRAW_PIXEL(x,y); + + offset += (EACH_BUFFER_SIZE/2) / LCD_REMOTE_WIDTH; + } + + lcd_remote_update(); + } + +#endif + + /* Note: This might be a good place to call the 'codec' later */ + + /* Check that we have the minimum amount of data to save or */ + /* that if it's closing time which mean we have to save.. */ + if (wav_file != -1) + { + if (num_ready >= WRITE_THRESHOLD || flush) + { + unsigned long *ptr = (unsigned long*)rec_buffers[read_index]; + int i; + + for (i=0; i= NUM_BUFFERS) + read_index -= NUM_BUFFERS; + } + + } else + { + /* In this case we must consume the buffers otherwise we will */ + /* get 'dma1 overrun' pretty fast */ + + read_index+=num_ready; + if (read_index >= NUM_BUFFERS) + read_index -= NUM_BUFFERS; + } +} + + + + +void pcmrec_dma_start(void) +{ + DAR1 = (unsigned long)rec_buffers[write_index++]; /* Destination address */ + SAR1 = (unsigned long)&PDIR2; /* Source address */ + BCR1 = EACH_BUFFER_SIZE; /* Bytes to transfer */ + + /* Start the DMA transfer.. */ + DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START; + + logf("dma1 started"); +} + + +/* DMA1 Interrupt is called when the DMA has finished transfering a chunk */ +void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); +void DMA1(void) +{ + int res = DSR1; + + DSR1 = 1; /* Clear interrupt */ + + int_count++; + + if (res & 0x70) + { + DCR1 = 0; /* Stop DMA transfer */ + error_count++; + is_recording = 0; + + logf("dma1 err 0x%x", res); + + } else + { + num_rec_bytes += EACH_BUFFER_SIZE; + + write_index++; + if (write_index >= NUM_BUFFERS) + write_index = 0; + + if (is_stopping || !is_recording) + { + DCR1 = 0; /* Stop DMA transfer */ + is_recording = 0; + + logf("dma1 stopping"); + + } else if (write_index == read_index) + { + DCR1 = 0; /* Stop DMA transfer */ + is_recording = 0; + + logf("dma1 overrun"); + + } else + { + DAR1 = (unsigned long)rec_buffers[write_index]; /* Destination address */ + BCR1 = EACH_BUFFER_SIZE; + + queue_post(&pcmrec_queue, PCMREC_GOT_DATA, NULL); + + } + } + + IPR |= (1<<15); /* Clear pending interrupt request */ +} + +static int start_wave(void) +{ + unsigned char header[44] = + { + 'R','I','F','F',0,0,0,0,'W','A','V','E','f','m','t',' ', + 0x10,0,0,0,1,0,2,0,0x44,0xac,0,0,0x10,0xb1,2,0, + 4,0,0x10,0,'d','a','t','a',0,0,0,0 + }; + + wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC); + if (wav_file < 0) + { + wav_file = -1; + logf("create failed: %d", wav_file); + return -1; + } + + if (sizeof(header) != write(wav_file, header, sizeof(header))) + { + close(wav_file); + wav_file = -1; + logf("write failed"); + return -2; + } + + return 0; +} + +/* Update header and set correct length values */ +static void close_wave(void) +{ + long l; + + l = SWAB32(num_rec_bytes + 36); + lseek(wav_file, 4, SEEK_SET); + write(wav_file, &l, 4); + + l = SWAB32(num_rec_bytes); + lseek(wav_file, 40, SEEK_SET); + write(wav_file, &l, 4); + + close(wav_file); + wav_file = -1; +} + +static void pcmrec_start(void) +{ + logf("pcmrec_start"); + + if (is_recording) + return; + + if (wav_file != -1) + close(wav_file); + + logf("rec: %s", recording_filename); + + start_wave(); /* todo: send signal to pcm_record if we have failed */ + + num_rec_bytes = 0; + + /* Store the current time */ + record_start_time = current_tick; + + write_index = 0; + read_index = 0; + + is_stopping = 0; + is_paused = 0; + is_recording = 1; + + pcmrec_dma_start(); + +} + +static void pcmrec_stop(void) +{ + /* wait for recording to finish */ + + /* todo: Abort current DMA transfer using DCR1.. */ + + logf("pcmrec_stop"); + + while (is_recording) + { + sleep(HZ >> 4); + } + + logf("pcmrec_stop done"); + + /* Write unfinished buffers to file */ + pcmrec_callback(true); + + close_wave(); + + is_stopping = 0; +} + +static void pcmrec_open(void) +{ + unsigned long buffer_start; + int i; + + show_waveform = 0; + is_recording = 0; + is_stopping = 0; + num_rec_bytes = 0; + wav_file = -1; + read_index = 0; + write_index = 0; + + buffer_start = (unsigned long)(&audiobuf[(audiobufend - audiobuf) - (ALL_BUFFERS_SIZE + 16)]); + buffer_start &= ~3; + + for (i=0; i