@ -30,7 +30,7 @@
* @author Lubomir Marinov
* /
public class MediaDeviceSession
implements ControllerListen er
extends PropertyChangeNotifi er
{
/ * *
@ -40,6 +40,14 @@ public class MediaDeviceSession
private static final Logger logger
= Logger . getLogger ( MediaDeviceSession . class ) ;
/ * *
* The name of the < tt > MediaDeviceSession < / tt > instance property the value
* of which represents the output < tt > DataSource < / tt > of the
* < tt > MediaDeviceSession < / tt > instance which provides the captured ( RTP )
* data to be sent by < tt > MediaStream < / tt > to < tt > MediaStreamTarget < / tt > .
* /
public static final String OUTPUT_DATA_SOURCE = "OUTPUT_DATA_SOURCE" ;
/ * *
* The JMF < tt > DataSource < / tt > of { @link # device } through which this
* instance accesses the media captured by it .
@ -58,6 +66,19 @@ public class MediaDeviceSession
* /
private final AbstractMediaDevice device ;
/ * *
* The last JMF < tt > Format < / tt > set to this instance by a call to its
* { @link # setFormat ( MediaFormat ) and to be set as the output format of
* { @link # processor } .
* /
private Format format ;
/ * *
* The < tt > ControllerListener < / tt > which listens to the < tt > Player < / tt >
* instances in { @link # players } for < tt > ControllerEvent < / tt > s .
* /
private ControllerListener playerControllerListener ;
/ * *
* The < tt > Player < / tt > s rendering < tt > ReceiveStream < / tt > s on the
* < tt > MediaDevice < / tt > represented by this instance . Associated with
@ -75,6 +96,32 @@ public class MediaDeviceSession
* /
private Processor processor ;
/ * *
* The < tt > ControllerListener < / tt > which listens to { @link # processor } for
* < tt > ControllerEvent < / tt > s .
* /
private ControllerListener processorControllerListener ;
/ * *
* The indicator which determines whether { @link # processor } has received
* a < tt > ControllerClosedEvent < / tt > at an unexpected time in its execution .
* A value of < tt > false < / tt > does not mean that < tt > processor < / tt > exists
* or that it is not closed , it just means that if < tt > processor < / tt > failed
* to be initialized or it received a < tt > ControllerClosedEvent < / tt > , it was
* at an expected time of its execution and that the fact in question was
* reflected , for example , by setting < tt > processor < / tt > to < tt > null < / tt > .
* If there is no < tt > processorIsPrematurelyClosed < / tt > field and
* < tt > processor < / tt > is set to < tt > null < / tt > or left existing after the
* receipt of < tt > ControllerClosedEvent < / tt > , it will either lead to not
* firing a < tt > PropertyChangeEvent < / tt > for < tt > OUTPUT_DATA_SOURCE < / tt >
* when it has actually changed and , consequently , cause the
* < tt > SendStream < / tt > s of < tt > MediaStreamImpl < / tt > to not be recreated or
* it will be impossible to detect that < tt > processor < / tt > cannot have its
* format set and will thus be left broken even for subsequent calls to
* { @link # setFormat ( MediaFormat ) } .
* /
private boolean processorIsPrematurelyClosed ;
/ * *
* The < tt > ReceiveStream < / tt > s rendered by this instance on its associated
* < tt > MediaDevice < / tt > . Mapped to < tt > DataSource < / tt > because extenders may
@ -170,7 +217,28 @@ protected void addReceiveStream(
exception ) ;
else
{
player . addControllerListener ( this ) ;
if ( playerControllerListener = = null )
playerControllerListener = new ControllerListener ( )
{
/ * *
* Notifies this < tt > ControllerListener < / tt > that
* the < tt > Controller < / tt > which it is registered
* with has generated an event .
*
* @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
* @see ControllerListener # controllerUpdate (
* ControllerEvent )
* /
public void controllerUpdate ( ControllerEvent event )
{
playerControllerUpdate ( event ) ;
}
} ;
player . addControllerListener ( playerControllerListener ) ;
player . realize ( ) ;
players . put ( receiveStreamDataSource , player ) ;
@ -260,6 +328,45 @@ public void close()
{
disposePlayers ( ) ;
disconnectCaptureDevice ( ) ;
closeProcessor ( ) ;
}
/ * *
* Makes sure { @link # processor } is closed .
* /
private void closeProcessor ( )
{
if ( processor ! = null )
{
if ( processorControllerListener ! = null )
processor . removeControllerListener ( processorControllerListener ) ;
processor . stop ( ) ;
if ( processor . getState ( ) = = Processor . Realized )
{
DataSource dataOutput = processor . getDataOutput ( ) ;
if ( dataOutput ! = null )
dataOutput . disconnect ( ) ;
}
processor . deallocate ( ) ;
processor . close ( ) ;
processorIsPrematurelyClosed = false ;
/ *
* Once the processor uses the captureDevice , the captureDevice has
* to be reconnected on its next use .
* /
disconnectCaptureDevice ( ) ;
}
}
/ * *
* Makes sure { @link # captureDevice } is disconnected .
* /
private void disconnectCaptureDevice ( )
{
if ( captureDevice ! = null )
{
/ *
@ -287,43 +394,6 @@ public void close()
captureDevice . disconnect ( ) ;
captureDeviceIsConnected = false ;
}
if ( processor ! = null )
{
processor . stop ( ) ;
if ( processor . getState ( ) = = Processor . Realized )
{
DataSource dataOutput = processor . getDataOutput ( ) ;
if ( dataOutput ! = null )
dataOutput . disconnect ( ) ;
}
processor . deallocate ( ) ;
processor . close ( ) ;
}
}
/ * *
* Notifies this < tt > ControllerListener < / tt > that the < tt > Controller < / tt >
* which it is registered with has generated an event .
*
* @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
* @see ControllerListener # controllerUpdate ( ControllerEvent )
* /
public void controllerUpdate ( ControllerEvent event )
{
if ( event instanceof RealizeCompleteEvent )
{
Player player = ( Player ) event . getSourceController ( ) ;
if ( player ! = null )
{
player . start ( ) ;
realizeComplete ( player ) ;
}
}
}
/ * *
@ -346,6 +416,8 @@ protected void disposePlayer(Player player)
break ;
}
if ( playerControllerListener ! = null )
player . removeControllerListener ( playerControllerListener ) ;
player . stop ( ) ;
player . deallocate ( ) ;
player . close ( ) ;
@ -383,8 +455,15 @@ private static Format findFirstMatchingFormat(
Format format )
{
for ( Format match : formats )
{
/ *
* TODO Is the encoding enough ? We ' ve been explicitly told what
* format to use so it may be that its non - encoding attributes which
* have been specified are also necessary .
* /
if ( match . isSameEncoding ( format ) )
return match ;
}
return null ;
}
@ -468,7 +547,9 @@ public MediaFormat getFormat()
{
Processor processor = getProcessor ( ) ;
if ( processor ! = null )
if ( ( processor ! = null )
& & ( this . processor = = processor )
& & ! processorIsPrematurelyClosed )
{
MediaType mediaType = getMediaType ( ) ;
@ -580,33 +661,46 @@ private Processor getProcessor()
. error (
"Failed to create Processor for " + captureDevice ,
exception ) ;
else if ( waitForState ( processor , Processor . Configured ) )
else
{
try
if ( processorControllerListener = = null )
processorControllerListener = new ControllerListener ( )
{
/ * *
* Notifies this < tt > ControllerListener < / tt > that
* the < tt > Controller < / tt > which it is registered
* with has generated an event .
*
* @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
* @see ControllerListener # controllerUpdate (
* ControllerEvent )
* /
public void controllerUpdate ( ControllerEvent event )
{
processorControllerUpdate ( event ) ;
}
} ;
processor
. addControllerListener ( processorControllerListener ) ;
if ( waitForState ( processor , Processor . Configured ) )
{
exception = null ;
processor
. setContentDescriptor (
new ContentDescriptor (
ContentDescriptor . RAW_RTP ) ) ;
this . processor = processor ;
processorIsPrematurelyClosed = false ;
}
catch ( NotConfiguredError nce )
else
{
// TODO
exception = nce ;
if ( processorControllerListener ! = null )
processor
. removeControllerListener (
processorControllerListener ) ;
processor = null ;
}
if ( exception ! = null )
logger
. error (
"Failed to set ContentDescriptor to Processor." ,
exception ) ;
else
this . processor = processor ;
}
else
processor = null ;
}
}
return processor ;
@ -624,7 +718,9 @@ public List<MediaFormat> getSupportedFormats()
Processor processor = getProcessor ( ) ;
Set < Format > supportedFormats = new HashSet < Format > ( ) ;
if ( processor ! = null )
if ( ( processor ! = null )
& & ( this . processor = = processor )
& & ! processorIsPrematurelyClosed )
{
MediaType mediaType = getMediaType ( ) ;
@ -656,6 +752,80 @@ public List<MediaFormat> getSupportedFormats()
return supportedMediaFormats ;
}
/ * *
* Gets notified about < tt > ControllerEvent < / tt > s generated by the
* < tt > Player < / tt > instances in { @link # players } .
*
* @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
* /
private void playerControllerUpdate ( ControllerEvent event )
{
if ( event instanceof RealizeCompleteEvent )
{
Player player = ( Player ) event . getSourceController ( ) ;
if ( player ! = null )
{
player . start ( ) ;
realizeComplete ( player ) ;
}
}
}
/ * *
* Gets notified about < tt > ControllerEvent < / tt > s generated by
* { @link # processor } .
*
* @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
* /
private void processorControllerUpdate ( ControllerEvent event )
{
if ( event instanceof ConfigureCompleteEvent )
{
Processor processor = ( Processor ) event . getSourceController ( ) ;
if ( processor ! = null )
{
try
{
processor
. setContentDescriptor (
new ContentDescriptor (
ContentDescriptor . RAW_RTP ) ) ;
}
catch ( NotConfiguredError nce )
{
logger
. error (
"Failed to set ContentDescriptor to Processor." ,
nce ) ;
}
if ( format ! = null )
setFormat ( processor , format ) ;
}
}
else if ( event instanceof ControllerClosedEvent )
{
Processor processor = ( Processor ) event . getSourceController ( ) ;
/ *
* If everything goes according to plan , we should ' ve removed the
* ControllerListener from the processor by now .
* /
logger . warn ( event ) ;
// TODO Should the access to processor be synchronized?
if ( ( processor ! = null ) & & ( this . processor = = processor ) )
processorIsPrematurelyClosed = true ;
}
}
/ * *
* Notifies this instance that a specific < tt > Player < / tt > of remote content
* has generated a < tt > RealizeCompleteEvent < / tt > . Allows extenders to carry
@ -714,72 +884,162 @@ public void setFormat(MediaFormat format)
MediaFormatImpl < ? extends Format > mediaFormatImpl
= ( MediaFormatImpl < ? extends Format > ) format ;
Processor processor = getProcessor ( ) ;
this . format = mediaFormatImpl . getFormat ( ) ;
/ *
* If the processor is after Configured , setting a different format will
* silently fail . Recreate the processor in order to be able to set the
* different format .
* /
if ( processor ! = null )
{
if ( ( processor . getState ( ) < Processor . Configured )
& & ! waitForState ( processor , Processor . Configured ) )
{
// TODO
return ;
}
int processorState = processor . getState ( ) ;
if ( processorState = = Processor . Configured )
setFormat ( processor , this . format ) ;
else if ( processorIsPrematurelyClosed
| | ( ( processorState > Processor . Configured )
& & ! format . equals ( getFormat ( ) ) ) )
setProcessor ( null ) ;
}
}
for ( TrackControl trackControl : processor . getTrackControls ( ) )
{
if ( ! trackControl . isEnabled ( ) )
continue ;
/ * *
* Sets the JMF < tt > Format < / tt > in which a specific < tt > Processor < / tt > is to
* output media data .
*
* @param processor the < tt > Processor < / tt > to set the output < tt > Format < / tt >
* of
* @param format the JMF < tt > Format < / tt > to set to < tt > processor < / tt >
* /
private void setFormat ( Processor processor , Format format )
{
TrackControl [ ] trackControls = processor . getTrackControls ( ) ;
MediaType mediaType = getMediaType ( ) ;
Format [ ] supportedFormats = trackControl . getSupportedFormats ( ) ;
for ( int trackIndex = 0 ;
trackIndex < trackControls . length ;
trackIndex + + )
{
TrackControl trackControl = trackControls [ trackIndex ] ;
if ( ( supportedFormats = = null ) | | ( supportedFormats . length < 1 ) )
{
trackControl . setEnabled ( false ) ;
continue ;
}
if ( ! trackControl . isEnabled ( ) )
continue ;
Format supportedFormat = null ;
Format [ ] supportedFormats = trackControl . getSupportedFormats ( ) ;
switch ( mediaType )
if ( ( supportedFormats = = null ) | | ( supportedFormats . length < 1 ) )
{
trackControl . setEnabled ( false ) ;
continue ;
}
Format supportedFormat = null ;
switch ( mediaType )
{
case AUDIO :
if ( supportedFormats [ 0 ] instanceof AudioFormat )
{
case AUDIO :
if ( supportedFormats [ 0 ] instanceof AudioFormat )
{
if ( FMJConditionals . FORCE_AUDIO_FORMAT ! = null )
trackControl
. setFormat ( FMJConditionals . FORCE_AUDIO_FORMAT ) ;
else
{
supportedFormat
= findFirstMatchingFormat (
supportedFormats ,
mediaFormatImpl . getFormat ( ) ) ;
}
}
break ;
case VIDEO :
if ( supportedFormats [ 0 ] instanceof VideoFormat )
if ( FMJConditionals . FORCE_AUDIO_FORMAT ! = null )
trackControl
. setFormat ( FMJConditionals . FORCE_AUDIO_FORMAT ) ;
else
{
supportedFormat
= findFirstMatchingFormat (
supportedFormats ,
mediaFormatImpl . getFormat ( ) ) ;
if ( supportedFormat ! = null )
supportedFormat
= assertSize ( ( VideoFormat ) supportedFormat ) ;
= findFirstMatchingFormat ( supportedFormats , format ) ;
/ *
* We ' ve failed to find a supported format so try to use
* whatever we ' ve been told and , if it fails , the caller
* will at least know why .
* /
if ( supportedFormat = = null )
supportedFormat = format ;
}
break ;
}
break ;
case VIDEO :
if ( supportedFormats [ 0 ] instanceof VideoFormat )
{
supportedFormat
= findFirstMatchingFormat ( supportedFormats , format ) ;
/ *
* We ' ve failed to find a supported format so try to use
* whatever we ' ve been told and , if it fails , the caller
* will at least know why .
* /
if ( supportedFormat = = null )
supportedFormat = format ;
if ( supportedFormat ! = null )
supportedFormat
= assertSize ( ( VideoFormat ) supportedFormat ) ;
}
break ;
}
if ( supportedFormat = = null )
trackControl . setEnabled ( false ) ;
else
trackControl . setFormat ( supportedFormat ) ;
if ( supportedFormat = = null )
trackControl . setEnabled ( false ) ;
else
{
Format setFormat = trackControl . setFormat ( supportedFormat ) ;
if ( setFormat = = null )
logger
. error (
"Failed to set format of track "
+ trackIndex
+ " to "
+ supportedFormat
+ ". Processor is in state "
+ processor . getState ( ) ) ;
else if ( setFormat ! = supportedFormat )
logger
. warn (
"Failed to change format of track "
+ trackIndex
+ " from "
+ setFormat
+ " to "
+ supportedFormat
+ ". Processor is in state "
+ processor . getState ( ) ) ;
else if ( logger . isTraceEnabled ( ) )
logger
. trace (
"Set format of track "
+ trackIndex
+ " to "
+ setFormat ) ;
}
}
}
/ * *
* Sets the JMF < tt > Processor < / tt > which is to transcode
* { @link # captureDevice } into the format of this instance .
*
* @param processor the JMF < tt > Processor < / tt > which is to transcode
* { @link # captureDevice } into the format of this instance
* /
private void setProcessor ( Processor processor )
{
if ( this . processor ! = processor )
{
closeProcessor ( ) ;
this . processor = processor ;
/ *
* Since the processor has changed , its output DataSource known to
* the public has also changed .
* /
firePropertyChange ( OUTPUT_DATA_SOURCE , null , null ) ;
}
}
/ * *
* Starts the processing of media in this instance in a specific direction .
*
@ -822,8 +1082,8 @@ public void stop(MediaDirection direction)
if ( MediaDirection . SENDRECV . equals ( direction )
| | MediaDirection . SENDONLY . equals ( direction ) )
if ( ( processor ! = null )
& & ( processor . getState ( ) == Processor . Start ed) )
processor . st art ( ) ;
& & ( processor . getState ( ) > Processor . Configur ed) )
processor . st op ( ) ;
}
/ * *