From e8debd2e454f9a7399104b5c77e44d5dce6aa203 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 10 Jan 2015 08:58:31 +0100 Subject: output/recorder: dynamic file name --- src/output/plugins/RecorderOutputPlugin.cxx | 178 ++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 9 deletions(-) (limited to 'src/output') diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx index 973e60b47..b0488080a 100644 --- a/src/output/plugins/RecorderOutputPlugin.cxx +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -21,17 +21,23 @@ #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 +#include + +static constexpr Domain recorder_domain("recorder"); class RecorderOutput { friend struct AudioOutputWrapper; @@ -48,6 +54,18 @@ class RecorderOutput { */ AllocatedPath path; + /** + * A string that will be used with FormatTag() to build the + * destination path. + */ + std::string format_path; + + /** + * The #AudioFormat that is currently active. This is used + * for switching to another file. + */ + AudioFormat effective_audio_format; + /** * The destination file. */ @@ -84,10 +102,18 @@ class RecorderOutput { 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 @@ -105,9 +131,20 @@ RecorderOutput::Configure(const config_param ¶m, Error &error) } path = param.GetBlockPath("path", error); - if (path.IsNull()) { - if (!error.IsDefined()) - error.Set(config_domain, "'path' not configured"); + if (error.IsDefined()) + return false; + + const char *fmt = param.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; } @@ -152,9 +189,19 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error) { /* create the output file */ - file = FileOutputStream::Create(path, error); - if (file == nullptr) - return false; + if (!HasDynamicPath()) { + assert(!path.IsNull()); + + file = FileOutputStream::Create(path, error); + if (file == nullptr) + return false; + } 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 */ @@ -163,10 +210,19 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error) return false; } - if (!EncoderToFile(error)) { + 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(); - delete file; - return false; } return true; @@ -175,6 +231,8 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error) inline bool RecorderOutput::Commit(Error &error) { + assert(!path.IsNull()); + /* flush the encoder and write the rest to the file */ bool success = encoder_end(encoder, error) && @@ -195,14 +253,108 @@ RecorderOutput::Commit(Error &error) inline void RecorderOutput::Close() { + if (file == nullptr) { + /* not currently encoding to a file; nothing needs to + be done now */ + assert(HasDynamicPath()); + assert(path.IsNull()); + return; + } + + Error error; + if (!Commit(error)) + LogError(error); + + if (HasDynamicPath()) { + assert(!path.IsNull()); + path.SetNull(); + } +} + +void +RecorderOutput::FinishFormat() +{ + assert(HasDynamicPath()); + + if (file == nullptr) + return; + Error error; if (!Commit(error)) LogError(error); + + file = nullptr; + path.SetNull(); +} + +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; + } + + /* 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; } inline void RecorderOutput::SendTag(const Tag &tag) { + 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; + } + + Error error; + AllocatedPath new_path = ParsePath(p, error); + free(p); + if (new_path.IsNull()) { + LogError(error); + FinishFormat(); + return; + } + + if (new_path != path) { + FinishFormat(); + + if (!ReopenFormat(std::move(new_path), error)) { + LogError(error); + return; + } + } + } + Error error; if (!encoder_pre_tag(encoder, error) || !EncoderToFile(error) || @@ -213,6 +365,14 @@ RecorderOutput::SendTag(const Tag &tag) inline size_t RecorderOutput::Play(const void *chunk, size_t size, Error &error) { + if (file == nullptr) { + /* not currently encoding to a file; discard incoming + data */ + assert(HasDynamicPath()); + assert(path.IsNull()); + return size; + } + return encoder_write(encoder, chunk, size, error) && EncoderToFile(error) ? size : 0; -- cgit v1.2.3