| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 | /*  *  Squeezelite for esp32 * *  (c) Sebastien 2019 *      Philippe G. 2019, philippe_44@outlook.com * *  This software is released under the MIT License. *  https://opensource.org/licenses/MIT * */#include "driver/gpio.h"#include "squeezelite.h"#include "equalizer.h"#include "perf_trace.h"#include "platform_config.h"#include <assert.h>extern struct outputstate output;extern struct buffer *outputbuf;extern struct buffer *streambuf;extern u8_t *silencebuf;#define LOCK   mutex_lock(outputbuf->mutex)#define UNLOCK mutex_unlock(outputbuf->mutex)#define LOCK_S   mutex_lock(streambuf->mutex)#define UNLOCK_S mutex_unlock(streambuf->mutex)#define FRAME_BLOCK MAX_SILENCE_FRAMES#define STATS_REPORT_DELAY_MS 15000extern void hal_bluetooth_init(const char * options);extern void hal_bluetooth_stop(void);extern u8_t config_spdif_gpio;static log_level loglevel;static bool running = false;static uint8_t *btout;static frames_t oframes;static bool stats;static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,								s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);								#define DECLARE_ALL_MIN_MAX \	DECLARE_MIN_MAX(req);\	DECLARE_MIN_MAX(rec);\	DECLARE_MIN_MAX(bt);\	DECLARE_MIN_MAX(under);\	DECLARE_MIN_MAX(stream_buf);\	DECLARE_MIN_MAX_DURATION(lock_out_time)									#define RESET_ALL_MIN_MAX \	RESET_MIN_MAX(bt);	\	RESET_MIN_MAX(req);  \	RESET_MIN_MAX(rec);  \	RESET_MIN_MAX(under);  \	RESET_MIN_MAX(stream_buf); \	RESET_MIN_MAX_DURATION(lock_out_time)	DECLARE_ALL_MIN_MAX;		void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {	loglevel = level;	running = true;	output.write_cb = &_write_frames;	hal_bluetooth_init(device);	char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);	stats = p && (*p == '1' || *p == 'Y' || *p == 'y');	free(p);}void output_close_bt(void) {	LOCK;	running = false;	UNLOCK;	hal_bluetooth_stop();	equalizer_close();}	static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,						 s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {		assert(btout != NULL);		if (!silence ) {						if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) {			_apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr);		}		_apply_gain(outputbuf, out_frames, gainL, gainR);#if BYTES_PER_FRAME == 4		memcpy(btout + oframes * BYTES_PER_FRAME, outputbuf->readp, out_frames * BYTES_PER_FRAME);#else	{		frames_t count = out_frames;		s32_t *_iptr = (s32_t*) outputbuf->readp;		s16_t *_optr = (s16_t*) (btout + oframes * BYTES_PER_FRAME);		while (count--) {			*_optr++ = *_iptr++ >> 16;			*_optr++ = *_iptr++ >> 16;		}	}#endif	} else {		u8_t *buf = silencebuf;		memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);	}		output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, ((gainL & ~MONO_FLAG) + (gainR & ~MONO_FLAG)) / 2);		oframes += out_frames;	return (int)out_frames;}int32_t output_bt_data(uint8_t *data, int32_t len) {	int32_t iframes = len / BYTES_PER_FRAME, start_timer = 0;	if (iframes <= 0 || data == NULL || !running) {		return 0;	}		btout = data;	oframes = 0;	// This is how the BTC layer calculates the number of bytes to	// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes	SET_MIN_MAX(len,req);	TIME_MEASUREMENT_START(start_timer);		LOCK;		SET_MIN_MAX_SIZED(_buf_used(outputbuf),bt,outputbuf->size);	output.device_frames = 0; 	output.updated = gettime_ms();	output.frames_played_dmp = output.frames_played;	_output_frames(iframes); 	output.frames_in_process = oframes;	UNLOCK;		equalizer_process(data, oframes * BYTES_PER_FRAME, output.current_sample_rate);	SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);	SET_MIN_MAX((len-oframes*BYTES_PER_FRAME), rec);	TIME_MEASUREMENT_START(start_timer);	return oframes * BYTES_PER_FRAME;}void output_bt_tick(void) {	static time_t lastTime=0;		if (!running) return;		LOCK_S;    SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);    UNLOCK_S;		if (stats && lastTime <= gettime_ms() )	{		lastTime = gettime_ms() + STATS_REPORT_DELAY_MS;		LOG_INFO("Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);		LOG_INFO("              +==========+==========+================+=====+================+");		LOG_INFO("              |      max |      min |        average | avg |          count |");		LOG_INFO("              |  (bytes) |  (bytes) |        (bytes) | pct |                |");		LOG_INFO("              +==========+==========+================+=====+================+");		LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("stream avl",stream_buf));		LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output avl",bt));		LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));		LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));		LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("underrun",under));		LOG_INFO( "              +==========+==========+================+=====+================+");		LOG_INFO("\n");		LOG_INFO("              ==========+==========+===========+===========+  ");		LOG_INFO("              max (us)  | min (us) |   avg(us) |  count    |  ");		LOG_INFO("              ==========+==========+===========+===========+  ");		LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Out Buf Lock",lock_out_time));		LOG_INFO("              ==========+==========+===========+===========+");		RESET_ALL_MIN_MAX;	}	}	
 |