Skip to content
Snippets Groups Projects
AudioMediaDeviceSession.java 9.48 KiB
/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jitsi.impl.neomedia.device;

import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;

import org.jitsi.impl.neomedia.audiolevel.*;
import org.jitsi.service.neomedia.event.*;
import org.jitsi.util.*;

/**
 * Extends <tt>MediaDeviceSession</tt> to add audio-specific functionality.
 *
 * @author Emil Ivov
 * @author Damian Minkov
 */
public class AudioMediaDeviceSession
    extends MediaDeviceSession
{
    /**
     * The <tt>Logger</tt> used by the <tt>AudioMediaDeviceSession</tt> class
     * and its instances for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(AudioMediaDeviceSession.class);

    /**
     * The <tt>Effect</tt> that we will register with our <tt>DataSource</tt> in
     * order to measure the audio levels of the local user.
     */
    private final AudioLevelEffect localUserAudioLevelEffect
        = new AudioLevelEffect();

    /**
     * The effect that we will register with our stream in order to measure
     * audio levels of the remote user audio.
     */
    private final AudioLevelEffect streamAudioLevelEffect
        = new AudioLevelEffect();

    /**
     * Initializes a new <tt>MediaDeviceSession</tt> instance which is to
     * represent the use of a specific <tt>MediaDevice</tt> by a
     * <tt>MediaStream</tt>.
     *
     * @param device the <tt>MediaDevice</tt> the use of which by a
     * <tt>MediaStream</tt> is to be represented by the new instance
     */
    protected AudioMediaDeviceSession(AbstractMediaDevice device)
    {
        super(device);
    }

    /**
     * Called by {@link MediaDeviceSession#playerControllerUpdate(
     * ControllerEvent event)} when the player associated with this session's
     * <tt>ReceiveStream</tt> moves enters the <tt>Configured</tt> state, so
     * we use the occasion to add our audio level effect.
     *
     * @param player the <tt>Player</tt> which is the source of a
     * <tt>ConfigureCompleteEvent</tt>
     * @see MediaDeviceSession#playerConfigureComplete(Processor)
     */
    @Override
    protected void playerConfigureComplete(Processor player)
    {
        super.playerConfigureComplete(player);

        TrackControl tcs[] = player.getTrackControls();

        if (tcs != null)
        {
            for (TrackControl tc : tcs)
            {
                if (tc.getFormat() instanceof AudioFormat)
                {
                    // Assume there is only one audio track.
                    try
                    {
                        registerStreamAudioLevelJMFEffect(tc);
                    }
                    catch (UnsupportedPlugInException upie)
                    {
                        logger.error(
                                "Failed to register stream audio level Effect",
                                upie);
                    }
                    break;
                }
            }
        }
    }

    /**
     * Gets notified about <tt>ControllerEvent</tt>s generated by the
     * processor reading our capture data source, calls the corresponding
     * method from the parent class so that it would initialize the processor
     * and then adds the level effect for the local user audio levels.
     *
     * @param event the <tt>ControllerEvent</tt> specifying the
     * <tt>Controller</tt> which is the source of the event and the very type of
     * the event
     */
    @Override
    protected void processorControllerUpdate(ControllerEvent event)
    {
        super.processorControllerUpdate(event);

        if (event instanceof ConfigureCompleteEvent)
        {
            Processor processor = (Processor) event.getSourceController();

            if (processor != null)
                registerLocalUserAudioLevelEffect(processor);
        }
    }

    /**
     * Creates an audio level effect and add its to the codec chain of the
     * <tt>TrackControl</tt> assuming that it only contains a single track.
     *
     * @param processor the processor on which track control we need
     * to register a level effect with.
     */
    protected void registerLocalUserAudioLevelEffect(Processor processor)
    {
        //we register the effect regardless of whether or not we have any
        //listeners at this point because we won't get a second chance.
        //however the effect would do next to nothing unless we register a
        //first listener with it.
        //
        //XXX: i am assuming that a single effect could be reused multiple times
        // if that turns out not to be the case we need to create a new instance
        // here.

        // here we add sound level indicator for captured media
        // from the microphone if there are interested listeners
        try
        {
            TrackControl tcs[] = processor.getTrackControls();

            if (tcs != null)
                for (TrackControl tc : tcs)
                    if (tc.getFormat() instanceof AudioFormat)
                    {
                        //we assume a single track
                        tc.setCodecChain(
                                new Codec[] { localUserAudioLevelEffect });
                        break;
                    }
        }
        catch (UnsupportedPlugInException ex)
        {
            logger.error(
                "Effects are not supported by the datasource.", ex);
        }
    }

    /**
     * Adds an audio level effect to the tracks of the specified
     * <tt>trackControl</tt> and so that we would notify interested listeners
     * of audio level changes.
     *
     * @param trackControl the <tt>TrackControl</tt> where we need to register
     * a level effect that would measure the audio levels of the
     * <tt>ReceiveStream</tt> associated with this class.
     *
     * @throws UnsupportedPlugInException if we fail to add our sound level
     * effect to the track control of <tt>mediaStream</tt>'s processor.
     */
    private void registerStreamAudioLevelJMFEffect(TrackControl trackControl)
        throws UnsupportedPlugInException
    {
        //we register the effect regardless of whether or not we have any
        //listeners at this point because we won't get a second chance.
        //however the effect would do next to nothing unless we register a
        //first listener with it.
        // Assume there is only one audio track
        trackControl.setCodecChain(new Codec[] { streamAudioLevelEffect });
    }

    /**
     * Sets the  <tt>SimpleAudioLevelListener</tt> that this session should be
     * notifying about changes in local audio level related information. This
     * class only supports a single listener for audio changes per source
     * (i.e. stream or data source). Audio changes are generally quite time
     * intensive (~ 50 per second) so we are doing this in order to reduce the
     * number of objects associated with the process (such as event instances
     * listener list iterators and sync copies).
     *
     * @param listener the <tt>SimpleAudioLevelListener</tt> to add
     */
    public void setLocalUserAudioLevelListener(
            SimpleAudioLevelListener listener)
    {
        this.localUserAudioLevelEffect.setAudioLevelListener(listener);
    }

    /**
     * Sets <tt>listener</tt> as the <tt>SimpleAudioLevelListener</tt> that we
     * are going to notify every time a change occurs in the audio level of
     * the media that this device session is receiving from the remote party.
     * This class only supports a single listener for audio changes per source
     * (i.e. stream or data source). Audio changes are generally quite time
     * intensive (~ 50 per second) so we are doing this in order to reduce the
     * number of objects associated with the process (such as event instances
     * listener list iterators and sync copies).
     *
     * @param listener the <tt>SimpleAudioLevelListener</tt> that we want
     * notified for audio level changes in the remote participant's media.
     */
    public void setStreamAudioLevelListener(SimpleAudioLevelListener listener)
    {
        this.streamAudioLevelEffect.setAudioLevelListener(listener);
    }

    /**
     * Returns the last audio level that was measured by this device session
     * for the specified <tt>ssrc</tt>.
     *
     * @param ssrc the SSRC ID whose last measured audio level we'd like to
     * retrieve.
     *
     * @return the audio level that was last measured for the specified
     * <tt>ssrc</tt> or <tt>-1</tt> if no level has been cached for that ID.
     */
    public int getLastMeasuredAudioLevel(long ssrc)
    {
        return -1;
    }

    /**
     * Returns the last audio level that was measured by the underlying
     * mixer for local user.
     *
     * @return the audio level that was last measured for the local user.
     */
    public int getLastMeasuredLocalUserAudioLevel()
    {
        return -1;
    }

    /**
     * Copies the playback part of a specific <tt>MediaDeviceSession</tt> into
     * this instance.
     *
     * @param deviceSession the <tt>MediaDeviceSession</tt> to copy the playback
     * part of into this instance
     */
    @Override
    public void copyPlayback(MediaDeviceSession deviceSession)
    {
        AudioMediaDeviceSession amds = (AudioMediaDeviceSession) deviceSession;

        this.setStreamAudioLevelListener(
            amds.streamAudioLevelEffect.getAudioLevelListener());
        this.setLocalUserAudioLevelListener(
            amds.localUserAudioLevelEffect.getAudioLevelListener());
    }
}