
org.red5.codec.ExtendedAudio Maven / Gradle / Ivy
package org.red5.codec;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.IoConstants;
import org.red5.util.ByteNibbler;
/**
* Extended audio codec used for enhanced RTMP encoded audio. This codec is used to signal that the audio data is
* encoded with a codec that is not originally supported by Red5. An actual codec implemenation will be identified in
* canHandleData() and returned by the getTrackCodec(trackId) method. An internal map contains all the codecs for
* each track.
*
* @author Paul Gregoire
*/
public class ExtendedAudio extends AbstractAudio {
// tracks for multitrack audio, if size = 1, theres only one track
private ConcurrentMap tracks = new ConcurrentSkipListMap<>();
// multitrack flag
private boolean multitrack;
// track codec - this is temporary when used for multitrack audio
private IAudioStreamCodec trackCodec;
// track length in bytes
private int trackSize = 0;
{
codec = AudioCodec.ExHeader;
enhanced = true;
}
/**
* Returns the codec for the specified track.
*
* @param trackId
* @return codec for the track id or null if not found
*/
public IAudioStreamCodec getTrackCodec(int trackId) {
return tracks.values().stream().filter(c -> c.getTrackId() == trackId).findFirst().orElse(null);
}
@Override
public boolean canHandleData(IoBuffer data) {
boolean result = false;
if (data != null && data.limit() > 0) {
result = ((data.get() & IoConstants.MASK_SOUND_FORMAT) >> 4) == codec.getId();
}
return result;
}
@SuppressWarnings("incomplete-switch")
@Override
public boolean addData(IoBuffer data) {
boolean result = false;
if (data.hasRemaining()) {
// mark
data.mark();
// set up the track(s) per enhanced codec entry
/*
SoundFormat: UB[4] Format of SoundData
Standard RTMP bits (for SoundFormat != 9):
soundRate = UB[2]
soundSize = UB[1]
soundType = UB[1]
Enhanced RTMP bits:
audioPacketType = UB[4] as AudioPacketType
if audioPacketType == Multitrack
audioMultitrackType = UB[4] as AvMultitrackType
audioPacketType = UB[4] as AudioPacketType
if audioMultitrackType != AvMultitrackType.ManyTracksManyCodecs
audioFourCc = FOURCC as AudioFourCc
else if audioPacketType != Multitrack
audioFourCc = FOURCC as AudioFourCc
*/
// attempt to parse the configuration
ByteNibbler nibbler = new ByteNibbler(data.get());
// codec id 9 is the extended audio codec, we need to determine the enhanced codec via fourcc
int codecId = nibbler.nibble(4);
if (codecId == codec.getId()) {
// The UB[4] bits are interpreted as AudioPacketType instead of sound rate, size and type
packetType = AudioPacketType.valueOf((byte) nibbler.nibble(4));
if (packetType == AudioPacketType.Multitrack) {
// set the multitrack flag
multitrack = true;
// set up for reading more bits
nibbler = new ByteNibbler(data.get());
multitrackType = AvMultitrackType.valueOf((byte) nibbler.nibble(4));
// Fetch AudioPacketType for all audio tracks in the message
// This fetch MUST not result in a AudioPacketType.Multitrack
packetType = AudioPacketType.valueOf(nibbler.nibble(4));
if (multitrackType != AvMultitrackType.ManyTracksManyCodecs) {
// The tracks are encoded with the same codec identified by the FOURCC
trackCodec = getTrackCodec(data);
}
} else {
// The tracks are encoded with the same codec identified by the FOURCC
trackCodec = getTrackCodec(data);
}
}
// read all the data
while (data.hasRemaining()) {
// handle multiple tracks
if (multitrack) {
// handle tracks that each have their own codec
if (multitrackType == AvMultitrackType.ManyTracksManyCodecs) {
// The tracks are encoded with their own codec identified by the FOURCC
trackCodec = getTrackCodec(data);
}
// track ordering
// For identifying the highest priority (a.k.a., default track) or highest quality track, it is RECOMMENDED
// to use trackId set to zero. For tracks of lesser priority or quality, use multiple instances of trackId
// with ascending numerical values. The concept of priority or quality can have multiple interpretations,
// including but not limited to bitrate, resolution, default angle, and language. This recommendation
// serves as a guideline intended to standardize track numbering across various applications.
trackId = data.get();
if (multitrackType != AvMultitrackType.OneTrack) {
// The 'sizeOfAudioTrack' specifies the size in bytes of the current track that is being processed.
// This size starts counting immediately after the position where the 'sizeOfAudioTrack' value is
// located. You can use this value as an offset to locate the next audio track in a multitrack system.
// The data pointer is positioned immediately after this field. Depending on the MultiTrack type, the
// offset points to either a 'fourCc' or a 'trackId.'
trackSize = (data.get() & 0xff) << 16 | (data.get() & 0xff) << 8 | data.get() & 0xff;
}
// we're multitrack and multicodec so update track id
if (multitrackType == AvMultitrackType.ManyTracksManyCodecs) {
trackCodec.setTrackId(trackId);
}
} else {
trackCodec = getTrackCodec(data);
}
switch (packetType) {
case CodedFrames: // pass coded data
result = trackCodec.addData(data);
break;
case MultichannelConfig:
// Specify a speaker for a channel as it appears in the bitstream. This is needed if the codec is
// not self-describing for channel mapping
AudioChannelOrder audioChannelOrder = AudioChannelOrder.valueOf(data.get());
// number of channels
channels = data.get() & 0xff;
if (audioChannelOrder == AudioChannelOrder.Custom) {
// Each entry specifies the speaker layout (see AudioChannel enum for layout definition)
// in the order that it appears in the bitstream. First entry (i.e., index 0) specifies the
// speaker layout for channel 1. Subsequent entries specify the speaker layout for the next
// channels (e.g., second entry for channel 2, third entry for channel 3, etc.).
audioChannelMap = new AudioChannel[channels];
for (int i = 0; i < channels; i++) {
audioChannelMap[i] = AudioChannel.fromChannel(data.get());
}
}
if (audioChannelOrder == AudioChannelOrder.Native) {
// audioChannelFlags indicates which channels are present in the multi-channel stream. You can
// perform a Bitwise AND (i.e., audioChannelFlags & AudioChannelMask.xxx) to see if a specific
// audio channel is present
audioChannelFlags = (data.get() & 0xff) << 24 | (data.get() & 0xff) << 16 | (data.get() & 0xff) << 8 | data.get() & 0xff;
}
break;
case SequenceStart: // start of sequence
break;
case SequenceEnd: // end of sequence
break;
}
// check for multiple tracks
if (multitrack && trackSize > 0) {
data.skip(trackSize);
continue;
}
// break out of the loop
break;
}
// we handled the data
result = true;
// go back to where we started
data.reset();
}
return result;
}
/**
* Get the track codec for the given data.
*
* @param data
* @return Audio codec
*/
protected IAudioStreamCodec getTrackCodec(IoBuffer data) {
Integer fourcc = data.getInt();
log.debug("Fourcc: {} pos: {}", fourcc, data.position());
if (!tracks.containsKey(fourcc)) {
// create a new codec instance
trackCodec = AudioCodec.valueOfByFourCc(fourcc).newInstance();
tracks.put(fourcc, trackCodec);
} else {
trackCodec = tracks.get(fourcc);
}
log.debug("Track codec: {}", trackCodec);
return trackCodec;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy