Commit c21c5b77 authored by Philippe Gorley's avatar Philippe Gorley

media: add video recording support

If there are 2 video streams, overlays local video over peer video. Peer
video will be upscaled if it is too small, while local video will be
scaled to 1/16th the size of the peer video.

If there is only 1 video stream, uses peer video settings if they are
valid, or fall back on local video settings.

Adds title and description metadata to recorded file.

Change-Id: I182de013e3ac2d18161eadf406fc6494e5f59a4e
parent 53797a32
......@@ -205,7 +205,8 @@ AudioSender::getLastSeqValue()
void
AudioSender::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
audioEncoder_->startRecorder(rec);
if (audioEncoder_)
audioEncoder_->startRecorder(rec);
}
class AudioReceiveThread
......@@ -383,7 +384,8 @@ AudioReceiveThread::startLoop()
void
AudioReceiveThread::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
audioDecoder_->startRecorder(rec);
if (audioDecoder_)
audioDecoder_->startRecorder(rec);
}
AudioRtpSession::AudioRtpSession(const std::string& id)
......@@ -517,8 +519,10 @@ AudioRtpSession::setMuted(bool isMuted)
void
AudioRtpSession::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
receiveThread_->startRecorder(rec);
sender_->startRecorder(rec);
if (receiveThread_)
receiveThread_->startRecorder(rec);
if (sender_)
sender_->startRecorder(rec);
}
} // namespace ring
......@@ -291,7 +291,7 @@ MediaDecoder::decode(VideoFrame& result)
#endif
if (auto rec = recorder_.lock()) {
if (!recordingStarted_) {
auto ms = MediaStream("", avStream_);
auto ms = MediaStream("", avStream_, frame->pts);
ms.format = frame->format; // might not match avStream_ if accel is used
if (rec->addStream(true, true, ms) >= 0)
recordingStarted_ = true;
......@@ -357,7 +357,7 @@ MediaDecoder::decode(const AudioFrame& decodedFrame)
if (auto rec = recorder_.lock()) {
if (!recordingStarted_) {
auto ms = MediaStream("", avStream_);
auto ms = MediaStream("", avStream_, frame->pts);
if (rec->addStream(false, true, ms) >= 0)
recordingStarted_ = true;
else
......@@ -518,8 +518,6 @@ void
MediaDecoder::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
// recording will start once we can send an AVPacket to the recorder
if (inputDecoder_->type != AVMEDIA_TYPE_AUDIO)
return;
recordingStarted_ = false;
recorder_ = rec;
if (auto r = recorder_.lock()) {
......
......@@ -453,7 +453,7 @@ MediaEncoder::encode(AVFrame* frame, int streamIdx)
if (auto rec = recorder_.lock()) {
bool isVideo = encoderCtx->codec_type == AVMEDIA_TYPE_VIDEO;
if (!recordingStarted_) {
auto ms = MediaStream("", outputCtx_->streams[streamIdx]);
auto ms = MediaStream("", outputCtx_->streams[streamIdx], frame->pts);
if (rec->addStream(isVideo, false, ms) >= 0)
recordingStarted_ = true;
else
......@@ -710,8 +710,6 @@ void
MediaEncoder::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
// recording will start once we can send an AVPacket to the recorder
if (encoders_[0]->codec_type != AVMEDIA_TYPE_AUDIO)
return;
recordingStarted_ = false;
recorder_ = rec;
if (auto r = recorder_.lock()) {
......
This diff is collapsed.
......@@ -44,6 +44,12 @@ class MediaRecorder {
std::string getFilename() const;
void audioOnly(bool audioOnly);
// default title is: "Ring recording at %Y-%m-%d %H:%M:%S"
// default description is: "Recorded at %Y-%m-%d %H:%M:%S with Ring https://ring.cx"
void setMetadata(const std::string& title, const std::string& desc);
void setRecordingPath(const std::string& dir);
// adjust nb of streams before recording
......@@ -66,27 +72,36 @@ class MediaRecorder {
NON_COPYABLE(MediaRecorder);
int initRecord();
MediaStream setupVideoOutput();
std::string buildVideoFilter();
MediaStream setupAudioOutput();
void emptyFilterGraph();
int sendToEncoder(AVFrame* frame, int streamIdx);
int flush();
std::unique_ptr<MediaEncoder> encoder_;
std::unique_ptr<MediaFilter> videoFilter_;
std::unique_ptr<MediaFilter> audioFilter_;
std::mutex mutex_; // protect against concurrent file writes
// isVideo is first key, fromPeer is second
std::map<bool, std::map<bool, MediaStream>> streamParams_;
std::map<bool, std::map<bool, int64_t>> nextTimestamp_;
std::map<bool, std::map<bool, MediaStream>> streams_;
std::tm startTime_;
std::string dir_;
std::string filename_;
std::string title_;
std::string description_;
unsigned nbExpectedStreams_ = 0;
unsigned nbReceivedVideoStreams_ = 0;
unsigned nbReceivedAudioStreams_ = 0;
int videoIdx_ = -1;
int audioIdx_ = -1;
bool isRecording_ = false;
bool isReady_ = false;
bool audioOnly_ = false;
};
}; // namespace ring
......@@ -33,6 +33,7 @@ struct MediaStream {
int format {-1};
bool isVideo {false};
rational<int> timeBase;
int64_t firstTimestamp {0};
int width {0};
int height {0};
rational<int> aspectRatio;
......@@ -65,10 +66,16 @@ struct MediaStream {
{}
MediaStream(std::string name, AVStream* st)
: MediaStream(name, st, 0)
{
}
MediaStream(std::string name, AVStream* st, int64_t firstTimestamp)
: name(name)
{
format = st->codecpar->format;
timeBase = st->time_base;
this->firstTimestamp = firstTimestamp;
switch (st->codecpar->codec_type) {
case AVMEDIA_TYPE_VIDEO:
isVideo = true;
......
......@@ -53,6 +53,7 @@ Recordable::toggleRecording()
if (!recording_ || !recorder_) {
recorder_.reset();
recorder_ = std::make_shared<MediaRecorder>();
recorder_->audioOnly(isAudioOnly_);
recorder_->setRecordingPath(Manager::instance().audioPreference.getRecordPath());
}
recording_ = recorder_->toggleRecording();
......
......@@ -69,9 +69,11 @@ transferFrameData(HardwareAccel accel, AVCodecContext* /*codecCtx*/, VideoFrame&
auto container = std::unique_ptr<VideoFrame>(new VideoFrame());
auto output = container->pointer();
auto pts = input->pts;
// most hardware accelerations output NV12, so skip extra conversions
output->format = AV_PIX_FMT_NV12;
int ret = av_hwframe_transfer_data(output, input, 0);
output->pts = pts;
// move output into input so the caller receives extracted image data
// but we have to delete input's data first
......
......@@ -241,7 +241,8 @@ VideoReceiveThread::triggerKeyFrameRequest()
void
VideoReceiveThread::startRecorder(std::shared_ptr<ring::MediaRecorder>& rec)
{
videoDecoder_->startRecorder(rec);
if (videoDecoder_)
videoDecoder_->startRecorder(rec);
}
}} // namespace ring::video
......@@ -565,11 +565,15 @@ VideoRtpSession::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
// video recording needs to start with keyframes
const constexpr int keyframes = 3;
receiveThread_->startRecorder(rec);
sender_->startRecorder(rec);
if (receiveThread_)
receiveThread_->startRecorder(rec);
if (sender_)
sender_->startRecorder(rec);
for (int i = 0; i < keyframes; ++i) {
receiveThread_->triggerKeyFrameRequest();
sender_->forceKeyFrame();
if (receiveThread_)
receiveThread_->triggerKeyFrameRequest();
if (sender_)
sender_->forceKeyFrame();
}
}
......
......@@ -109,7 +109,8 @@ VideoSender::useCodec(const ring::AccountVideoCodecInfo* codec) const
void
VideoSender::startRecorder(std::shared_ptr<MediaRecorder>& rec)
{
videoEncoder_->startRecorder(rec);
if (videoEncoder_)
videoEncoder_->startRecorder(rec);
}
}} // namespace ring::video
......@@ -1154,9 +1154,14 @@ SIPCall::toggleRecording()
{
const bool startRecording = Call::toggleRecording();
if (startRecording) {
avformatrtp_->startRecorder(recorder_);
std::stringstream ss;
ss << "Ring call between " << getSIPAccount().getUserUri() << " and "
<< (!peerRegistredName_.empty() ? peerRegistredName_ : getPeerNumber());
recorder_->setMetadata(ss.str(), ""); // use default description
if (avformatrtp_)
avformatrtp_->startRecorder(recorder_);
#ifdef RING_VIDEO
if (!isAudioOnly_)
if (!isAudioOnly_ && videortp_)
videortp_->startRecorder(recorder_);
#endif
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment