617b67e769
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10856 a1c6a512-1295-4272-9138-f99709370657
1176 lines
32 KiB
C
1176 lines
32 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 <kernel.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "cpu.h"
|
|
#include "i2c.h"
|
|
#include "power.h"
|
|
#ifdef HAVE_UDA1380
|
|
#include "uda1380.h"
|
|
#endif
|
|
#ifdef HAVE_TLV320
|
|
#include "tlv320.h"
|
|
#endif
|
|
#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 "sound.h"
|
|
#include "id3.h"
|
|
#include "pcm_record.h"
|
|
|
|
extern int boost_counter; /* used for boost check */
|
|
|
|
/***************************************************************************/
|
|
|
|
static bool is_recording; /* We are recording */
|
|
static bool is_paused; /* We have paused */
|
|
static bool is_error; /* An error has occured */
|
|
|
|
static unsigned long num_rec_bytes; /* Num bytes recorded */
|
|
static unsigned long num_file_bytes; /* Num bytes written to current file */
|
|
static int error_count; /* Number of DMA errors */
|
|
static unsigned long num_pcm_samples; /* Num pcm samples written to current file */
|
|
|
|
static long record_start_time; /* current_tick when recording was started */
|
|
static long pause_start_time; /* current_tick when pause was started */
|
|
static unsigned int sample_rate; /* Sample rate at time of recording start */
|
|
static int rec_source; /* Current recording source */
|
|
|
|
static int wav_file;
|
|
static char recording_filename[MAX_PATH];
|
|
|
|
static volatile bool init_done, close_done, record_done;
|
|
static volatile bool stop_done, pause_done, resume_done, new_file_done;
|
|
|
|
static int peak_left, peak_right;
|
|
|
|
#ifdef IAUDIO_X5
|
|
#define SET_IIS_PLAY(x) IIS1CONFIG = (x);
|
|
#define SET_IIS_REC(x) IIS1CONFIG = (x);
|
|
#else
|
|
#define SET_IIS_PLAY(x) IIS2CONFIG = (x);
|
|
#define SET_IIS_REC(x) IIS1CONFIG = (x);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
use 2 circular buffers of same size:
|
|
rec_buffer=DMA output buffer: chunks (8192 Bytes) of raw pcm audio data
|
|
enc_buffer=encoded audio buffer: storage for encoder output data
|
|
|
|
Flow:
|
|
1. when entering recording_screen DMA feeds the ringbuffer rec_buffer
|
|
2. if enough pcm data are available the encoder codec does encoding of pcm
|
|
chunks (4-8192 Bytes) into ringbuffer enc_buffer in codec_thread
|
|
3. pcmrec_callback detects enc_buffer 'near full' and writes data to disk
|
|
|
|
Functions calls:
|
|
1.main: codec_load_encoder(); start the encoder
|
|
2.encoder: enc_get_inputs(); get encoder buffsize, mono/stereo, quality
|
|
3.encoder: enc_set_parameters(); set the encoder parameters (max.chunksize)
|
|
4.encoder: enc_get_wav_data(); get n bytes of unprocessed pcm data
|
|
5.encoder: enc_wavbuf_near_empty();if true: reduce cpu_boost
|
|
6.encoder: enc_alloc_chunk(); get a ptr to next enc chunk
|
|
7.encoder: <process enc chunk> compress and store data to enc chunk
|
|
8.encoder: enc_free_chunk(); inform main about chunk process finished
|
|
9.encoder: repeat 4. to 8.
|
|
A.main: enc_set_header_callback(); create the current format header (file)
|
|
****************************************************************************/
|
|
#define NUM_CHUNKS 256 /* Power of 2 */
|
|
#define CHUNK_SIZE 8192 /* Power of 2 */
|
|
#define MAX_FEED_SIZE 20000 /* max pcm size passed to encoder */
|
|
#define CHUNK_MASK (NUM_CHUNKS * CHUNK_SIZE - 1)
|
|
#define WRITE_THRESHOLD (44100 * 5 / enc_samp_per_chunk) /* 5sec */
|
|
#define GET_CHUNK(x) (long*)(&rec_buffer[x])
|
|
#define GET_ENC_CHUNK(x) (long*)(&enc_buffer[enc_chunk_size*(x)])
|
|
|
|
static int audio_enc_id; /* current encoder id */
|
|
static unsigned char *rec_buffer; /* Circular recording buffer */
|
|
static unsigned char *enc_buffer; /* Circular encoding buffer */
|
|
static unsigned char *enc_head_buffer; /* encoder header buffer */
|
|
static int enc_head_size; /* used size in header buffer */
|
|
static int write_pos; /* Current chunk pos for DMA writing */
|
|
static int read_pos; /* Current chunk pos for encoding */
|
|
static long pre_record_ticks;/* pre-record time expressed in ticks */
|
|
static int enc_wr_index; /* Current encoding chunk write index */
|
|
static int enc_rd_index; /* Current encoding chunk read index */
|
|
static int enc_chunk_size; /* maximum encoder chunk size */
|
|
static int enc_num_chunks; /* number of chunks in ringbuffer */
|
|
static int enc_buffer_size; /* encode buffer size */
|
|
static int enc_channels; /* 1=mono 2=stereo */
|
|
static int enc_quality; /* mp3: 64,96,128,160,192,320 kBit */
|
|
static int enc_samp_per_chunk;/* pcm samples per encoder chunk */
|
|
static bool wav_queue_empty; /* all wav chunks processed? */
|
|
static unsigned long avrg_bit_rate; /* average bit rates from chunks */
|
|
static unsigned long curr_bit_rate; /* cumulated bit rates from chunks */
|
|
static unsigned long curr_chunk_cnt; /* number of processed chunks */
|
|
|
|
void (*enc_set_header_callback)(void *head_buffer, int head_size,
|
|
int num_pcm_samples, bool is_file_header);
|
|
|
|
/***************************************************************************/
|
|
|
|
static struct event_queue pcmrec_queue;
|
|
static long pcmrec_stack[2*DEFAULT_STACK_SIZE/sizeof(long)];
|
|
static const char pcmrec_thread_name[] = "pcmrec";
|
|
|
|
static void pcmrec_thread(void);
|
|
static void pcmrec_dma_start(void);
|
|
static void pcmrec_dma_stop(void);
|
|
static void close_wave(void);
|
|
|
|
/* Event IDs */
|
|
#define PCMREC_INIT 1 /* Enable recording */
|
|
#define PCMREC_CLOSE 2
|
|
|
|
#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
|
|
|
|
/*******************************************************************/
|
|
/* Functions that are not executing in the pcmrec_thread first */
|
|
/*******************************************************************/
|
|
|
|
/* Creates pcmrec_thread */
|
|
void pcm_rec_init(void)
|
|
{
|
|
queue_init(&pcmrec_queue);
|
|
create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack),
|
|
pcmrec_thread_name);
|
|
}
|
|
|
|
|
|
int audio_get_encoder_id(void)
|
|
{
|
|
return audio_enc_id;
|
|
}
|
|
|
|
/* Initializes recording:
|
|
* - Set up the UDA1380/TLV320 for recording
|
|
* - Prepare for DMA transfers
|
|
*/
|
|
|
|
void audio_init_recording(unsigned int buffer_offset)
|
|
{
|
|
(void)buffer_offset;
|
|
|
|
init_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_INIT, 0);
|
|
|
|
while(!init_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
}
|
|
|
|
void audio_close_recording(void)
|
|
{
|
|
close_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_CLOSE, 0);
|
|
|
|
while(!close_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
|
|
audio_remove_encoder();
|
|
}
|
|
|
|
unsigned long pcm_rec_status(void)
|
|
{
|
|
unsigned long ret = 0;
|
|
|
|
if (is_recording)
|
|
ret |= AUDIO_STATUS_RECORD;
|
|
if (is_paused)
|
|
ret |= AUDIO_STATUS_PAUSE;
|
|
if (is_error)
|
|
ret |= AUDIO_STATUS_ERROR;
|
|
if (!is_recording && pre_record_ticks && init_done && !close_done)
|
|
ret |= AUDIO_STATUS_PRERECORD;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int pcm_rec_current_bitrate(void)
|
|
{
|
|
return avrg_bit_rate;
|
|
}
|
|
|
|
unsigned long audio_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 audio_num_recorded_bytes(void)
|
|
{
|
|
if (is_recording)
|
|
return num_rec_bytes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
/* Only the last six of these are standard rates, but all sample rates are
|
|
* possible, so we support some other common ones as well.
|
|
*/
|
|
static unsigned long spdif_sample_rates[] = {
|
|
8000, 11025, 12000, 16000, 22050, 24000,
|
|
32000, 44100, 48000, 64000, 88200, 96000
|
|
};
|
|
|
|
/* Return SPDIF sample rate. Since we base our reading on the actual SPDIF
|
|
* sample rate (which might be a bit inaccurate), we round off to the closest
|
|
* sample rate that is supported by SPDIF.
|
|
*/
|
|
unsigned long audio_get_spdif_sample_rate(void)
|
|
{
|
|
int i = 0;
|
|
unsigned long measured_rate;
|
|
const int upper_bound = sizeof(spdif_sample_rates)/sizeof(long) - 1;
|
|
|
|
/* The following formula is specified in MCF5249 user's manual section
|
|
* 17.6.1. The 3*(1 << 13) part will need changing if the setup of the
|
|
* PHASECONFIG register is ever changed. The 128 divide is because of the
|
|
* fact that the SPDIF clock is the sample rate times 128.
|
|
*/
|
|
measured_rate = (unsigned long)((unsigned long long)FREQMEAS*CPU_FREQ/
|
|
((1 << 15)*3*(1 << 13))/128);
|
|
/* Find which SPDIF sample rate we're closest to. */
|
|
while (spdif_sample_rates[i] < measured_rate && i < upper_bound) ++i;
|
|
if (i > 0 && i < upper_bound)
|
|
{
|
|
long diff1 = measured_rate - spdif_sample_rates[i - 1];
|
|
long diff2 = spdif_sample_rates[i] - measured_rate;
|
|
|
|
if (diff2 > diff1) --i;
|
|
}
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* not needed atm */
|
|
#ifdef HAVE_SPDIF_POWER
|
|
static bool spdif_power_setting;
|
|
|
|
void audio_set_spdif_power_setting(bool on)
|
|
{
|
|
spdif_power_setting = on;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* Sets recording parameters
|
|
*
|
|
* This functions starts feeding the CPU with audio data over the I2S bus
|
|
*/
|
|
void audio_set_recording_options(int frequency, int quality,
|
|
int source, int channel_mode,
|
|
bool editable, int prerecord_time)
|
|
{
|
|
/* TODO: */
|
|
(void)editable;
|
|
|
|
/* NOTE: Coldfire UDA based recording does not yet support anything other
|
|
* than 44.1kHz sampling rate, so we limit it to that case here now. SPDIF
|
|
* based recording will overwrite this value with the proper sample rate in
|
|
* audio_record(), and will not be affected by this.
|
|
*/
|
|
frequency = 44100;
|
|
enc_quality = quality;
|
|
rec_source = source;
|
|
enc_channels = channel_mode == CHN_MODE_MONO ? 1 : 2;
|
|
pre_record_ticks = prerecord_time * HZ;
|
|
|
|
switch (source)
|
|
{
|
|
case AUDIO_SRC_MIC:
|
|
case AUDIO_SRC_LINEIN:
|
|
#ifdef HAVE_FMRADIO_IN
|
|
case AUDIO_SRC_FMRADIO:
|
|
#endif
|
|
/* Generate int. when 6 samples in FIFO, PDIR2 src = IIS1recv */
|
|
DATAINCONTROL = 0xc020;
|
|
break;
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
case AUDIO_SRC_SPDIF:
|
|
/* Int. when 6 samples in FIFO. PDIR2 source = ebu1RcvData */
|
|
DATAINCONTROL = 0xc038;
|
|
break;
|
|
#endif /* HAVE_SPDIF_IN */
|
|
}
|
|
|
|
sample_rate = frequency;
|
|
|
|
/* Monitoring: route the signals through the coldfire audio interface. */
|
|
|
|
SET_IIS_PLAY(0x800); /* Reset before reprogram */
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
if (source == AUDIO_SRC_SPDIF)
|
|
{
|
|
/* SCLK2 = Audioclk/4 (can't use EBUin clock), TXSRC = EBU1rcv, 64 bclk/wclk */
|
|
IIS2CONFIG = (6 << 12) | (7 << 8) | (4 << 2);
|
|
/* S/PDIF feed-through already configured */
|
|
}
|
|
else
|
|
{
|
|
/* SCLK2 follow IIS1 (UDA clock), TXSRC = IIS1rcv, 64 bclk/wclk */
|
|
IIS2CONFIG = (8 << 12) | (4 << 8) | (4 << 2);
|
|
|
|
EBU1CONFIG = 0x800; /* Reset before reprogram */
|
|
/* SCLK2, TXSRC = IIS1recv, validity, normal operation */
|
|
EBU1CONFIG = (7 << 12) | (4 << 8) | (1 << 5) | (5 << 2);
|
|
}
|
|
#else
|
|
/* SCLK2 follow IIS1 (UDA clock), TXSRC = IIS1rcv, 64 bclk/wclk */
|
|
SET_IIS_PLAY( (8 << 12) | (4 << 8) | (4 << 2) );
|
|
#endif
|
|
|
|
audio_load_encoder(rec_quality_info_afmt[quality]);
|
|
}
|
|
|
|
|
|
/**
|
|
* Note that microphone is mono, only left value is used
|
|
* See {uda1380,tlv320}_set_recvol() for exact ranges.
|
|
*
|
|
* @param type 0=line-in (radio), 1=mic
|
|
*
|
|
*/
|
|
void audio_set_recording_gain(int left, int right, int type)
|
|
{
|
|
//logf("rcmrec: t=%d l=%d r=%d", type, left, right);
|
|
#if defined(HAVE_UDA1380)
|
|
uda1380_set_recvol(left, right, type);
|
|
#elif defined (HAVE_TLV320)
|
|
tlv320_set_recvol(left, right, type);
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Start recording
|
|
*
|
|
* Use audio_set_recording_options first to select recording options
|
|
*/
|
|
void audio_record(const char *filename)
|
|
{
|
|
if (is_recording)
|
|
{
|
|
logf("record while recording");
|
|
return;
|
|
}
|
|
|
|
strncpy(recording_filename, filename, MAX_PATH - 1);
|
|
recording_filename[MAX_PATH - 1] = 0;
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
if (rec_source == AUDIO_SRC_SPDIF)
|
|
sample_rate = audio_get_spdif_sample_rate();
|
|
#endif
|
|
|
|
record_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_START, 0);
|
|
|
|
while(!record_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
}
|
|
|
|
|
|
void audio_new_file(const char *filename)
|
|
{
|
|
logf("pcm_new_file");
|
|
|
|
new_file_done = false;
|
|
|
|
strncpy(recording_filename, filename, MAX_PATH - 1);
|
|
recording_filename[MAX_PATH - 1] = 0;
|
|
|
|
queue_post(&pcmrec_queue, PCMREC_NEW_FILE, 0);
|
|
|
|
while(!new_file_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
|
|
logf("pcm_new_file done");
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void audio_stop_recording(void)
|
|
{
|
|
if (!is_recording)
|
|
return;
|
|
|
|
logf("pcm_stop");
|
|
|
|
is_paused = true; /* fix pcm write ptr at current position */
|
|
stop_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_STOP, 0);
|
|
|
|
while(!stop_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
|
|
logf("pcm_stop done");
|
|
}
|
|
|
|
void audio_pause_recording(void)
|
|
{
|
|
if (!is_recording)
|
|
{
|
|
logf("pause when not recording");
|
|
return;
|
|
}
|
|
if (is_paused)
|
|
{
|
|
logf("pause when paused");
|
|
return;
|
|
}
|
|
|
|
pause_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_PAUSE, 0);
|
|
|
|
while(!pause_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
}
|
|
|
|
void audio_resume_recording(void)
|
|
{
|
|
if (!is_paused)
|
|
{
|
|
logf("resume when not paused");
|
|
return;
|
|
}
|
|
|
|
resume_done = false;
|
|
queue_post(&pcmrec_queue, PCMREC_RESUME, 0);
|
|
|
|
while(!resume_done)
|
|
sleep_thread();
|
|
wake_up_thread();
|
|
}
|
|
|
|
/* return peaks as int, so convert from short first
|
|
note that peak values are always positive */
|
|
void pcm_rec_get_peaks(int *left, int *right)
|
|
{
|
|
if (left)
|
|
*left = peak_left;
|
|
if (right)
|
|
*right = peak_right;
|
|
peak_left = 0;
|
|
peak_right = 0;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* Functions that executes in the context of pcmrec_thread */
|
|
/***************************************************************************/
|
|
|
|
/**
|
|
* Process the chunks
|
|
*
|
|
* This function is called when queue_get_w_tmo times out.
|
|
*
|
|
* Other functions can also call this function with flush = true when
|
|
* they want to save everything in the buffers to disk.
|
|
*
|
|
*/
|
|
static void pcmrec_callback(bool flush)
|
|
{
|
|
int i, num_ready, size_yield;
|
|
long *enc_chunk, chunk_size;
|
|
|
|
if (!is_recording && !flush)
|
|
return;
|
|
|
|
num_ready = enc_wr_index - enc_rd_index;
|
|
if (num_ready < 0)
|
|
num_ready += enc_num_chunks;
|
|
|
|
/* calculate an estimate of recorded bytes */
|
|
num_rec_bytes = num_file_bytes + num_ready * /* enc_chunk_size */
|
|
((avrg_bit_rate * 1000 / 8 * enc_samp_per_chunk + 22050) / 44100);
|
|
|
|
/* near full state reached: less than 5sec remaining space */
|
|
if (enc_num_chunks - num_ready < WRITE_THRESHOLD || flush)
|
|
{
|
|
bool must_boost = (boost_counter ? false : true);
|
|
|
|
logf("writing: %d (%d)", num_ready, flush);
|
|
|
|
if(must_boost)
|
|
cpu_boost(true);
|
|
|
|
size_yield = 0;
|
|
for (i=0; i<num_ready; i++)
|
|
{
|
|
enc_chunk = GET_ENC_CHUNK(enc_rd_index);
|
|
chunk_size = *enc_chunk++;
|
|
|
|
/* safety net: if size entry got corrupted => limit */
|
|
if (chunk_size > (long)(enc_chunk_size - sizeof(long)))
|
|
chunk_size = enc_chunk_size - sizeof(long);
|
|
|
|
if (enc_set_header_callback != NULL)
|
|
enc_set_header_callback(enc_chunk, enc_chunk_size,
|
|
num_pcm_samples, false);
|
|
|
|
if (write(wav_file, enc_chunk, chunk_size) != chunk_size)
|
|
{
|
|
close_wave();
|
|
if(must_boost)
|
|
cpu_boost(false);
|
|
logf("pcmrec: write err");
|
|
is_error = true;
|
|
break;
|
|
}
|
|
|
|
num_file_bytes += chunk_size;
|
|
num_pcm_samples += enc_samp_per_chunk;
|
|
size_yield += chunk_size;
|
|
|
|
if (size_yield >= 32768)
|
|
{ /* yield when 32kB written */
|
|
size_yield = 0;
|
|
yield();
|
|
}
|
|
|
|
enc_rd_index = (enc_rd_index + 1) % enc_num_chunks;
|
|
}
|
|
|
|
if(must_boost)
|
|
cpu_boost(false);
|
|
|
|
/* sync file */
|
|
fsync(wav_file);
|
|
|
|
logf("done");
|
|
}
|
|
}
|
|
|
|
/* Abort dma transfer */
|
|
static void pcmrec_dma_stop(void)
|
|
{
|
|
DCR1 = 0;
|
|
|
|
error_count++;
|
|
|
|
DSR1 = 1; /* Clear interrupt */
|
|
IPR |= (1<<15); /* Clear pending interrupt request */
|
|
|
|
logf("dma1 stopped");
|
|
}
|
|
|
|
static void pcmrec_dma_start(void)
|
|
{
|
|
DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */
|
|
SAR1 = (unsigned long)&PDIR2; /* Source address */
|
|
BCR1 = CHUNK_SIZE; /* Bytes to transfer */
|
|
|
|
/* Start the DMA transfer.. */
|
|
#ifdef HAVE_SPDIF_IN
|
|
INTERRUPTCLEAR = 0x03c00000;
|
|
#endif
|
|
|
|
/* 16Byte transfers prevents from sporadic errors during cpu_boost() */
|
|
DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_DSIZE(3) | 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 */
|
|
|
|
if (res & 0x70)
|
|
{
|
|
DCR1 = 0; /* Stop DMA transfer */
|
|
error_count++;
|
|
|
|
logf("dma1 err: 0x%x", res);
|
|
|
|
DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */
|
|
BCR1 = CHUNK_SIZE;
|
|
DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START;
|
|
|
|
/* Flush recorded data to disk and stop recording */
|
|
queue_post(&pcmrec_queue, PCMREC_STOP, NULL);
|
|
}
|
|
#ifdef HAVE_SPDIF_IN
|
|
else if ((rec_source == AUDIO_SRC_SPDIF) &&
|
|
(INTERRUPTSTAT & 0x01c00000)) /* valnogood, symbolerr, parityerr */
|
|
{
|
|
INTERRUPTCLEAR = 0x03c00000;
|
|
error_count++;
|
|
|
|
logf("spdif err");
|
|
|
|
DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */
|
|
BCR1 = CHUNK_SIZE;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
long peak_l, peak_r;
|
|
long *ptr, j;
|
|
|
|
ptr = GET_CHUNK(write_pos);
|
|
|
|
if (!is_paused) /* advance write position */
|
|
write_pos = (write_pos + CHUNK_SIZE) & CHUNK_MASK;
|
|
|
|
DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */
|
|
BCR1 = CHUNK_SIZE;
|
|
|
|
peak_l = peak_r = 0;
|
|
|
|
/* only peak every 4th sample */
|
|
for (j=0; j<CHUNK_SIZE/4; j+=4)
|
|
{
|
|
long value = ptr[j];
|
|
#ifdef ROCKBOX_BIG_ENDIAN
|
|
if (value > peak_l) peak_l = value;
|
|
else if (-value > peak_l) peak_l = -value;
|
|
|
|
value <<= 16;
|
|
if (value > peak_r) peak_r = value;
|
|
else if (-value > peak_r) peak_r = -value;
|
|
#else
|
|
if (value > peak_r) peak_r = value;
|
|
else if (-value > peak_r) peak_r = -value;
|
|
|
|
value <<= 16;
|
|
if (value > peak_l) peak_l = value;
|
|
else if (-value > peak_l) peak_l = -value;
|
|
#endif
|
|
}
|
|
|
|
peak_left = (int)(peak_l >> 16);
|
|
peak_right = (int)(peak_r >> 16);
|
|
}
|
|
|
|
IPR |= (1<<15); /* Clear pending interrupt request */
|
|
}
|
|
|
|
/* Create WAVE file and write header */
|
|
/* Sets returns 0 if success, -1 on failure */
|
|
static int start_wave(void)
|
|
{
|
|
wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC);
|
|
|
|
if (wav_file < 0)
|
|
{
|
|
wav_file = -1;
|
|
logf("rec: create failed: %d", wav_file);
|
|
is_error = true;
|
|
return -1;
|
|
}
|
|
|
|
/* add main file header (enc_head_size=0 for encoders without) */
|
|
if (enc_head_size != write(wav_file, enc_head_buffer, enc_head_size))
|
|
{
|
|
close(wav_file);
|
|
wav_file = -1;
|
|
logf("rec: write failed");
|
|
is_error = true;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Update header and set correct length values */
|
|
static void close_wave(void)
|
|
{
|
|
unsigned char head[100]; /* assume maximum 100 bytes for file header */
|
|
int size_read;
|
|
|
|
if (wav_file != -1)
|
|
{
|
|
/* update header before closing the file (wav+wv encoder will do) */
|
|
if (enc_set_header_callback != NULL)
|
|
{
|
|
lseek(wav_file, 0, SEEK_SET);
|
|
/* try to read the head size (but we'll accept less) */
|
|
size_read = read(wav_file, head, sizeof(head));
|
|
|
|
enc_set_header_callback(head, size_read, num_pcm_samples, true);
|
|
lseek(wav_file, 0, SEEK_SET);
|
|
write(wav_file, head, size_read);
|
|
}
|
|
close(wav_file);
|
|
wav_file = -1;
|
|
}
|
|
}
|
|
|
|
static void pcmrec_start(void)
|
|
{
|
|
long max_pre_chunks, pre_ticks, max_pre_ticks;
|
|
|
|
logf("pcmrec_start");
|
|
|
|
if (is_recording)
|
|
{
|
|
logf("already recording");
|
|
record_done = true;
|
|
return;
|
|
}
|
|
|
|
if (wav_file != -1)
|
|
close_wave();
|
|
|
|
if (start_wave() != 0)
|
|
{
|
|
/* failed to create the file */
|
|
record_done = true;
|
|
return;
|
|
}
|
|
|
|
/* calculate maximum available chunks & resulting ticks */
|
|
max_pre_chunks = (enc_wr_index - enc_rd_index +
|
|
enc_num_chunks) % enc_num_chunks;
|
|
if (max_pre_chunks > enc_num_chunks - WRITE_THRESHOLD)
|
|
max_pre_chunks = enc_num_chunks - WRITE_THRESHOLD;
|
|
max_pre_ticks = max_pre_chunks * HZ * enc_samp_per_chunk / 44100;
|
|
|
|
/* limit prerecord if not enough data available */
|
|
pre_ticks = pre_record_ticks > max_pre_ticks ?
|
|
max_pre_ticks : pre_record_ticks;
|
|
max_pre_chunks = 44100 * pre_ticks / HZ / enc_samp_per_chunk;
|
|
enc_rd_index = (enc_wr_index - max_pre_chunks +
|
|
enc_num_chunks) % enc_num_chunks;
|
|
|
|
record_start_time = current_tick - pre_ticks;
|
|
|
|
num_rec_bytes = enc_num_chunks * CHUNK_SIZE;
|
|
num_file_bytes = 0;
|
|
num_pcm_samples = 0;
|
|
pause_start_time = 0;
|
|
|
|
is_paused = false;
|
|
is_recording = true;
|
|
record_done = true;
|
|
}
|
|
|
|
static void pcmrec_stop(void)
|
|
{
|
|
logf("pcmrec_stop");
|
|
|
|
if (is_recording)
|
|
{
|
|
/* wait for encoding finish */
|
|
is_paused = true;
|
|
while(!wav_queue_empty)
|
|
sleep_thread();
|
|
|
|
wake_up_thread();
|
|
is_recording = false;
|
|
|
|
/* Flush buffers to file */
|
|
pcmrec_callback(true);
|
|
close_wave();
|
|
}
|
|
|
|
is_paused = false;
|
|
stop_done = true;
|
|
|
|
logf("pcmrec_stop done");
|
|
}
|
|
|
|
static void pcmrec_new_file(void)
|
|
{
|
|
logf("pcmrec_new_file");
|
|
|
|
if (!is_recording)
|
|
{
|
|
logf("not recording");
|
|
new_file_done = true;
|
|
return;
|
|
}
|
|
|
|
/* Since pcmrec_callback() blocks until the data has been written,
|
|
here is a good approximation when recording to the new file starts
|
|
*/
|
|
record_start_time = current_tick;
|
|
|
|
if (is_paused)
|
|
pause_start_time = record_start_time;
|
|
|
|
/* Flush what we got in buffers to file */
|
|
pcmrec_callback(true);
|
|
|
|
close_wave();
|
|
|
|
num_rec_bytes = 0;
|
|
num_file_bytes = 0;
|
|
num_pcm_samples = 0;
|
|
|
|
/* start the new file */
|
|
if (start_wave() != 0)
|
|
{
|
|
logf("new_file failed");
|
|
pcmrec_stop();
|
|
}
|
|
|
|
new_file_done = true;
|
|
logf("pcmrec_new_file done");
|
|
}
|
|
|
|
static void pcmrec_pause(void)
|
|
{
|
|
logf("pcmrec_pause");
|
|
|
|
if (!is_recording)
|
|
{
|
|
logf("pause: not recording");
|
|
pause_done = true;
|
|
return;
|
|
}
|
|
|
|
pause_start_time = current_tick;
|
|
is_paused = true;
|
|
pause_done = true;
|
|
|
|
logf("pcmrec_pause done");
|
|
}
|
|
|
|
|
|
static void pcmrec_resume(void)
|
|
{
|
|
logf("pcmrec_resume");
|
|
|
|
if (!is_paused)
|
|
{
|
|
logf("resume: not paused");
|
|
resume_done = true;
|
|
return;
|
|
}
|
|
|
|
is_paused = false;
|
|
is_recording = true;
|
|
|
|
/* Compensate for the time we have been paused */
|
|
if (pause_start_time)
|
|
{
|
|
record_start_time += current_tick - pause_start_time;
|
|
pause_start_time = 0;
|
|
}
|
|
|
|
resume_done = true;
|
|
logf("pcmrec_resume done");
|
|
}
|
|
|
|
/**
|
|
* audio_init_recording calls this function using PCMREC_INIT
|
|
*
|
|
*/
|
|
static void pcmrec_init(void)
|
|
{
|
|
wav_file = -1;
|
|
read_pos = 0;
|
|
write_pos = 0;
|
|
enc_wr_index = 0;
|
|
enc_rd_index = 0;
|
|
|
|
avrg_bit_rate = 0;
|
|
curr_bit_rate = 0;
|
|
curr_chunk_cnt = 0;
|
|
|
|
peak_left = 0;
|
|
peak_right = 0;
|
|
|
|
num_rec_bytes = 0;
|
|
num_file_bytes = 0;
|
|
num_pcm_samples = 0;
|
|
record_start_time = 0;
|
|
pause_start_time = 0;
|
|
|
|
close_done = false;
|
|
is_recording = false;
|
|
is_paused = false;
|
|
is_error = false;
|
|
|
|
rec_buffer = (unsigned char*)(((long)audiobuf + 15) & ~15);
|
|
enc_buffer = rec_buffer + NUM_CHUNKS * CHUNK_SIZE + MAX_FEED_SIZE;
|
|
/* 8000Bytes at audiobufend */
|
|
enc_buffer_size = audiobufend - enc_buffer - 8000;
|
|
|
|
SET_IIS_PLAY(0x800); /* Stop any playback */
|
|
AUDIOGLOB |= 0x180; /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */
|
|
DATAINCONTROL = 0xc000; /* Generate Interrupt when 6 samples in fifo */
|
|
|
|
DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */
|
|
DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */
|
|
DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2;
|
|
ICR7 = 0x1c; /* Enable interrupt at level 7, priority 0 */
|
|
IMR &= ~(1<<15); /* bit 15 is DMA1 */
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
PHASECONFIG = 0x34; /* Gain = 3*2^13, source = EBUIN */
|
|
#endif
|
|
pcmrec_dma_start();
|
|
|
|
init_done = 1;
|
|
}
|
|
|
|
static void pcmrec_close(void)
|
|
{
|
|
DMAROUTE = (DMAROUTE & 0xffff00ff);
|
|
ICR7 = 0x00; /* Disable interrupt */
|
|
IMR |= (1<<15); /* bit 15 is DMA1 */
|
|
|
|
pcmrec_dma_stop();
|
|
|
|
/* Reset PDIR2 data flow */
|
|
DATAINCONTROL = 0x200;
|
|
close_done = true;
|
|
init_done = false;
|
|
}
|
|
|
|
static void pcmrec_thread(void)
|
|
{
|
|
struct event ev;
|
|
|
|
logf("thread pcmrec start");
|
|
|
|
error_count = 0;
|
|
|
|
while(1)
|
|
{
|
|
queue_wait_w_tmo(&pcmrec_queue, &ev, HZ / 4);
|
|
|
|
switch (ev.id)
|
|
{
|
|
case PCMREC_INIT:
|
|
pcmrec_init();
|
|
break;
|
|
|
|
case PCMREC_CLOSE:
|
|
pcmrec_close();
|
|
break;
|
|
|
|
case PCMREC_START:
|
|
pcmrec_start();
|
|
break;
|
|
|
|
case PCMREC_STOP:
|
|
pcmrec_stop();
|
|
break;
|
|
|
|
case PCMREC_PAUSE:
|
|
pcmrec_pause();
|
|
break;
|
|
|
|
case PCMREC_RESUME:
|
|
pcmrec_resume();
|
|
break;
|
|
|
|
case PCMREC_NEW_FILE:
|
|
pcmrec_new_file();
|
|
break;
|
|
|
|
case SYS_TIMEOUT:
|
|
pcmrec_callback(false);
|
|
break;
|
|
|
|
case SYS_USB_CONNECTED:
|
|
if (!is_recording)
|
|
{
|
|
pcmrec_close();
|
|
usb_acknowledge(SYS_USB_CONNECTED_ACK);
|
|
usb_wait_for_disconnect(&pcmrec_queue);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
logf("thread pcmrec done");
|
|
}
|
|
|
|
/* Select VINL & VINR source: 0=Line-in, 1=FM Radio */
|
|
void pcm_rec_mux(int source)
|
|
{
|
|
#ifdef IRIVER_H300_SERIES
|
|
if(source == 0)
|
|
and_l(~0x40000000, &GPIO_OUT); /* Line In */
|
|
else
|
|
or_l(0x40000000, &GPIO_OUT); /* FM radio */
|
|
|
|
or_l(0x40000000, &GPIO_ENABLE);
|
|
or_l(0x40000000, &GPIO_FUNCTION);
|
|
#elif defined(IRIVER_H100_SERIES)
|
|
if(source == 0)
|
|
and_l(~0x00800000, &GPIO_OUT); /* Line In */
|
|
else
|
|
or_l(0x00800000, &GPIO_OUT); /* FM radio */
|
|
|
|
or_l(0x00800000, &GPIO_ENABLE);
|
|
or_l(0x00800000, &GPIO_FUNCTION);
|
|
|
|
#elif defined(IAUDIO_X5)
|
|
if(source == 0)
|
|
or_l((1<<29), &GPIO_OUT); /* Line In */
|
|
else
|
|
and_l(~(1<<29), &GPIO_OUT); /* FM radio */
|
|
|
|
or_l((1<<29), &GPIO_ENABLE);
|
|
or_l((1<<29), &GPIO_FUNCTION);
|
|
|
|
/* iAudio x5 */
|
|
#endif
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
/* */
|
|
/* following functions will be called by the encoder codec */
|
|
/* */
|
|
/****************************************************************************/
|
|
|
|
/* pass the encoder buffer pointer/size, mono/stereo, quality to the encoder */
|
|
void enc_get_inputs(int *buffer_size, int *channels, int *quality)
|
|
{
|
|
*buffer_size = enc_buffer_size;
|
|
*channels = enc_channels;
|
|
*quality = enc_quality;
|
|
}
|
|
|
|
/* set the encoder dimensions (called by encoder codec at initialization) */
|
|
void enc_set_parameters(int chunk_size, int num_chunks, int samp_per_chunk,
|
|
char *head_ptr, int head_size, int enc_id)
|
|
{
|
|
/* set read_pos just in front of current write_pos */
|
|
read_pos = (write_pos - CHUNK_SIZE) & CHUNK_MASK;
|
|
|
|
enc_rd_index = 0; /* reset */
|
|
enc_wr_index = 0; /* reset */
|
|
enc_chunk_size = chunk_size; /* max chunk size */
|
|
enc_num_chunks = num_chunks; /* total number of chunks */
|
|
enc_samp_per_chunk = samp_per_chunk; /* pcm samples / encoderchunk */
|
|
enc_head_buffer = head_ptr; /* optional file header data (wav) */
|
|
enc_head_size = head_size; /* optional file header data (wav) */
|
|
audio_enc_id = enc_id; /* AFMT_* id */
|
|
}
|
|
|
|
/* allocate encoder chunk */
|
|
unsigned int *enc_alloc_chunk(void)
|
|
{
|
|
return (unsigned int*)(enc_buffer + enc_wr_index * enc_chunk_size);
|
|
}
|
|
|
|
/* free previously allocated encoder chunk */
|
|
void enc_free_chunk(void)
|
|
{
|
|
unsigned long *enc_chunk;
|
|
|
|
enc_chunk = GET_ENC_CHUNK(enc_wr_index);
|
|
curr_chunk_cnt++;
|
|
/* curr_bit_rate += *enc_chunk * 44100 * 8 / (enc_samp_per_chunk * 1000); */
|
|
curr_bit_rate += *enc_chunk * 441 * 8 / (enc_samp_per_chunk * 10 );
|
|
avrg_bit_rate = (curr_bit_rate + curr_chunk_cnt / 2) / curr_chunk_cnt;
|
|
|
|
/* advance enc_wr_index to the next chunk */
|
|
enc_wr_index = (enc_wr_index + 1) % enc_num_chunks;
|
|
|
|
/* buffer full: advance enc_rd_index (for prerecording purpose) */
|
|
if (enc_rd_index == enc_wr_index)
|
|
{
|
|
enc_rd_index = (enc_rd_index + 1) % enc_num_chunks;
|
|
}
|
|
}
|
|
|
|
/* checks near empty state on wav input buffer */
|
|
int enc_wavbuf_near_empty(void)
|
|
{
|
|
/* less than 1sec raw data? => unboost encoder */
|
|
if (((write_pos - read_pos) & CHUNK_MASK) < 44100*4)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* passes a pointer to next chunk of unprocessed wav data */
|
|
char *enc_get_wav_data(int size)
|
|
{
|
|
char *ptr;
|
|
int avail;
|
|
|
|
/* limit the requested pcm data size */
|
|
if(size > MAX_FEED_SIZE)
|
|
size = MAX_FEED_SIZE;
|
|
|
|
avail = (write_pos - read_pos) & CHUNK_MASK;
|
|
|
|
if (avail >= size)
|
|
{
|
|
ptr = rec_buffer + read_pos;
|
|
read_pos = (read_pos + size) & CHUNK_MASK;
|
|
|
|
/* ptr must point to continous data at wraparound position */
|
|
if (read_pos < size)
|
|
memcpy(rec_buffer + NUM_CHUNKS * CHUNK_SIZE,
|
|
rec_buffer, read_pos);
|
|
|
|
wav_queue_empty = false;
|
|
return ptr;
|
|
}
|
|
|
|
wav_queue_empty = true;
|
|
return NULL;
|
|
}
|