forked from len0rd/rockbox
		
	Forced audio start was left out when a third codec attempts to start a second track transition. Only one pending transition is allowed at a time. There wouldn't be enough PCM in the buffer to trigger audio playback and audio would just return without giving the pcm buffer a kick. Fixes FS#13100 - Player failed on short tracks Change-Id: I338b0b12022c591930451fd5ed26a2a73008835f
		
			
				
	
	
		
			1434 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1434 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 | |
|  *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 | |
|  *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 | |
|  *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 | |
|  *                     \/            \/     \/    \/            \/
 | |
|  * $Id$
 | |
|  *
 | |
|  * Copyright (C) 2005 by Miika Pekkarinen
 | |
|  * Copyright (C) 2011 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 <stdio.h>
 | |
| #include "config.h"
 | |
| #include "system.h"
 | |
| #include "debug.h"
 | |
| #include <kernel.h>
 | |
| #include "pcm.h"
 | |
| #include "pcm_mixer.h"
 | |
| #include "pcmbuf.h"
 | |
| #include "dsp-util.h"
 | |
| #include "playback.h"
 | |
| #include "codec_thread.h"
 | |
| 
 | |
| /* Define LOGF_ENABLE to enable logf output in this file */
 | |
| /*#define LOGF_ENABLE*/
 | |
| #include "logf.h"
 | |
| #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
 | |
| #include "cpu.h"
 | |
| #endif
 | |
| #include "settings.h"
 | |
| #include "audio.h"
 | |
| #include "voice_thread.h"
 | |
| 
 | |
| /* 2 channels * 2 bytes/sample, interleaved */
 | |
| #define PCMBUF_SAMPLE_SIZE   (2 * 2)
 | |
| 
 | |
| /* This is the target fill size of chunks on the pcm buffer
 | |
|    Can be any number of samples but power of two sizes make for faster and
 | |
|    smaller math - must be < 65536 bytes */
 | |
| #define PCMBUF_CHUNK_SIZE    8192u
 | |
| 
 | |
| /* Small guard buf to give decent space near end */
 | |
| #define PCMBUF_GUARD_SIZE    (PCMBUF_CHUNK_SIZE / 8)
 | |
| 
 | |
| /* Mnemonics for common data commit thresholds */
 | |
| #define COMMIT_CHUNKS        PCMBUF_CHUNK_SIZE
 | |
| #define COMMIT_ALL_DATA      1u
 | |
| 
 | |
| /* Size of the crossfade buffer where codec data is written to be faded
 | |
|    on commit */
 | |
| #define CROSSFADE_BUFSIZE    PCMBUF_CHUNK_SIZE
 | |
| 
 | |
| /* Maximum contiguous space that PCM buffer will allow (to avoid excessive
 | |
|    draining between inserts and observe low-latency mode) */
 | |
| #define PCMBUF_MAX_BUFFER    (PCMBUF_CHUNK_SIZE * 4)
 | |
| 
 | |
| /* Forced buffer insert constraint can thus be from 1KB to 32KB using 8KB
 | |
|    chunks */
 | |
| 
 | |
| /* Return data level in 1/4-second increments */
 | |
| #define DATA_LEVEL(quarter_secs) (pcmbuf_sampr * (quarter_secs))
 | |
| 
 | |
| /* Number of bytes played per second */
 | |
| #define BYTERATE            (pcmbuf_sampr * PCMBUF_SAMPLE_SIZE)
 | |
| 
 | |
| #if MEMORYSIZE > 2
 | |
| /* Keep watermark high for large memory target - at least (2s) */
 | |
| #define PCMBUF_WATERMARK    (BYTERATE * 2)
 | |
| #define MIN_BUFFER_SIZE     (BYTERATE * 3)
 | |
| /* 1 seconds of buffer is low data */
 | |
| #define LOW_DATA            DATA_LEVEL(4)
 | |
| #else
 | |
| #define PCMBUF_WATERMARK    (BYTERATE / 4)  /* 0.25 seconds */
 | |
| #define MIN_BUFFER_SIZE     (BYTERATE * 1)
 | |
| /* under watermark is low data */
 | |
| #define LOW_DATA            pcmbuf_watermark
 | |
| #endif
 | |
| 
 | |
| /* Describes each audio packet - keep it small since there are many of them */
 | |
| struct chunkdesc
 | |
| {
 | |
|     uint16_t       size;    /* Actual size (0 < size <= PCMBUF_CHUNK_SIZE) */
 | |
|     uint16_t       pos_key; /* Who put the position info in
 | |
|                                (undefined: 0, valid: 1..POSITION_KEY_MAX) */
 | |
|     unsigned long  elapsed; /* Elapsed time to use */
 | |
|     off_t          offset;  /* Offset to use */
 | |
| };
 | |
| 
 | |
| #define POSITION_KEY_MAX    UINT16_MAX
 | |
| 
 | |
| 
 | |
| /* General PCM buffer data */
 | |
| #define INVALID_BUF_INDEX   ((size_t)0 - (size_t)1)
 | |
| 
 | |
| static void *pcmbuf_buffer;
 | |
| static void *pcmbuf_guardbuf;
 | |
| static size_t pcmbuf_size;
 | |
| static struct chunkdesc *pcmbuf_descriptors;
 | |
| static unsigned int pcmbuf_desc_count;
 | |
| static unsigned int position_key = 1;
 | |
| static unsigned int pcmbuf_sampr = 0;
 | |
| 
 | |
| static size_t chunk_ridx;
 | |
| static size_t chunk_widx;
 | |
| 
 | |
| static size_t pcmbuf_bytes_waiting;
 | |
| static struct chunkdesc *current_desc;
 | |
| static size_t chunk_transidx;
 | |
| 
 | |
| static size_t pcmbuf_watermark = 0;
 | |
| 
 | |
| static bool low_latency_mode = false;
 | |
| 
 | |
| static bool pcmbuf_sync_position = false;
 | |
| 
 | |
| /* Fade effect */
 | |
| static unsigned int fade_vol = MIX_AMP_UNITY;
 | |
| static enum
 | |
| {
 | |
|     PCM_NOT_FADING = 0,
 | |
|     PCM_FADING_IN,
 | |
|     PCM_FADING_OUT,
 | |
| } fade_state = PCM_NOT_FADING;
 | |
| static bool fade_out_complete = false;
 | |
| 
 | |
| /* Voice */
 | |
| static bool soft_mode = false;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
| /* Crossfade related state */
 | |
| 
 | |
| static int  crossfade_setting;
 | |
| static int  crossfade_enable_request;
 | |
| 
 | |
| static enum
 | |
| {
 | |
|     CROSSFADE_INACTIVE = 0, /* Crossfade is OFF */
 | |
|     CROSSFADE_ACTIVE,       /* Crossfade is fading in */
 | |
|     CROSSFADE_START,        /* New crossfade is starting */
 | |
|     CROSSFADE_CONTINUE,     /* Next track continues fade */
 | |
| } crossfade_status = CROSSFADE_INACTIVE;
 | |
| 
 | |
| static bool crossfade_mixmode;
 | |
| static bool crossfade_auto_skip;
 | |
| static size_t crossfade_widx;
 | |
| static size_t crossfade_bufidx;
 | |
| 
 | |
| struct mixfader
 | |
| {
 | |
|     int32_t factor; /* Current volume factor to use */
 | |
|     int32_t endfac; /* Saturating end factor */
 | |
|     int32_t nsamp2; /* Twice the number of samples */
 | |
|     int32_t dfact2; /* Twice the range of factors */
 | |
|     int32_t ferr;   /* Current error accumulator */
 | |
|     int32_t dfquo;  /* Quotient of fade range / sample range */
 | |
|     int32_t dfrem;  /* Remainder of fade range / sample range */
 | |
|     int32_t dfinc;  /* Base increment (-1 or +1) */
 | |
|     bool    alloc;  /* Allocate blocks if needed else abort at EOB */
 | |
| } crossfade_infader;
 | |
| 
 | |
| /* Defines for operations on position info when mixing/fading -
 | |
|    passed in offset parameter */
 | |
| enum
 | |
| {
 | |
|     MIXFADE_KEEP_POS = -1,    /* Keep position info in chunk */
 | |
|     MIXFADE_NULLIFY_POS = -2, /* Ignore position info in chunk */
 | |
|     /* Positive values cause stamping/restamping */
 | |
| };
 | |
| 
 | |
| #define MIXFADE_UNITY_BITS  16
 | |
| #define MIXFADE_UNITY       (1 << MIXFADE_UNITY_BITS)
 | |
| 
 | |
| static void crossfade_cancel(void);
 | |
| static void crossfade_start(void);
 | |
| static void write_to_crossfade(size_t size, unsigned long elapsed,
 | |
|                                off_t offset);
 | |
| static void pcmbuf_finish_crossfade_enable(void);
 | |
| #else
 | |
| #define crossfade_cancel() do {} while(0)
 | |
| #endif /* HAVE_CROSSFADE */
 | |
| 
 | |
| /* Thread */
 | |
| #ifdef HAVE_PRIORITY_SCHEDULING
 | |
| static int codec_thread_priority = PRIORITY_PLAYBACK;
 | |
| #endif
 | |
| 
 | |
| /* Callbacks into playback.c */
 | |
| extern void audio_pcmbuf_position_callback(unsigned long elapsed,
 | |
|                                            off_t offset, unsigned int key);
 | |
| extern void audio_pcmbuf_track_change(bool pcmbuf);
 | |
| extern bool audio_pcmbuf_may_play(void);
 | |
| extern void audio_pcmbuf_sync_position(void);
 | |
| 
 | |
| 
 | |
| /**************************************/
 | |
| 
 | |
| /* start PCM if callback says it's alright */
 | |
| static void start_audio_playback(void)
 | |
| {
 | |
|      if (audio_pcmbuf_may_play())
 | |
|         pcmbuf_play_start();
 | |
| }
 | |
| 
 | |
| /* Return number of commited bytes in buffer (committed chunks count as
 | |
|    a full chunk even if only partially filled) */
 | |
| static size_t pcmbuf_unplayed_bytes(void)
 | |
| {
 | |
|     size_t ridx = chunk_ridx;
 | |
|     size_t widx = chunk_widx;
 | |
| 
 | |
|     if (ridx > widx)
 | |
|         widx += pcmbuf_size;
 | |
| 
 | |
|     return widx - ridx;
 | |
| }
 | |
| 
 | |
| /* Returns TRUE if amount of data is under the target fill size */
 | |
| static bool pcmbuf_data_critical(void)
 | |
| {
 | |
|     return pcmbuf_unplayed_bytes() < LOW_DATA;
 | |
| }
 | |
| 
 | |
| /* Return the next PCM chunk in the PCM buffer given a byte index into it */
 | |
| static size_t index_next(size_t index)
 | |
| {
 | |
|     index = ALIGN_DOWN(index + PCMBUF_CHUNK_SIZE, PCMBUF_CHUNK_SIZE);
 | |
| 
 | |
|     if (index >= pcmbuf_size)
 | |
|         index -= pcmbuf_size;
 | |
| 
 | |
|     return index;
 | |
| }
 | |
| 
 | |
| /* Convert a byte offset in the PCM buffer into a pointer in the buffer */
 | |
| static FORCE_INLINE void * index_buffer(size_t index)
 | |
| {
 | |
|     return pcmbuf_buffer + index;
 | |
| }
 | |
| 
 | |
| /* Convert a pointer in the buffer into an index offset */
 | |
| static FORCE_INLINE size_t buffer_index(void *p)
 | |
| {
 | |
|     return (uintptr_t)p - (uintptr_t)pcmbuf_buffer;
 | |
| }
 | |
| 
 | |
| /* Return a chunk descriptor for a byte index in the buffer */
 | |
| static struct chunkdesc * index_chunkdesc(size_t index)
 | |
| {
 | |
|     return &pcmbuf_descriptors[index / PCMBUF_CHUNK_SIZE];
 | |
| }
 | |
| 
 | |
| /* Return the first byte of a chunk for a byte index in the buffer, offset by 'offset'
 | |
|    chunks */
 | |
| static size_t index_chunk_offs(size_t index, int offset)
 | |
| {
 | |
|     int i = index / PCMBUF_CHUNK_SIZE;
 | |
| 
 | |
|     if (offset != 0)
 | |
|     {
 | |
|         i = (i + offset) % (int)pcmbuf_desc_count;
 | |
| 
 | |
|         /* remainder => modulus */
 | |
|         if (i < 0)
 | |
|             i += pcmbuf_desc_count;
 | |
|     }
 | |
| 
 | |
|     return i * PCMBUF_CHUNK_SIZE;
 | |
| }
 | |
| 
 | |
| /* Test if a buffer index lies within the committed data region */
 | |
| static bool index_committed(size_t index)
 | |
| {
 | |
|     if (index == INVALID_BUF_INDEX)
 | |
|         return false;
 | |
| 
 | |
|     size_t ridx = chunk_ridx;
 | |
|     size_t widx = chunk_widx;
 | |
| 
 | |
|     if (widx < ridx)
 | |
|     {
 | |
|         widx += pcmbuf_size;
 | |
| 
 | |
|         if (index < ridx)
 | |
|             index += pcmbuf_size;
 | |
|     }
 | |
| 
 | |
|     return index >= ridx && index < widx;
 | |
| }
 | |
| 
 | |
| /* Snip the tail of buffer at chunk of specified index plus chunk offset */
 | |
| void snip_buffer_tail(size_t index, int offset)
 | |
| {
 | |
|     /* Call with PCM lockout */
 | |
|     if (index == INVALID_BUF_INDEX)
 | |
|         return;
 | |
| 
 | |
|     index = index_chunk_offs(index, offset);
 | |
| 
 | |
|     if (!index_committed(index) && index != chunk_widx)
 | |
|         return;
 | |
| 
 | |
|     chunk_widx = index;
 | |
|     pcmbuf_bytes_waiting = 0;
 | |
|     index_chunkdesc(index)->pos_key = 0;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     /* Kill crossfade if it would now be operating in the void */
 | |
|     if (crossfade_status != CROSSFADE_INACTIVE &&
 | |
|         !index_committed(crossfade_widx) && crossfade_widx != chunk_widx)
 | |
|     {
 | |
|         crossfade_cancel();
 | |
|     }
 | |
| #endif /* HAVE_CROSSFADE */
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Accept new PCM data */
 | |
| 
 | |
| /* Split the uncommitted data as needed into chunks, stopping when uncommitted
 | |
|    data is below the threshold */
 | |
| static void commit_chunks(size_t threshold)
 | |
| {
 | |
|     size_t index = chunk_widx;
 | |
|     size_t end_index = index + pcmbuf_bytes_waiting;
 | |
| 
 | |
|     /* Copy to the beginning of the buffer all data that must wrap */
 | |
|     if (end_index > pcmbuf_size)
 | |
|         memcpy(pcmbuf_buffer, pcmbuf_guardbuf, end_index - pcmbuf_size);
 | |
| 
 | |
|     struct chunkdesc *desc = index_chunkdesc(index);
 | |
| 
 | |
|     do
 | |
|     {
 | |
|         size_t size = MIN(pcmbuf_bytes_waiting, PCMBUF_CHUNK_SIZE);
 | |
|         pcmbuf_bytes_waiting -= size;
 | |
| 
 | |
|         /* Fill in the values in the new buffer chunk */
 | |
|         desc->size = (uint16_t)size;
 | |
| 
 | |
|         /* Advance the current write chunk and make it available to the
 | |
|            PCM callback */
 | |
|         chunk_widx = index = index_next(index);
 | |
|         desc = index_chunkdesc(index);
 | |
| 
 | |
|         /* Reset it before using it */
 | |
|         desc->pos_key = 0;
 | |
|     }
 | |
|     while (pcmbuf_bytes_waiting >= threshold);
 | |
| }
 | |
| 
 | |
| /* If uncommitted data count is above or equal to the threshold, commit it */
 | |
| static FORCE_INLINE void commit_if_needed(size_t threshold)
 | |
| {
 | |
|     if (pcmbuf_bytes_waiting >= threshold)
 | |
|         commit_chunks(threshold);
 | |
| }
 | |
| 
 | |
| /* Place positioning information in the chunk */
 | |
| static void stamp_chunk(struct chunkdesc *desc, unsigned long elapsed,
 | |
|                         off_t offset)
 | |
| {
 | |
|     /* One-time stamping of a given chunk by the same track - new track may
 | |
|        overwrite */
 | |
|     unsigned int key = position_key;
 | |
| 
 | |
|     if (desc->pos_key != key)
 | |
|     {
 | |
|         desc->pos_key = key;
 | |
|         desc->elapsed = elapsed;
 | |
|         desc->offset = offset;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Set priority of the codec thread */
 | |
| #ifdef HAVE_PRIORITY_SCHEDULING
 | |
| /*
 | |
|  * expects pcm_fill_state in tenth-% units (e.g. full pcm buffer is 10) */
 | |
| static void boost_codec_thread(int pcm_fill_state)
 | |
| {
 | |
|     static const int8_t prios[11] =
 | |
|     {
 | |
|         PRIORITY_PLAYBACK_MAX,      /*   0 - 10% */
 | |
|         PRIORITY_PLAYBACK_MAX+1,    /*  10 - 20% */
 | |
|         PRIORITY_PLAYBACK_MAX+3,    /*  20 - 30% */
 | |
|         PRIORITY_PLAYBACK_MAX+5,    /*  30 - 40% */
 | |
|         PRIORITY_PLAYBACK_MAX+7,    /*  40 - 50% */
 | |
|         PRIORITY_PLAYBACK_MAX+8,    /*  50 - 60% */
 | |
|         PRIORITY_PLAYBACK_MAX+9,    /*  60 - 70% */
 | |
|         /* raising priority above 70% shouldn't be needed */
 | |
|         PRIORITY_PLAYBACK,          /*  70 - 80% */
 | |
|         PRIORITY_PLAYBACK,          /*  80 - 90% */
 | |
|         PRIORITY_PLAYBACK,          /*  90 -100% */
 | |
|         PRIORITY_PLAYBACK,          /*      100% */
 | |
|     };
 | |
| 
 | |
|     int new_prio = prios[pcm_fill_state];
 | |
| 
 | |
|     /* Keep voice and codec threads at the same priority or else voice
 | |
|      * will starve if the codec thread's priority is boosted. */
 | |
|     if (new_prio != codec_thread_priority)
 | |
|     {
 | |
|         codec_thread_set_priority(new_prio);
 | |
|         voice_thread_set_priority(new_prio);
 | |
|         codec_thread_priority = new_prio;
 | |
|     }
 | |
| }
 | |
| #else
 | |
| #define boost_codec_thread(pcm_fill_state) do{}while(0)
 | |
| #endif /* HAVE_PRIORITY_SCHEDULING */
 | |
| 
 | |
| /* Get the next available buffer and size - assumes adequate space exists */
 | |
| static void * get_write_buffer(size_t *size)
 | |
| {
 | |
|     /* Obtain current chunk fill address */
 | |
|     size_t index = chunk_widx + pcmbuf_bytes_waiting;
 | |
|     size_t index_end = pcmbuf_size + PCMBUF_GUARD_SIZE;
 | |
| 
 | |
|     /* Get count to the end of the buffer where a wrap will happen +
 | |
|        the guard */
 | |
|     size_t endsize = index_end - index;
 | |
| 
 | |
|     /* Return available unwrapped space */
 | |
|     *size = MIN(*size, endsize);
 | |
| 
 | |
|     return index_buffer(index);
 | |
| }
 | |
| 
 | |
| /* Commit outstanding data leaving less than a chunk size remaining */
 | |
| static void commit_write_buffer(size_t size)
 | |
| {
 | |
|     /* Add this data and commit if one or more chunks are ready */
 | |
|     pcmbuf_bytes_waiting += size;
 | |
|     commit_if_needed(COMMIT_CHUNKS);
 | |
| }
 | |
| 
 | |
| /* Request space in the buffer for writing output samples */
 | |
| void * pcmbuf_request_buffer(int *count)
 | |
| {
 | |
|     size_t size = *count * PCMBUF_SAMPLE_SIZE;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     /* We're going to crossfade to a new track, which is now on its way */
 | |
|     if (crossfade_status > CROSSFADE_ACTIVE)
 | |
|         crossfade_start();
 | |
| 
 | |
|     /* If crossfade has begun, put the new track samples in the crossfade
 | |
|        buffer area */
 | |
|     if (crossfade_status != CROSSFADE_INACTIVE && size > CROSSFADE_BUFSIZE)
 | |
|         size = CROSSFADE_BUFSIZE;
 | |
|     else
 | |
| #endif /* HAVE_CROSSFADE */
 | |
|     if (size > PCMBUF_MAX_BUFFER)
 | |
|         size = PCMBUF_MAX_BUFFER; /* constrain request */
 | |
| 
 | |
|     enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK);
 | |
|     size_t remaining = pcmbuf_unplayed_bytes();
 | |
| 
 | |
|     /* Need to have length bytes to prevent wrapping overwriting - leave one
 | |
|        descriptor free to guard so that 0 != full in ring buffer */
 | |
|     size_t freespace = pcmbuf_free();
 | |
| 
 | |
|     if (pcmbuf_sync_position)
 | |
|         audio_pcmbuf_sync_position();
 | |
| 
 | |
|     if (freespace < size + PCMBUF_CHUNK_SIZE)
 | |
|         return NULL;
 | |
| 
 | |
|     /* Maintain the buffer level above the watermark */
 | |
|     if (status != CHANNEL_STOPPED)
 | |
|     {
 | |
|         if (low_latency_mode)
 | |
|         {
 | |
|             /* 1/4s latency. */
 | |
|             if (remaining > DATA_LEVEL(1))
 | |
|                 return NULL;
 | |
|         }
 | |
| 
 | |
|         /* Boost CPU if necessary */
 | |
|         size_t realrem = pcmbuf_size - freespace;
 | |
| 
 | |
|         if (realrem < pcmbuf_watermark)
 | |
|             trigger_cpu_boost();
 | |
| 
 | |
|         boost_codec_thread(realrem*10 / pcmbuf_size);
 | |
|     }
 | |
|     else    /* !playing */
 | |
|     {
 | |
|         /* Boost CPU for pre-buffer */
 | |
|         trigger_cpu_boost();
 | |
| 
 | |
|         /* If pre-buffered to the watermark, start playback */
 | |
|         if (!pcmbuf_data_critical())
 | |
|             start_audio_playback();
 | |
|     }
 | |
| 
 | |
|     void *buf;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     if (crossfade_status != CROSSFADE_INACTIVE)
 | |
|     {
 | |
|         crossfade_bufidx = index_chunk_offs(chunk_ridx, -1);
 | |
|         buf = index_buffer(crossfade_bufidx); /* always CROSSFADE_BUFSIZE */
 | |
|     }
 | |
|     else
 | |
| #endif
 | |
|     {
 | |
|         /* Give the maximum amount available if there's more */
 | |
|         if (size + PCMBUF_CHUNK_SIZE < freespace)
 | |
|             size = freespace - PCMBUF_CHUNK_SIZE;
 | |
| 
 | |
|         buf = get_write_buffer(&size);
 | |
|     }
 | |
| 
 | |
|     *count = size / PCMBUF_SAMPLE_SIZE;
 | |
|     return buf;
 | |
| }
 | |
| 
 | |
| /* Handle new samples to the buffer */
 | |
| void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset)
 | |
| {
 | |
|     size_t size = count * PCMBUF_SAMPLE_SIZE;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     if (crossfade_status != CROSSFADE_INACTIVE)
 | |
|     {
 | |
|         write_to_crossfade(size, elapsed, offset);
 | |
|     }
 | |
|     else
 | |
| #endif
 | |
|     {
 | |
|         stamp_chunk(index_chunkdesc(chunk_widx), elapsed, offset);
 | |
|         commit_write_buffer(size);
 | |
|     }
 | |
| 
 | |
|     /* Revert to position updates by PCM */
 | |
|     pcmbuf_sync_position = false;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Init */
 | |
| static unsigned int get_next_required_pcmbuf_chunks(void)
 | |
| {
 | |
|     size_t size = MIN_BUFFER_SIZE;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     if (crossfade_enable_request != CROSSFADE_ENABLE_OFF)
 | |
|     {
 | |
|         size_t seconds = global_settings.crossfade_fade_out_delay +
 | |
|                          global_settings.crossfade_fade_out_duration;
 | |
|         size += seconds * BYTERATE;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     logf("pcmbuf len: %lu", (unsigned long)(size / BYTERATE));
 | |
|     return size / PCMBUF_CHUNK_SIZE;
 | |
| }
 | |
| 
 | |
| /* Initialize the ringbuffer state */
 | |
| static void init_buffer_state(void)
 | |
| {
 | |
|     /* Reset counters */
 | |
|     chunk_ridx = chunk_widx = 0;
 | |
|     pcmbuf_bytes_waiting = 0;
 | |
| 
 | |
|     /* Reset first descriptor */
 | |
|     if (pcmbuf_descriptors)
 | |
|         pcmbuf_descriptors->pos_key = 0;
 | |
| 
 | |
|     /* Clear change notification */
 | |
|     chunk_transidx = INVALID_BUF_INDEX;
 | |
| }
 | |
| 
 | |
| /* Initialize the PCM buffer. The structure looks like this:
 | |
|  * ...|---------PCMBUF---------|GUARDBUF|DESCS| */
 | |
| size_t pcmbuf_init(void *bufend)
 | |
| {
 | |
|     void *bufstart;
 | |
| 
 | |
|     /* Set up the buffers */
 | |
|     pcmbuf_desc_count = get_next_required_pcmbuf_chunks();
 | |
|     pcmbuf_size = pcmbuf_desc_count * PCMBUF_CHUNK_SIZE;
 | |
|     pcmbuf_descriptors = (struct chunkdesc *)bufend - pcmbuf_desc_count;
 | |
| 
 | |
|     pcmbuf_buffer = (void *)pcmbuf_descriptors -
 | |
|                     pcmbuf_size - PCMBUF_GUARD_SIZE;
 | |
| 
 | |
|     /* Mem-align buffer chunks for more efficient handling in lower layers */
 | |
|     pcmbuf_buffer = ALIGN_DOWN(pcmbuf_buffer, (uintptr_t)MEM_ALIGN_SIZE);
 | |
| 
 | |
|     pcmbuf_guardbuf = pcmbuf_buffer + pcmbuf_size;
 | |
|     bufstart = pcmbuf_buffer;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     pcmbuf_finish_crossfade_enable();
 | |
| #else 
 | |
|     pcmbuf_watermark = PCMBUF_WATERMARK;
 | |
| #endif /* HAVE_CROSSFADE */
 | |
| 
 | |
|     init_buffer_state();
 | |
| 
 | |
|     pcmbuf_soft_mode(false);
 | |
| 
 | |
|     return bufend - bufstart;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Track change */
 | |
| 
 | |
| /* Place a track change notification in a specific descriptor or post it
 | |
|    immediately if the buffer is empty or the index is invalid */
 | |
| static void pcmbuf_monitor_track_change_ex(size_t index)
 | |
| {
 | |
|     /* Call with PCM lockout */
 | |
|     if (chunk_ridx != chunk_widx && index != INVALID_BUF_INDEX)
 | |
|     {
 | |
|         /* If monitoring, set flag for one previous to specified chunk */
 | |
|         index = index_chunk_offs(index, -1);
 | |
| 
 | |
|         /* Ensure PCM playback hasn't already played this out */
 | |
|         if (index_committed(index))
 | |
|         {
 | |
|             chunk_transidx = index;
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Post now if buffer is no longer coming up */
 | |
|     chunk_transidx = INVALID_BUF_INDEX;
 | |
|     audio_pcmbuf_track_change(false);
 | |
| }
 | |
| 
 | |
| /* Clear end of track and optionally the positioning info for all data */
 | |
| static void pcmbuf_cancel_track_change(bool position)
 | |
| {
 | |
|     /* Call with PCM lockout */
 | |
|     snip_buffer_tail(chunk_transidx, 1);
 | |
| 
 | |
|     chunk_transidx = INVALID_BUF_INDEX;
 | |
| 
 | |
|     if (!position)
 | |
|         return;
 | |
| 
 | |
|     size_t index = chunk_ridx;
 | |
| 
 | |
|     while (1)
 | |
|     {
 | |
|         index_chunkdesc(index)->pos_key = 0;
 | |
| 
 | |
|         if (index == chunk_widx)
 | |
|             break;
 | |
| 
 | |
|         index = index_next(index);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Place a track change notification at the end of the buffer or post it
 | |
|    immediately if the buffer is empty */
 | |
| void pcmbuf_monitor_track_change(bool monitor)
 | |
| {
 | |
|     pcm_play_lock();
 | |
| 
 | |
|     if (monitor)
 | |
|         pcmbuf_monitor_track_change_ex(chunk_widx);
 | |
|     else
 | |
|         pcmbuf_cancel_track_change(false);
 | |
| 
 | |
|     pcm_play_unlock();
 | |
| }
 | |
| 
 | |
| void pcmbuf_start_track_change(enum pcm_track_change_type type)
 | |
| {
 | |
|     /* Commit all outstanding data before starting next track - tracks don't
 | |
|        comingle inside a single buffer chunk */
 | |
|     commit_if_needed(COMMIT_ALL_DATA);
 | |
| 
 | |
|     if (type == TRACK_CHANGE_AUTO_PILEUP)
 | |
|     {
 | |
|         /* Fill might not have been above watermark */
 | |
|         start_audio_playback();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     bool crossfade = false;
 | |
| #endif
 | |
|     bool auto_skip = type != TRACK_CHANGE_MANUAL;
 | |
| 
 | |
|     /* Update position key so that:
 | |
|        1) Positions are keyed to the track to which they belong for sync
 | |
|           purposes
 | |
| 
 | |
|        2) Buffers stamped with the outgoing track's positions are restamped
 | |
|           to the incoming track's positions when crossfading
 | |
|     */
 | |
|     if (++position_key > POSITION_KEY_MAX)
 | |
|         position_key = 1;
 | |
| 
 | |
|     if (type == TRACK_CHANGE_END_OF_DATA)
 | |
|     {
 | |
|         crossfade_cancel();
 | |
| 
 | |
|         /* Fill might not have been above watermark */
 | |
|         start_audio_playback();
 | |
|     }
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     /* Determine whether this track change needs to crossfaded and how */
 | |
|     else if (crossfade_setting != CROSSFADE_ENABLE_OFF)
 | |
|     {
 | |
|         if (crossfade_status == CROSSFADE_INACTIVE &&
 | |
|             pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) &&
 | |
|             !low_latency_mode)
 | |
|         {
 | |
|             switch (crossfade_setting)
 | |
|             {
 | |
|             case CROSSFADE_ENABLE_AUTOSKIP:
 | |
|                 crossfade = auto_skip;
 | |
|                 break;
 | |
|             case CROSSFADE_ENABLE_MANSKIP:
 | |
|                 crossfade = !auto_skip;
 | |
|                 break;
 | |
|             case CROSSFADE_ENABLE_SHUFFLE:
 | |
|                 crossfade = global_settings.playlist_shuffle;
 | |
|                 break;
 | |
|             case CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP:
 | |
|                 crossfade = global_settings.playlist_shuffle || !auto_skip;
 | |
|                 break;
 | |
|             case CROSSFADE_ENABLE_ALWAYS:
 | |
|                 crossfade = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (crossfade)
 | |
|     {
 | |
|         logf("crossfade track change");
 | |
| 
 | |
|         /* Don't enable mix mode when skipping tracks manually */
 | |
|         crossfade_mixmode = auto_skip &&
 | |
|             global_settings.crossfade_fade_out_mixmode;
 | |
| 
 | |
|         crossfade_auto_skip = auto_skip;
 | |
| 
 | |
|         crossfade_status = CROSSFADE_START;
 | |
| 
 | |
|         pcmbuf_monitor_track_change(auto_skip);
 | |
| 
 | |
|         trigger_cpu_boost();
 | |
|     }
 | |
|     else
 | |
| #endif /* HAVE_CROSSFADE */
 | |
|     if (auto_skip)
 | |
|     {        
 | |
|         /* The codec is moving on to the next track, but the current track will
 | |
|          * continue to play, so mark the last write chunk as the last one in
 | |
|          * the track */
 | |
|         logf("gapless track change");
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|         if (crossfade_status == CROSSFADE_ACTIVE)
 | |
|             crossfade_status = CROSSFADE_CONTINUE;
 | |
| #endif
 | |
| 
 | |
|         pcmbuf_monitor_track_change(true);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* Discard old data; caller needs no transition notification */
 | |
|         logf("manual track change");
 | |
|         pcmbuf_play_stop();
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Playback */
 | |
| 
 | |
| /* PCM driver callback */
 | |
| static void pcmbuf_pcm_callback(const void **start, size_t *size)
 | |
| {
 | |
|     /*- Process the chunk that just finished -*/
 | |
|     size_t index = chunk_ridx;
 | |
|     struct chunkdesc *desc = current_desc;
 | |
| 
 | |
|     if (desc)
 | |
|     {
 | |
|         /* If last chunk in the track, notify of track change */
 | |
|         if (index == chunk_transidx)
 | |
|         {
 | |
|             chunk_transidx = INVALID_BUF_INDEX;
 | |
|             audio_pcmbuf_track_change(true);
 | |
|         }
 | |
| 
 | |
|         /* Free it for reuse */
 | |
|         chunk_ridx = index = index_next(index);
 | |
|     }
 | |
| 
 | |
|     /*- Process the new one -*/
 | |
|     if (index != chunk_widx && !fade_out_complete)
 | |
|     {
 | |
|         current_desc = desc = index_chunkdesc(index);
 | |
| 
 | |
|         *start = index_buffer(index);
 | |
|         *size = desc->size;
 | |
| 
 | |
|         if (desc->pos_key != 0)
 | |
|         {
 | |
|             /* Positioning chunk - notify playback */
 | |
|             audio_pcmbuf_position_callback(desc->elapsed, desc->offset,
 | |
|                                            desc->pos_key);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Force playback */
 | |
| void pcmbuf_play_start(void)
 | |
| {
 | |
|     logf("pcmbuf_play_start");
 | |
| 
 | |
|     if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED &&
 | |
|         chunk_widx != chunk_ridx)
 | |
|     {
 | |
|         current_desc = NULL;
 | |
|         mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, pcmbuf_pcm_callback,
 | |
|                                 NULL, 0);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Stop channel, empty and reset buffer */
 | |
| void pcmbuf_play_stop(void)
 | |
| {
 | |
|     logf("pcmbuf_play_stop");
 | |
| 
 | |
|     /* Reset channel */
 | |
|     mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
 | |
| 
 | |
|     /* Reset buffer */
 | |
|     init_buffer_state();
 | |
| 
 | |
|     /* Revert to position updates by PCM */
 | |
|     pcmbuf_sync_position = false;
 | |
| 
 | |
|     /* Fader OFF */
 | |
|     crossfade_cancel();
 | |
| 
 | |
|     /* Can unboost the codec thread here no matter who's calling,
 | |
|      * pretend full pcm buffer to unboost */
 | |
|     boost_codec_thread(10);
 | |
| }
 | |
| 
 | |
| void pcmbuf_pause(bool pause)
 | |
| {
 | |
|     logf("pcmbuf_pause: %s", pause?"pause":"play");
 | |
| 
 | |
|     if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED)
 | |
|         mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause);
 | |
|     else if (!pause)
 | |
|         pcmbuf_play_start();
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Crossfade */
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
| 
 | |
| /* Initialize a fader */
 | |
| static void mixfader_init(struct mixfader *faderp, int32_t start_factor,
 | |
|                           int32_t end_factor, size_t size, bool alloc)
 | |
| {
 | |
|     /* Linear fade */
 | |
|     faderp->endfac = end_factor;
 | |
|     faderp->nsamp2 = size / PCMBUF_SAMPLE_SIZE * 2;
 | |
|     faderp->alloc  = alloc;
 | |
| 
 | |
|     if (faderp->nsamp2 == 0)
 | |
|     {
 | |
|         /* No data; set up as if fader finished the fade */
 | |
|         faderp->factor = end_factor;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     int32_t dfact2 = 2*abs(end_factor - start_factor);
 | |
|     faderp->factor = start_factor;
 | |
|     faderp->ferr   = dfact2 / 2;
 | |
|     faderp->dfquo  = dfact2 / faderp->nsamp2;
 | |
|     faderp->dfrem  = dfact2 - faderp->dfquo*faderp->nsamp2;
 | |
|     faderp->dfinc  = end_factor < start_factor ? -1 : +1;
 | |
|     faderp->dfquo *= faderp->dfinc;
 | |
| }
 | |
| 
 | |
| /* Query if the fader has finished its envelope */
 | |
| static inline bool mixfader_finished(const struct mixfader *faderp)
 | |
| {
 | |
|     return faderp->factor == faderp->endfac;
 | |
| }
 | |
| 
 | |
| /* Step fader by one sample */
 | |
| static inline void mixfader_step(struct mixfader *faderp)
 | |
| {
 | |
|     if (mixfader_finished(faderp))
 | |
|         return;
 | |
| 
 | |
|     faderp->factor += faderp->dfquo;
 | |
|     faderp->ferr += faderp->dfrem;
 | |
| 
 | |
|     if (faderp->ferr >= faderp->nsamp2)
 | |
|     {
 | |
|         faderp->factor += faderp->dfinc;
 | |
|         faderp->ferr -= faderp->nsamp2;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static FORCE_INLINE int32_t mixfade_sample(const struct mixfader *faderp, int32_t s)
 | |
| {
 | |
|     return (faderp->factor * s + MIXFADE_UNITY/2) >> MIXFADE_UNITY_BITS;
 | |
| }
 | |
| 
 | |
| /* Cancel crossfade operation */
 | |
| static void crossfade_cancel(void)
 | |
| {
 | |
|     crossfade_status = CROSSFADE_INACTIVE;
 | |
|     crossfade_widx = INVALID_BUF_INDEX;
 | |
| }
 | |
| 
 | |
| /* Find the buffer index that's 'size' bytes away from 'index' */
 | |
| static size_t crossfade_find_index(size_t index, size_t size)
 | |
| {
 | |
|     if (index != INVALID_BUF_INDEX)
 | |
|     {
 | |
|         size_t i = index_chunk_offs(index, 0);
 | |
|         size += index - i;
 | |
| 
 | |
|         while (i != chunk_widx)
 | |
|         {
 | |
|             size_t desc_size = index_chunkdesc(i)->size;
 | |
| 
 | |
|             if (size < desc_size)
 | |
|             {
 | |
|                 index = i + size;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             size -= desc_size;
 | |
|             i = index_next(i);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return index;
 | |
| }
 | |
| 
 | |
| /* Align the needed buffer area up to the end of existing data */
 | |
| static size_t crossfade_find_buftail(bool auto_skip, size_t buffer_rem,
 | |
|                                      size_t buffer_need, size_t *buffer_rem_outp)
 | |
| {
 | |
|     size_t index = chunk_ridx;
 | |
| 
 | |
|     if (buffer_rem > buffer_need)
 | |
|     {
 | |
|         size_t distance;
 | |
| 
 | |
|         if (auto_skip)
 | |
|         {
 | |
|             /* Automatic track changes only modify the last part of the buffer,
 | |
|              * so find the right chunk and sample to start the crossfade */
 | |
|             distance = buffer_rem - buffer_need;
 | |
|             buffer_rem = buffer_need;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* Manual skips occur immediately, but give 1/5s to process */
 | |
|             distance = MIN(BYTERATE / 5, buffer_rem);
 | |
|             buffer_rem -= distance;
 | |
|         }
 | |
| 
 | |
|         index = crossfade_find_index(index, distance);
 | |
|     }
 | |
| 
 | |
|     if (buffer_rem_outp)
 | |
|         *buffer_rem_outp = buffer_rem;
 | |
| 
 | |
|     return index;
 | |
| }
 | |
| 
 | |
| /* Run a fader on some buffers */
 | |
| static void crossfade_mix_fade(struct mixfader *faderp, size_t size,
 | |
|                                void *input_buf, size_t *out_index,
 | |
|                                unsigned long elapsed, off_t offset)
 | |
| {
 | |
|     if (size == 0)
 | |
|         return;
 | |
| 
 | |
|     size_t index = *out_index;
 | |
| 
 | |
|     if (index == INVALID_BUF_INDEX)
 | |
|         return;
 | |
| 
 | |
|     int16_t *inbuf = input_buf;
 | |
| 
 | |
|     bool alloced = inbuf && faderp->alloc &&
 | |
|                    index_chunk_offs(index, 0) == chunk_widx;
 | |
| 
 | |
|     while (size)
 | |
|     {
 | |
|         struct chunkdesc *desc = index_chunkdesc(index);
 | |
|         int16_t *outbuf = index_buffer(index);
 | |
| 
 | |
|         switch (offset)
 | |
|         {
 | |
|         case MIXFADE_NULLIFY_POS:
 | |
|             /* Stop position updates for the chunk */
 | |
|             desc->pos_key = 0;
 | |
|             break;
 | |
|         case MIXFADE_KEEP_POS:
 | |
|             /* Keep position info as it is */
 | |
|             break;
 | |
|         default:
 | |
|             /* Replace position info */
 | |
|             stamp_chunk(desc, elapsed, offset);
 | |
|         }
 | |
| 
 | |
|         size_t amount = (alloced ? PCMBUF_CHUNK_SIZE : desc->size)
 | |
|                             - (index % PCMBUF_CHUNK_SIZE);
 | |
|         int16_t *chunkend = SKIPBYTES(outbuf, amount);
 | |
| 
 | |
|         if (size < amount)
 | |
|             amount = size;
 | |
| 
 | |
|         size -= amount;
 | |
| 
 | |
|         if (alloced)
 | |
|         {
 | |
|             /* Fade the input buffer into the new destination chunk */
 | |
|             for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE)
 | |
|             {
 | |
|                 *outbuf++ = mixfade_sample(faderp, *inbuf++);
 | |
|                 *outbuf++ = mixfade_sample(faderp, *inbuf++);
 | |
|                 mixfader_step(faderp);
 | |
|             }
 | |
| 
 | |
|             commit_write_buffer(amount);
 | |
|         }
 | |
|         else if (inbuf)
 | |
|         {
 | |
|             /* Fade the input buffer and mix into the destination chunk */
 | |
|             for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE)
 | |
|             {
 | |
|                 int32_t left  = outbuf[0];
 | |
|                 int32_t right = outbuf[1];
 | |
|                 left  += mixfade_sample(faderp, *inbuf++);
 | |
|                 right += mixfade_sample(faderp, *inbuf++);
 | |
|                 *outbuf++ = clip_sample_16(left);
 | |
|                 *outbuf++ = clip_sample_16(right);
 | |
|                 mixfader_step(faderp);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* Fade the chunk in place */
 | |
|             for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE)
 | |
|             {
 | |
|                 int32_t left  = outbuf[0];
 | |
|                 int32_t right = outbuf[1];
 | |
|                 *outbuf++ = mixfade_sample(faderp, left);
 | |
|                 *outbuf++ = mixfade_sample(faderp, right);
 | |
|                 mixfader_step(faderp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (outbuf < chunkend)
 | |
|         {
 | |
|             index += amount;
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         /* Move destination to next chunk as needed */
 | |
|         index = index_next(index);
 | |
| 
 | |
|         if (index == chunk_widx)
 | |
|         {
 | |
|             /* End of existing data */
 | |
|             if (!inbuf || !faderp->alloc)
 | |
|             {
 | |
|                 index = INVALID_BUF_INDEX;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             alloced = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     *out_index = index;
 | |
| }
 | |
| 
 | |
| /* Initializes crossfader, calculates all necessary parameters and performs
 | |
|  * fade-out with the PCM buffer.  */
 | |
| static void crossfade_start(void)
 | |
| {
 | |
|     logf("crossfade_start");
 | |
| 
 | |
|     pcm_play_lock();
 | |
| 
 | |
|     if (crossfade_status == CROSSFADE_CONTINUE)
 | |
|     {
 | |
|         logf("fade-in continuing");
 | |
| 
 | |
|         crossfade_status = CROSSFADE_ACTIVE;
 | |
| 
 | |
|         if (crossfade_auto_skip)
 | |
|             pcmbuf_monitor_track_change_ex(crossfade_widx);
 | |
| 
 | |
|         pcm_play_unlock();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Initialize the crossfade buffer size to all of the buffered data that
 | |
|      * has not yet been sent to the DMA */
 | |
|     size_t unplayed = pcmbuf_unplayed_bytes();
 | |
| 
 | |
|     /* Reject crossfade if less than .5s of data */
 | |
|     if (unplayed < DATA_LEVEL(2))
 | |
|     {
 | |
|         logf("crossfade rejected");
 | |
|         crossfade_cancel();
 | |
|         pcm_play_unlock();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Fading will happen */
 | |
|     crossfade_status = CROSSFADE_ACTIVE;
 | |
| 
 | |
|     /* Get fade info from settings. */
 | |
|     size_t fade_out_delay = global_settings.crossfade_fade_out_delay * BYTERATE;
 | |
|     size_t fade_out_rem = global_settings.crossfade_fade_out_duration * BYTERATE;
 | |
|     size_t fade_in_delay = global_settings.crossfade_fade_in_delay * BYTERATE;
 | |
|     size_t fade_in_duration = global_settings.crossfade_fade_in_duration * BYTERATE;
 | |
| 
 | |
|     if (!crossfade_auto_skip)
 | |
|     {
 | |
|         /* Forego fade-in delay on manual skip - do the best to preserve auto skip
 | |
|            relationship */
 | |
|         fade_out_delay -= MIN(fade_out_delay, fade_in_delay);
 | |
|         fade_in_delay = 0;
 | |
|     }
 | |
| 
 | |
|     size_t fade_out_need = fade_out_delay + fade_out_rem;
 | |
| 
 | |
|     if (!crossfade_mixmode)
 | |
|     {
 | |
|         /* Completely process the crossfade fade-out effect with current PCM buffer */
 | |
|         size_t buffer_rem;
 | |
|         size_t index = crossfade_find_buftail(crossfade_auto_skip, unplayed,
 | |
|                                               fade_out_need, &buffer_rem);
 | |
| 
 | |
|         pcm_play_unlock();
 | |
| 
 | |
|         if (buffer_rem < fade_out_need)
 | |
|         {
 | |
|             /* Existing buffers are short */
 | |
|             size_t fade_out_short = fade_out_need - buffer_rem;
 | |
| 
 | |
|             if (fade_out_delay >= fade_out_short)
 | |
|             {
 | |
|                 /* Truncate fade-out delay */
 | |
|                 fade_out_delay -= fade_out_short;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /* Truncate fade-out and eliminate fade-out delay */
 | |
|                 fade_out_rem = buffer_rem;
 | |
|                 fade_out_delay = 0;
 | |
|             }
 | |
| 
 | |
|             fade_out_need = fade_out_delay + fade_out_rem;
 | |
|         }
 | |
| 
 | |
|         /* Find the right chunk and sample to start fading out */
 | |
|         index = crossfade_find_index(index, fade_out_delay);
 | |
| 
 | |
|         /* Fade out the specified amount of the already processed audio */
 | |
|         struct mixfader outfader;
 | |
| 
 | |
|         mixfader_init(&outfader, MIXFADE_UNITY, 0, fade_out_rem, false);
 | |
|         crossfade_mix_fade(&outfader, fade_out_rem, NULL, &index, 0,
 | |
|                            MIXFADE_KEEP_POS);
 | |
| 
 | |
|         /* Zero-out the rest of the buffer */
 | |
|         crossfade_mix_fade(&outfader, pcmbuf_size, NULL, &index, 0,
 | |
|                            MIXFADE_NULLIFY_POS);
 | |
| 
 | |
|         pcm_play_lock();
 | |
|     }
 | |
| 
 | |
|     /* Initialize fade-in counters */
 | |
|     mixfader_init(&crossfade_infader, 0, MIXFADE_UNITY, fade_in_duration, true);
 | |
| 
 | |
|     /* Find the right chunk and sample to start fading in - redo from read
 | |
|        chunk in case original position were/was overrun in callback - the
 | |
|        track change event _must not_ ever fail to happen */
 | |
|     unplayed = pcmbuf_unplayed_bytes() + fade_in_delay;
 | |
| 
 | |
|     crossfade_widx = crossfade_find_buftail(crossfade_auto_skip, unplayed,
 | |
|                                             fade_out_need, NULL);
 | |
| 
 | |
|     /* Move track transistion to chunk before the first one of incoming track */
 | |
|     if (crossfade_auto_skip)
 | |
|         pcmbuf_monitor_track_change_ex(crossfade_widx);
 | |
| 
 | |
|     pcm_play_unlock();
 | |
| 
 | |
|     logf("crossfade_start done!");
 | |
| }
 | |
| 
 | |
| /* Perform fade-in of new track */
 | |
| static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset)
 | |
| {
 | |
|     /* Mix the data */
 | |
|     crossfade_mix_fade(&crossfade_infader, size, index_buffer(crossfade_bufidx),
 | |
|                        &crossfade_widx, elapsed, offset);
 | |
| 
 | |
|     /* If no more fading-in to do, stop the crossfade */
 | |
|     if (mixfader_finished(&crossfade_infader) &&
 | |
|         index_chunk_offs(crossfade_widx, 0) == chunk_widx)
 | |
|     {
 | |
|         crossfade_cancel();
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void pcmbuf_finish_crossfade_enable(void)
 | |
| {
 | |
|     /* Copy the pending setting over now */
 | |
|     crossfade_setting = crossfade_enable_request;
 | |
| 
 | |
|     pcmbuf_watermark = (crossfade_setting != CROSSFADE_ENABLE_OFF && pcmbuf_size) ?
 | |
|         /* If crossfading, try to keep the buffer full other than 1 second */
 | |
|         (pcmbuf_size - BYTERATE) :
 | |
|         /* Otherwise, just use the default */
 | |
|         PCMBUF_WATERMARK;
 | |
| }
 | |
| 
 | |
| void pcmbuf_request_crossfade_enable(int setting)
 | |
| {
 | |
|     /* Next setting to be used, not applied now */
 | |
|     crossfade_enable_request = setting;
 | |
| }
 | |
| 
 | |
| bool pcmbuf_is_same_size(void)
 | |
| {
 | |
|     /* if pcmbuf_buffer is NULL, then not set up yet even once so always */
 | |
|     bool same_size = pcmbuf_buffer ?
 | |
|         (get_next_required_pcmbuf_chunks() == pcmbuf_desc_count) : true;
 | |
| 
 | |
|     /* no buffer change needed, so finish crossfade setup now */
 | |
|     if (same_size)
 | |
|         pcmbuf_finish_crossfade_enable();
 | |
| 
 | |
|     return same_size;
 | |
| }
 | |
| #endif /* HAVE_CROSSFADE */
 | |
| 
 | |
| 
 | |
| /** Debug menu, other metrics */
 | |
| 
 | |
| /* Amount of bytes left in the buffer, accounting for uncommitted bytes */
 | |
| size_t pcmbuf_free(void)
 | |
| {
 | |
|     return pcmbuf_size - pcmbuf_unplayed_bytes() - pcmbuf_bytes_waiting;
 | |
| }
 | |
| 
 | |
| /* Data bytes allocated for buffer */
 | |
| size_t pcmbuf_get_bufsize(void)
 | |
| {
 | |
|     return pcmbuf_size;
 | |
| }
 | |
| 
 | |
| /* Number of committed descriptors */
 | |
| int pcmbuf_used_descs(void)
 | |
| {
 | |
|     return pcmbuf_unplayed_bytes() / PCMBUF_CHUNK_SIZE;
 | |
| }
 | |
| 
 | |
| /* Total number of descriptors allocated */
 | |
| int pcmbuf_descs(void)
 | |
| {
 | |
|     return pcmbuf_desc_count;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Fading and channel volume control */
 | |
| 
 | |
| /* Sync the channel amplitude to all states */
 | |
| static void pcmbuf_update_volume(void)
 | |
| {
 | |
|     unsigned int vol = fade_vol;
 | |
| 
 | |
|     if (soft_mode)
 | |
|         vol >>= 2;
 | |
| 
 | |
|     mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, vol);
 | |
| }
 | |
| 
 | |
| /* Tick that does the fade for the playback channel */
 | |
| static void pcmbuf_fade_tick(void)
 | |
| {
 | |
|     /* ~1/3 second for full range fade */
 | |
|     const unsigned int fade_step = MIX_AMP_UNITY / (HZ / 3);
 | |
| 
 | |
|     if (fade_state == PCM_FADING_IN)
 | |
|         fade_vol += MIN(fade_step, MIX_AMP_UNITY - fade_vol);
 | |
|     else if (fade_state == PCM_FADING_OUT)
 | |
|         fade_vol -= MIN(fade_step, fade_vol - MIX_AMP_MUTE);
 | |
| 
 | |
|     pcmbuf_update_volume();
 | |
| 
 | |
|     if (fade_vol == MIX_AMP_MUTE || fade_vol == MIX_AMP_UNITY)
 | |
|     {
 | |
|         /* Fade is complete */
 | |
|         tick_remove_task(pcmbuf_fade_tick);
 | |
|         if (fade_state == PCM_FADING_OUT)
 | |
|         {
 | |
|             /* Tell PCM to stop at its earliest convenience */
 | |
|             fade_out_complete = true;
 | |
|         }
 | |
| 
 | |
|         fade_state = PCM_NOT_FADING;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Fade channel in or out in the background */
 | |
| void pcmbuf_fade(bool fade, bool in)
 | |
| {
 | |
|     /* Must pause any active fade */
 | |
|     pcm_play_lock();
 | |
| 
 | |
|     if (fade_state != PCM_NOT_FADING)
 | |
|         tick_remove_task(pcmbuf_fade_tick);
 | |
| 
 | |
|     fade_out_complete = false;
 | |
| 
 | |
|     pcm_play_unlock();
 | |
| 
 | |
|     if (!fade)
 | |
|     {
 | |
|         /* Simply set the level */
 | |
|         fade_state = PCM_NOT_FADING;
 | |
|         fade_vol = in ? MIX_AMP_UNITY : MIX_AMP_MUTE;
 | |
|         pcmbuf_update_volume();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* Set direction and resume fade from current point */
 | |
|         fade_state = in ? PCM_FADING_IN : PCM_FADING_OUT;
 | |
|         tick_add_task(pcmbuf_fade_tick);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Return 'true' if fade is in progress */
 | |
| bool pcmbuf_fading(void)
 | |
| {
 | |
|     return fade_state != PCM_NOT_FADING;
 | |
| }
 | |
| 
 | |
| /* Quiet-down the channel if 'shhh' is true or else play at normal level */
 | |
| void pcmbuf_soft_mode(bool shhh)
 | |
| {
 | |
|     /* Have to block the tick or improper order could leave volume in soft
 | |
|        mode if fading reads the old value first but updates after us. */
 | |
|     int res = fade_state != PCM_NOT_FADING ?
 | |
|                 tick_remove_task(pcmbuf_fade_tick) : -1;
 | |
| 
 | |
|     soft_mode = shhh;
 | |
|     pcmbuf_update_volume();
 | |
| 
 | |
|     if (res == 0)
 | |
|         tick_add_task(pcmbuf_fade_tick);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Time and position */
 | |
| 
 | |
| /* Return the current position key value */
 | |
| unsigned int pcmbuf_get_position_key(void)
 | |
| {
 | |
|     return position_key;
 | |
| }
 | |
| 
 | |
| /* Set position updates to be synchronous and immediate in addition to during
 | |
|    PCM frames - cancelled upon first codec insert or upon stopping */
 | |
| void pcmbuf_sync_position_update(void)
 | |
| {
 | |
|     pcmbuf_sync_position = true;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /** Misc */
 | |
| 
 | |
| bool pcmbuf_is_lowdata(void)
 | |
| {
 | |
|     enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK);
 | |
| 
 | |
|     if (status != CHANNEL_PLAYING)
 | |
|         return false;
 | |
| 
 | |
| #ifdef HAVE_CROSSFADE
 | |
|     if (crossfade_status != CROSSFADE_INACTIVE)
 | |
|         return false;
 | |
| #endif
 | |
| 
 | |
|     return pcmbuf_data_critical();
 | |
| }
 | |
| 
 | |
| void pcmbuf_set_low_latency(bool state)
 | |
| {
 | |
|     low_latency_mode = state;
 | |
| }
 | |
| 
 | |
| void pcmbuf_update_frequency(void)
 | |
| {
 | |
|     pcmbuf_sampr = mixer_get_frequency();
 | |
| }
 | |
| 
 | |
| unsigned int pcmbuf_get_frequency(void)
 | |
| {
 | |
|     return pcmbuf_sampr;
 | |
| }
 |