Skip to content
Snippets Groups Projects
Commit 03780c7b authored by Philippe Gorley's avatar Philippe Gorley Committed by Adrien Béraud
Browse files

recorder: add doc and reorganize code

Change-Id: I68c9b2b2ea3863653f86fcabd17ad4128f0ebcfd
parent dd6199ac
Branches
No related tags found
No related merge requests found
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
namespace ring { namespace ring {
// Replaces every occurrence of @from with @to in @str
static std::string static std::string
replaceAll(const std::string& str, const std::string& from, const std::string& to) replaceAll(const std::string& str, const std::string& from, const std::string& to)
{ {
...@@ -61,10 +62,16 @@ MediaRecorder::MediaRecorder() ...@@ -61,10 +62,16 @@ MediaRecorder::MediaRecorder()
MediaRecorder::~MediaRecorder() MediaRecorder::~MediaRecorder()
{ {
if (isRecording_)
flush();
if (loop_.isRunning()) if (loop_.isRunning())
loop_.join(); loop_.join();
if (isRecording_)
flush();
}
bool
MediaRecorder::isRecording() const
{
return isRecording_;
} }
std::string std::string
...@@ -82,24 +89,17 @@ MediaRecorder::audioOnly(bool audioOnly) ...@@ -82,24 +89,17 @@ MediaRecorder::audioOnly(bool audioOnly)
audioOnly_ = audioOnly; audioOnly_ = audioOnly;
} }
void
MediaRecorder::setMetadata(const std::string& title, const std::string& desc)
{
title_ = title;
description_ = desc;
}
void void
MediaRecorder::setPath(const std::string& path) MediaRecorder::setPath(const std::string& path)
{ {
if (!path.empty())
path_ = path; path_ = path;
} }
bool void
MediaRecorder::isRecording() const MediaRecorder::setMetadata(const std::string& title, const std::string& desc)
{ {
return isRecording_; title_ = title;
description_ = desc;
} }
int int
...@@ -136,28 +136,13 @@ MediaRecorder::stopRecording() ...@@ -136,28 +136,13 @@ MediaRecorder::stopRecording()
flush(); flush();
emitSignal<DRing::CallSignal::RecordPlaybackStopped>(getPath()); emitSignal<DRing::CallSignal::RecordPlaybackStopped>(getPath());
} }
resetToDefaults(); streams_.clear();
} videoIdx_ = audioIdx_ = -1;
isRecording_ = false;
void audioOnly_ = false;
MediaRecorder::update(Observable<std::shared_ptr<AudioFrame>>* ob, const std::shared_ptr<AudioFrame>& a) videoFilter_.reset();
{ audioFilter_.reset();
std::string name; encoder_.reset();
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 int
...@@ -181,18 +166,32 @@ MediaRecorder::addStream(const MediaStream& ms) ...@@ -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 int
MediaRecorder::recordData(AVFrame* frame, const MediaStream& ms) MediaRecorder::recordData(AVFrame* frame, const MediaStream& ms)
{ {
// recorder may be recording, but not ready for the first frames // recorder may be recording, but not ready for the first frames
if (!isRecording_) if (!isRecording_ || !loop_.isRunning())
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
return 0; return 0;
const auto& params = streams_.at(ms.name); const auto& params = streams_.at(ms.name);
...@@ -288,29 +287,18 @@ MediaRecorder::initRecord() ...@@ -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();
std::unique_ptr<MediaIOHandle> ioHandle;
try { try {
std::unique_ptr<MediaIOHandle> ioHandle;
encoder_->setIOContext(ioHandle); encoder_->setIOContext(ioHandle);
encoder_->startIO(); encoder_->startIO();
} catch (const MediaEncoderException& e) { } catch (const MediaEncoderException& e) {
RING_ERR() << "Could not start recorder: " << e.what(); RING_ERR() << "Could not start recorder: " << e.what();
return -1; return -1;
} }
RING_DBG() << "Recording initialized"; RING_DBG() << "Recording initialized";
loop_.start();
return 0; return 0;
} else {
RING_ERR() << "Failed to initialize recorder";
return -1;
}
} }
MediaStream MediaStream
...@@ -508,19 +496,6 @@ MediaRecorder::flush() ...@@ -508,19 +496,6 @@ MediaRecorder::flush()
return 0; return 0;
} }
void
MediaRecorder::resetToDefaults()
{
streams_.clear();
videoIdx_ = audioIdx_ = -1;
isRecording_ = false;
isReady_ = false;
audioOnly_ = false;
videoFilter_.reset();
audioFilter_.reset();
encoder_.reset();
}
void void
MediaRecorder::process() MediaRecorder::process()
{ {
......
...@@ -53,69 +53,92 @@ public: ...@@ -53,69 +53,92 @@ public:
MediaRecorder(); MediaRecorder();
~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); void audioOnly(bool audioOnly);
// replaces %TIMESTAMP with time at start of recording /**
// default title: "Conversation at %Y-%m-%d %H:%M:%S" * Sets output file path.
// default description: "Recorded with Jami https://jami.net" *
* 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); 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(); int startRecording();
/**
* Stops the record. Streams must be removed using Observable::detach afterwards.
*/
void stopRecording(); void stopRecording();
int addStream(const MediaStream& ms); /**
* Updates the recorder with an audio or video frame.
/* Observer methods*/ */
void update(Observable<std::shared_ptr<AudioFrame>>* ob, const std::shared_ptr<AudioFrame>& a) override; 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; void update(Observable<std::shared_ptr<VideoFrame>>* ob, const std::shared_ptr<VideoFrame>& v) override;
private: private:
NON_COPYABLE(MediaRecorder); NON_COPYABLE(MediaRecorder);
int recordData(AVFrame* frame, const MediaStream& ms); int recordData(AVFrame* frame, const MediaStream& ms);
int flush();
int initRecord(); int initRecord();
MediaStream setupVideoOutput(); MediaStream setupVideoOutput();
std::string buildVideoFilter(const std::vector<MediaStream>& peers, const MediaStream& local) const; std::string buildVideoFilter(const std::vector<MediaStream>& peers, const MediaStream& local) const;
MediaStream setupAudioOutput(); MediaStream setupAudioOutput();
std::string buildAudioFilter(const std::vector<MediaStream>& peers, const MediaStream& local) const; 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::mutex mutex_; // protect against concurrent file writes
std::map<std::string, const MediaStream> streams_; std::map<std::string, const MediaStream> streams_;
std::string path_;
std::tm startTime_; std::tm startTime_;
std::string title_; std::string title_;
std::string description_; std::string description_;
std::string path_; std::unique_ptr<MediaEncoder> encoder_;
std::unique_ptr<MediaFilter> videoFilter_;
// NOTE do not use dir_ or filename_, use path_ instead std::unique_ptr<MediaFilter> audioFilter_;
std::string dir_;
std::string filename_;
bool hasAudio_ {false}; bool hasAudio_ {false};
bool hasVideo_ {false}; bool hasVideo_ {false};
int videoIdx_ = -1; int videoIdx_ = -1;
int audioIdx_ = -1; int audioIdx_ = -1;
bool isRecording_ = false; bool isRecording_ = false;
bool isReady_ = false;
bool audioOnly_ = false; bool audioOnly_ = false;
struct RecordFrame { struct RecordFrame {
...@@ -131,6 +154,8 @@ private: ...@@ -131,6 +154,8 @@ private:
}; };
InterruptedThreadLoop loop_; InterruptedThreadLoop loop_;
void process(); void process();
void emptyFilterGraph();
int sendToEncoder(AVFrame* frame, int streamIdx);
std::mutex qLock_; std::mutex qLock_;
std::deque<RecordFrame> frames_; std::deque<RecordFrame> frames_;
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment