| /*************************************************************************** |
| * __________ __ ___. |
| * 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 */ |