diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp index c182b673e2e72277f8052db4d21c04e44d0816ad..ad6cd6e0e000bdd06e5c7795aee52a7bc371a9ed 100644 --- a/src/media/media_recorder.cpp +++ b/src/media/media_recorder.cpp @@ -39,6 +39,7 @@ namespace ring { +// Replaces every occurrence of @from with @to in @str static std::string replaceAll(const std::string& str, const std::string& from, const std::string& to) { @@ -61,10 +62,16 @@ MediaRecorder::MediaRecorder() MediaRecorder::~MediaRecorder() { - if (isRecording_) - flush(); if (loop_.isRunning()) loop_.join(); + if (isRecording_) + flush(); +} + +bool +MediaRecorder::isRecording() const +{ + return isRecording_; } std::string @@ -82,24 +89,17 @@ MediaRecorder::audioOnly(bool audioOnly) audioOnly_ = audioOnly; } -void -MediaRecorder::setMetadata(const std::string& title, const std::string& desc) -{ - title_ = title; - description_ = desc; -} - void MediaRecorder::setPath(const std::string& path) { - if (!path.empty()) - path_ = path; + path_ = path; } -bool -MediaRecorder::isRecording() const +void +MediaRecorder::setMetadata(const std::string& title, const std::string& desc) { - return isRecording_; + title_ = title; + description_ = desc; } int @@ -136,28 +136,13 @@ MediaRecorder::stopRecording() flush(); emitSignal<DRing::CallSignal::RecordPlaybackStopped>(getPath()); } - resetToDefaults(); -} - -void -MediaRecorder::update(Observable<std::shared_ptr<AudioFrame>>* ob, const std::shared_ptr<AudioFrame>& a) -{ - std::string name; - if (dynamic_cast<AudioReceiveThread*>(ob)) - name = "a:remote"; - else // ob is of type AudioInput* - name = "a:local"; - recordData(a->pointer(), streams_[name]); -} - -void MediaRecorder::update(Observable<std::shared_ptr<VideoFrame>>* ob, const std::shared_ptr<VideoFrame>& v) -{ - std::string name; - if (dynamic_cast<video::VideoReceiveThread*>(ob)) - name = "v:remote"; - else // ob is of type VideoInput* - name = "v:local"; - recordData(v->pointer(), streams_[name]); + streams_.clear(); + videoIdx_ = audioIdx_ = -1; + isRecording_ = false; + audioOnly_ = false; + videoFilter_.reset(); + audioFilter_.reset(); + encoder_.reset(); } int @@ -181,18 +166,32 @@ MediaRecorder::addStream(const MediaStream& ms) } } +void +MediaRecorder::update(Observable<std::shared_ptr<AudioFrame>>* ob, const std::shared_ptr<AudioFrame>& a) +{ + std::string name; + if (dynamic_cast<AudioReceiveThread*>(ob)) + name = "a:remote"; + else // ob is of type AudioInput* + name = "a:local"; + recordData(a->pointer(), streams_[name]); +} + +void MediaRecorder::update(Observable<std::shared_ptr<VideoFrame>>* ob, const std::shared_ptr<VideoFrame>& v) +{ + std::string name; + if (dynamic_cast<video::VideoReceiveThread*>(ob)) + name = "v:remote"; + else // ob is of type VideoInput* + name = "v:local"; + recordData(v->pointer(), streams_[name]); +} + int MediaRecorder::recordData(AVFrame* frame, const MediaStream& ms) { // recorder may be recording, but not ready for the first frames - if (!isRecording_) - return 0; - - if (!isReady_ && streams_.find(ms.name) == streams_.end()) - if (addStream(ms) < 0) - return -1; - - if (!isReady_ || !loop_.isRunning()) // check again in case initRecord was called + if (!isRecording_ || !loop_.isRunning()) return 0; const auto& params = streams_.at(ms.name); @@ -288,29 +287,18 @@ MediaRecorder::initRecord() } } - // ready to start recording if audio stream index and video stream index are valid - bool audioIsReady = hasAudio_ && audioIdx_ >= 0; - bool videoIsReady = !audioOnly_ && hasVideo_ && videoIdx_ >= 0; - isReady_ = audioIsReady && videoIsReady; - - if (isReady_) { - if (!loop_.isRunning()) - loop_.start(); - + try { std::unique_ptr<MediaIOHandle> ioHandle; - try { - encoder_->setIOContext(ioHandle); - encoder_->startIO(); - } catch (const MediaEncoderException& e) { - RING_ERR() << "Could not start recorder: " << e.what(); - return -1; - } - RING_DBG() << "Recording initialized"; - return 0; - } else { - RING_ERR() << "Failed to initialize recorder"; + encoder_->setIOContext(ioHandle); + encoder_->startIO(); + } catch (const MediaEncoderException& e) { + RING_ERR() << "Could not start recorder: " << e.what(); return -1; } + + RING_DBG() << "Recording initialized"; + loop_.start(); + return 0; } MediaStream @@ -508,19 +496,6 @@ MediaRecorder::flush() return 0; } -void -MediaRecorder::resetToDefaults() -{ - streams_.clear(); - videoIdx_ = audioIdx_ = -1; - isRecording_ = false; - isReady_ = false; - audioOnly_ = false; - videoFilter_.reset(); - audioFilter_.reset(); - encoder_.reset(); -} - void MediaRecorder::process() { diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h index 7dd4d1faa1459035701769a8465225fee8376286..286acc01b64eb4efd0ebefab82f710d13f06d7de 100644 --- a/src/media/media_recorder.h +++ b/src/media/media_recorder.h @@ -53,69 +53,92 @@ public: MediaRecorder(); ~MediaRecorder(); - std::string getPath() const; + /** + * Gets whether or not the recorder is active. + */ + bool isRecording() const; - void setPath(const std::string& path); + /** + * Get file path of file to be recorded. Same path as sent to @setPath, but with + * file extension appended. + * + * NOTE @audioOnly must be called to have the right extension. + */ + std::string getPath() const; + /** + * Sets whether or not output file will have audio. Determines the extension of the + * output file (.ogg or .webm). + */ void audioOnly(bool audioOnly); - // replaces %TIMESTAMP with time at start of recording - // default title: "Conversation at %Y-%m-%d %H:%M:%S" - // default description: "Recorded with Jami https://jami.net" + /** + * Sets output file path. + * + * NOTE An empty path will put the output file in the working directory. + */ + void setPath(const std::string& path); + + /** + * Sets title and description metadata for the file. Uses default if either is empty. + * Default title is "Conversation at %Y-%m-%d %H:%M:%S". + * Default description is "Recorded with Jami https://jami.net". + * + * NOTE replaces %TIMESTAMP with time at start of recording + */ void setMetadata(const std::string& title, const std::string& desc); - bool isRecording() const; + /** + */ + int addStream(const MediaStream& ms); + /** + * Starts the record. Streams must have been added using Observable::attach and + * @addStream. + */ int startRecording(); + /** + * Stops the record. Streams must be removed using Observable::detach afterwards. + */ void stopRecording(); - int addStream(const MediaStream& ms); - - /* Observer methods*/ + /** + * Updates the recorder with an audio or video frame. + */ void update(Observable<std::shared_ptr<AudioFrame>>* ob, const std::shared_ptr<AudioFrame>& a) override; - void update(Observable<std::shared_ptr<VideoFrame>>* ob, const std::shared_ptr<VideoFrame>& v) override; private: NON_COPYABLE(MediaRecorder); int recordData(AVFrame* frame, const MediaStream& ms); + int flush(); int initRecord(); MediaStream setupVideoOutput(); std::string buildVideoFilter(const std::vector<MediaStream>& peers, const MediaStream& local) const; MediaStream setupAudioOutput(); std::string buildAudioFilter(const std::vector<MediaStream>& peers, const MediaStream& local) const; - void emptyFilterGraph(); - int sendToEncoder(AVFrame* frame, int streamIdx); - int flush(); - void resetToDefaults(); // clear saved data for next recording - - std::unique_ptr<MediaEncoder> encoder_; - std::unique_ptr<MediaFilter> videoFilter_; - std::unique_ptr<MediaFilter> audioFilter_; std::mutex mutex_; // protect against concurrent file writes std::map<std::string, const MediaStream> streams_; + std::string path_; std::tm startTime_; std::string title_; std::string description_; - std::string path_; - - // NOTE do not use dir_ or filename_, use path_ instead - std::string dir_; - std::string filename_; + std::unique_ptr<MediaEncoder> encoder_; + std::unique_ptr<MediaFilter> videoFilter_; + std::unique_ptr<MediaFilter> audioFilter_; bool hasAudio_ {false}; bool hasVideo_ {false}; int videoIdx_ = -1; int audioIdx_ = -1; bool isRecording_ = false; - bool isReady_ = false; bool audioOnly_ = false; struct RecordFrame { @@ -131,6 +154,8 @@ private: }; InterruptedThreadLoop loop_; void process(); + void emptyFilterGraph(); + int sendToEncoder(AVFrame* frame, int streamIdx); std::mutex qLock_; std::deque<RecordFrame> frames_; };