rockbox/firmware/pcm.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

442 lines
9.8 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Michael Sevakis
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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
SHAREDBSS_ATTR = NULL;
/* PCM playback state */
volatile bool pcm_playing SHAREDBSS_ATTR = false;
/* PCM paused state. paused implies playing */
volatile bool pcm_paused SHAREDBSS_ATTR = false;
/* samplerate of currently playing audio - undefined if stopped */
unsigned long pcm_curr_sampr SHAREDBSS_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 SHAREDBSS_ATTR = NULL;
/* the registered callback function for when more data is available */
volatile pcm_more_callback_type2
pcm_callback_more_ready SHAREDBSS_ATTR = NULL;
/* DMA transfer in is currently active */
volatile bool pcm_recording SHAREDBSS_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 */