refactor step 1 - BT

This commit is contained in:
philippe44
2019-06-28 23:08:46 -07:00
parent 0e3bc18748
commit 6c5ce6ba80
21 changed files with 452 additions and 1120 deletions

View File

@@ -134,7 +134,7 @@ menu "Squeezelite-ESP32"
config OUTPUT_RATES
string "Output rates"
default "48000,44100"
default "44100"
help
<rates>[:<delay>] Sample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms
menu "DAC I2S settings"
@@ -196,13 +196,6 @@ menu "Squeezelite-ESP32"
default 1000
help
Increasing this value will give more chance for less stable connections to be established.
config A2DP_DISCONNECT_MS
int "Time in ms before disconnecting from A2DP audio sink. Set to 0 for no disconnect."
default 10000
help
Controls how long to wait before disconnecting from the A2DP audio sink after playback is stopped
Longer delay will ensure better responsiveness at the expense of locking the audio sink for a longer period.
A shorter period may cause the player to disconnect between tracks change.
endmenu
endmenu

View File

@@ -2,7 +2,7 @@
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \
CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \
-I$(COMPONENT_PATH)/../components/codecs/inc \
-I$(COMPONENT_PATH)/../components/codecs/inc/mad \
-I$(COMPONENT_PATH)/../components/codecs/inc/alac \
@@ -10,7 +10,7 @@ CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DT
-I$(COMPONENT_PATH)/../components/codecs/inc/vorbis \
-I$(COMPONENT_PATH)/../components/codecs/inc/soxr \
-I$(COMPONENT_PATH)/../components/codecs/inc/resample16 \
-I$(COMPONENT_PATH)/../components/platform_esp32
-I$(COMPONENT_PATH)/../components/platform_esp32
LDFLAGS += -s

View File

@@ -199,8 +199,8 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud
LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
mutex_create(decode.mutex);
PTHREAD_SET_NAME("decode");
#if LINUX || OSX || FREEBSD || POSIX
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
@@ -208,6 +208,9 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud
#endif
pthread_create(&thread, &attr, decode_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "decode");
#endif
#endif
#if WIN
thread = CreateThread(NULL, DECODE_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&decode_thread, NULL, 0, NULL);

44
main/embedded.c Normal file
View File

@@ -0,0 +1,44 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, philippe_44@outlook.com
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#include "esp_pthread.h"
#include "esp_system.h"
void get_mac(u8_t mac[]) {
esp_read_mac(mac, ESP_MAC_WIFI_STA);
}
_sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}
void *audio_calloc(size_t nmemb, size_t size) {
return calloc(nmemb, size);
}
int pthread_setname_np(pthread_t thread, const char *name) {
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= name;
cfg.inherit_cfg = true;
return esp_pthread_set_cfg(&cfg);
}

View File

@@ -1,6 +1,22 @@
#pragma once
#if defined(ESP_PLATFORM)
#include "sdkconfig.h"
#include "esp_pthread.h"
#define PTHREAD_SET_NAME(n) { esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); cfg.thread_name= n; cfg.inherit_cfg = true; esp_pthread_set_cfg(&cfg); }
#ifndef EMBEDDED_H
#define EMBEDDED_H
#include <inttypes.h>
#define HAS_MUTEX_CREATE_P 0
#define HAS_PTHREAD_SETNAME_NP 1
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
typedef unsigned long long u64_t;
#define exit(code) { int ret = code; pthread_exit(&ret); }
int pthread_setname_np(pthread_t thread, const char *name);
#endif // EMBEDDED_H

View File

