feat: adds option to split audio recording in multiple files by length

FreeSWITCH sometimes drop frames in very long recordings generating
a raw file smaller than the total time recorded. The other issue is the
occasional drifiting.

This adds option to split audio recording in multiple files by
duration to avoid this problem in setups that are prone to it. Disabled
by default.
This commit is contained in:
germanocaumo 2020-10-02 18:51:04 -03:00 committed by prlanzarin
parent 6a3a3bdf1d
commit 4514649c4f
4 changed files with 109 additions and 7 deletions

View File

@ -41,6 +41,8 @@ trait SystemConfiguration {
lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings")
lazy val voiceConfRecordCodec = Try(config.getString("voiceConf.recordCodec")).getOrElse("wav")
lazy val voiceConfRecordEnableFileSplitter = Try(config.getBoolean("voiceConf.recordEnableFileSplitter")).getOrElse(false)
lazy val voiceConfRecordFileSplitterIntervalInMinutes = Try(config.getInt("voiceConf.recordFileSplitterIntervalInMinutes")).getOrElse(15)
lazy val checkVoiceRecordingInterval = Try(config.getInt("voiceConf.checkRecordingInterval")).getOrElse(19)
lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43)
lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(true)

View File

@ -0,0 +1,76 @@
package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder
class RecordingFileSplitter(
val liveMeeting: LiveMeeting,
val outGW: OutMsgRouter,
val stream: String
) extends SystemConfiguration {
var startRecTimer: java.util.Timer = null
var stopRecTimer: java.util.Timer = null
var currentStreamPath: String = stream
var previousStreamPath: String = stream
val lastPointPos = stream.lastIndexOf('.')
val pathWithoutExtension: String = stream.substring(0, lastPointPos)
val extension: String = stream.substring(lastPointPos, stream.length())
class RecordingFileSplitterStopTask extends java.util.TimerTask {
def run() {
val event = MsgBuilder.buildStopRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
previousStreamPath
)
outGW.send(event)
}
}
class RecordingFileSplitterStartTask extends java.util.TimerTask {
var currentFileNumber: Int = 0
def run() {
val newStreamPath = pathWithoutExtension + "_" + String.valueOf(currentFileNumber) + extension;
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, newStreamPath)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
newStreamPath
)
outGW.send(event)
if (currentStreamPath == stream) {
// first file, no previous one to stop
previousStreamPath = newStreamPath
} else {
previousStreamPath = currentStreamPath
// stop previous recording, wait 5 seconds to avoid interruptions
stopRecTimer = new java.util.Timer()
stopRecTimer.schedule(new RecordingFileSplitterStopTask(), 5000L)
}
currentStreamPath = newStreamPath
currentFileNumber = currentFileNumber + 1
}
}
def stop() {
startRecTimer.cancel()
if (stopRecTimer != null) {
stopRecTimer.cancel()
}
}
def start(): Unit = {
startRecTimer = new java.util.Timer()
startRecTimer.schedule(
new RecordingFileSplitterStartTask(),
0L, //initial delay
voiceConfRecordFileSplitterIntervalInMinutes * 60000L //subsequent rate
);
}
}

View File

@ -22,6 +22,7 @@ import scala.concurrent.duration._
object VoiceApp extends SystemConfiguration {
// Key is userId
var toggleListenOnlyTasks: Map[String, Cancellable] = Map()
var recordingFileSplitters: Map[String, RecordingFileSplitter] = Map()
def genRecordPath(
recordDir: String,
@ -45,6 +46,13 @@ object VoiceApp extends SystemConfiguration {
}
def stopRecordingVoiceConference(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
// stop file splitter
val voiceconf = liveMeeting.props.voiceProp.voiceConf
if (recordingFileSplitters.contains(voiceconf)) {
recordingFileSplitters(voiceconf).stop();
recordingFileSplitters = recordingFileSplitters - (voiceconf)
}
val recStreams = MeetingStatus2x.getVoiceRecordingStreams(liveMeeting.status)
recStreams foreach { rs =>
@ -68,13 +76,24 @@ object VoiceApp extends SystemConfiguration {
now,
voiceConfRecordCodec
)
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, recordFile)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
recordFile
)
outGW.send(event)
if (voiceConfRecordEnableFileSplitter) {
val voiceconf = liveMeeting.props.voiceProp.voiceConf
if (recordingFileSplitters.contains(voiceconf)) {
recordingFileSplitters(voiceconf).stop();
recordingFileSplitters = recordingFileSplitters - (voiceconf)
}
val fileSplitter = new RecordingFileSplitter(liveMeeting, outGW, recordFile)
recordingFileSplitters = recordingFileSplitters + (voiceconf -> fileSplitter)
fileSplitter.start()
} else {
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, recordFile)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
recordFile
)
outGW.send(event)
}
}
def broadcastUserMutedVoiceEvtMsg(

View File

@ -104,6 +104,11 @@ voiceConf {
# Use ogg instead of wav to get smaller audio files.
# Valid values "wav", "ogg", "flac", "opus"
recordCodec = "opus"
# FreeSWITCH sometimes loses audio frames in long recordings
# this enable split audio recording in smaller files
recordEnableFileSplitter = false
# Duration of each recorded audio file if splitter is enabled
recordFileSplitterIntervalInMinutes = 15
# Interval seconds to check if FreeSWITCH is recording.
checkRecordingInterval = 23
# Internval seconds to sync voice users status.