Skip to content

Commit

Permalink
Fixes to recording (#1808)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arri98 committed Mar 29, 2022
1 parent ed24236 commit 4f8e19e
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 45 deletions.
50 changes: 34 additions & 16 deletions erizo/src/erizo/media/ExternalOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ namespace erizo {
DEFINE_LOGGER(ExternalOutput, "media.ExternalOutput");
ExternalOutput::ExternalOutput(std::shared_ptr<Worker> worker, const std::string& output_url,
const std::vector<RtpMap> rtp_mappings,
const std::vector<erizo::ExtMap> ext_mappings)
const std::vector<erizo::ExtMap> ext_mappings, bool hasAudio, bool hasVideo)
: worker_{worker}, pipeline_{Pipeline::create()}, audio_queue_{5.0, 10.0}, video_queue_{5.0, 10.0},
inited_{false}, video_stream_{nullptr},
audio_stream_{nullptr}, video_source_ssrc_{0},
first_video_timestamp_{-1}, first_audio_timestamp_{-1},
first_data_received_{}, video_offset_ms_{-1}, audio_offset_ms_{-1},
need_to_send_fir_{true}, rtp_mappings_{rtp_mappings}, video_codec_{AV_CODEC_ID_NONE},
audio_codec_{AV_CODEC_ID_NONE}, pipeline_initialized_{false}, ext_processor_{ext_mappings} {
need_to_send_fir_{true}, rtp_mappings_{rtp_mappings}, hasAudio_{hasAudio}, hasVideo_{hasVideo},
video_codec_{AV_CODEC_ID_NONE}, audio_codec_{AV_CODEC_ID_NONE},
pipeline_initialized_{false}, ext_processor_{ext_mappings}
{
ELOG_DEBUG("Creating output to %s", output_url.c_str());

ELOG_DEBUG("Has audio %d has video %d", hasAudio, hasVideo);
// TODO(pedro): these should really only be called once per application run
av_register_all();
avcodec_register_all();
Expand Down Expand Up @@ -88,7 +90,6 @@ bool ExternalOutput::init() {
return true;
}


ExternalOutput::~ExternalOutput() {
ELOG_DEBUG("Destructing");
}
Expand Down Expand Up @@ -365,15 +366,24 @@ int ExternalOutput::deliverEvent_(MediaEventPtr event) {
}

bool ExternalOutput::initContext() {
if (video_codec_ != AV_CODEC_ID_NONE &&
audio_codec_ != AV_CODEC_ID_NONE &&
video_stream_ == nullptr &&
audio_stream_ == nullptr) {
bool init_video = false;
bool init_audio = false;

if (hasVideo_ && video_codec_ == AV_CODEC_ID_NONE) {
return false;
}

if (hasAudio_ && audio_codec_ == AV_CODEC_ID_NONE) {
return false;
}

if (hasVideo_ && video_stream_ == nullptr) {
AVCodec* video_codec = avcodec_find_encoder(video_codec_);
if (video_codec == nullptr) {
ELOG_ERROR("Could not find video codec");
return false;
}
init_video = true;
need_to_send_fir_ = true;
video_queue_.setTimebase(video_map_.clock_rate);
video_stream_ = avformat_new_stream(context_, video_codec);
Expand All @@ -390,13 +400,16 @@ bool ExternalOutput::initContext() {
video_stream_->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
context_->oformat->flags |= AVFMT_VARIABLE_FPS;
context_->streams[0] = video_stream_;
}

if (hasAudio_ && audio_stream_ == nullptr) {
AVCodec* audio_codec = avcodec_find_encoder(audio_codec_);
if (audio_codec == nullptr) {
ELOG_ERROR("Could not find audio codec");
return false;
}

init_audio = true;
audio_stream_ = avformat_new_stream(context_, audio_codec);
audio_stream_->id = 1;
audio_stream_->codec->codec_id = audio_codec_;
Expand All @@ -407,8 +420,18 @@ bool ExternalOutput::initContext() {
audio_stream_->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}

context_->streams[0] = video_stream_;
if (!hasVideo_) {
// To avoid the following matroska errors, we add CODEC_FLAG_GLOBAL_HEADER...
// - Codec for stream 0 does not use global headers but container format requires global headers
// - Only audio, video, and subtitles are supported for Matroska.
video_stream_ = avformat_new_stream(context_, nullptr);
video_stream_->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
context_->streams[0] = video_stream_;
}
context_->streams[1] = audio_stream_;
}

if ( init_audio || init_video ) {
if (avio_open(&context_->pb, context_->filename, AVIO_FLAG_WRITE) < 0) {
ELOG_ERROR("Error opening output file");
return false;
Expand All @@ -435,11 +458,6 @@ void ExternalOutput::queueData(char* buffer, int length, packetType type) {

if (first_data_received_ == time_point()) {
first_data_received_ = clock::now();
if (getAudioSinkSSRC() == 0) {
ELOG_DEBUG("No audio detected");
audio_map_ = RtpMap{0, "PCMU", 8000, AUDIO_TYPE, 1};
audio_codec_ = AV_CODEC_ID_PCM_MULAW;
}
}
if (need_to_send_fir_ && video_source_ssrc_) {
sendFirPacket();
Expand Down
6 changes: 4 additions & 2 deletions erizo/src/erizo/media/ExternalOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class ExternalOutput : public MediaSink, public RawDataReceiver, public Feedback
public:
explicit ExternalOutput(std::shared_ptr<Worker> worker, const std::string& output_url,
const std::vector<RtpMap> rtp_mappings,
const std::vector<erizo::ExtMap> ext_mappings);
const std::vector<erizo::ExtMap> ext_mappings,
bool hasAudio, bool hasVideo);
virtual ~ExternalOutput();
bool init();
void receiveRawData(const RawDataPacket& packet) override;
Expand All @@ -65,7 +66,6 @@ class ExternalOutput : public MediaSink, public RawDataReceiver, public Feedback
boost::condition_variable cond_;
AVStream *video_stream_, *audio_stream_;
AVFormatContext *context_;

uint32_t video_source_ssrc_;
std::unique_ptr<Depacketizer> depacketizer_;

Expand Down Expand Up @@ -104,6 +104,8 @@ class ExternalOutput : public MediaSink, public RawDataReceiver, public Feedback
// so the second scheme seems not applicable. Too bad.
bool need_to_send_fir_;
std::vector<RtpMap> rtp_mappings_;
bool hasAudio_;
bool hasVideo_;
enum AVCodecID video_codec_;
enum AVCodecID audio_codec_;
std::map<uint, RtpMap> video_maps_;
Expand Down
9 changes: 8 additions & 1 deletion erizoAPI/ExternalOutput.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,14 @@ NAN_METHOD(ExternalOutput::New) {
ext_mappings.push_back({value++, *ext_map_it});
}
}

bool hasAudio = Nan::To<bool>((info[3])).FromJust();
bool hasVideo = Nan::To<bool>((info[4])).FromJust();

std::shared_ptr<erizo::Worker> worker = thread_pool->me->getLessUsedWorker();

ExternalOutput* obj = new ExternalOutput();
obj->me = std::make_shared<erizo::ExternalOutput>(worker, url, rtp_mappings, ext_mappings);
obj->me = std::make_shared<erizo::ExternalOutput>(worker, url, rtp_mappings, ext_mappings, hasAudio, hasVideo);

obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
Expand All @@ -159,3 +163,6 @@ NAN_METHOD(ExternalOutput::init) {
int r = me->init();
info.GetReturnValue().Set(Nan::New(r));
}



8 changes: 6 additions & 2 deletions erizo_controller/erizoController/models/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,11 @@ class Client extends events.EventEmitter {
return;
}
if (stream.hasAudio() || stream.hasVideo() || stream.hasScreen()) {
const mediaOptions = { mediaConfiguration: this.token.mediaConfiguration };
const mediaOptions = { mediaConfiguration:
this.token.mediaConfiguration,
hasAudio: stream.hasAudio(),
hasVideo: stream.hasVideo() || stream.hasScreen(),
};
stream.addExternalOutputSubscriber(url);
stream.updateExternalOutputSubscriberState(url, StreamStates.SUBSCRIBER_CREATED);
this.room.controller.addExternalOutput(streamId, url, mediaOptions, () => {
Expand Down Expand Up @@ -613,7 +617,7 @@ class Client extends events.EventEmitter {
this.room.streamManager.forEachPublishedStream((stream) => {
if (stream.hasExternalOutputSubscriber(url)) {
stream.removeExternalOutputSubscriber(url);
this.room.controller.removeExternalOutput(options.id, url, callback);
this.room.controller.removeExternalOutput(stream.id, url, callback);
removed = true;
}
});
Expand Down
6 changes: 5 additions & 1 deletion erizo_controller/erizoJS/models/Publisher.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,15 @@ class Source extends NodeClass {

addExternalOutput(url, options) {
const eoId = `${url}_${this.streamId}`;
const hasVideo = options.hasVideo === null || options.hasVideo;
const hasAudio = options.hasAudio === null || options.hasAudio;

log.info(`message: Adding ExternalOutput, id: ${eoId}, url: ${url},`,
logger.objectToLog(this.options), logger.objectToLog(this.options.metadata));
const externalOutput = new erizo.ExternalOutput(this.threadPool, url,
Helpers.getMediaConfiguration(options.mediaConfiguration));
Helpers.getMediaConfiguration(options.mediaConfiguration), hasAudio, hasVideo);
externalOutput.id = eoId;

externalOutput.init();
this.muxer.addExternalOutput(externalOutput, url);
this.externalOutputs[url] = externalOutput;
Expand Down
1 change: 0 additions & 1 deletion extras/basic_example/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<div>
<button id="startButton" onclick="startBasicExample()" disabled>Start</button>
<button id="testConnection" onclick="testConnection()">Test Connection</button>
<button id="recordButton" onclick="startRecording()" disabled>Toggle Recording</button>
<button id="slideShowMode" onclick="toggleSlideShowMode()" disabled>Toggle Slide Show Mode</button>
</div>
<div style="margin-top: 5px;">
Expand Down
51 changes: 29 additions & 22 deletions extras/basic_example/public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
const serverUrl = '/';
let localStream;
let room;
let recording = false;
let recordingId = '';
let localStreamIndex = 0;
const localStreams = new Map();
const configFlags = {
Expand Down Expand Up @@ -76,11 +74,40 @@ const createPublisherContainer = (stream, index) => {
document.getElementById('videoContainer').removeChild(container);
};

const stopRecordButton = document.createElement('button');
stopRecordButton.textContent = 'Stop record';
stopRecordButton.setAttribute('style', 'float:left;');
stopRecordButton.setAttribute('hidden', 'true');

const recordButton = document.createElement('button');
recordButton.textContent = 'Record';
recordButton.setAttribute('style', 'float:left;');

let recordId;
recordButton.onclick = () => {
console.log(stream);
room.startRecording(stream, (id) => {
recordId = id;
});
recordButton.hidden = true;
stopRecordButton.hidden = false;
};

stopRecordButton.onclick = () => {
console.log(stream);
room.stopRecording(recordId);
recordButton.hidden = false;
stopRecordButton.hidden = true;
};


const div = document.createElement('div');
div.setAttribute('style', 'width: 320px; height: 240px; float:left');
div.setAttribute('id', `myVideo${index}`);
container.appendChild(div);
container.appendChild(unpublishButton);
container.appendChild(recordButton);
container.appendChild(stopRecordButton);
document.getElementById('videoContainer').appendChild(container);
};

Expand Down Expand Up @@ -111,24 +138,6 @@ const testConnection = () => {
window.location = '/connection_test.html';
};


// eslint-disable-next-line no-unused-vars
function startRecording() {
if (room !== undefined) {
if (!recording) {
room.startRecording(localStream, (id) => {
recording = true;
recordingId = id;
window.recordingId = recordingId;
});
} else {
room.stopRecording(recordingId);
recording = false;
}
window.recording = recording;
}
}

let slideShowMode = false;

// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -178,7 +187,6 @@ const startBasicExample = () => {
document.getElementById('publishOnlyAudio').disabled = false;
document.getElementById('startWarning').hidden = true;
document.getElementById('startButton').hidden = true;
recording = false;
console.log('Selected Room', configFlags.room, 'of type', configFlags.type);
const config = { audio: true,
video: !configFlags.onlyAudio,
Expand Down Expand Up @@ -291,7 +299,6 @@ const startBasicExample = () => {
localStream.setAttributes({ type: 'publisher' });
}
subscribeToStreams(streams);
document.getElementById('recordButton').disabled = false;
});

room.addEventListener('stream-removed', (streamEvent) => {
Expand Down

0 comments on commit 4f8e19e

Please sign in to comment.