From 2a6b0b62c396923449eeb2f06e233227dbbbdcbe Mon Sep 17 00:00:00 2001 From: Calvin Walton <calvin.walton@kepstin.ca> Date: Wed, 17 Mar 2021 16:07:07 -0400 Subject: [PATCH] Rework the ffmpeg filter generation in the audio rendering. This gives the following advantages over the previous code: * The ffmpeg input filters are loaded from a filter "script" file instead of passed on the command line. This fixes some cases of recordings failing to process because the ffmpeg command line generated for the audio processing exceeded the max command line length limit. (Although that only really happens due to BBB bugs...) * Use absolute positions when trimming audio segments for cuts. Previously segments were trimmed to the length of the segment, and the results were concatenated. There's some possibility of accumulated errors in the segment lengths causing audio desync over time. The new code incrementally concatenates the segments, and cuts each segment end based on the absolute time since the start of the meeting, to avoid error accumulation. --- .../core/lib/recordandplayback/edl/audio.rb | 106 ++++++++---------- 1 file changed, 46 insertions(+), 60 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/edl/audio.rb b/record-and-playback/core/lib/recordandplayback/edl/audio.rb index e6c500c0d8..e5758a2815 100644 --- a/record-and-playback/core/lib/recordandplayback/edl/audio.rb +++ b/record-and-playback/core/lib/recordandplayback/edl/audio.rb @@ -42,7 +42,6 @@ module BigBlueButton end def self.render(edl, output_basename) - sections = [] audioinfo = {} corrupt_audios = Set.new @@ -61,13 +60,14 @@ module BigBlueButton audioinfo.keys.each do |audiofile| BigBlueButton.logger.debug " #{audiofile}" info = audio_info(audiofile) - BigBlueButton.logger.debug " sample rate: #{info[:sample_rate]}, duration: #{info[:duration]}" - if !info[:audio] || !info[:duration] BigBlueButton.logger.warn " This audio file is corrupt! It will be removed from the output." corrupt_audios << audiofile end + BigBlueButton.logger.debug " format: #{info[:format][:format_name]}, codec: #{info[:audio][:codec_name]}" + BigBlueButton.logger.debug " sample rate: #{info[:sample_rate]}, duration: #{info[:duration]}" + audioinfo[audiofile] = info end @@ -82,70 +82,60 @@ module BigBlueButton dump(edl) end - input_index = 0 - output_index = 0 ffmpeg_inputs = [] - ffmpeg_filters = [] + ffmpeg_filter = '' BigBlueButton.logger.info "Generating ffmpeg command" for i in 0...(edl.length - 1) entry = edl[i] audio = entry[:audio] duration = entry[:next_timestamp] - entry[:timestamp] - # Check for and handle audio files with mismatched lengths (generated - # by buggy versions of freeswitch in old BigBlueButton - if audio and entry[:original_duration] and - (audioinfo[audio[:filename]][:duration].to_f / entry[:original_duration]) < 0.997 and - ((entry[:original_duration] - audioinfo[audio[:filename]][:duration]).to_f / - entry[:original_duration]).abs < 0.05 - speed = audioinfo[audio[:filename]][:duration].to_f / entry[:original_duration] - BigBlueButton.logger.info " Using input #{audio[:filename]}" + ffmpeg_filter << "[a_edl#{i}_prev];\n" if i > 0 - BigBlueButton.logger.warn " Audio file length mismatch, adjusting speed to #{speed}" - - # Have to calculate the start point after the atempo filter in this case, - # since it can affect the audio start time. - # Also reset the pts to start at 0, so the duration trim works correctly. - 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}]" - ffmpeg_filters << filter - - ffmpeg_inputs << { - :filename => audio[:filename], - :seek => 0 - } - - input_index += 1 - output_index += 1 - - # Normal audio input handling. Skip this input and generate silence - # if the seekpoint is past the end of the audio, which can happen - # if events are slightly misaligned and you get unlucky with a - # start/stop or chapter break. - elsif audio and audio[:timestamp] < audioinfo[audio[:filename]][:duration] + if audio BigBlueButton.logger.info " Using input #{audio[:filename]}" - filter = "[#{input_index}] " - filter << "#{FFMPEG_AFORMAT},apad,atrim=end=#{ms_to_s(duration)} [out#{output_index}]" - ffmpeg_filters << filter + speed = 1 + seek = audio[:timestamp] - ffmpeg_inputs << { - :filename => audio[:filename], - :seek => audio[:timestamp] - } + # Check for and handle audio files with mismatched lengths (generated + # by buggy versions of freeswitch in old BigBlueButton + if entry[:original_duration] && (audioinfo[audio[:filename]][:duration].to_f / entry[:original_duration]) < 0.997 && + ((entry[:original_duration] - audioinfo[audio[:filename]][:duration]).to_f / + entry[:original_duration]).abs < 0.05 + BigBlueButton.logger.warn " Audio file length mismatch, adjusting speed to #{speed}" + speed = audioinfo[audio[:filename]][:duration].to_f / entry[:original_duration] + seek = 0 + end + + # Skip this input and generate silence if the seekpoint is past the end of the audio, which can happen + # if events are slightly misaligned and you get unlucky with a start/stop or chapter break. + if audio[:timestamp] < (audioinfo[audio[:filename]][:duration] * speed) + input_index = ffmpeg_inputs.length + ffmpeg_inputs << { + filename: audio[:filename], + seek: seek + } + ffmpeg_filter << "[#{input_index}]#{FFMPEG_AFORMAT},apad" + else + ffmpeg_filter << "#{FFMPEG_AEVALSRC},#{FFMPEG_AFORMAT}" + end - input_index += 1 - output_index += 1 + ffmpeg_filter << ",atempo=#{speed},atrim=start=#{ms_to_s(audio[:timestamp])}" if speed != 1 + ffmpeg_filter << ",asetpts=PTS-STARTPTS" else BigBlueButton.logger.info " Generating silence" - ffmpeg_filters << "#{FFMPEG_AEVALSRC},#{FFMPEG_AFORMAT},atrim=end=#{ms_to_s(duration)} [out#{output_index}]" + ffmpeg_filter << "#{FFMPEG_AEVALSRC},#{FFMPEG_AFORMAT}" + end - output_index += 1 + if i > 0 + ffmpeg_filter << "[a_edl#{i}];\n" + ffmpeg_filter << "[a_edl#{i}_prev][a_edl#{i}]concat=n=2:a=1:v=0" end + + ffmpeg_filter << ",atrim=end=#{ms_to_s(entry[:next_timestamp])}" end ffmpeg_cmd = [*FFMPEG] @@ -157,19 +147,15 @@ module BigBlueButton end ffmpeg_cmd += ['-i', input[:filename]] end - ffmpeg_filter = ffmpeg_filters.join(' ; ') - - if output_index > 1 - # Add the final concat filter - ffmpeg_filter << " ; " - (0...output_index).each { |i| ffmpeg_filter << "[out#{i}]" } - ffmpeg_filter << " concat=n=#{output_index}:a=1:v=0" - else - # Only one input, no need for concat filter - ffmpeg_filter << " ; [out0] anull" + + BigBlueButton.logger.debug(' ffmpeg filter_complex_script:') + BigBlueButton.logger.debug(ffmpeg_filter) + filter_complex_script = "#{output_basename}.filter" + File.open(filter_complex_script, 'w') do |io| + io.write(ffmpeg_filter) end - ffmpeg_cmd += ['-filter_complex', ffmpeg_filter] + ffmpeg_cmd << '-filter_complex_script' << filter_complex_script output = "#{output_basename}.#{WF_EXT}" ffmpeg_cmd += [*FFMPEG_WF_ARGS, output] -- GitLab