diff options
Diffstat (limited to 'src/output/plugins/RecorderOutputPlugin.cxx')
-rw-r--r-- | src/output/plugins/RecorderOutputPlugin.cxx | 351 |
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 ¶m, Error &error_r) { - return base.Configure(param, error_r); + ~RecorderOutput() { + if (encoder != nullptr) + encoder->Dispose(); } - bool Configure(const config_param ¶m, 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 ¶m, 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 ¶m, 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 ¶m, 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, |