rockbox/firmware/pcm.c

441 lines
9.7 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Michael Sevakis
*
* 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 <stdlib.h>
#include "system.h"
#include "kernel.h"
#include "logf.h"
#include "audio.h"
#include "sound.h"
/**
* Aspects implemented in the target-specific portion:
*
* ==Playback==
* Public -
* pcm_postinit
* pcm_get_bytes_waiting
* pcm_play_lock
* pcm_play_unlock
* Semi-private -
* pcm_play_dma_init
* pcm_play_dma_init
* pcm_play_dma_start
* pcm_play_dma_stop
* pcm_play_dma_pause
* pcm_play_dma_get_peak_buffer
* Data Read/Written within TSP -
* pcm_curr_sampr (RW)
* pcm_callback_for_more (R)
* pcm_playing (R)
* pcm_paused (R)
*
* ==Recording==
* Public -
* pcm_rec_lock
* pcm_rec_unlock
* Semi-private -
* pcm_rec_dma_init
* pcm_rec_dma_close
* pcm_rec_dma_start
* pcm_rec_dma_stop
* pcm_rec_dma_get_peak_buffer
* Data Read/Written within TSP -
* pcm_rec_peak_addr (RW)
* pcm_callback_more_ready (R)
* pcm_recording (R)
*
* States are set _after_ the target's pcm driver is called so that it may
* know from whence the state is changed. One exception is init.
*
*/
/* the registered callback function to ask for more mp3 data */
volatile pcm_more_callback_type pcm_callback_for_more
NOCACHEBSS_ATTR = NULL;
/* PCM playback state */
volatile bool pcm_playing NOCACHEBSS_ATTR = false;
/* PCM paused state. paused implies playing */
volatile bool pcm_paused NOCACHEBSS_ATTR = false;
/* samplerate of currently playing audio - undefined if stopped */
unsigned long pcm_curr_sampr NOCACHEBSS_ATTR = 0;
/**
* Do peak calculation using distance squared from axis and save a lot
* of jumps and negation. Don't bother with the calculations of left or
* right only as it's never really used and won't save much time.
*
* Used for recording and playback.
*/
static void pcm_peak_peeker(const void *addr, int count, int peaks[2])
{
int32_t peak_l = 0, peak_r = 0;
int32_t peaksq_l = 0, peaksq_r = 0;
do
{
int32_t value = *(int32_t *)addr;
int32_t ch, chsq;
#ifdef ROCKBOX_BIG_ENDIAN
ch = value >> 16;
#else
ch = (int16_t)value;
#endif
chsq = ch*ch;
if (chsq > peaksq_l)
peak_l = ch, peaksq_l = chsq;
#ifdef ROCKBOX_BIG_ENDIAN
ch = (int16_t)value;
#else
ch = value >> 16;
#endif
chsq = ch*ch;
if (chsq > peaksq_r)
peak_r = ch, peaksq_r = chsq;
addr += 16;
count -= 4;
}
while (count > 0);
peaks[0] = abs(peak_l);
peaks[1] = abs(peak_r);
}
void pcm_calculate_peaks(int *left, int *right)
{
static int peaks[2] = { 0, 0 };
static unsigned long last_peak_tick = 0;
static unsigned long frame_period = 0;
long tick = current_tick;
/* Throttled peak ahead based on calling period */
long period = tick - last_peak_tick;
/* Keep reasonable limits on period */
if (period < 1)
period = 1;
else if (period > HZ/5)
period = HZ/5;
frame_period = (3*frame_period + period) >> 2;
last_peak_tick = tick;
if (pcm_playing && !pcm_paused)
{
const void *addr;
int count, framecount;
addr = pcm_play_dma_get_peak_buffer(&count);
framecount = frame_period*pcm_curr_sampr / HZ;
count = MIN(framecount, count);
if (count > 0)
pcm_peak_peeker(addr, count, peaks);
}
else
{
peaks[0] = peaks[1] = 0;
}
if (left)
*left = peaks[0];
if (right)
*right = peaks[1];
}
/****************************************************************************
* Functions that do not require targeted implementation but only a targeted
* interface
*/
/* This should only be called at startup before any audio playback or
recording is attempted */
void pcm_init(void)
{
logf("pcm_init");
pcm_play_dma_stopped_callback();
logf(" pcm_play_dma_init");
pcm_play_dma_init();
}
/* Common code to pcm_play_data and pcm_play_pause */
static void pcm_play_data_start(unsigned char *start, size_t size)
{
if (!(start && size))
{
pcm_more_callback_type get_more = pcm_callback_for_more;
size = 0;
if (get_more)
{
logf(" get_more");
get_more(&start, &size);
}
}
if (start && size)
{
logf(" pcm_play_dma_start");
pcm_play_dma_start(start, size);
pcm_playing = true;
pcm_paused = false;
return;
}
/* Force a stop */
logf(" pcm_play_dma_stop");
pcm_play_dma_stop();
pcm_play_dma_stopped_callback();
}
void pcm_play_data(pcm_more_callback_type get_more,
unsigned char *start, size_t size)
{
logf("pcm_play_data");
pcm_play_lock();
pcm_callback_for_more = get_more;
logf(" pcm_play_dma_start");
pcm_play_data_start(start, size);
pcm_play_unlock();
}
void pcm_play_pause(bool play)
{
logf("pcm_play_pause: %s", play ? "play" : "pause");
pcm_play_lock();
if (play == pcm_paused && pcm_playing)
{
if (!play)
{
logf(" pcm_play_dma_pause");
pcm_play_dma_pause(true);
pcm_paused = true;
}
else if (pcm_get_bytes_waiting() > 0)
{
logf(" pcm_play_dma_pause");
pcm_play_dma_pause(false);
pcm_paused = false;
}
else
{
logf(" pcm_play_dma_start: no data");
pcm_play_data_start(NULL, 0);
}
}
else
{
logf(" no change");
}
pcm_play_unlock();
}
void pcm_play_stop(void)
{
logf("pcm_play_stop");
pcm_play_lock();
if (pcm_playing)
{
logf(" pcm_play_dma_stop");
pcm_play_dma_stop();
pcm_play_dma_stopped_callback();
}
else
{
logf(" not playing");
}
pcm_play_unlock();
}
void pcm_play_dma_stopped_callback(void)
{
pcm_callback_for_more = NULL;
pcm_paused = false;
pcm_playing = false;
}
/**/
bool pcm_is_playing(void)
{
return pcm_playing;
}
bool pcm_is_paused(void)
{
return pcm_paused;
}
void pcm_mute(bool mute)
{
#ifndef SIMULATOR
audiohw_mute(mute);
#endif
if (mute)
sleep(HZ/16);
}
#ifdef HAVE_RECORDING
/** Low level pcm recording apis **/
/* Next start for recording peaks */
const volatile void *pcm_rec_peak_addr NOCACHEBSS_ATTR = NULL;
/* the registered callback function for when more data is available */
volatile pcm_more_callback_type2
pcm_callback_more_ready NOCACHEBSS_ATTR = NULL;
/* DMA transfer in is currently active */
volatile bool pcm_recording NOCACHEBSS_ATTR = false;
/**
* Return recording peaks - From the end of the last peak up to
* current write position.
*/
void pcm_calculate_rec_peaks(int *left, int *right)
{
static int peaks[2];
if (pcm_recording)
{
const void *addr;
int count;
addr = pcm_rec_dma_get_peak_buffer(&count);
if (count > 0)
{
pcm_peak_peeker(addr, count, peaks);
if (addr == pcm_rec_peak_addr)
pcm_rec_peak_addr = (int32_t *)addr + count;
}
}
else
{
peaks[0] = peaks[1] = 0;
}
if (left)
*left = peaks[0];
if (right)
*right = peaks[1];
} /* pcm_calculate_rec_peaks */
/****************************************************************************
* Functions that do not require targeted implementation but only a targeted
* interface
*/
void pcm_init_recording(void)
{
logf("pcm_init_recording");
/* Recording init is locked unlike general pcm init since this is not
* just a one-time event at startup and it should and must be safe by
* now. */
pcm_rec_lock();
logf(" pcm_rec_dma_init");
pcm_rec_dma_stopped_callback();
pcm_rec_dma_init();
pcm_rec_unlock();
}
void pcm_close_recording(void)
{
logf("pcm_close_recording");
pcm_rec_lock();
if (pcm_recording)
{
logf(" pcm_rec_dma_stop");
pcm_rec_dma_stop();
pcm_rec_dma_stopped_callback();
}
logf(" pcm_rec_dma_close");
pcm_rec_dma_close();
pcm_rec_unlock();
}
void pcm_record_data(pcm_more_callback_type2 more_ready,
void *start, size_t size)
{
logf("pcm_record_data");
if (!(start && size))
{
logf(" no buffer");
return;
}
pcm_rec_lock();
pcm_callback_more_ready = more_ready;
logf(" pcm_rec_dma_start");
pcm_rec_dma_start(start, size);
pcm_recording = true;
pcm_rec_unlock();
} /* pcm_record_data */
void pcm_stop_recording(void)
{
logf("pcm_stop_recording");
pcm_rec_lock();
if (pcm_recording)
{
logf(" pcm_rec_dma_stop");
pcm_rec_dma_stop();
pcm_rec_dma_stopped_callback();
}
pcm_rec_unlock();
} /* pcm_stop_recording */
void pcm_rec_dma_stopped_callback(void)
{
pcm_recording = false;
pcm_callback_more_ready = NULL;
}
#endif /* HAVE_RECORDING */