aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/plugins/RecorderOutputPlugin.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/plugins/RecorderOutputPlugin.cxx')
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx351
1 files changed, 246 insertions, 105 deletions
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
index 87e23f55a..115ee534d 100644
--- a/src/output/plugins/RecorderOutputPlugin.cxx
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * Copyright (C) 2003-2015 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,21 +20,28 @@
#include "config.h"
#include "RecorderOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
+#include "tag/Format.hxx"
+#include "encoder/ToOutputStream.hxx"
+#include "encoder/EncoderInterface.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "config/ConfigError.hxx"
+#include "config/ConfigPath.hxx"
+#include "Log.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/io/FileOutputStream.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "system/fd_util.h"
-#include "open.h"
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
+#include <stdlib.h>
+
+static constexpr Domain recorder_domain("recorder");
+
+class RecorderOutput {
+ friend struct AudioOutputWrapper<RecorderOutput>;
-struct RecorderOutput {
AudioOutput base;
/**
@@ -45,44 +52,77 @@ struct RecorderOutput {
/**
* The destination file name.
*/
- const char *path;
+ AllocatedPath path;
+
+ /**
+ * A string that will be used with FormatTag() to build the
+ * destination path.
+ */
+ std::string format_path;
/**
- * The destination file descriptor.
+ * The #AudioFormat that is currently active. This is used
+ * for switching to another file.
*/
- int fd;
+ AudioFormat effective_audio_format;
/**
- * The buffer for encoder_read().
+ * The destination file.
*/
- char buffer[32768];
+ FileOutputStream *file;
RecorderOutput()
- :base(recorder_output_plugin) {}
+ :base(recorder_output_plugin),
+ encoder(nullptr),
+ path(AllocatedPath::Null()) {}
- bool Initialize(const config_param &param, Error &error_r) {
- return base.Configure(param, error_r);
+ ~RecorderOutput() {
+ if (encoder != nullptr)
+ encoder->Dispose();
}
- bool Configure(const config_param &param, Error &error);
+ bool Initialize(const ConfigBlock &block, Error &error_r) {
+ return base.Configure(block, error_r);
+ }
+
+ static RecorderOutput *Create(const ConfigBlock &block, Error &error);
+
+ bool Configure(const ConfigBlock &block, Error &error);
- bool WriteToFile(const void *data, size_t length, Error &error);
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
/**
* Writes pending data from the encoder to the output file.
*/
bool EncoderToFile(Error &error);
-};
-static constexpr Domain recorder_output_domain("recorder_output");
+ void SendTag(const Tag &tag);
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+private:
+ gcc_pure
+ bool HasDynamicPath() const {
+ return !format_path.empty();
+ }
+
+ /**
+ * Finish the encoder and commit the file.
+ */
+ bool Commit(Error &error);
+
+ void FinishFormat();
+ bool ReopenFormat(AllocatedPath &&new_path, Error &error);
+};
inline bool
-RecorderOutput::Configure(const config_param &param, Error &error)
+RecorderOutput::Configure(const ConfigBlock &block, Error &error)
{
/* read configuration */
const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
+ block.GetBlockValue("encoder", "vorbis");
const auto encoder_plugin = encoder_plugin_get(encoder_name);
if (encoder_plugin == nullptr) {
error.Format(config_domain,
@@ -90,167 +130,268 @@ RecorderOutput::Configure(const config_param &param, Error &error)
return false;
}
- path = param.GetBlockValue("path");
- if (path == nullptr) {
+ path = block.GetBlockPath("path", error);
+ if (error.IsDefined())
+ return false;
+
+ const char *fmt = block.GetBlockValue("format_path", nullptr);
+ if (fmt != nullptr)
+ format_path = fmt;
+
+ if (path.IsNull() && fmt == nullptr) {
error.Set(config_domain, "'path' not configured");
return false;
}
+ if (!path.IsNull() && fmt != nullptr) {
+ error.Set(config_domain, "Cannot have both 'path' and 'format_path'");
+ return false;
+ }
+
/* initialize encoder */
- encoder = encoder_init(*encoder_plugin, param, error);
+ encoder = encoder_init(*encoder_plugin, block, error);
if (encoder == nullptr)
return false;
return true;
}
-static AudioOutput *
-recorder_output_init(const config_param &param, Error &error)
+RecorderOutput *
+RecorderOutput::Create(const ConfigBlock &block, Error &error)
{
RecorderOutput *recorder = new RecorderOutput();
- if (!recorder->Initialize(param, error)) {
+ if (!recorder->Initialize(block, error)) {
delete recorder;
return nullptr;
}
- if (!recorder->Configure(param, error)) {
+ if (!recorder->Configure(block, error)) {
delete recorder;
return nullptr;
}
- return &recorder->base;
+ return recorder;
}
-static void
-recorder_output_finish(AudioOutput *ao)
+inline bool
+RecorderOutput::EncoderToFile(Error &error)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ assert(file != nullptr);
+ assert(file->IsDefined());
- encoder_finish(recorder->encoder);
- delete recorder;
+ return EncoderToOutputStream(*file, *encoder, error);
}
inline bool
-RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
+RecorderOutput::Open(AudioFormat &audio_format, Error &error)
{
- assert(length > 0);
-
- const uint8_t *data = (const uint8_t *)_data, *end = data + length;
-
- while (true) {
- ssize_t nbytes = write(fd, data, end - data);
- if (nbytes > 0) {
- data += nbytes;
- if (data == end)
- return true;
- } else if (nbytes == 0) {
- /* shouldn't happen for files */
- error.Set(recorder_output_domain,
- "write() returned 0");
+ /* create the output file */
+
+ if (!HasDynamicPath()) {
+ assert(!path.IsNull());
+
+ file = FileOutputStream::Create(path, error);
+ if (file == nullptr)
return false;
- } else if (errno != EINTR) {
- error.FormatErrno("Failed to write to '%s'", path);
+ } else {
+ /* don't open the file just yet; wait until we have
+ a tag that we can use to build the path */
+ assert(path.IsNull());
+
+ file = nullptr;
+ }
+
+ /* open the encoder */
+
+ if (!encoder->Open(audio_format, error)) {
+ delete file;
+ return false;
+ }
+
+ if (!HasDynamicPath()) {
+ if (!EncoderToFile(error)) {
+ encoder->Close();
+ delete file;
return false;
}
+ } else {
+ /* remember the AudioFormat for ReopenFormat() */
+ effective_audio_format = audio_format;
+
+ /* close the encoder for now; it will be opened as
+ soon as we have received a tag */
+ encoder->Close();
}
+
+ return true;
}
inline bool
-RecorderOutput::EncoderToFile(Error &error)
+RecorderOutput::Commit(Error &error)
{
- assert(fd >= 0);
+ assert(!path.IsNull());
- while (true) {
- /* read from the encoder */
+ /* flush the encoder and write the rest to the file */
- size_t size = encoder_read(encoder, buffer, sizeof(buffer));
- if (size == 0)
- return true;
+ bool success = encoder_end(encoder, error) &&
+ EncoderToFile(error);
- /* write everything into the file */
+ /* now really close everything */
- if (!WriteToFile(buffer, size, error))
- return false;
- }
+ encoder->Close();
+
+ if (success && !file->Commit(error))
+ success = false;
+
+ delete file;
+
+ return success;
}
-static bool
-recorder_output_open(AudioOutput *ao,
- AudioFormat &audio_format,
- Error &error)
+inline void
+RecorderOutput::Close()
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ if (file == nullptr) {
+ /* not currently encoding to a file; nothing needs to
+ be done now */
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ return;
+ }
- /* create the output file */
+ Error error;
+ if (!Commit(error))
+ LogError(error);
- recorder->fd = open_cloexec(recorder->path,
- O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
- 0666);
- if (recorder->fd < 0) {
- error.FormatErrno("Failed to create '%s'", recorder->path);
- return false;
+ if (HasDynamicPath()) {
+ assert(!path.IsNull());
+ path.SetNull();
}
+}
- /* open the encoder */
+void
+RecorderOutput::FinishFormat()
+{
+ assert(HasDynamicPath());
+
+ if (file == nullptr)
+ return;
+
+ Error error;
+ if (!Commit(error))
+ LogError(error);
+
+ file = nullptr;
+ path.SetNull();
+}
- if (!encoder_open(recorder->encoder, audio_format, error)) {
- close(recorder->fd);
- unlink(recorder->path);
+inline bool
+RecorderOutput::ReopenFormat(AllocatedPath &&new_path, Error &error)
+{
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ assert(file == nullptr);
+
+ FileOutputStream *new_file =
+ FileOutputStream::Create(new_path, error);
+ if (new_file == nullptr)
+ return false;
+
+ AudioFormat new_audio_format = effective_audio_format;
+ if (!encoder->Open(new_audio_format, error)) {
+ delete new_file;
return false;
}
- if (!recorder->EncoderToFile(error)) {
- encoder_close(recorder->encoder);
- close(recorder->fd);
- unlink(recorder->path);
+ /* reopening the encoder must always result in the same
+ AudioFormat as before */
+ assert(new_audio_format == effective_audio_format);
+
+ if (!EncoderToOutputStream(*new_file, *encoder, error)) {
+ encoder->Close();
+ delete new_file;
return false;
}
+ path = std::move(new_path);
+ file = new_file;
+
+ FormatDebug(recorder_domain, "Recording to \"%s\"", path.c_str());
+
return true;
}
-static void
-recorder_output_close(AudioOutput *ao)
+inline void
+RecorderOutput::SendTag(const Tag &tag)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- /* flush the encoder and write the rest to the file */
+ if (HasDynamicPath()) {
+ char *p = FormatTag(tag, format_path.c_str());
+ if (p == nullptr || *p == 0) {
+ /* no path could be composed with this tag:
+ don't write a file */
+ free(p);
+ FinishFormat();
+ return;
+ }
- if (encoder_end(recorder->encoder, IgnoreError()))
- recorder->EncoderToFile(IgnoreError());
+ Error error;
+ AllocatedPath new_path = ParsePath(p, error);
+ free(p);
+ if (new_path.IsNull()) {
+ LogError(error);
+ FinishFormat();
+ return;
+ }
- /* now really close everything */
+ if (new_path != path) {
+ FinishFormat();
- encoder_close(recorder->encoder);
+ if (!ReopenFormat(std::move(new_path), error)) {
+ LogError(error);
+ return;
+ }
+ }
+ }
- close(recorder->fd);
+ Error error;
+ if (!encoder_pre_tag(encoder, error) ||
+ !EncoderToFile(error) ||
+ !encoder_tag(encoder, tag, error))
+ LogError(error);
}
-static size_t
-recorder_output_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+RecorderOutput::Play(const void *chunk, size_t size, Error &error)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ if (file == nullptr) {
+ /* not currently encoding to a file; discard incoming
+ data */
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ return size;
+ }
- return encoder_write(recorder->encoder, chunk, size, error) &&
- recorder->EncoderToFile(error)
+ return encoder_write(encoder, chunk, size, error) &&
+ EncoderToFile(error)
? size : 0;
}
+typedef AudioOutputWrapper<RecorderOutput> Wrapper;
+
const struct AudioOutputPlugin recorder_output_plugin = {
"recorder",
nullptr,
- recorder_output_init,
- recorder_output_finish,
- nullptr,
+ &Wrapper::Init,
+ &Wrapper::Finish,
nullptr,
- recorder_output_open,
- recorder_output_close,
nullptr,
+ &Wrapper::Open,
+ &Wrapper::Close,
nullptr,
- recorder_output_play,
+ &Wrapper::SendTag,
+ &Wrapper::Play,
nullptr,
nullptr,
nullptr,