From c40dfa7486363f1266d278cdde413d18efe6e857 Mon Sep 17 00:00:00 2001 From: Paul Gregoire Date: Mon, 18 Mar 2024 06:23:51 -0700 Subject: [PATCH] Refactor codecs, logger, and rtmp client executor. Update spring --- client/pom.xml | 2 +- .../main/java/org/red5/client/Red5Client.java | 2 +- .../net/rtmp/BaseRTMPClientHandler.java | 10 ++ .../net/rtmp/RTMPClientConnManager.java | 2 +- common/pom.xml | 4 +- .../red5/logging/LoggingContextSelector.java | 7 +- .../org/red5/logging/Red5LoggerFactory.java | 66 ++++---- .../main/java/org/red5/server/api/Red5.java | 4 +- .../red5/server/net/rtmp/RTMPConnection.java | 5 +- .../server/stream/ClientBroadcastStream.java | 148 ++++++++++-------- io/pom.xml | 2 +- io/src/main/java/org/red5/codec/AACAudio.java | 8 +- io/src/main/java/org/red5/codec/AVCVideo.java | 60 +------ .../java/org/red5/codec/AbstractAudio.java | 9 +- .../java/org/red5/codec/AbstractVideo.java | 42 ++++- .../main/java/org/red5/codec/AudioCodec.java | 2 +- .../main/java/org/red5/codec/HEVCVideo.java | 64 +------- .../org/red5/codec/IAudioStreamCodec.java | 19 ++- .../org/red5/codec/IVideoStreamCodec.java | 31 ++-- io/src/main/java/org/red5/codec/MP3Audio.java | 6 +- .../main/java/org/red5/codec/OpusAudio.java | 4 + .../main/java/org/red5/codec/SpeexAudio.java | 4 + .../main/java/org/red5/codec/ULAWAudio.java | 4 + pom.xml | 4 +- server/pom.xml | 2 +- .../red5/server/plugin/PluginLauncher.java | 1 + server/src/main/server/conf/jee-container.xml | 2 +- service/pom.xml | 2 +- servlet/pom.xml | 2 +- 29 files changed, 249 insertions(+), 269 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index 1d2614a71..2def3a12b 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-client diff --git a/client/src/main/java/org/red5/client/Red5Client.java b/client/src/main/java/org/red5/client/Red5Client.java index 5d168b825..2c9f61801 100644 --- a/client/src/main/java/org/red5/client/Red5Client.java +++ b/client/src/main/java/org/red5/client/Red5Client.java @@ -18,7 +18,7 @@ public final class Red5Client { /** * Current server version with revision */ - public static final String VERSION = "Red5 Client 1.3.29"; + public static final String VERSION = "Red5 Client 1.3.30"; /** * Create a new Red5Client object using the connection local to the current thread A bit of magic that lets you access the red5 scope diff --git a/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java b/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java index c18f9f8e5..a3e7867f0 100644 --- a/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java +++ b/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java @@ -52,6 +52,7 @@ import org.red5.server.stream.consumer.ConnectionConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * Base class for clients (RTMP and RTMPT) @@ -907,6 +908,15 @@ public void setProtocol(String protocol) throws Exception { public void setConnection(RTMPConnection conn) { this.conn = conn; this.conn.setHandler(this); + if (conn.getExecutor() == null) { + // setup executor + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setDaemon(true); + executor.setMaxPoolSize(1); + executor.initialize(); + conn.setExecutor(executor); + } } /** diff --git a/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java b/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java index 8bdc4ad71..b9e5d08c0 100644 --- a/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java +++ b/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java @@ -42,7 +42,7 @@ public class RTMPClientConnManager implements IConnectionManager private static int executorQueueCapacity = 32; // whether or not to use the ThreadPoolTaskExecutor for incoming messages - protected static boolean enableTaskExecutor; + protected static boolean enableTaskExecutor = true; protected static IConnectionManager instance; diff --git a/common/pom.xml b/common/pom.xml index 713c995de..1ea3a1e97 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-server-common @@ -113,7 +113,7 @@ net.engio mbassador - 1.3.29 + 1.3.30 --> junit diff --git a/common/src/main/java/org/red5/logging/LoggingContextSelector.java b/common/src/main/java/org/red5/logging/LoggingContextSelector.java index 8fe6edd1d..37390b9be 100644 --- a/common/src/main/java/org/red5/logging/LoggingContextSelector.java +++ b/common/src/main/java/org/red5/logging/LoggingContextSelector.java @@ -8,7 +8,6 @@ package org.red5.logging; import java.net.URL; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -37,7 +36,7 @@ public class LoggingContextSelector implements ContextSelector { private static final Semaphore lock = new Semaphore(1, true); - private static final ConcurrentMap contextMap = new ConcurrentHashMap<>(6, 0.9f, 1); + private static final ConcurrentMap contextMap = new ConcurrentHashMap<>(); private static LoggerContext DEFAULT_CONTEXT; @@ -194,9 +193,7 @@ public LoggerContext detachLoggerContext(String contextName) { } public List getContextNames() { - List list = new ArrayList<>(); - list.addAll(contextMap.keySet()); - return list; + return List.copyOf(contextMap.keySet()); } public void setContextConfigFile(String contextConfigFile) { diff --git a/common/src/main/java/org/red5/logging/Red5LoggerFactory.java b/common/src/main/java/org/red5/logging/Red5LoggerFactory.java index e5b3bbaa9..dd74ad112 100644 --- a/common/src/main/java/org/red5/logging/Red5LoggerFactory.java +++ b/common/src/main/java/org/red5/logging/Red5LoggerFactory.java @@ -29,11 +29,20 @@ public class Red5LoggerFactory { public static boolean DEBUG = true; + // root logger + private static Logger rootLogger; + + // context selector + private static ContextSelector contextSelector; + static { DEBUG = Boolean.valueOf(System.getProperty("logback.debug", "false")); try { - Logger logger = LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME); - logger.debug("Red5LoggerFactory instanced by Thread: {}", Thread.currentThread().getName()); + rootLogger = LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.debug("Red5LoggerFactory instanced by Thread: {}", Thread.currentThread().getName()); + rootLogger.debug("Logging context selector: {} impl: {}", System.getProperty("logback.ContextSelector"), getContextSelector()); + // get the context selector here + contextSelector = getContextSelector(); } catch (Throwable t) { t.printStackTrace(); } @@ -43,7 +52,7 @@ public static Logger getLogger(Class clazz) { if (DEBUG) { System.out.printf("getLogger for: %s thread: %s%n", clazz.getName(), Thread.currentThread().getName()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); - System.out.printf("class loader: %s%n", cl); + rootLogger.debug("Class loader: {}", cl); // if cl is WebappClassLoader type we can probably get the context from it //if (cl instanceof WebappClassLoader) { // getContextName() @@ -52,28 +61,14 @@ public static Logger getLogger(Class clazz) { Logger logger = null; if (useLogback) { // determine the red5 app name or servlet context name - String contextName = CoreConstants.DEFAULT_CONTEXT_NAME; + final String threadName = Thread.currentThread().getName(); // route the Launcher entries to the correct context - String[] parts = Thread.currentThread().getName().split("Loader:/"); - if (parts.length > 1) { - contextName = parts[1]; + if (threadName.startsWith("Loader:/")) { + String contextName = threadName.split("Loader:/")[1]; + logger = getLogger(clazz, contextName); + } else { + logger = getLogger(clazz, CoreConstants.DEFAULT_CONTEXT_NAME); } - logger = Red5LoggerFactory.getLogger(clazz, contextName); - /* - * // get a reference to our caller Class caller = Thread.currentThread().getStackTrace()[2].getClassName(); if (DEBUG) { System.out.printf("Caller class: %s classloader: %s%n", - * caller, caller.getClassLoader()); } // if the incoming class extends StatefulScopeWrappingAdapter we lookup the context by scope name boolean scopeAware = - * StatefulScopeWrappingAdapter.class.isAssignableFrom(caller); if (DEBUG) { System.out.printf("scopeAware: %s%n", scopeAware); } if (scopeAware) { try { Class wrapper = null; if - * ((wrapper = caller.asSubclass(StatefulScopeWrappingAdapter.class)) != null) { Method getScope = wrapper.getMethod("getScope", new Class[0]); // NPE will occur here if the scope - * is not yet set on the application adapter IScope scope = (IScope) getScope.invoke(null, new Object[0]); if (DEBUG) { System.out.printf("scope: %s%n", scope); } contextName = - * scope.getName(); } } catch (Exception cce) { //cclog.warn("Exception {}", e); } } else { // if the incoming class is a servlet we lookup the context name boolean - * servletScopeAware = Servlet.class.isAssignableFrom(caller); if (DEBUG) { System.out.printf("servletScopeAware: %s%n", servletScopeAware); } if (servletScopeAware) { try { Class - * wrapper = null; if ((wrapper = caller.asSubclass(Servlet.class)) != null) { //ServletConfig getServletConfig Method getServletConfig = wrapper.getMethod("getServletConfig", new - * Class[0]); // NPE will occur here if the scope is not yet set on the application adapter ServletConfig config = (ServletConfig) getServletConfig.invoke(null, new Object[0]); if - * (DEBUG) { System.out.printf("config: %s%n", config); } contextName = config.getServletContext().getContextPath().replaceAll("/", ""); if ("".equals(contextName)) { contextName = - * "root"; } } } catch (Exception cce) { //cclog.warn("Exception {}", e); } } else { // route the Launcher entries to the correct context String[] parts = - * Thread.currentThread().getName().split("Loader:/"); if (parts.length > 1) { contextName = parts[1]; } else { contextName = CoreConstants.DEFAULT_CONTEXT_NAME; } } } logger = - * Red5LoggerFactory.getLogger(clazz, contextName); - */ } if (logger == null) { logger = LoggerFactory.getLogger(clazz); @@ -81,8 +76,7 @@ public static Logger getLogger(Class clazz) { return logger; } - @SuppressWarnings({ "rawtypes" }) - public static Logger getLogger(Class clazz, String contextName) { + public static Logger getLogger(Class clazz, String contextName) { return getLogger(clazz.getName(), contextName); } @@ -97,9 +91,8 @@ public static Logger getLogger(String name, String contextName) { contextName = CoreConstants.DEFAULT_CONTEXT_NAME; } try { - ContextSelector selector = Red5LoggerFactory.getContextSelector(); // get the context for the given context name or default if null - LoggerContext context = selector.getLoggerContext(contextName); + LoggerContext context = contextSelector.getLoggerContext(contextName); // and if we get here, fall back to the default context if (context == null) { System.err.printf("No context named %s was found!!%n", contextName); @@ -107,12 +100,13 @@ public static Logger getLogger(String name, String contextName) { // get the logger from the context or default context if (context != null) { logger = context.getLogger(name); - // System.out.printf("Application name: %s in context: %s%n", context.getProperty(KEY_APP_NAME), contextName); + if (DEBUG) { + rootLogger.debug("Application name: {} in context: {}", context.getProperty(CoreConstants.CONTEXT_NAME_KEY), contextName); + } } } catch (Exception e) { // no logback, use whatever logger is in-place - System.err.printf("Exception %s%n", e.getMessage()); - e.printStackTrace(); + rootLogger.error("Exception {}", e); } } if (logger == null) { @@ -122,26 +116,26 @@ public static Logger getLogger(String name, String contextName) { } public static ContextSelector getContextSelector() { + ContextSelector selector = null; if (useLogback) { ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton(); - ContextSelector selector = contextSelectorBinder.getContextSelector(); + selector = contextSelectorBinder.getContextSelector(); if (selector == null) { if (DEBUG) { - System.err.println("Context selector was null, creating default context"); + rootLogger.error("Context selector was null, creating default context"); } LoggerContext defaultLoggerContext = new LoggerContext(); defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); try { contextSelectorBinder.init(defaultLoggerContext, null); selector = contextSelectorBinder.getContextSelector(); + rootLogger.debug("Context selector: {}", selector.getClass().getName()); } catch (Exception e) { - e.printStackTrace(); + rootLogger.error("Exception {}", e); } } - //System.out.printf("Context selector: %s%n", selector.getClass().getName()); - return selector; } - return null; + return selector; } public static void setUseLogback(boolean useLogback) { diff --git a/common/src/main/java/org/red5/server/api/Red5.java b/common/src/main/java/org/red5/server/api/Red5.java index a2dc5dc3d..a7da45f8a 100644 --- a/common/src/main/java/org/red5/server/api/Red5.java +++ b/common/src/main/java/org/red5/server/api/Red5.java @@ -57,12 +57,12 @@ public final class Red5 { /** * Server version with revision */ - public static final String VERSION = "Red5 Server 1.3.29"; + public static final String VERSION = "Red5 Server 1.3.30"; /** * Server version for fmsVer requests */ - public static final String FMS_VERSION = "RED5/1,3,29,0"; + public static final String FMS_VERSION = "RED5/1,3,30,0"; /** * Server capabilities diff --git a/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java b/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java index ef3932829..c2ceefa17 100755 --- a/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java +++ b/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java @@ -1483,10 +1483,9 @@ public void onSuccess(Packet packet) { } log.info("Rejected task: {}", task); } catch (Throwable e) { - log.error("Incoming message failed task: {}", task, e); + log.warn("Incoming message failed task: {}", task, e); if (isDebug) { - log.debug("Execution rejected on {} - {}", getSessionId(), RTMP.states[getStateCode()]); - log.debug("Lock permits - decode: {} encode: {}", decoderLock.availablePermits(), encoderLock.availablePermits()); + log.debug("Execution rejected on {} - {} lock permits - decode: {} encode: {}", getSessionId(), RTMP.states[getStateCode()], decoderLock.availablePermits(), encoderLock.availablePermits()); } } } diff --git a/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java b/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java index 9f2bed669..61b149120 100644 --- a/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java +++ b/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java @@ -7,6 +7,11 @@ package org.red5.server.stream; +import static org.red5.server.net.rtmp.message.Constants.TYPE_AUDIO_DATA; +import static org.red5.server.net.rtmp.message.Constants.TYPE_INVOKE; +import static org.red5.server.net.rtmp.message.Constants.TYPE_NOTIFY; +import static org.red5.server.net.rtmp.message.Constants.TYPE_VIDEO_DATA; + import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; @@ -65,7 +70,6 @@ import org.red5.server.messaging.PipeConnectionEvent; import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.net.rtmp.event.IRTMPEvent; -import org.red5.server.net.rtmp.event.Invoke; import org.red5.server.net.rtmp.event.Notify; import org.red5.server.net.rtmp.event.VideoData; import org.red5.server.net.rtmp.message.Constants; @@ -97,6 +101,8 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie private static final Logger log = LoggerFactory.getLogger(ClientBroadcastStream.class); + private static final boolean isDebug = log.isDebugEnabled(); + /** * Whether or not to automatically record the associated stream. */ @@ -264,7 +270,7 @@ public void dispatchEvent(IEvent event) { case STREAM_CONTROL: case STREAM_DATA: // create the event - IRTMPEvent rtmpEvent; + final IRTMPEvent rtmpEvent; try { rtmpEvent = (IRTMPEvent) event; } catch (ClassCastException e) { @@ -276,13 +282,7 @@ public void dispatchEvent(IEvent event) { if (rtmpEvent.getSourceType() != Constants.SOURCE_TYPE_LIVE) { rtmpEvent.setSourceType(Constants.SOURCE_TYPE_LIVE); } - /* - * if (log.isTraceEnabled()) { // If this is first packet save its timestamp; expect it is // absolute? no matter: it's never used! if (firstPacketTime == -1) { firstPacketTime = - * rtmpEvent.getTimestamp(); log.trace(String.format("CBS=@%08x: rtmpEvent=%s creation=%s firstPacketTime=%d", System.identityHashCode(this), rtmpEvent.getClass().getSimpleName(), - * creationTime, firstPacketTime)); } else { log.trace(String.format("CBS=@%08x: rtmpEvent=%s creation=%s firstPacketTime=%d timestamp=%d", System.identityHashCode(this), - * rtmpEvent.getClass().getSimpleName(), creationTime, firstPacketTime, eventTime)); } } - */ - //get the buffer only once per call + // get the buffer only once per call IoBuffer buf = null; if (rtmpEvent instanceof IStreamData && (buf = ((IStreamData) rtmpEvent).getData()) != null) { bytesReceived += buf.limit(); @@ -294,65 +294,71 @@ public void dispatchEvent(IEvent event) { info = (StreamCodecInfo) codecInfo; } //log.trace("Stream codec info: {}", info); - if (rtmpEvent instanceof AudioData) { - //log.trace("Audio: {}", eventTime); - IAudioStreamCodec audioStreamCodec = null; - if (checkAudioCodec) { - // dont try to read codec info from 0 length audio packets - if (buf.limit() > 0) { - audioStreamCodec = AudioCodecFactory.getAudioCodec(buf); + switch (rtmpEvent.getDataType()) { + case TYPE_AUDIO_DATA: // AudioData + //log.trace("Audio: {}", eventTime); + IAudioStreamCodec audioStreamCodec = null; + if (checkAudioCodec) { + // dont try to read codec info from 0 length audio packets + if (buf.limit() > 0) { + audioStreamCodec = AudioCodecFactory.getAudioCodec(buf); + if (info != null) { + info.setAudioCodec(audioStreamCodec); + } + checkAudioCodec = false; + } + } else if (codecInfo != null) { + audioStreamCodec = codecInfo.getAudioCodec(); + } + if (audioStreamCodec != null) { + audioStreamCodec.addData(buf); + } + if (info != null) { + info.setHasAudio(true); + } + break; + case TYPE_VIDEO_DATA: // VideoData + //log.trace("Video: {}", eventTime); + IVideoStreamCodec videoStreamCodec = null; + if (checkVideoCodec) { + videoStreamCodec = VideoCodecFactory.getVideoCodec(buf); if (info != null) { - info.setAudioCodec(audioStreamCodec); + info.setVideoCodec(videoStreamCodec); } - checkAudioCodec = false; + checkVideoCodec = false; + } else if (codecInfo != null) { + videoStreamCodec = codecInfo.getVideoCodec(); + } + if (videoStreamCodec != null) { + videoStreamCodec.addData(buf, eventTime); } - } else if (codecInfo != null) { - audioStreamCodec = codecInfo.getAudioCodec(); - } - if (audioStreamCodec != null) { - audioStreamCodec.addData(buf); - } - if (info != null) { - info.setHasAudio(true); - } - } else if (rtmpEvent instanceof VideoData) { - //log.trace("Video: {}", eventTime); - IVideoStreamCodec videoStreamCodec = null; - if (checkVideoCodec) { - videoStreamCodec = VideoCodecFactory.getVideoCodec(buf); if (info != null) { - info.setVideoCodec(videoStreamCodec); + info.setHasVideo(true); } - checkVideoCodec = false; - } else if (codecInfo != null) { - videoStreamCodec = codecInfo.getVideoCodec(); - } - if (videoStreamCodec != null) { - videoStreamCodec.addData(buf, eventTime); - } - if (info != null) { - info.setHasVideo(true); - } - } else if (rtmpEvent instanceof Invoke) { - //Invoke invokeEvent = (Invoke) rtmpEvent; - //log.debug("Invoke action: {}", invokeEvent.getAction()); - // event / stream listeners will not be notified of invokes - return; - } else if (rtmpEvent instanceof Notify) { - Notify notifyEvent = (Notify) rtmpEvent; - String action = notifyEvent.getAction(); - //if (log.isDebugEnabled()) { - //log.debug("Notify action: {}", action); - //} - if ("onMetaData".equals(action)) { - // store the metadata - try { - //log.debug("Setting metadata"); - setMetaData(notifyEvent.duplicate()); - } catch (Exception e) { - log.warn("Metadata could not be duplicated for this stream", e); + break; + case TYPE_NOTIFY: + Notify notifyEvent = (Notify) rtmpEvent; + String action = notifyEvent.getAction(); + //if (isDebug) { + //log.debug("Notify action: {}", action); + //} + if ("onMetaData".equals(action)) { + // store the metadata + try { + //log.debug("Setting metadata"); + setMetaData(notifyEvent.duplicate()); + } catch (Exception e) { + log.warn("Metadata could not be duplicated for this stream", e); + } } - } + break; + case TYPE_INVOKE: + //Invoke invokeEvent = (Invoke) rtmpEvent; + //log.debug("Invoke action: {}", invokeEvent.getAction()); + // event / stream listeners will not be notified of invokes + return; + default: + log.debug("Unknown: {}", rtmpEvent); } // update last event time if (eventTime > latestTimeStamp) { @@ -367,7 +373,7 @@ public void dispatchEvent(IEvent event) { // create new RTMP message, initialize it and push through pipe RTMPMessage msg = RTMPMessage.build(rtmpEvent, eventTime); livePipe.pushMessage(msg); - } else if (log.isDebugEnabled()) { + } else if (isDebug) { log.debug("Live pipe was null, message was not pushed"); } } catch (IOException err) { @@ -379,7 +385,7 @@ public void dispatchEvent(IEvent event) { try { listener.packetReceived(this, (IStreamPacket) rtmpEvent); } catch (Exception e) { - log.error("Error while notifying listener {}", listener, e); + log.warn("Error while notifying listener {}", listener, e); if (listener instanceof RecordingListener) { sendRecordFailedNotify(e.getMessage()); } @@ -389,10 +395,14 @@ public void dispatchEvent(IEvent event) { break; default: // ignored event - //log.debug("Ignoring event: {}", event.getType()); + if (isDebug) { + log.debug("Ignoring event: {}", event.getType()); + } } } else { - log.debug("Event was of wrong type or stream is closed ({})", closed); + if (isDebug) { + log.debug("Event was of wrong type or stream is closed ({})", closed); + } } } @@ -551,7 +561,7 @@ protected void notifyBroadcastStart() { params.put("creationdate", ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.of("UTC")).format(DateTimeFormatter.ISO_INSTANT)); cal.setTimeInMillis(startTime); params.put("startdate", ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.of("UTC")).format(DateTimeFormatter.ISO_INSTANT)); - if (log.isDebugEnabled()) { + if (isDebug) { log.debug("Params: {}", params); } out.writeMap(params); @@ -627,7 +637,7 @@ public void onPipeConnectionEvent(PipeConnectionEvent event) { break; case PROVIDER_DISCONNECT: //log.debug("Provider disconnect"); - //if (log.isDebugEnabled() && livePipe != null) { + //if (isDebug && livePipe != null) { //log.debug("Provider: {}", livePipe.getClass().getName()); //} if (livePipe == event.getSource()) { @@ -637,7 +647,7 @@ public void onPipeConnectionEvent(PipeConnectionEvent event) { case CONSUMER_CONNECT_PUSH: //log.debug("Consumer connect"); IPipe pipe = (IPipe) event.getSource(); - //if (log.isDebugEnabled() && pipe != null) { + //if (isDebug && pipe != null) { //log.debug("Consumer: {}", pipe.getClass().getName()); //} if (livePipe == pipe) { diff --git a/io/pom.xml b/io/pom.xml index c97c00f29..85f9c267d 100644 --- a/io/pom.xml +++ b/io/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-io diff --git a/io/src/main/java/org/red5/codec/AACAudio.java b/io/src/main/java/org/red5/codec/AACAudio.java index 4f05729f6..122ac7a6c 100644 --- a/io/src/main/java/org/red5/codec/AACAudio.java +++ b/io/src/main/java/org/red5/codec/AACAudio.java @@ -26,11 +26,6 @@ public class AACAudio extends AbstractAudio { public static final int[] AAC_SAMPLERATES = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; - /** - * AAC audio codec constant - */ - static final String CODEC_NAME = "AAC"; - /** * Block of data (AAC DecoderConfigurationRecord) */ @@ -38,13 +33,14 @@ public class AACAudio extends AbstractAudio { /** Constructs a new AACAudio */ public AACAudio() { + codec = AudioCodec.AAC; this.reset(); } /** {@inheritDoc} */ @Override public String getName() { - return CODEC_NAME; + return codec.name(); } /** {@inheritDoc} */ diff --git a/io/src/main/java/org/red5/codec/AVCVideo.java b/io/src/main/java/org/red5/codec/AVCVideo.java index 9fa1bda0f..9b504a2da 100644 --- a/io/src/main/java/org/red5/codec/AVCVideo.java +++ b/io/src/main/java/org/red5/codec/AVCVideo.java @@ -7,9 +7,6 @@ package org.red5.codec; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicInteger; - import org.apache.mina.core.buffer.IoBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,40 +21,17 @@ public class AVCVideo extends AbstractVideo { private static Logger log = LoggerFactory.getLogger(AVCVideo.class); - /** - * AVC video codec constant - */ - static final String CODEC_NAME = "AVC"; + private static boolean isDebug = log.isDebugEnabled(); /** Video decoder configuration data */ private FrameData decoderConfiguration; - /** - * Storage for frames buffered since last key frame - */ - private final CopyOnWriteArrayList interframes = new CopyOnWriteArrayList<>(); - - /** - * Number of frames buffered since last key frame - */ - private final AtomicInteger numInterframes = new AtomicInteger(0); - - /** - * Whether or not to buffer interframes - */ - private boolean bufferInterframes = false; - /** Constructs a new AVCVideo. */ public AVCVideo() { + codec = VideoCodec.AVC; this.reset(); } - /** {@inheritDoc} */ - @Override - public String getName() { - return CODEC_NAME; - } - /** {@inheritDoc} */ @Override public boolean canDropFrames() { @@ -109,8 +83,7 @@ public boolean addData(IoBuffer data, int timestamp) { if ((frameType & 0x0f) == VideoCodec.AVC.getId()) { // check for keyframe if ((frameType & 0xf0) == FLV_FRAME_KEY) { - //log.trace("Key frame"); - if (log.isDebugEnabled()) { + if (isDebug) { log.debug("Keyframe - AVC type: {}", avcType); } // rewind @@ -139,7 +112,7 @@ public boolean addData(IoBuffer data, int timestamp) { //log.trace("Keyframes: {}", keyframes.size()); } else if (bufferInterframes) { //log.trace("Interframe"); - if (log.isDebugEnabled()) { + if (isDebug) { log.debug("Interframe - AVC type: {}", avcType); } // rewind @@ -153,7 +126,7 @@ public boolean addData(IoBuffer data, int timestamp) { interframes.add(new FrameData(data)); } } catch (Throwable e) { - log.error("Failed to buffer interframe", e); + log.warn("Failed to buffer interframe", e); } //log.trace("Interframes: {}", interframes.size()); } @@ -176,27 +149,4 @@ public IoBuffer getDecoderConfiguration() { return decoderConfiguration.getFrame(); } - /** {@inheritDoc} */ - @Override - public int getNumInterframes() { - return numInterframes.get(); - } - - /** {@inheritDoc} */ - @Override - public FrameData getInterframe(int index) { - if (index < numInterframes.get()) { - return interframes.get(index); - } - return null; - } - - public boolean isBufferInterframes() { - return bufferInterframes; - } - - public void setBufferInterframes(boolean bufferInterframes) { - this.bufferInterframes = bufferInterframes; - } - } diff --git a/io/src/main/java/org/red5/codec/AbstractAudio.java b/io/src/main/java/org/red5/codec/AbstractAudio.java index 6cd7c80f8..d7a4b36fe 100644 --- a/io/src/main/java/org/red5/codec/AbstractAudio.java +++ b/io/src/main/java/org/red5/codec/AbstractAudio.java @@ -4,9 +4,16 @@ public class AbstractAudio implements IAudioStreamCodec { + protected AudioCodec codec; + + @Override + public AudioCodec getCodec() { + return codec; + } + @Override public String getName() { - return null; + return codec.name(); } @Override diff --git a/io/src/main/java/org/red5/codec/AbstractVideo.java b/io/src/main/java/org/red5/codec/AbstractVideo.java index 48e03e652..e0ccc7c45 100644 --- a/io/src/main/java/org/red5/codec/AbstractVideo.java +++ b/io/src/main/java/org/red5/codec/AbstractVideo.java @@ -1,12 +1,15 @@ package org.red5.codec; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.mina.core.buffer.IoBuffer; import org.red5.io.IoConstants; public class AbstractVideo implements IVideoStreamCodec, IoConstants { + protected VideoCodec codec; + /** Current timestamp for the stored keyframe */ protected int keyframeTimestamp; @@ -15,9 +18,29 @@ public class AbstractVideo implements IVideoStreamCodec, IoConstants { */ protected final CopyOnWriteArrayList keyframes = new CopyOnWriteArrayList<>(); + /** + * Storage for frames buffered since last key frame + */ + protected final CopyOnWriteArrayList interframes = new CopyOnWriteArrayList<>(); + + /** + * Number of frames buffered since last key frame + */ + protected final AtomicInteger numInterframes = new AtomicInteger(0); + + /** + * Whether or not to buffer interframes + */ + protected boolean bufferInterframes = false; + + @Override + public VideoCodec getCodec() { + return codec; + } + @Override public String getName() { - return null; + return codec.name(); } @Override @@ -69,14 +92,27 @@ public FrameData[] getKeyframes() { return keyframes.toArray(new FrameData[0]); } + /** {@inheritDoc} */ @Override public int getNumInterframes() { - return 0; + return numInterframes.get(); } + /** {@inheritDoc} */ @Override - public FrameData getInterframe(int idx) { + public FrameData getInterframe(int index) { + if (index < numInterframes.get()) { + return interframes.get(index); + } return null; } + public boolean isBufferInterframes() { + return bufferInterframes; + } + + public void setBufferInterframes(boolean bufferInterframes) { + this.bufferInterframes = bufferInterframes; + } + } diff --git a/io/src/main/java/org/red5/codec/AudioCodec.java b/io/src/main/java/org/red5/codec/AudioCodec.java index 74de6e38d..3d72d7fba 100644 --- a/io/src/main/java/org/red5/codec/AudioCodec.java +++ b/io/src/main/java/org/red5/codec/AudioCodec.java @@ -19,7 +19,7 @@ */ public enum AudioCodec { - PCM((byte) 0), ADPCM((byte) 0x01), MP3((byte) 0x02), PCM_LE((byte) 0x03), NELLY_MOSER_16K((byte) 0x04), NELLY_MOSER_8K((byte) 0x05), NELLY_MOSER((byte) 0x06), PCM_ALAW((byte) 0x07), PCM_MULAW((byte) 0x08), RESERVED((byte) 0x09), AAC((byte) 0x0a), SPEEX((byte) 0x0b), MP2((byte) 0x0c), OPUS((byte) 0x0d), MP3_8K((byte) 0x0e), DEVICE_SPECIFIC((byte) 0x0f); + PCM((byte) 0), ADPCM((byte) 0x01), MP3((byte) 0x02), PCM_LE((byte) 0x03), NELLY_MOSER_16K((byte) 0x04), NELLY_MOSER_8K((byte) 0x05), NELLY_MOSER((byte) 0x06), PCM_ALAW((byte) 0x07), PCM_MULAW((byte) 0x08), L16((byte) 0x09), AAC((byte) 0x0a), SPEEX((byte) 0x0b), MP2((byte) 0x0c), OPUS((byte) 0x0d), MP3_8K((byte) 0x0e), DEVICE_SPECIFIC((byte) 0x0f); /** * Codecs which have private / config data or type identifiers included. diff --git a/io/src/main/java/org/red5/codec/HEVCVideo.java b/io/src/main/java/org/red5/codec/HEVCVideo.java index 12ded0e62..a01ed381f 100644 --- a/io/src/main/java/org/red5/codec/HEVCVideo.java +++ b/io/src/main/java/org/red5/codec/HEVCVideo.java @@ -7,9 +7,6 @@ package org.red5.codec; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicInteger; - import org.apache.mina.core.buffer.IoBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,40 +20,17 @@ public class HEVCVideo extends AbstractVideo { private static Logger log = LoggerFactory.getLogger(HEVCVideo.class); - /** - * HEVC video codec constant - */ - static final String CODEC_NAME = "HEVC"; + private static boolean isDebug = log.isDebugEnabled(); /** Video decoder configuration data */ private FrameData decoderConfiguration; - /** - * Storage for frames buffered since last key frame - */ - private final CopyOnWriteArrayList interframes = new CopyOnWriteArrayList<>(); - - /** - * Number of frames buffered since last key frame - */ - private final AtomicInteger numInterframes = new AtomicInteger(0); - - /** - * Whether or not to buffer interframes - */ - private boolean bufferInterframes = false; - /** Constructs a new AVCVideo. */ public HEVCVideo() { + codec = VideoCodec.HEVC; this.reset(); } - /** {@inheritDoc} */ - @Override - public String getName() { - return CODEC_NAME; - } - /** {@inheritDoc} */ @Override public boolean canDropFrames() { @@ -108,8 +82,7 @@ public boolean addData(IoBuffer data, int timestamp) { if ((frameType & 0x0f) == VideoCodec.HEVC.getId()) { // check for keyframe if ((frameType & 0xf0) == FLV_FRAME_KEY) { - //log.trace("Key frame"); - if (log.isDebugEnabled()) { + if (isDebug) { log.debug("Keyframe - HEVC type: {}", avcType); } // rewind @@ -129,7 +102,9 @@ public boolean addData(IoBuffer data, int timestamp) { keyframes.add(new FrameData(data)); break; case 0: // configuration - //log.trace("Decoder configuration"); + if (isDebug) { + log.debug("Decoder configuration"); + } // Store HEVCDecoderConfigurationRecord data decoderConfiguration.setData(data); softReset(); @@ -138,7 +113,7 @@ public boolean addData(IoBuffer data, int timestamp) { //log.trace("Keyframes: {}", keyframes.size()); } else if (bufferInterframes) { //log.trace("Interframe"); - if (log.isDebugEnabled()) { + if (isDebug) { log.debug("Interframe - HEVC type: {}", avcType); } // rewind @@ -152,7 +127,7 @@ public boolean addData(IoBuffer data, int timestamp) { interframes.add(new FrameData(data)); } } catch (Throwable e) { - log.error("Failed to buffer interframe", e); + log.warn("Failed to buffer interframe", e); } //log.trace("Interframes: {}", interframes.size()); } @@ -175,27 +150,4 @@ public IoBuffer getDecoderConfiguration() { return decoderConfiguration.getFrame(); } - /** {@inheritDoc} */ - @Override - public int getNumInterframes() { - return numInterframes.get(); - } - - /** {@inheritDoc} */ - @Override - public FrameData getInterframe(int index) { - if (index < numInterframes.get()) { - return interframes.get(index); - } - return null; - } - - public boolean isBufferInterframes() { - return bufferInterframes; - } - - public void setBufferInterframes(boolean bufferInterframes) { - this.bufferInterframes = bufferInterframes; - } - } diff --git a/io/src/main/java/org/red5/codec/IAudioStreamCodec.java b/io/src/main/java/org/red5/codec/IAudioStreamCodec.java index 03f76623b..9e8220612 100644 --- a/io/src/main/java/org/red5/codec/IAudioStreamCodec.java +++ b/io/src/main/java/org/red5/codec/IAudioStreamCodec.java @@ -16,15 +16,20 @@ */ public interface IAudioStreamCodec { + /** + * @return the codec type. + */ + AudioCodec getCodec(); + /** * @return the name of the audio codec. */ - public String getName(); + String getName(); /** * Reset the codec to its initial state. */ - public void reset(); + void reset(); /** * Returns true if the codec knows how to handle the passed stream data. @@ -33,7 +38,7 @@ public interface IAudioStreamCodec { * some sample data to see if this codec can handle it. * @return can this code handle the data. */ - public boolean canHandleData(IoBuffer data); + boolean canHandleData(IoBuffer data); /** * Update the state of the codec with the passed data. @@ -42,7 +47,7 @@ public interface IAudioStreamCodec { * data to tell the codec we're adding * @return true for success. false for error. */ - public boolean addData(IoBuffer data); + boolean addData(IoBuffer data); /** * Add audio data with a time stamp and a flag identifying the content as AMF or not. @@ -52,13 +57,15 @@ public interface IAudioStreamCodec { * @param amf if true, data is in AMF format otherwise its most likely from non-AMF source like RTP * @return true if data is added and false otherwise */ - public boolean addData(IoBuffer data, int timestamp, boolean amf); + boolean addData(IoBuffer data, int timestamp, boolean amf); /** * Returns information used to configure the decoder. * * @return the data for decoder setup. */ - public IoBuffer getDecoderConfiguration(); + default IoBuffer getDecoderConfiguration() { + return null; + } } diff --git a/io/src/main/java/org/red5/codec/IVideoStreamCodec.java b/io/src/main/java/org/red5/codec/IVideoStreamCodec.java index a0eb9f396..e7d7c97cd 100644 --- a/io/src/main/java/org/red5/codec/IVideoStreamCodec.java +++ b/io/src/main/java/org/red5/codec/IVideoStreamCodec.java @@ -19,22 +19,27 @@ public interface IVideoStreamCodec { */ static final byte FLV_FRAME_KEY = 0x10; + /** + * @return the codec type. + */ + VideoCodec getCodec(); + /** * @return the name of the video codec. */ - public String getName(); + String getName(); /** * Reset the codec to its initial state. */ - public void reset(); + void reset(); /** * Check if the codec supports frame dropping. * * @return if the codec supports frame dropping. */ - public boolean canDropFrames(); + boolean canDropFrames(); /** * Returns true if the codec knows how to handle the passed stream data. @@ -43,7 +48,7 @@ public interface IVideoStreamCodec { * some sample data to see if this codec can handle it * @return can this code handle the data. */ - public boolean canHandleData(IoBuffer data); + boolean canHandleData(IoBuffer data); /** * Update the state of the codec with the passed data. @@ -52,7 +57,7 @@ public interface IVideoStreamCodec { * data to tell the codec we're adding * @return true for success. false for error */ - public boolean addData(IoBuffer data); + boolean addData(IoBuffer data); /** * Update the state of the codec with the passed data. @@ -62,7 +67,7 @@ public interface IVideoStreamCodec { * @param timestamp time associated with the data * @return true for success. false for error */ - public boolean addData(IoBuffer data, int timestamp); + boolean addData(IoBuffer data, int timestamp); /** * Add video data with a time stamp and a flag identifying the content as AMF or not. @@ -72,35 +77,37 @@ public interface IVideoStreamCodec { * @param amf if true, data is in AMF format otherwise its most likely from non-AMF source like RTP * @return true if data is added and false otherwise */ - public boolean addData(IoBuffer data, int timestamp, boolean amf); + boolean addData(IoBuffer data, int timestamp, boolean amf); /** * Returns keyframe data. * * @return the data for a keyframe */ - public IoBuffer getKeyframe(); + IoBuffer getKeyframe(); /** * Returns all the keyframe data. * * @return array of keyframe data */ - public FrameData[] getKeyframes(); + FrameData[] getKeyframes(); /** * Returns information used to configure the decoder. * * @return the data for decoder setup */ - public IoBuffer getDecoderConfiguration(); + default IoBuffer getDecoderConfiguration() { + return null; + } /** * Returns the number of interframes collected from last keyframe. * * @return number of interframes */ - public int getNumInterframes(); + int getNumInterframes(); /** * Gets data of interframe with the specified index. @@ -109,7 +116,7 @@ public interface IVideoStreamCodec { * of interframe * @return data of the interframe or null if index is not valid */ - public FrameData getInterframe(int index); + FrameData getInterframe(int index); /** * Holder for video frame data. diff --git a/io/src/main/java/org/red5/codec/MP3Audio.java b/io/src/main/java/org/red5/codec/MP3Audio.java index 7704ffcd2..f1af9cae4 100644 --- a/io/src/main/java/org/red5/codec/MP3Audio.java +++ b/io/src/main/java/org/red5/codec/MP3Audio.java @@ -17,11 +17,13 @@ */ public class MP3Audio extends AbstractAudio { - static final String CODEC_NAME = "MP3"; + { + codec = AudioCodec.MP3; + } @Override public String getName() { - return CODEC_NAME; + return codec.name(); } @Override diff --git a/io/src/main/java/org/red5/codec/OpusAudio.java b/io/src/main/java/org/red5/codec/OpusAudio.java index c6c8aea2e..46a982c3a 100644 --- a/io/src/main/java/org/red5/codec/OpusAudio.java +++ b/io/src/main/java/org/red5/codec/OpusAudio.java @@ -43,6 +43,10 @@ public class OpusAudio extends AbstractAudio { // 48k stereo is the default configuration. private volatile boolean needConfig; + { + codec = AudioCodec.OPUS; + } + @Override public String getName() { return CODEC_NAME; diff --git a/io/src/main/java/org/red5/codec/SpeexAudio.java b/io/src/main/java/org/red5/codec/SpeexAudio.java index 1bc77f040..26d54bc24 100644 --- a/io/src/main/java/org/red5/codec/SpeexAudio.java +++ b/io/src/main/java/org/red5/codec/SpeexAudio.java @@ -19,6 +19,10 @@ public class SpeexAudio extends AbstractAudio { static final String CODEC_NAME = "Speex"; + { + codec = AudioCodec.SPEEX; + } + @Override public String getName() { return CODEC_NAME; diff --git a/io/src/main/java/org/red5/codec/ULAWAudio.java b/io/src/main/java/org/red5/codec/ULAWAudio.java index 447d43349..11e164c43 100644 --- a/io/src/main/java/org/red5/codec/ULAWAudio.java +++ b/io/src/main/java/org/red5/codec/ULAWAudio.java @@ -19,6 +19,10 @@ public class ULAWAudio extends AbstractAudio { static final String CODEC_NAME = "PCM uLaw"; + { + codec = AudioCodec.PCM_MULAW; + } + @Override public String getName() { return CODEC_NAME; diff --git a/pom.xml b/pom.xml index d70d05420..ad51276ad 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ Red5 The Red5 server org.red5 - 1.3.29 + 1.3.30 https://github.com/Red5/red5-server 2005 @@ -105,7 +105,7 @@ 1.62 2.0.23 - 5.3.31 + 5.3.32 8.5.95 [4.13.1,) 1.9.39 diff --git a/server/pom.xml b/server/pom.xml index b7fb4bd45..77edd3e8d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-server diff --git a/server/src/main/java/org/red5/server/plugin/PluginLauncher.java b/server/src/main/java/org/red5/server/plugin/PluginLauncher.java index 9f548ad3a..74687249f 100644 --- a/server/src/main/java/org/red5/server/plugin/PluginLauncher.java +++ b/server/src/main/java/org/red5/server/plugin/PluginLauncher.java @@ -88,6 +88,7 @@ public boolean accept(File dir, String name) { try { pluginClass = Class.forName(pluginMainClass, true, loader); } catch (ClassNotFoundException e) { + log.warn("Error loading plugin class: {}", pluginMainClass, e); continue; } try { diff --git a/server/src/main/server/conf/jee-container.xml b/server/src/main/server/conf/jee-container.xml index a676b946c..63b27613a 100644 --- a/server/src/main/server/conf/jee-container.xml +++ b/server/src/main/server/conf/jee-container.xml @@ -21,7 +21,7 @@ --> - + diff --git a/service/pom.xml b/service/pom.xml index 523f84833..9cbef4099 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-service diff --git a/servlet/pom.xml b/servlet/pom.xml index 4167e38f7..d6868eda3 100644 --- a/servlet/pom.xml +++ b/servlet/pom.xml @@ -3,7 +3,7 @@ org.red5 red5-parent - 1.3.29 + 1.3.30 4.0.0 red5-servlet