diff --git a/record-and-playback/core/lib/recordandplayback/edl/audio.rb b/record-and-playback/core/lib/recordandplayback/edl/audio.rb index e6c500c0d87764da1697eb6ab46b9af77d95c1b8..84fb6be4eee1bfe1baa1e321378087048e440180 100644 --- a/record-and-playback/core/lib/recordandplayback/edl/audio.rb +++ b/record-and-playback/core/lib/recordandplayback/edl/audio.rb @@ -41,6 +41,25 @@ module BigBlueButton end end + def self.mixer(inputs, output_basename) + BigBlueButton.logger.debug "Mixing audio files" + + ffmpeg_cmd = [*FFMPEG] + inputs.each do |input| + ffmpeg_cmd += ['-i', input] + end + ffmpeg_cmd += ['-filter_complex', "amix"] + + output = "#{output_basename}.#{WF_EXT}" + ffmpeg_cmd += [*FFMPEG_WF_ARGS, output] + + BigBlueButton.logger.info "Running audio mixer..." + exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd) + raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0 + + output + end + def self.render(edl, output_basename) sections = [] audioinfo = {} @@ -109,7 +128,7 @@ module BigBlueButton filter = "[#{input_index}] " filter << "atempo=#{speed},atrim=start=#{ms_to_s(audio[:timestamp])}," filter << "asetpts=PTS-STARTPTS," - filter << "#{FFMPEG_AFORMAT},apad,atrim=end=#{ms_to_s(duration)} [out#{output_index}]" + filter << "#{FFMPEG_AFORMAT},apad,atrim=end=#{ms_to_s(duration)} ,afifo[out#{output_index}]" ffmpeg_filters << filter ffmpeg_inputs << { @@ -128,7 +147,7 @@ module BigBlueButton BigBlueButton.logger.info " Using input #{audio[:filename]}" filter = "[#{input_index}] " - filter << "#{FFMPEG_AFORMAT},apad,atrim=end=#{ms_to_s(duration)} [out#{output_index}]" + filter << "#{FFMPEG_AFORMAT},apad,atrim=end=#{ms_to_s(duration)} ,afifo[out#{output_index}]" ffmpeg_filters << filter ffmpeg_inputs << { @@ -155,6 +174,7 @@ module BigBlueButton if audioinfo[input[:filename]][:format][:format_name] == 'wav' ffmpeg_cmd += ['-ignore_length', '1'] end + ffmpeg_cmd += ['-vsync', 'vfr'] ffmpeg_cmd += ['-i', input[:filename]] end ffmpeg_filter = ffmpeg_filters.join(' ; ') diff --git a/record-and-playback/core/lib/recordandplayback/generators/audio.rb b/record-and-playback/core/lib/recordandplayback/generators/audio.rb index 98c94dbd73b7a89b8af46b93217731605defcf7b..80c11ea88b6e2a20dfd5365207bef6511a558748 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/audio.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/audio.rb @@ -73,5 +73,76 @@ module BigBlueButton return audio_edl end + def self.create_deskshare_audio_edl(events, deskshare_dir) + audio_edl = [] + + initial_timestamp = BigBlueButton::Events.first_event_timestamp(events) + final_timestamp = BigBlueButton::Events.last_event_timestamp(events) + filename = "" + + # Initially start with silence + audio_edl << { + :timestamp => 0, + :audio => nil + } + + deskshare_start = { + :filename => "", + :timestamp => 0 + } + + events.xpath('/recording/event[@module="Deskshare" or (@module="bbb-webrtc-sfu" and (@eventname="StartWebRTCDesktopShareEvent" or @eventname="StopWebRTCDesktopShareEvent"))]').each do |event| + # check if deskshare has audio + filename = event.at_xpath('filename').text + fileHasAudio = !BigBlueButton::EDL::Audio.audio_info(filename)[:audio].nil? + if (fileHasAudio) + timestamp = event['timestamp'].to_i - initial_timestamp + # Determine the audio filename + case event['eventname'] + when 'DeskshareStartedEvent', 'DeskshareStoppedEvent' + filename = event.at_xpath('file').text + filename = "#{deskshare_dir}/#{File.basename(filename)}" + when 'StartWebRTCDesktopShareEvent', 'StopWebRTCDesktopShareEvent' + uri = event.at_xpath('filename').text + filename = "#{deskshare_dir}/#{File.basename(uri)}" + end + raise "Couldn't determine audio filename" if filename.nil? + + # Add the audio to the EDL + case event['eventname'] + when 'DeskshareStartedEvent', 'StartWebRTCDesktopShareEvent' + audio_edl << { + :timestamp => timestamp, + :audio => { :filename => filename, :timestamp => 0 } + } + when 'DeskshareStoppedEvent', 'StopWebRTCDesktopShareEvent' + if audio_edl.last[:audio] && audio_edl.last[:audio][:filename] == filename + # Fill in the original/expected audo duration when available + duration = event.at_xpath('duration') + if !duration.nil? + duration = duration.text.to_i + audio_edl.last[:original_duration] = duration * 1000 + else + audio_edl.last[:original_duration] = timestamp - audio_edl.last[:timestamp] + end + audio_edl << { + :timestamp => timestamp, + :audio => nil + } + end + end + else + BigBlueButton.logger.debug " Screenshare without audio, ignoring..." + end + end + + audio_edl << { + :timestamp => final_timestamp - initial_timestamp, + :audio => nil + } + + return audio_edl + end + end end diff --git a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb index 9f5129c14487cb1a93944a3d1774aac451a3f31b..df589797e866047ea9633eb5e5cc3aeeec212e2d 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb @@ -36,7 +36,6 @@ module BigBlueButton def self.process(archive_dir, file_basename) BigBlueButton.logger.info("AudioProcessor.process: Processing audio...") - audio_dir = "#{archive_dir}/audio" events_xml = "#{archive_dir}/events.xml" events = Nokogiri::XML(File.open(events_xml)) @@ -52,12 +51,37 @@ module BigBlueButton BigBlueButton::EDL::Audio.dump(audio_edl) target_dir = File.dirname(file_basename) - audio_dir = "#{archive_dir}/audio" events_xml = "#{archive_dir}/events.xml" + # getting users audio... @audio_file = BigBlueButton::EDL::Audio.render( audio_edl, File.join(target_dir, 'recording')) + # and mixing it with deskshare audio + if BigBlueButton::Events.screenshare_has_audio?(events_xml) + BigBlueButton.logger.info("AudioProcessor.process: processing Deskshare audio...") + + deskshare_dir = "#{archive_dir}/deskshare" + mixed_dir = "#{archive_dir}/mixed" + + deskshare_audio_edl = BigBlueButton::AudioEvents.create_deskshare_audio_edl(events, deskshare_dir) + BigBlueButton::EDL::Audio.dump(deskshare_audio_edl) + + BigBlueButton.logger.info "Applying recording start/stop events to Deskshare audio" + deskshare_audio_edl = BigBlueButton::Events.edl_match_recording_marks_audio( + deskshare_audio_edl, events, start_time, end_time) + BigBlueButton.logger.debug "Trimmed Deskshare Audio EDL:" + BigBlueButton::EDL::Audio.dump(deskshare_audio_edl) + + audio_inputs = [] + audio_inputs << @audio_file + audio_inputs << BigBlueButton::EDL::Audio.render(deskshare_audio_edl, deskshare_dir) + + @audio_file = BigBlueButton::EDL::Audio.mixer(audio_inputs, mixed_dir) + else + BigBlueButton.logger.info("AudioProcessor.process: no Deskshare audio to process.") + end + ogg_format = { :extension => 'ogg', :parameters => [ [ '-c:a', 'copy', '-f', 'ogg' ] ] diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index 236c95d229215a7ed8768a3e6e008a833a2c9947..6dd353a388953389a2683ad2d5d2d72f031abc7f 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -692,5 +692,18 @@ module BigBlueButton end end + # Check if any screenshare files has audio + def self.screenshare_has_audio?(events_xml) + events = Nokogiri::XML(File.open(events_xml)) + events.xpath('/recording/event[@eventname="StartWebRTCDesktopShareEvent"]').each do |event| + filename = event.at_xpath('filename').text + fileHasAudio = !BigBlueButton::EDL::Audio.audio_info(filename)[:audio].nil? + if fileHasAudio + return true + end + end + return false + end + end end