@@ -169,6 +169,9 @@ static void usage(const char *argv0) {
"18"
#endif
#endif
#if EMBEDDED
" EMBEDDED"
#endif
#if EVENTFD
" EVENTFD"
#endif
@@ -381,7 +384,7 @@ int main(int argc, char **argv) {
} else {
fprintf(stderr, "\nOption error: -%s\n\n", opt);
usage(argv[0]);
local_exit(1);
exit(1);
}
switch (opt[0]) {
@@ -432,7 +435,7 @@ int main(int argc, char **argv) {
} else {
fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
usage(argv[0]);
local_exit(1);
exit(1);
}
}
break;
@@ -532,7 +535,7 @@ int main(int argc, char **argv) {
pidfile = optarg;
break;
#endif
#if !CONFIG_DACAUDIO && !CONFIG_BTAUDIO
#ifndef EMBEDDED
case 'l':
list_devices();
exit(0);
@@ -675,12 +678,11 @@ int main(int argc, char **argv) {
#endif
case 't':
license();
local_exit(0);
break; // mute compiler warning
exit(0);
case '?':
usage(argv[0]);
local_exit(0);
break; // mute compiler warning
exit(0);
break;
default:
fprintf(stderr, "Arg error: %s\n", argv[optind]);
break;
@@ -691,7 +693,7 @@ int main(int argc, char **argv) {
if (optind < argc) {
fprintf(stderr, "\nError: command line argument error\n\n");
usage(argv[0]);
local_exit(1);
exit(1);
}
signal(SIGINT, sighandler);
@@ -757,13 +759,9 @@ int main(int argc, char **argv) {
#endif
stream_init(log_stream, stream_buf_size);
#ifdef EMBEDDED
if(strstr(output_device,"BT")!=NULL || strstr(output_device,"bt")!=NULL) {
output_init_bt(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
}
else if(strstr(output_device,"DAC")!=NULL || strstr(output_device,"dac")!=NULL){
output_init_dac(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
}
#if EMBEDDED
output_init_embedded(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#else
if (!strcmp(output_device, "-")) {
output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay);
@@ -804,7 +802,7 @@ else if(strstr(output_device,"DAC")!=NULL || strstr(output_device,"dac")!=NULL){
if (name && namefile) {
fprintf(stderr, "-n and -N option should not be used at same time\n");
local_exit(1);
exit(1);
}
slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate);
@@ -812,10 +810,8 @@ else if(strstr(output_device,"DAC")!=NULL || strstr(output_device,"dac")!=NULL){
decode_close();
stream_close();
#if CONFIG_DACAUDIO
output_close_dac();
#elif CONFIG_BTAUDIO
output_close_bt();
#if EMBEDDED
output_close_embedded();
#else
if (!strcmp(output_device, "-")) {
output_close_stdout();
@@ -848,5 +844,5 @@ else if(strstr(output_device,"DAC")!=NULL || strstr(output_device,"dac")!=NULL){
free_ssl_symbols();
#endif
local_exit(0);
exit(0);
}

View File

@@ -352,13 +352,13 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
buf_init(outputbuf, output_buf_size);
if (!outputbuf->buf) {
LOG_ERROR("unable to malloc output buffer");
local_exit(0);
exit(0);
}
silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf) {
LOG_ERROR("unable to malloc silence buffer");
local_exit(0);
exit(0);
}
memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
@@ -389,7 +389,7 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
else {
if (!test_open(output.device, output.supported_rates, user_rates)) {
LOG_ERROR("unable to open output device: %s", output.device);
local_exit(0);
exit(0);
}
}

View File

@@ -1,352 +1,59 @@
#include "squeezelite.h"
#include "perf_trace.h"
static log_level loglevel;
static bool running = true;
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)
#ifdef USE_BT_RING_BUFFER
size_t bt_buffer_size=0;
uint8_t bt_buf_used_threshold = 25;
uint16_t output_bt_thread_heartbeat_ms=1000;
thread_type thread_bt;
#define LOCK_BT mutex_lock(btbuf->mutex)
#define UNLOCK_BT mutex_unlock(btbuf->mutex)
thread_cond_type output_bt_suspend_cond;
mutex_type output_bt_suspend_mutex;
static struct buffer bt_buf_structure;
struct buffer *btbuf=&bt_buf_structure;
static void *output_thread_bt();
extern void wait_for_frames(size_t frames, uint8_t pct);
#else
uint8_t * btout;
#endif
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
extern u8_t *silencebuf;
#define STATS_REPORT_DELAY_MS 15000
extern void hal_bluetooth_init(const char * options);
static log_level loglevel;
uint8_t * btout;
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(o);\
DECLARE_MIN_MAX(s);\
DECLARE_MIN_MAX(locbtbuff);\
DECLARE_MIN_MAX(bt);\
DECLARE_MIN_MAX(under);\
DECLARE_MIN_MAX_DURATION(mutex1);\
DECLARE_MIN_MAX_DURATION(mutex2);\
DECLARE_MIN_MAX_DURATION(buffering);\
DECLARE_MIN_MAX_DURATION(sleep_time);
DECLARE_MIN_MAX(stream_buf);\
DECLARE_MIN_MAX_DURATION(lock_out_time)
#define RESET_ALL_MIN_MAX \
RESET_MIN_MAX(o); \
RESET_MIN_MAX(s); \
RESET_MIN_MAX(locbtbuff); \
RESET_MIN_MAX(bt); \
RESET_MIN_MAX(req); \
RESET_MIN_MAX(rec); \
RESET_MIN_MAX(under); \
RESET_MIN_MAX_DURATION(mutex1); \
RESET_MIN_MAX_DURATION(mutex2); \
RESET_MIN_MAX_DURATION(sleep_time); \
RESET_MIN_MAX_DURATION(buffering);
#if CONFIG_BTAUDIO
void set_volume_bt(unsigned left, unsigned right) {
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
LOCK;
output.gainL = left;
output.gainR = right;
UNLOCK;
}
#endif
void output_bt_check_buffer()
{
#ifdef USE_BT_RING_BUFFER
LOCK_BT;
uint8_t tot_buf_used_pct=100*_buf_used(btbuf)/btbuf->size;
UNLOCK_BT;
if(tot_buf_used_pct<bt_buf_used_threshold)
{
// tell the thread to resume
LOG_SDEBUG("Below threshold. Locking suspend mutex.");
mutex_lock(output_bt_suspend_mutex);
LOG_SDEBUG("Broadcasting suspend condition.");
mutex_broadcast_cond(output_bt_suspend_cond);
LOG_SDEBUG("Unlocking suspend mutex.");
mutex_unlock(output_bt_suspend_mutex);
}
#endif
}
void output_bt_suspend()
{
#ifdef USE_BT_RING_BUFFER
struct timespec ts;
struct timeval tp;
int rc;
// if suspended, suspend until resumed
LOG_SDEBUG("Locking suspend mutex.");
mutex_lock(output_bt_suspend_mutex);
LOG_SDEBUG("Waiting on condition to be signaled.");
// suspend for up to a predetermined wait time.
// this will allow flushing the BT buffer when the
// playback stops.
gettimeofday(&tp, NULL);
/* Convert from timeval to timespec */
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_nsec += output_bt_thread_heartbeat_ms*1000000; // micro seconds to nanosecs
rc = pthread_cond_timedwait(&output_bt_suspend_cond,&output_bt_suspend_mutex,&ts);
if(rc==ETIMEDOUT)
{
LOG_SDEBUG("Wait timed out. Resuming output.");
}
LOG_SDEBUG("Unlocking suspend mutex.");
mutex_unlock(output_bt_suspend_mutex);
#endif
}
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;
LOG_INFO("init output BT");
memset(&output, 0, sizeof(output));
// ensure output rate is specified to avoid test open
if (!rates[0]) {
rates[0] = 44100;
}
hal_bluetooth_init(device);
/*
* Bluetooth audio source init Start
*/
// device = CONFIG_OUTPUT_NAME;
output_init_common(level, device, output_buf_size, rates, idle);
#ifdef USE_BT_RING_BUFFER
LOG_DEBUG("Allocating local BT transfer buffer of %u bytes.",bt_buffer_size);
buf_init(btbuf, bt_buffer_size );
if (!btbuf->buf) {
LOG_ERROR("unable to malloc BT buffer");
exit(0);
}
mutex_create_p(output_bt_suspend_mutex);
mutex_cond_init(output_bt_suspend_cond);
PTHREAD_SET_NAME("output_bt");
#if LINUX || OSX || FREEBSD || POSIX
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
#endif
pthread_create(&thread_bt, &attr, output_thread_bt, NULL);
#endif
pthread_attr_destroy(&attr);
#if WIN
thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread_bt, NULL, 0, NULL);
#endif
#else
output.start_frames = FRAME_BLOCK;
output.write_cb = &_write_frames;
output.rate_delay = rate_delay;
#endif
LOG_INFO("Init completed.");
}
/****************************************************************************************
* Main output thread
*/
#ifdef USE_BT_RING_BUFFER
static void *output_thread_bt() {
frames_t frames=0;
frames_t requested_frames=0;
uint32_t timer_start=0, mutex_start=0;
unsigned btbuf_used=0;
output_state state;
DECLARE_ALL_MIN_MAX;
while (running) {
frames=0;
requested_frames=0;
TIME_MEASUREMENT_START(timer_start);
// Get output state
TIME_MEASUREMENT_START(mutex_start);
LOCK;
state=output.state;
SET_MIN_MAX(TIME_MEASUREMENT_GET(mutex_start),mutex1);
if(state < OUTPUT_STOPPED ){
// Flushing the buffer will automatically
// lock the mutex
LOG_SDEBUG("Flushing BT buffer");
buf_flush(btbuf);
}
if (state == OUTPUT_OFF) {
UNLOCK;
LOG_SDEBUG("Output state is off.");
usleep(200000);
continue;
}
output.device_frames = 0; // todo: check if this is the right way do to this.
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
TIME_MEASUREMENT_START(mutex_start);
LOCK_BT;
SET_MIN_MAX(TIME_MEASUREMENT_GET(mutex_start),mutex2);
btbuf_used=_buf_used(btbuf);
SET_MIN_MAX_SIZED(btbuf_used,locbtbuff,btbuf->size);
// only output more frames if we need them
// so we can release the mutex as quickly as possible
requested_frames = min(_buf_space(btbuf), _buf_cont_write(btbuf))/BYTES_PER_FRAME;
SET_MIN_MAX( requested_frames*BYTES_PER_FRAME,req);
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
if(requested_frames>0)
{
frames = _output_frames( requested_frames ); // Keep the transfer buffer full
SET_MIN_MAX(frames*BYTES_PER_FRAME,rec);
if(requested_frames>frames){
SET_MIN_MAX((requested_frames-frames)*BYTES_PER_FRAME,under);
}
}
UNLOCK;
UNLOCK_BT;
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),buffering);
SET_MIN_MAX( requested_frames,req);
// When playback has started, we want to
// hold the BT out thread
// so the BT data callback isn't constantly interrupted.
TIME_MEASUREMENT_START(timer_start);
if(state>OUTPUT_BUFFER){
output_bt_suspend();
}
SET_MIN_MAX(TIME_MEASUREMENT_GET(timer_start),sleep_time);
/*
* Statistics reporting
*/
static time_t lastTime=0;
if (lastTime <= gettime_ms() )
{
#define STATS_PERIOD_MS 15000
lastTime = gettime_ms() + STATS_PERIOD_MS;
LOG_DEBUG(LINE_MIN_MAX_FORMAT_HEAD1);
LOG_DEBUG(LINE_MIN_MAX_FORMAT_HEAD2);
LOG_DEBUG(LINE_MIN_MAX_FORMAT_HEAD3);
LOG_DEBUG(LINE_MIN_MAX_FORMAT_HEAD4);
LOG_DEBUG(LINE_MIN_MAX_FORMAT_STREAM, LINE_MIN_MAX_STREAM("stream",s));
LOG_DEBUG(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output",o));
LOG_DEBUG(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("local bt buf",locbtbuff));
LOG_DEBUG(LINE_MIN_MAX_FORMAT_FOOTER );
LOG_DEBUG(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
LOG_DEBUG(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
LOG_DEBUG(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("Underrun",under));
LOG_DEBUG(LINE_MIN_MAX_FORMAT_FOOTER );
LOG_DEBUG("");
LOG_DEBUG(" ----------+----------+-----------+-----------+ ");
LOG_DEBUG(" max (us) | min (us) | avg(us) | count | ");
LOG_DEBUG(" ----------+----------+-----------+-----------+ ");
LOG_DEBUG(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Buffering(us)",buffering));
LOG_DEBUG(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Output mux(us)",mutex1));
LOG_DEBUG(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("BT mux(us)",mutex2));
LOG_DEBUG(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("sleep(us)",mutex2));
LOG_DEBUG(" ----------+----------+-----------+-----------+");
RESET_ALL_MIN_MAX;
}
/*
* End Statistics reporting
*/
}
return NULL;
}
#endif
void output_close_bt(void) {
LOG_INFO("close output");
LOCK;
running = false;
UNLOCK;
#ifdef USE_BT_RING_BUFFER
LOCK_BT;
buf_destroy(btbuf);
UNLOCK_BT;
#endif
output_close_common();
}
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) {
#ifdef USE_BT_RING_BUFFER
assert(btout != NULL);
if (!silence ) {
DEBUG_LOG_TIMED(200,"Not silence, Writing audio out.");
// TODO need 16 bit fix
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);
}
if (gainL != FIXED_ONE || gainR!= FIXED_ONE) {
_apply_gain(outputbuf, out_frames, gainL, gainR);
}
#if BYTES_PER_FRAME == 4
memcpy(btbuf->writep, outputbuf->readp, out_frames * BYTES_PER_FRAME);
_buf_inc_writep(btbuf,out_frames * BYTES_PER_FRAME);
#else
{
frames_t count = out_frames;
s32_t *_iptr = (s32_t*) outputbuf->readp;
s16_t *_optr = (s16_t*) bt_optr;
while (count--) {
*_optr++ = *_iptr++ >> 16;
*_optr++ = *_iptr++ >> 16;
}
}
#endif
} else if(output.state >OUTPUT_BUFFER){
// Don't fill our local buffer with silence frames.
u8_t *buf = silencebuf;
memcpy(btbuf->writep, buf, out_frames * BYTES_PER_FRAME);
_buf_inc_writep(btbuf,out_frames * BYTES_PER_FRAME);
}
#else
assert(btout!=NULL);
if (!silence ) {
DEBUG_LOG_TIMED(200,"Not silence, Writing audio out.");
// TODO need 16 bit fix
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);
}
@@ -374,7 +81,74 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
u8_t *buf = silencebuf;
memcpy(btout, buf, out_frames * BYTES_PER_FRAME);
}
#endif
return (int)out_frames;
}
int32_t output_bt_data(uint8_t *data, int32_t len) {
int32_t avail_data = 0, wanted_len = 0, start_timer = 0;
if (len < 0 || data == NULL ) {
return 0;
}
btout = data;
// 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
wanted_len=len;
SET_MIN_MAX(len,req);
TIME_MEASUREMENT_START(start_timer);
LOCK;
output.device_frames = 0; // todo: check if this is the right way do to this.
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
SET_MIN_MAX_SIZED(_buf_used(outputbuf),bt,outputbuf->size);
do {
avail_data = _output_frames( wanted_len/BYTES_PER_FRAME )*BYTES_PER_FRAME; // Keep the transfer buffer full
wanted_len-=avail_data;
} while (wanted_len > 0 && avail_data != 0);
if (wanted_len > 0) {
SET_MIN_MAX(wanted_len, under);
}
UNLOCK;
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
SET_MIN_MAX((len-wanted_len), rec);
TIME_MEASUREMENT_START(start_timer);
return len-wanted_len;
}
void output_bt_tick(void) {
static time_t lastTime=0;
LOCK_S;
SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);
UNLOCK_S;
if (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;
}
}

View File

@@ -1,162 +0,0 @@
#include "squeezelite.h"
#include "driver/i2s.h"
static log_level loglevel;
static bool running = true;
extern struct outputstate output;
extern struct buffer *outputbuf;
extern struct buffer *streambuf;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
extern u8_t *silencebuf;
#define I2S_NUM (0)
#define WAVE_FREQ_HZ (100)
#define PI (3.14159265)
#define I2S_BCK_IO (GPIO_NUM_26)
#define I2S_WS_IO (GPIO_NUM_25)
#define I2S_DO_IO (GPIO_NUM_22)
#define I2S_DI_IO (-1)
// buffer length is expressed in number of samples
#define I2S_BUF_LEN 60
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);
void set_volume(unsigned left, unsigned right) {
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
LOCK;
output.gainL = left;
output.gainR = right;
// TODO
output.gainL = FIXED_ONE;
output.gainR = FIXED_ONE;
UNLOCK;
}
static void *output_thread(void *arg) {
bool start = true;
bool output_off = (output.state == OUTPUT_OFF);
bool probe_device = (arg != NULL);
int err;
while (running) {
// todo: implement output off logic?
// todo: call i2s_set_clock here if rate is changed
LOCK;
output.device_frames = 0;
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
_output_frames(I2S_BUF_LEN*2); // fill at least one DMA buffer with stereo signal
UNLOCK;
}
return 0;
}
static pthread_t thread;
void output_init_dac(log_level level, char *device, unsigned output_buf_size,
char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
LOG_INFO("init output DAC");
memset(&output, 0, sizeof(output));
output.start_frames = 0; //CONFIG_ //FRAME_BLOCK * 2;
output.write_cb = &_write_frames;
output.rate_delay = rate_delay;
// ensure output rate is specified to avoid test open
if (!rates[0]) {
rates[0] = 44100;
}
device = "DAC";
output_init_common(level, device, output_buf_size, rates, idle);
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX
.sample_rate = output.current_sample_rate,
.bits_per_sample = BYTES_PER_FRAME * 8,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = I2S_COMM_FORMAT_I2S
| I2S_COMM_FORMAT_I2S_MSB,
.dma_buf_count = 6, //todo: tune this parameter. Expressed in numbrer of buffers
.dma_buf_len = I2S_BUF_LEN, // todo: tune this parameter. Expressed in number of samples. Byte size depends on bit depth
.use_apll = false,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 //Interrupt level 1
};
i2s_pin_config_t pin_config = { .bck_io_num = I2S_BCK_IO, .ws_io_num =
I2S_WS_IO, .data_out_num = I2S_DO_IO, .data_in_num = I2S_DI_IO //Not used
};
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM, &pin_config);
i2s_set_clk(I2S_NUM, output.current_sample_rate, i2s_config.bits_per_sample, 2);
#if LINUX || OSX || FREEBSD || POSIX
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr,
PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
#endif
pthread_create(&thread, &attr, output_thread, NULL);
pthread_attr_destroy(&attr);
#endif
#if WIN
thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL);
#endif
}
void output_close_dac(void) {
LOG_INFO("close output");
LOCK;
running = false;
UNLOCK;
output_close_common();
}
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) {
u8_t *obuf;
size_t i2s_bytes_write = 0;
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);
}
obuf = outputbuf->readp;
} else {
obuf = silencebuf;
}
//_scale_and_pack_frames(buf + buffill * bytes_per_frame, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format);
// buffill += out_frames;
i2s_write(I2S_NUM, obuf, out_frames *BYTES_PER_FRAME, &i2s_bytes_write, 100);
return (int)i2s_bytes_write * BYTES_PER_FRAME;
}

