SndioOutputPlugin.cxx 4.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
 * Copyright (C) 2016 The Music Player Daemon Project
 * http://www.musicpd.org
 *
 * 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 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <sndio.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "SndioOutputPlugin.hxx"
#include "config/ConfigError.hxx"
#include "../OutputAPI.hxx"
#include "../Wrapper.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"

33 34 35 36 37
#ifndef SIO_DEVANY
/* this macro is missing in libroar-dev 1.0~beta2-3 (Debian Wheezy) */
#define SIO_DEVANY "default"
#endif

38 39
static constexpr unsigned MPD_SNDIO_BUFFER_TIME_MS = 250;

40 41
static constexpr Domain sndio_output_domain("sndio_output");

42 43 44
class SndioOutput {
	friend struct AudioOutputWrapper<SndioOutput>;
	AudioOutput base;
45
	const char *device;
46
	unsigned buffer_time; /* in ms */
47 48 49 50
	struct sio_hdl *sio_hdl;

public:
	SndioOutput()
51
		:base(sndio_output_plugin) {}
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	~SndioOutput() {}

	bool Configure(const ConfigBlock &block, Error &error);

	static SndioOutput *Create(const ConfigBlock &block, Error &error);

	bool Open(AudioFormat &audio_format, Error &error);
	void Close();
	unsigned Delay() const;
	size_t Play(const void *chunk, size_t size, Error &error);
	void Cancel();
};

bool
SndioOutput::Configure(const ConfigBlock &block, Error &error)
{
	if (!base.Configure(block, error))
		return false;
70
	device = block.GetBlockValue("device", SIO_DEVANY);
71
	buffer_time = block.GetBlockValue("buffer_time",
72
	                                  MPD_SNDIO_BUFFER_TIME_MS);
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	return true;
}

SndioOutput *
SndioOutput::Create(const ConfigBlock &block, Error &error)
{
	SndioOutput *ao = new SndioOutput();

	if (!ao->Configure(block, error)) {
		delete ao;
		return nullptr;
	}

	return ao;
}

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
static bool
sndio_test_default_device()
{
	struct sio_hdl *sio_hdl;

	sio_hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
	if (!sio_hdl) {
		FormatError(sndio_output_domain,
		            "Error opening default sndio device");
		return false;
	}

	sio_close(sio_hdl);
	return true;
}

105
bool
106
SndioOutput::Open(AudioFormat &audio_format, Error &error)
107 108 109 110
{
	struct sio_par par;
	unsigned bits, rate, chans;

111
	sio_hdl = sio_open(device, SIO_PLAY, 0);
112 113 114 115 116 117 118 119 120 121
	if (!sio_hdl) {
		error.Format(sndio_output_domain, -1,
		             "Failed to open default sndio device");
		return false;
	}

	switch (audio_format.format) {
	case SampleFormat::S16:
		bits = 16;
		break;
122 123 124
	case SampleFormat::S24_P32:
		bits = 24;
		break;
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
	case SampleFormat::S32:
		bits = 32;
		break;
	default:
		audio_format.format = SampleFormat::S16;
		bits = 16;
		break;
	}

	rate = audio_format.sample_rate;
	chans = audio_format.channels;

	sio_initpar(&par);
	par.bits = bits;
	par.rate = rate;
	par.pchan = chans;
	par.sig = 1;
	par.le = SIO_LE_NATIVE;
143
	par.appbufsz = rate * buffer_time / 1000;
144 145 146 147 148

	if (!sio_setpar(sio_hdl, &par) ||
	    !sio_getpar(sio_hdl, &par)) {
		error.Format(sndio_output_domain, -1,
		             "Failed to set/get audio params");
149 150
		sio_close(sio_hdl);
		return false;
151 152 153 154 155 156 157 158 159 160
	}

	if (par.bits != bits ||
	    par.rate < rate * 995 / 1000 ||
	    par.rate > rate * 1005 / 1000 ||
	    par.pchan != chans ||
	    par.sig != 1 ||
	    par.le != SIO_LE_NATIVE) {
		error.Format(sndio_output_domain, -1,
		             "Requested audio params cannot be satisfied");
161 162
		sio_close(sio_hdl);
		return false;
163 164 165 166 167
	}

	if (!sio_start(sio_hdl)) {
		error.Format(sndio_output_domain, -1,
		             "Failed to start audio device");
168 169
		sio_close(sio_hdl);
		return false;
170 171 172 173 174 175 176 177
	}

	return true;
}

void
SndioOutput::Close()
{
178
	sio_close(sio_hdl);
179 180 181 182 183
}

size_t
SndioOutput::Play(const void *chunk, size_t size, Error &error)
{
184
	size_t n;
185

186 187 188 189
	n = sio_write(sio_hdl, chunk, size);
	if (n == 0 && sio_eof(sio_hdl) != 0)
		error.Set(sndio_output_domain, -1, "sndio write failed");
	return n;
190 191 192 193 194 195
}

typedef AudioOutputWrapper<SndioOutput> Wrapper;

const struct AudioOutputPlugin sndio_output_plugin = {
	"sndio",
196
	sndio_test_default_device,
197 198 199 200 201 202
	&Wrapper::Init,
	&Wrapper::Finish,
	nullptr,
	nullptr,
	&Wrapper::Open,
	&Wrapper::Close,
203
	nullptr,
204 205 206
	nullptr,
	&Wrapper::Play,
	nullptr,
207
	nullptr,
208 209 210
	nullptr,
	nullptr,
};