recorder_output_plugin.c 5.48 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2011 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * 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.
 */

20
#include "config.h"
21
#include "recorder_output_plugin.h"
22 23 24
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
25
#include "fd_util.h"
26
#include "open.h"
27 28 29 30 31 32 33 34 35 36 37

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "recorder"

struct recorder_output {
38 39
	struct audio_output base;

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	/**
	 * The configured encoder plugin.
	 */
	struct encoder *encoder;

	/**
	 * The destination file name.
	 */
	const char *path;

	/**
	 * The destination file descriptor.
	 */
	int fd;

	/**
	 * The buffer for encoder_read().
	 */
	char buffer[32768];
};

/**
 * The quark used for GError.domain.
 */
static inline GQuark
recorder_output_quark(void)
{
	return g_quark_from_static_string("recorder_output");
}

70 71
static struct audio_output *
recorder_output_init(const struct config_param *param, GError **error_r)
72 73
{
	struct recorder_output *recorder = g_new(struct recorder_output, 1);
74 75 76 77 78 79
	if (!ao_base_init(&recorder->base, &recorder_output_plugin, param,
			  error_r)) {
		g_free(recorder);
		return NULL;
	}

80 81 82 83 84 85 86 87 88 89
	const char *encoder_name;
	const struct encoder_plugin *encoder_plugin;

	/* read configuration */

	encoder_name = config_get_block_string(param, "encoder", "vorbis");
	encoder_plugin = encoder_plugin_get(encoder_name);
	if (encoder_plugin == NULL) {
		g_set_error(error_r, recorder_output_quark(), 0,
			    "No such encoder: %s", encoder_name);
90
		goto failure;
91 92 93 94 95 96
	}

	recorder->path = config_get_block_string(param, "path", NULL);
	if (recorder->path == NULL) {
		g_set_error(error_r, recorder_output_quark(), 0,
			    "'path' not configured");
97
		goto failure;
98 99 100 101 102 103
	}

	/* initialize encoder */

	recorder->encoder = encoder_init(encoder_plugin, param, error_r);
	if (recorder->encoder == NULL)
104
		goto failure;
105

106
	return &recorder->base;
107 108

failure:
109
	ao_base_finish(&recorder->base);
110 111
	g_free(recorder);
	return NULL;
112 113 114
}

static void
115
recorder_output_finish(struct audio_output *ao)
116
{
117
	struct recorder_output *recorder = (struct recorder_output *)ao;
118 119

	encoder_finish(recorder->encoder);
120
	ao_base_finish(&recorder->base);
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
	g_free(recorder);
}

/**
 * Writes pending data from the encoder to the output file.
 */
static bool
recorder_output_encoder_to_file(struct recorder_output *recorder,
			      GError **error_r)
{
	size_t size = 0, position, nbytes;

	assert(recorder->fd >= 0);

	/* read from the encoder */

	size = encoder_read(recorder->encoder, recorder->buffer,
			    sizeof(recorder->buffer));
	if (size == 0)
		return true;

	/* write everything into the file */

	position = 0;
	while (true) {
		nbytes = write(recorder->fd, recorder->buffer + position,
			       size - position);
		if (nbytes > 0) {
			position += (size_t)nbytes;
			if (position >= size)
				return true;
		} else if (nbytes == 0) {
			/* shouldn't happen for files */
			g_set_error(error_r, recorder_output_quark(), 0,
				    "write() returned 0");
			return false;
		} else if (errno != EINTR) {
			g_set_error(error_r, recorder_output_quark(), 0,
				    "Failed to write to '%s': %s",
				    recorder->path, g_strerror(errno));
			return false;
		}
	}
}

static bool
167 168
recorder_output_open(struct audio_output *ao,
		     struct audio_format *audio_format,
169 170
		     GError **error_r)
{
171
	struct recorder_output *recorder = (struct recorder_output *)ao;
172 173 174 175
	bool success;

	/* create the output file */

176 177
	recorder->fd = open_cloexec(recorder->path,
				    O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
178
				    0666);
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
	if (recorder->fd < 0) {
		g_set_error(error_r, recorder_output_quark(), 0,
			    "Failed to create '%s': %s",
			    recorder->path, g_strerror(errno));
		return false;
	}

	/* open the encoder */

	success = encoder_open(recorder->encoder, audio_format, error_r);
	if (!success) {
		close(recorder->fd);
		unlink(recorder->path);
		return false;
	}

	return true;
}

static void
199
recorder_output_close(struct audio_output *ao)
200
{
201
	struct recorder_output *recorder = (struct recorder_output *)ao;
202 203 204

	/* flush the encoder and write the rest to the file */

205
	if (encoder_end(recorder->encoder, NULL))
206 207 208 209 210 211 212 213 214 215
		recorder_output_encoder_to_file(recorder, NULL);

	/* now really close everything */

	encoder_close(recorder->encoder);

	close(recorder->fd);
}

static size_t
216
recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
217 218
		     GError **error_r)
{
219
	struct recorder_output *recorder = (struct recorder_output *)ao;
220 221 222 223 224 225 226 227 228 229 230 231 232 233

	return encoder_write(recorder->encoder, chunk, size, error_r) &&
		recorder_output_encoder_to_file(recorder, error_r)
		? size : 0;
}

const struct audio_output_plugin recorder_output_plugin = {
	.name = "recorder",
	.init = recorder_output_init,
	.finish = recorder_output_finish,
	.open = recorder_output_open,
	.close = recorder_output_close,
	.play = recorder_output_play,
};