119
main/output_embedded.c Normal file
View File

@@ -0,0 +1,119 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, philippe_44@outlook.com
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
extern struct outputstate output;
extern struct buffer *outputbuf;
#define FRAME_BLOCK MAX_SILENCE_FRAMES
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
extern void set_volume_i2s(unsigned left, unsigned right);
extern void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
extern void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
static log_level loglevel;
static void (*volume_cb)(unsigned left, unsigned right);
static void (*close_cb)(void);
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
LOG_INFO("init device: %s", device);
memset(&output, 0, sizeof(output));
output_init_common(level, device, output_buf_size, rates, idle);
output.start_frames = FRAME_BLOCK;
output.rate_delay = rate_delay;
if (strstr(device, "BT ")) {
LOG_INFO("init Bluetooth");
output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle);
} else {
LOG_INFO("init I2S");
//volume_cb = set_volume_i2s;
//close_cb = output_close_i2s;
//output_init_i2s(level, device, output_buf_size, params, rates, rate_delay, idle);
}
LOG_INFO("init completed.");
}
void output_close_embedded(void) {
LOG_INFO("close output");
output_close_common();
if (close_cb) (*close_cb)();
}
void set_volume(unsigned left, unsigned right) {
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
if (!volume_cb) {
LOCK;
output.gainL = left;
output.gainR = right;
UNLOCK;
} else (*volume_cb)(left, right);
}
bool test_open(const char *device, unsigned rates[], bool userdef_rates) {
memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
if (!strcmp(device, "BT")) {
rates[0] = 44100;
} else {
unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 };
memcpy(rates, _rates, sizeof(_rates));
}
return true;
}
char* output_state_str(void){
output_state state;
LOCK;
state = output.state;
UNLOCK;
switch (state) {
case OUTPUT_OFF: return STR(OUTPUT_OFF);
case OUTPUT_STOPPED: return STR(OUTPUT_STOPPED);
case OUTPUT_BUFFER: return STR(OUTPUT_BUFFER);
case OUTPUT_RUNNING: return STR(OUTPUT_RUNNING);
case OUTPUT_PAUSE_FRAMES: return STR(OUTPUT_PAUSE_FRAMES);
case OUTPUT_SKIP_FRAMES: return STR(OUTPUT_SKIP_FRAMES);
case OUTPUT_START_AT: return STR(OUTPUT_START_AT);
default: return "OUTPUT_UNKNOWN_STATE";
}
}
bool output_stopped(void) {
output_state state;
LOCK;
state = output.state;
UNLOCK;
return state <= OUTPUT_STOPPED;
}

