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:
parent
6a3a3bdf1d
commit
4514649c4f
@ -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)
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user