View File

@@ -436,13 +436,8 @@ static void process_audg(u8_t *pkt, int len) {
audg->gainR = unpackN(&audg->gainR);
LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
#if CONFIG_BTAUDIO
set_volume_bt(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
#elif CONFIG_DACAUDIO
set_volume_dac(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
#else
set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
#endif
}
static void process_setd(u8_t *pkt, int len) {

View File

@@ -34,6 +34,7 @@
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION
#endif
#if !defined(MODEL_NAME)
#define MODEL_NAME SqueezeLite
#endif
@@ -42,16 +43,11 @@
#define STR(macro) QUOTE(macro)
#define MODEL_NAME_STRING STR(MODEL_NAME)
#if defined(EMBEDDED)
#define POSIX 1
#include "embedded.h"
#endif
#ifndef PTHREAD_SET_NAME
#define PTHREAD_SET_NAME(n)
#endif
// build detection
#if defined(linux)
// build detection
#if defined (EMBEDDED)
#undef EMBEDDED
#define EMBEDDED 1
#elif defined(linux)
#define LINUX 1
#define OSX 0
#define WIN 0
@@ -78,27 +74,19 @@
#define PA18API 1
#define OSX 0
#define WIN 0
#elif defined (POSIX)
#undef POSIX
#define POSIX 1
#else
#error unknown target
#endif
#if defined(CONFIG_DACAUDIO)
#undef CONFIG_DACAUDIO
#define CONFIG_DACAUDIO 1
#elif defined(CONFIG_BTAUDIO)
#undef CONFIG_BTAUDIO
#define CONFIG_BTAUDIO 1
#elif LINUX && !defined(PORTAUDIO)
#if !EMBEDDED
#if LINUX && !defined(PORTAUDIO)
#define ALSA 1
#define PORTAUDIO 0
#else
#define ALSA 0
#define PORTAUDIO 1
#endif
#endif
#if !defined(LOOPBACK)
#if SUN
@@ -278,18 +266,18 @@
#include <limits.h>
#include <sys/types.h>
#if LINUX || OSX || FREEBSD || POSIX
#if EMBEDDED
#include "embedded.h"
#endif
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <unistd.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#if POSIX
#include <sys/poll.h>
#else
#include <poll.h>
#endif
#if !LINKALL
#include <dlfcn.h>
#endif
@@ -299,14 +287,10 @@
#include <sys/types.h>
#endif /* SUN */
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
#define STREAM_THREAD_STACK_SIZE 8 * 1024
#define DECODE_THREAD_STACK_SIZE 20 * 1024
#define OUTPUT_THREAD_STACK_SIZE 8 * 1024
#define IR_THREAD_STACK_SIZE 8 * 1024
#define STREAM_THREAD_STACK_SIZE 64 * 1024
#define DECODE_THREAD_STACK_SIZE 128 * 1024
#define OUTPUT_THREAD_STACK_SIZE 64 * 1024
#define IR_THREAD_STACK_SIZE 64 * 1024
#if !OSX
#define thread_t pthread_t;
#endif
@@ -314,13 +298,12 @@
#define last_error() errno
#define ERROR_WOULDBLOCK EWOULDBLOCK
#if !EMBEDDED
#ifdef SUN
typedef uint8_t u8_t;
typedef uint16_t u16_t;
typedef uint32_t u32_t;
typedef uint64_t u64_t;
#elif POSIX
typedef unsigned long long u64_t;
#else
typedef u_int8_t u8_t;
typedef u_int16_t u16_t;
@@ -330,29 +313,20 @@ typedef u_int64_t u64_t;
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
#endif
#define mutex_type pthread_mutex_t
#define mutex_create(m) pthread_mutex_init(&m, NULL)
#if POSIX
#define mutex_create_p(m) mutex_create(m)
#else
#if HAS_MUTEX_CREATE_P
#define mutex_create_p(m) pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(&m, &attr); pthread_mutexattr_destroy(&attr)
#else
#define mutex_create_p(m) mutex_create(m)
#endif
#define mutex_lock(m) pthread_mutex_lock(&m)
#define mutex_unlock(m) pthread_mutex_unlock(&m)
#define mutex_destroy(m) pthread_mutex_destroy(&m)
#define mutex_broadcast_cond(m) pthread_cond_broadcast(&m)
#define mutex_cond_wait(c,m) pthread_cond_wait(&c, &m)
#define mutex_cond_init(c) pthread_cond_init(&c, NULL)
#define thread_cond_type pthread_cond_t
#define thread_type pthread_t
#endif
#ifdef EMBEDDED
#define local_exit(r) {static int ret=r; pthread_exit(&ret);}
#else
#define local_exit(r) exit(r)
#endif
#if WIN
@@ -491,24 +465,7 @@ void logprint(const char *fmt, ...);
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
static inline void DEBUG_LOG_TIMED(uint32_t delayms, char * strFmt, ...)
{
static log_level loglevel;
va_list args;
va_start(args, strFmt);
static uint32_t nextDebugLog=0;
if(esp_timer_get_time()>nextDebugLog)
{
if (loglevel >= lDEBUG)
{
logprint("%s %s:%d ", logtime(), __FUNCTION__, __LINE__);
logprint(strFmt , args);
logprint("\n");
}
nextDebugLog=esp_timer_get_time()+delayms*1000;
}
}
// utils.c (non logging)
typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type;
#if WIN && USE_SSL
@@ -670,8 +627,6 @@ typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_
#else
typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, S24_BE, S24_3BE, S16_BE, S8_BE } output_format;
#endif
extern uint8_t get_bytes_per_frame(output_format fmt);
typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state;
typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir;
@@ -763,26 +718,17 @@ void output_close_pa(void);
void _pa_open(void);
#endif
// output_dac.c
void set_volume_dac(unsigned left, unsigned right);
// output_embedded.c
#if EMBEDDED
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_dac(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_dac(void);
void hal_dac_init(const char * options);
//output_bt.c
void set_volume_bt(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_bt(void);
extern void hal_bluetooth_init(const char * options);
void output_bt_check_buffer();
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_embedded(void);
#else
// output_stdout.c
void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay);
void output_close_stdout(void);
#endif
// output_pack.c
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format);

View File

@@ -373,7 +373,7 @@ void stream_init(log_level level, unsigned stream_buf_size) {
buf_init(streambuf, stream_buf_size);
if (streambuf->buf == NULL) {
LOG_ERROR("unable to malloc buffer");
local_exit(0);
exit(0);
}
#if USE_SSL
@@ -405,8 +405,8 @@ void stream_init(log_level level, unsigned stream_buf_size) {
#if LINUX || FREEBSD
touch_memory(streambuf->buf, streambuf->size);
#endif
PTHREAD_SET_NAME("stream");
#if LINUX || OSX || FREEBSD || POSIX
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
@@ -414,6 +414,9 @@ PTHREAD_SET_NAME("stream");
#endif
pthread_create(&thread, &attr, stream_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "stream");
#endif
#endif
#if WIN
thread = CreateThread(NULL, STREAM_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&stream_thread, NULL, 0, NULL);

View File

@@ -21,7 +21,7 @@
#include "squeezelite.h"
#if LINUX || OSX || FREEBSD || POSIX
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
@@ -103,7 +103,7 @@ u32_t gettime_ms(void) {
#if WIN
return GetTickCount();
#else
#if LINUX || FREEBSD || POSIX
#if LINUX || FREEBSD || EMBEDDED
struct timespec ts;
#ifdef CLOCK_MONOTONIC
if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
@@ -561,77 +561,3 @@ char *strcasestr(const char *haystack, const char *needle) {
return NULL;
}
#endif
uint8_t get_bytes_per_frame(output_format fmt)
{
uint8_t bpf=0;
switch (fmt) {
case S32_LE:
bpf=4*2;
break;
case S24_LE:
bpf=3*2;
break;
case S24_3LE:
bpf=3*2;
break;
case S16_LE:
bpf=2*2;
break;
case S24_BE:
bpf=3*2;
break;
case S24_3BE:
bpf=3*2;
break;
case S16_BE:
bpf=2*2;
break;
case S8_BE:
bpf=2*2;
break;
#if DSD
case U8:
bpf=1*2;
break;
case U16_LE:
bpf=2*2;
break;
case U16_BE:
bpf=2*2;
break;
case U32_LE:
bpf=4*2;
break;
case U32_BE:
bpf=4*2;
break;
#endif
default:
break;
}
assert(bpf>0);
return bpf;
}
char * get_output_state_desc(output_state state){
switch (state) {
case OUTPUT_OFF:
return STR(OUTPUT_OFF);
case OUTPUT_STOPPED:
return STR(OUTPUT_STOPPED);
case OUTPUT_BUFFER:
return STR(OUTPUT_BUFFER);
case OUTPUT_RUNNING:
return STR(OUTPUT_RUNNING);
case OUTPUT_PAUSE_FRAMES:
return STR(OUTPUT_PAUSE_FRAMES);
case OUTPUT_SKIP_FRAMES:
return STR(OUTPUT_SKIP_FRAMES);
case OUTPUT_START_AT:
return STR(OUTPUT_START_AT);
default:
return "OUTPUT_UNKNOWN_STATE";
}
return "";
}